SQL Server sum and subtract prevent null results - sql

I'm using this code:
SELECT
(SELECT SUM(TrnQty) AS Total
FROM InventoryTrans
WHERE InventoryItemID = (select MAX(InventoryItemID)
from inventorymaster)
AND CustomerID = '0') -
(SELECT SUM(TrnQty) AS Total
FROM InventoryTrans
WHERE InventoryItemID = (select MAX(InventoryItemID)
from InventoryMaster)
AND CustomerID > '0'
)
If the result of one them is NULL i take totally result NULL. How can I SUM if one of them is Null? Should I use CASE or something?

Here is a correct way to do it
SELECT Sum(CASE
WHEN customerid = '0' THEN trnqty
ELSE 0
END) - Sum(CASE
WHEN customerid > '0' THEN trnqty
ELSE 0
END) AS Total
FROM inventorytrans
WHERE inventoryitemid = (SELECT Max (inventoryitemid)
FROM inventorymaster)
EDIT:
I [#GordonLinoff] am providing a slightly simpler version of this answer. The use of conditional aggregation is correct, but there is a simpler way of writing it:
SELECT SUM(CASE WHEN it.customerid = '0' THEN it.trnqty
WHEN it.customerid > '0' THEN - it.trnqty
ELSE 0
END) as Total
FROM inventorytrans it
WHERE it.inventoryitemid = (SELECT Max(im.inventoryitemid)
FROM inventorymaster im
)

use
SELECT coalesce(SUM(TrnQty), 0) ...

You can also use
SELECT ISNULL(SUM(TrnQty),0)...

Simply wrap your existing inner select statements in an ISNULL function. It is simple to implement and your intent is clear, if your SELECT returns a null value, use 0.
SELECT
IsNull(
(
SELECT SUM(TrnQty) AS Total
FROM InventoryTrans
WHERE InventoryItemID =
(
select MAX (InventoryItemID) from inventorymaster
)
and CustomerID='0'
),0) -
IsNull(
(
SELECT SUM(TrnQty) AS Total
FROM InventoryTrans
WHERE InventoryItemID =
(
select MAX (InventoryItemID) from InventoryMaster
)
and CustomerID > '0'
),0)
Is this a great example of SQL, not really, but you can see how wrapping the select statements with IsNull is a minor change to your existing query to give you the results you need.
This query could be re-written into a single set based operation, here's my go at it:
SELECT SUM(CASE customerid WHEN '0' THEN -TrnQty ELSE TrnQty END) AS Total
FROM InventoryTrans
WHERE InventoryItemID =
(
select MAX (InventoryItemID) from inventorymaster
)
It is not necessary in this new query to check for null, (either with IsNull or Coalesce) unless you need to ensure that the end result is not null, this query will only return null if ALL of the TrnQty fields are also null. If that is a problem for you logic, wrap the SUM function in an IsNull function as well.

Related

How to check unique values in SQL

I have a table named Bank that contains a Bank_Values column. I need a calculated Bank_Value_Unique column to shows whether each Bank_Value exists somewhere else in the table (i.e. whether its count is greater than 1).
I prepared this query, but it does not work. Could anyone help me with this and/or modify this query?
SELECT
CASE
WHEN NULLIF(LTRIM(RTRIM(Bank_Value)), '') =
(SELECT Bank_Value
FROM [Bank]
GROUP BY Bank_Value
HAVING COUNT(*) = 1)
THEN '0' ELSE '1'
END AS Bank_Key_Unique
FROM [Bank]
A windowed count should work:
SELECT
*,
CASE
COUNT(*) OVER (PARTITION BY Bank_Value)
WHEN 1 THEN 1 ELSE 0
END AS Bank_Value_Unique
FROM
Bank
;
It works also, but I found solution also:
select CASE WHEN NULLIF(LTRIM(RTRIM(Bank_Value)),'') =
(select Bank_Value
from Bank
group by Bank_Value
having (count(distinct Bank_Value) > 2 )) THEN '1' ELSE '0' END AS
Bank_Value_Uniquness
from Bank
It was missing "distinct" in having part.

Improving a SQL teradata query

I have a table like below and I want 'Y' in front of Ref 345 and 789 in the result-set on basis of count(Ref) = 1 where the amount is less than 0. I am using this query to get the desired output. My question is, is there any other (and more efficient) way to do it in Teradata?
SELECT T.Ref,T.AMOUNT, R.Refund_IND as Refund_IND
FROM Table1 t
LEFT JOIN (select 'Y' as Refund_IND, Ref from Table1 where Ref in
(select Ref from Table1 where amount < 0)
group by Ref having count(Ref) = 1) R on t.Ref = R.Ref
You can use window functions to test these conditions:
SELECT
Ref,
Amount,
CASE WHEN COUNT(*) OVER (PARTITION BY REF) = 1 AND Amount < 0 THEN 'Y' ELSE '' END AS Refund_Ind
FROM Table1

How to return 'blank' using Sum function in Sql, if Sum is resulting 0 in SQL

I am using Sql Server 2008. I am adding some column value using Sum function. Like the code below:
SELECT 'RCOAuthorizer LMS',
'' AS Consumer_Loan,
'' AS Auto_Loan,
'' AS Credit_Card,
SUM(CASE
WHEN sq2.loan_type = 'Loan Amendment' THEN sq2.user_count
ELSE ''
END ) AS Loan_Amendment,
SUM(CASE
WHEN sq2.loan_type = 'Pre-Payment' THEN sq2.user_count
ELSE ''
END ) AS Pre_Payment,
SUM(CASE
WHEN sq2.loan_type = 'Corporate Credit card' THEN sq2.user_count
ELSE ''
END ) AS Corporate_Credit_card,
'' AS Auto_Payment_Release,
'' AS Car_Mulkiya
FROM
( SELECT 'RCOAuthorizer' AS ws_name,
'Loan Amendment' AS loan_type,
COUNT (DISTINCT a.bpm_referenceno) AS user_count,
a.user_id AS user_id
FROM BM_LMS_DecisionHistoryGrid a
INNER JOIN
( SELECT m.bpm_referenceno
FROM BM_LMS_EXTTABLE m
WHERE m.request_type = 'Loan Amendment' ) sq1 ON a.bpm_referenceno = sq1.bpm_referenceno
WHERE workstep_name = 'RCOAuthorizer'
GROUP BY a.user_id
UNION SELECT 'RCOAuthorizer',
'Pre-Payment',
COUNT (DISTINCT a.bpm_referenceno), a.user_id
FROM BM_LMS_DecisionHistoryGrid a
INNER JOIN
( SELECT m.bpm_referenceno
FROM BM_LMS_EXTTABLE m
WHERE m.request_type = 'Pre-Payment' ) sq1 ON a.bpm_referenceno = sq1.bpm_referenceno
WHERE workstep_name = 'RCOAuthorizer'
GROUP BY a.user_id
UNION SELECT 'RCOAuthorizer',
'Corporate Credit card',
COUNT (DISTINCT a.bpm_referenceno), a.user_id
FROM BM_LMS_DecisionHistoryGrid a
INNER JOIN
( SELECT m.bpm_referenceno
FROM BM_LMS_EXTTABLE m
WHERE m.request_type = 'Corporate Credit card' ) sq1 ON a.bpm_referenceno = sq1.bpm_referenceno
WHERE workstep_name = 'RCOAuthorizer'
GROUP BY a.user_id ) sq2
GROUP BY sq2.ws_name
The above query will return Sum of all the numbers available in 'a' column. But in case, there is no record, then it will return '0' as result.
I require that if there is no record, it must show blank instead of showing '0'. How to handle the same.
To start, you don't need an ISNULL with a back value of 0 (the neutral for adding) inside a SUM aggregate, as the SUM already ignores NULL values. So SUM(ISNULL(Column, 0)) is equal to SUM(Column) (but different from ISNULL(SUM(Column), 0)!).
Seems that you want a VARCHAR result instead of a numeric one. You can solve this with a CASE.
Select
CASE WHEN Sum(a) = 0 THEN '' ELSE CONVERT(VARCHAR(100), Sum(a)) END
from
table;
If you don't want to repeat the SUM expression:
;WITH SumResult AS
(
Select CONVERT(VARCHAR(100), Sum(a)) AS SumTotal
from table
)
SELECT
CASE WHEN R.SumTotal = '0' THEN '' ELSE R.SumTotal END
FROM
SumResult AS R
Keep in mind that in these both cases, if there is no record to calculate the sum from, the result will be NULL.
EDIT: There is no point in adding '' inside your SUM, as it's converted to 0 to be able to sum. The solution is still the same as I posted before.
Change
SUM(CASE
WHEN sq2.loan_type = 'Pre-Payment' THEN sq2.user_count
ELSE ''
END ) AS Pre_Payment,
for
CASE
WHEN SUM(CASE WHEN sq2.loan_type = 'Pre-Payment' THEN sq2.user_count END) = 0 THEN ''
ELSE CONVERT(VARCHAR(100), SUM(CASE WHEN sq2.loan_type = 'Pre-Payment' THEN sq2.user_count)) END AS Pre_Payment,
Just try this ( use isnull again ):
Select isnull(Sum(isnull(a,0)),0) from table_;
I used table_ instead of table, because table is a reserved keyword.
SQL Fiddle Demo

How to do a SUM() inside a case statement in SQL server

I want to add some calculation inside my case statement to dynamically create the contents of a new column but I get the error:
Column 'Test1.qrank' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
This is the code I'm working on
case
when test1.TotalType = 'Average' then Test2.avgscore
when test1.TotalType = 'PercentOfTot' then (cnt/SUM(test1.qrank))
else cnt
end as displayscore
I did try to group but it didn't work.
Any hints?
The error you posted can happen when you're using a clause in the GROUP BY statement without including it in the select.
Example
This one works!
SELECT t.device,
SUM(case when transits.direction = 1 then 1 else 0 end) ,
SUM(case when transits.direction = 0 then 1 else 0 end) from t1 t
where t.device in ('A','B') group by t.device
This one not (omitted t.device from the select)
SELECT
SUM(case when transits.direction = 1 then 1 else 0 end) ,
SUM(case when transits.direction = 0 then 1 else 0 end) from t1 t
where t.device in ('A','B') group by t.device
This will produce your error complaining that I'm grouping for something that is not included in the select
Please, provide all the query to get more support.
You could use a Common Table Expression to create the SUM first, join it to the table, and then use the WHEN to to get the value from the CTE or the original table as necessary.
WITH PercentageOfTotal (Id, Percentage)
AS
(
SELECT Id, (cnt / SUM(AreaId)) FROM dbo.MyTable GROUP BY Id
)
SELECT
CASE
WHEN o.TotalType = 'Average' THEN r.avgscore
WHEN o.TotalType = 'PercentOfTot' THEN pt.Percentage
ELSE o.cnt
END AS [displayscore]
FROM PercentageOfTotal pt
JOIN dbo.MyTable t ON pt.Id = t.Id
If you're using SQL Server 2005 or above, you can use the windowing function SUM() OVER ().
case
when test1.TotalType = 'Average' then Test2.avgscore
when test1.TotalType = 'PercentOfTot' then (cnt/SUM(test1.qrank) over ())
else cnt
end as displayscore
But it'll be better if you show your full query to get context of what you actually need.

Subselect Query Improvement

How can I improve the SQL query below (SQL Server 2008)? I want to try to avoid sub-selects, and I'm using a couple of them to produce results like this
StateId TotalCount SFRCount OtherCount
---------------------------------------------------------
AZ 102 50 52
CA 2931 2750 181
etc...
SELECT
StateId,
COUNT(*) AS TotalCount,
(SELECT COUNT(*) AS Expr1 FROM Property AS P2
WHERE (PropertyTypeId = 1) AND (StateId = P.StateId)) AS SFRCount,
(SELECT COUNT(*) AS Expr1 FROM Property AS P3
WHERE (PropertyTypeId <> 1) AND (StateId = P.StateId)) AS OtherCount
FROM Property AS P
GROUP BY StateId
HAVING (COUNT(*) > 99)
ORDER BY StateId
This may work the same, hard to test without data
SELECT
StateId,
COUNT(*) AS TotalCount,
SUM(CASE WHEN PropertyTypeId = 1 THEN 1 ELSE 0 END) as SFRCount,
SUM(CASE WHEN PropertyTypeId <> 1 THEN 1 ELSE 0 END) as OtherCount
FROM Property AS P
GROUP BY StateId
HAVING (COUNT(*) > 99)
ORDER BY StateId
Your alternative is a single self-join of Property using your WHERE conditions as a join parameter. The OtherCount can be derived by subtracting the TotalCount - SFRCount in a derived query.
Another alternative would be to use the PIVOT function like this:
SELECT StateID, [1] + [2] AS TotalCount, [1] AS SFRCount, [2] AS OtherCount
FROM Property
PIVOT ( COUNT(PropertyTypeID)
FOR PropertyTypeID IN ([1],[2])
) AS pvt
WHERE [1] + [2] > 99
You would need to add an entry for each property type which could be daunting but it is another alternative. Scott has a great answer.
If PropertyTypeId is not null then you could do this with a single join. Count is faster than Sum. But is Count plus Join faster than Sum. The test case below mimics your data. docSVsys has 800,000 rows and there are about 300 unique values for caseID. The Count plus Join in this test case is slightly faster than the Sum. But if I remove the with (nolock) then Sum is about 1/4 faster. You would need to test with your data.
select GETDATE()
go;
select caseID, COUNT(*) as Ttl,
SUM(CASE WHEN mimeType = 'message/rfc822' THEN 1 ELSE 0 END) as SFRCount,
SUM(CASE WHEN mimeType <> 'message/rfc822' THEN 1 ELSE 0 END) as OtherCount,
COUNT(*) - SUM(CASE WHEN mimeType = 'message/rfc822' THEN 1 ELSE 0 END) as OtherCount2
from docSVsys with (nolock)
group by caseID
having COUNT(*) > 1000
select GETDATE()
go;
select docSVsys.caseID, COUNT(*) as Ttl
, COUNT(primaryCount.sID) as priCount
, COUNT(*) - COUNT(primaryCount.sID) as otherCount
from docSVsys with (nolock)
left outer join docSVsys as primaryCount with (nolock)
on primaryCount.sID = docSVsys.sID
and primaryCount.mimeType = 'message/rfc822'
group by docSVsys.caseID
having COUNT(*) > 1000
select GETDATE()
go;