How to replace an aggregate null value by 0 - sql-server

I need to calculate a percentage, based on the amount of money on an user account and group the data by the account ID. I make in my calculation a sum of every payment that is used. A problem is that I also need to show acocunts without payments. My idea was to use a CASE statement to check if the aggragate sum gives a null or a value. When it returns null, I replace it by 0.
I have following query
SELECT
DA.ACCOUNT_ID,
ROUND((1 - ((CASE
WHEN SUM(FPP.AMOUNT_IN_DEFAULT_CURRENCY) IS NOT NULL THEN SUM(FPP.AMOUNT_IN_DEFAULT_CURRENCY)
ELSE 0
END) / FAT.PERCENTAGE_INDICATOR)) * 100,0) AS "Percentage"
FROM DIM_ACCOUNT DA
JOIN TRANSACTION_TABLE FAT ON FAT.ACCOUNT_ID = DA.ID
JOIN PAYMENTS_TABLE FPP ON FPP.ACCOUNT_ID = DA.ID
GROUP BY DA.ACCOUNT_ID
But when I execute this with test data, it doesn't work. The account is not added in my list. Is something wrong with my NULL handling?
When I strip of the query and only do the sum at the payment table, I get following output:
< null > (without spaces)
How can I make this work?

NULL is ignored by aggregate functions like sum/max/min...
If all column values are null then it will give error.
Sum(column) cannot be null
SELECT sum(t.num) AS sum_val
FROM (
SELECT null AS num
) t
Operand data type NULL is invalid for sum operator

if by 'without payments' you mean with no related records in PAYMENTS_TABLE then use a LEFT JOIN:
SELECT
DA.ACCOUNT_ID,
ROUND((1 - (ISNULL(SUM(FPP.AMOUNT_IN_DEFAULT_CURRENCY), 0) / FAT.PERCENTAGE_INDICATOR)) * 100,0) AS "Percentage"
FROM DIM_ACCOUNT DA
JOIN TRANSACTION_TABLE FAT ON FAT.ACCOUNT_ID = DA.ID
LEFT JOIN PAYMENTS_TABLE FPP ON FPP.ACCOUNT_ID = DA.ID
GROUP BY DA.ACCOUNT_ID;
a INNER JOIN does exclude the accounts without payments because requires a matching record in all the involved tables.

Related

Execution order of CASE statement SQL Server

Does a CASE statement that has multiple parts that is part of an INSERTstatement execute in order and do the 'rules' for lack of a better word stay in place even after the next line? in the query below, does the PO_TYPE assignment overrule the next command - to look in a list of articles for example? So even if that article was in the list in the second part of the statement if it was type 05 or 07 it will still assign to Andrew?
Thanks.
/*INSERT values into the table using SELECT making sure to exclude vendor 20800 - (see last line of code)*/
INSERT INTO SCM_PO_EMPLOYEE_NAME (PO_NUMBER, PO_ITEM_NUMBER, MATERIAL, BUSINESS_UNIT_CODE,PO_TYPE,TEAM_MEMBER_NAME)
SELECT I.PO_NUMBER,
I.PO_ITEM_NUMBER,
I.MATERIAL,
B.BU_CODE,
H.PO_TYPE,
CASE WHEN H.PO_TYPE IN ('05','07') -- Promo PO type - should be on both po type and stock category
AND I.STOCK_CATEGORY LIKE ('A60383%') -- stock category is second part of the check
THEN 'AZ'
WHEN H.PO_TYPE = '02' -- ma PO type
THEN 'MB'
WHEN I.MATERIAL IN ( SELECT ARTICLE
FROM ADI_USER_MAINTAINED.dbo.SCM_EMPLOYEE_ARTICLE A ) -- Check the Employee to article table next
THEN A.TEAM_MEMBER_NAME -- If the PO number matches that conditions then assign the employee from the employee article table
WHEN M.BUSINESS_UNIT_CODE = B.BU_CODE -- if not use then go to the BU assignment (below)
THEN B.TEAM_MEMBER_NAME --- Use the team member name from the Employee_BU table
END AS [TEAM_MEMBER_NAME]
FROM PDX_SAP_USER.dbo.VW_PO_HEADER H
JOIN PDX_SAP_USER.dbo.VW_PO_ITEM I ON H.PO_NUMBER = I.PO_NUMBER
JOIN PDX_SAP_USER.dbo.VW_MM_MATERIAL M ON I.MATERIAL = M.MATERIAL
JOIN ADI_USER_MAINTAINED.dbo.SCM_EMPLOYEE_ARTICLE A ON I.MATERIAL = A.ARTICLE
JOIN ADI_USER_MAINTAINED.dbo.SCM_EMPLOYEE_BU B ON B.BU_CODE = M.BUSINESS_UNIT_CODE
WHERE H.VENDOR_NO <> '20800'; --Exclude '20800' as a vendor!!
A case expression is evaluated sequentially.
So, the second then is only evaluated when the first then does not return true. As an extreme example of this, consider:
select (case when 1=1 then 'true'
when 1/0 = 0 then 'error'
end)
This returns 'true' instead of error'ing out.

Conversion failed when converting the varchar value 'RN' to data type int

Im getting a conversion error when using the rownumber function on sql-server. I know plenty of similar threads have been posted but ive looked at them and they have different issues.
Conversion failed when converting the varchar value 'RN' to data type int.
It doesnt tell me where the problem is occuring. Does anyone have an idea of where it could be going wrong apart from the rownumber function?
This CTE is getting a previous transaction
, PRETRANSACTIONS AS (
SELECT DISTINCT
CONT.POH_ID AS POH_ID
,CONT.POH_POLICYNUMBER AS Contract_Number
,row_number() over (
partition by CONT.POH_ID
order by TRANS.txh_effectivedate desc
) AS 'RN'
,TXN_STAT.Txs_DESCRIPTION_I AS Transaction_Status_2
,TRANS.txh_effectivedate AS Transaction_Date_2
,TXN_DES.Txt_DESCRIPTION_I AS Transaction_Type_2
,TRX_RES.TRE_AMTPROCESSED AS Transaction_Amount_2
From Se2FAST.dbo.Cm_Opt_Poh_PolicyHdr_S CONT
JOIN BASE BASE
ON BASE.Poh_ID = CONT.POH_ID
INNER JOIN Se2FAST.dbo.Cm_Opt_Pch_PolicyCovHdr_S policyCov
ON CONT.Poh_ID = policyCov.Pch_POLICYHDRID
AND policycov.pch_sequence = 1
INNER JOIN [dbo].[Cm_Sys_Pst_PolicyStatus_I] PST
ON PST.Pst_ID_I = CONT.Poh_Status
LEFT JOIN se2Fast.DBO.CM_OPT_TXH_TRXHDR_S TRANS
ON TRANS.TXH_POLICYHDRID = CONT.POH_ID
AND TRANS.txh_effectivedate < ( SELECT TOP 1 TRAN1.txh_effectivedate
FROM se2Fast.DBO.CM_OPT_TXH_TRXHDR_S TRAN1
WHERE TRAN1.TXH_POLICYHDRID = CONT.POH_ID
ORDER BY txh_effectivedate DESC)
LEFT JOIN se2Fast.dbo.Cm_Opt_Tre_TrxRes_S AS TRX_RES
ON TRANS.Txh_ID = TRX_RES.Tre_TRXHDRID
LEFT JOIN se2Fast.dbo.Cm_Sys_Txt_TrxType_I TXN_DES
ON TRANS.Txh_TRXTYPEID = TXN_DES.Txt_ID_I
--AND TXN_DES.Txt_DESCRIPTION_I NOT IN (
--'Anniversary','Monthiversary','Quarterversary','Calendar Year End')
--AND TXN_DES.Txt_DESCRIPTION_I IS NOT NULL
LEFT JOIN se2Fast.dbo.Cm_Sys_Txs_TrxStatus_I TXN_STAT
ON Trans.Txh_TRXSTATUS = TXN_STAT.Txs_ID_I
AND TXN_STAT.Txs_DESCRIPTION_I = 'Completed'
WHERE 'RN' = 2
)
WHERE 'RN' = 2 is comparing two values, the string 'RN' and the integer 2. When SQL Server parses the expression, it is attempting to convert the literal value 'RN' to a numeric value to perform the integer comparison. Obviously, RN cannot be converted to a numeric value so you are getting an error.
In SQL Server, aliases are materialized after the WHERE/GROUP/HAVING clauses, but before the ORDER BY clause. This means you can't use the alias within the filter of the query it was assigned. You need to generate your column alias in a subquery before you can use it in a comparison.
It looks like you've mixed up your query. You've started as CTE named 'PRETRANSACTIONS', but then you've tried to alias it as a column after the closing parenthesis, AS 'RN'. A CTE must be referenced in the FROM before you can use the columns in the SELECT. If this is just a column expression, get rid of 'PRETRANSACTIONS AS' at the beginning.
WITH PRETRANSACTIONS AS (
SELECT DISTINCT
CONT.POH_ID AS POH_ID
,CONT.POH_POLICYNUMBER AS Contract_Number
,row_number() over (
partition by CONT.POH_ID
order by TRANS.txh_effectivedate desc)
AS RN)
SELECT RN,
TXN_STAT.Txs_DESCRIPTION_I AS Transaction_Status_2
,TRANS.txh_effectivedate AS Transaction_Date_2
,TXN_DES.Txt_DESCRIPTION_I AS Transaction_Type_2
,TRX_RES.TRE_AMTPROCESSED AS Transaction_Amount_2
From Se2FAST.dbo.Cm_Opt_Poh_PolicyHdr_S CONT
JOIN BASE BASE
ON BASE.Poh_ID = CONT.POH_ID
JOIN PRETRANSACTIONS
ON BASE.POH_ID = PRETRANSACTIONS.POH_ID
INNER JOIN Se2FAST.dbo.Cm_Opt_Pch_PolicyCovHdr_S policyCov
ON CONT.Poh_ID = policyCov.Pch_POLICYHDRID
AND policycov.pch_sequence = 1
INNER JOIN [dbo].[Cm_Sys_Pst_PolicyStatus_I] PST
ON PST.Pst_ID_I = CONT.Poh_Status
LEFT JOIN se2Fast.DBO.CM_OPT_TXH_TRXHDR_S TRANS
ON TRANS.TXH_POLICYHDRID = CONT.POH_ID
AND TRANS.txh_effectivedate < ( SELECT TOP 1 TRAN1.txh_effectivedate
FROM se2Fast.DBO.CM_OPT_TXH_TRXHDR_S TRAN1
WHERE TRAN1.TXH_POLICYHDRID = CONT.POH_ID
ORDER BY txh_effectivedate DESC)
LEFT JOIN se2Fast.dbo.Cm_Opt_Tre_TrxRes_S AS TRX_RES
ON TRANS.Txh_ID = TRX_RES.Tre_TRXHDRID
LEFT JOIN se2Fast.dbo.Cm_Sys_Txt_TrxType_I TXN_DES
ON TRANS.Txh_TRXTYPEID = TXN_DES.Txt_ID_I
--AND TXN_DES.Txt_DESCRIPTION_I NOT IN (
--'Anniversary','Monthiversary','Quarterversary','Calendar Year End')
--AND TXN_DES.Txt_DESCRIPTION_I IS NOT NULL
LEFT JOIN se2Fast.dbo.Cm_Sys_Txs_TrxStatus_I TXN_STAT
ON Trans.Txh_TRXSTATUS = TXN_STAT.Txs_ID_I
AND TXN_STAT.Txs_DESCRIPTION_I = 'Completed'
WHERE PRETRANSACTIONS.RN = 2
On a separate note, never use DISTINCT in the same set as ROW_NUMBER. ROW_NUMBER generates a unique number for every row, therefore every row is distinct. All that will do is add sorting overhead without eliminating any rows. You can use RANK or DENSE_RANK instead.
dont use 'RN', just RN without quotes.
You are getting this error because you are comparing the Text value 'RN' with an Integer value 2 in the where clause. But since 'RN' is a column and you want to return all the files that have a value 2 in the field 'RN' instead of
WHERE **'RN'** = 2
just type
WHERE RN = 2
RN without quotes as it is the name of the column.

Complete Select Statement ELSE a defaulted value?

I'm wondering if there is a way to have a complete select statement and if no row is returned to return a row with a default value in a specific field (SQL Server)? Here's a generic version of my SQL to better explain:
SELECT COUNT(CASE WHEN CAST(c.InjuryDate as DATE)>DATEADD(dd,-60, getdate ()) THEN b.InjuryID end) InjuryCount, a.PersonID
FROM Person_Info a
JOIN Injury_Subject b on b.PersonID=a.PersonID
JOIN Injury_Info c on c.InjuryID=b.InjuryID
WHERE EXISTS (SELECT * FROM Hospital_Record d WHERE d.PersonID=b.PersonID and d.InjuryID=b.InjuryID) --There could be multiple people associated with the same InjuryID
GROUP BY a.PersonID
If NOT EXISTS (SELECT * FROM Hospital_Record d WHERE d.PersonID=a.PersonID) THEN '0' in InjuryCount
I want a row for each person who has had an injury to display. Then I'd like a count of how many injuries resulted in hospitalizations in the last 60 days. If they were not hospitalized, I'd like the row to still be generated, but display '0' in InjuryCount column. I've played with this a bunch, moving my date from the WHERE to the SELECT, trying IF ELSE combos, etc. Could someone help me figure out how to get what I want please?
It's hard to tell without an example of input and desired output, but I think this is what you are going for:
select
InjuryCount = count(case
when cast(ii.InjuryDate as date) > dateadd(day,-60,getdate())
then i.InjuryId
else null
end
)
, p.PersonId
from Person_Info p
left join Hosptal_Record h on p.PersonId = h.PersonId
left join Injury_Subject i on i.PersonId = h.PersonId
and h.InjuryId = i.InjuryId
left join Injury_Info ii on ii.InjuryId = i.InjuryId
group by p.PersonId;

TSQL Select Statement using Case or Join

I am a little stuck on a situation that I have been trying to fight through. I have a page that allows a user to select all the filter options they want to search by and then it runs the query on that data.
Every field requires something to be picked but on a new field I am introducing, it's going to be optional.
It allows you to provide a list of supervisors and it will then provide all records where the agents supervisor is in the list provided; pretty straight forward. However, I am trying to make this optional as I don't want to always search by users. If I don't provide a name in the UI to pass to the stored procedure, then I want to ignore this part of the statement and get me everything regardless of the manager.
Here is the query I am working with:
SELECT a.[escID],
a.[escReasonID],
b.[ArchibusLocationName],
c.[ArchibusLocationName],
b.[DepartmentDesc],
c.[DepartmentDesc],
a.[escCreatedBy],
a.[escWorkedBy],
a.[escNotes],
a.[preventable],
a.[escalationCreated],
a.[escalationTracked],
a.[feedbackID],
typ.[EscalationType],
typ.[EscalationTypeText] AS escalationType,
d.reasonText AS reasonText
FROM [red].[dbo].[TFS_Escalations] AS a
LEFT OUTER JOIN
red.dbo.EmployeeTable AS b
ON a.escCreatedBy = b.QID
LEFT OUTER JOIN
red.dbo.EmployeeTable AS c
ON a.escWorkedBy = c.QID
LEFT OUTER JOIN
red.dbo.TFS_Escalation_Reasons AS d
ON a.escReasonID = d.ReasonID
INNER JOIN
dbo.TFS_EscalationTypes AS typ
ON d.escType = typ.EscalationType
WHERE B.[ArchibusLocationName] IN (SELECT location
FROM #tmLocations)
AND C.[ArchibusLocationName] IN (SELECT location
FROM #subLocations)
AND B.[DepartmentDesc] IN (SELECT department
FROM #tmDepartments)
AND C.[DepartmentDesc] IN (SELECT department
FROM #subDepartments)
AND DATEDIFF(second, '19700101', CAST (CONVERT (DATETIME, A.[escalationCreated], 121) AS INT)) >= #startDate
AND DATEDIFF(second, '19700101', CAST (CONVERT (DATETIME, A.[escalationCreated], 121) AS INT)) <= #endDate
AND a.[PREVENTABLE] IN (SELECT PREVENTABLE FROM #preventable)
AND b.MgrQID IN (SELECT leaderQID FROM #sourceLeaders)
The part that I am trying to make option is the very last line of the query:
AND b.MgrQID IN (SELECT leaderQID FROM #sourceLeaders)
Essentially, if there is no data in the temp table #sourceLeaders then it should ignore that piece of the query.
In all of the other instances of the WHERE clause, something is always required for those fields which is why that all works fine. I just cant figure out the best way to make this piece optional depending on if the temp table has data in it (the temp table is populated by the names entered in the UI that a user COULD search by).
So this line should be TRUE if something matches data in the table variable OR there is nothing in the table variable
AND
(
b.MgrQID IN (SELECT leaderQID FROM #sourceLeaders)
OR
NOT EXISTS (SELECT 1 FROM #sourceLeaders)
)
Similar to Nick.McDermaid's, but uses a case statement instead :
AND
(
1 = CASE WHEN NOT EXISTS(SELECT 1 FROM #sourceLeaders) THEN 1
WHEN b.MgrQID IN (SELECT leaderQID FROM #sourceLeaders) THEN 1
ELSE 0
END
)
Maybe at the top so you have a single check
DECLARE #EmptySourceLeaders CHAR(1)
IF EXISTS (SELECT 1 FROM #sourceLeaders)
SET #EmptySourceLeaders = 'N'
ELSE
SET #EmptySourceLeaders = 'Y'
Then in the joins
LEFT OUTER JOIN #SourceLeaders SL
ON b.MgrQID = SL.leaderQID
Then in the WHERE
AND (#EmptySourceLeaders = 'Y' OR SL.leaderQID IS NOT NULL)
lots of ways to do it.

SQL Case Statement - If first 'when' returns null then complete the 2nd when

Just wondering if anyone can help me , I am running a case statement that references a different table. It needs to look up the make, model and year of a car as well as the position (FL,FR,BL,BR) and return the kit number.
Up to 4 entries can exist in the table for the same vehicle with the fitting position column specifying which kit number to be selected, in order to only return 1 result i believe i need to put this in the where section of the query, if i add it anywhere else more than 1 value is returned.
However 4 entries won't always exist for the vehicle. A kit can exist for FL & BL but not FR and BR. Because of me adding the position column into the where section 'null' is returned.Rather than it returning nothing i want it to return the next part of the case statement.
This is where the sql works because a kit is available for FL
SELECT CAST (CASE WHEN '002' != 'UNI' THEN T0.U_MPLFK ELSE 'NOKIT' END AS VARCHAR)
FROM
[#CSOL_MILFORD] T0 INNER JOIN [dbo].[#CSOL_VEHICLES] T1 ON T0.[U_VehicleRef] = T1.[U_VehicleRef]
WHERE
T1.U_Manufacturer = 'Ford'
AND
T1.U_Model = 'Galaxy'
AND
T0.U_MPLFK > 1
AND
T0.U_FittingPosition = 'FL'
However when it changes to
SELECT CAST (CASE WHEN '002' != 'UNI' THEN T0.U_MPLFK ELSE 'NOKIT' END AS VARCHAR)
FROM
[#CSOL_MILFORD] T0 INNER JOIN [dbo].[#CSOL_VEHICLES] T1 ON T0.[U_VehicleRef] = T1.[U_VehicleRef]
WHERE
T1.U_Manufacturer = 'Ford'
AND
T1.U_Model = 'Galaxy'
AND
T0.U_MPLFK > 1
AND
T0.U_FittingPosition = 'FR'
I get no value retuned, i want it to return 'NOKIT'
Many Thanks,
Roisin
A left join returns a row with null columns if its on condition fails. So you could move the conditions to the on part of a left join. Something like:
...
FROM #CSOL_VEHICLES T1
LEFT JOIN
#CSOL_MILFORD T0
ON T0.U_VehicleRef = T1.U_VehicleRef
AND T0.U_MPLFK > 1
AND T0.U_FittingPosition = 'FR'
WHERE T1.U_Manufacturer = 'Ford'
AND T1.U_Model = 'Galaxy'
Having the condition in the on clause instead of the where clause means you'll get a row with nulls instead of no row.

Resources