Select sum based on value of other column - sql

I have a table with values like
ID | CODE | QUANTITY
====================
1 | 2 | 20
2 | 2 | 40
3 | 5 | 10
4 | 6 | 15
5 | 5 | 20
6 | 6 | 50
7 | 6 | 10
8 | 7 | 20
9 | 8 | 100
I have a requirement to get the sum of all quantities with "CODE" = 2. However, if the sum is 0
then return the sum of all quantities where "CODE" in (5,6). The idea is to ignore all other codes except 2, 5, and 6, with 2 as the first preference for sum.
I have tried this
WITH CTE AS(
SELECT
SUM(CASE WHEN CODE = '2' THEN QUANTITY ELSE 0 END) AS QUANTITY1,
SUM(CASE WHEN CODE IN ('5', '6') THEN QUANTITY ELSE 0 END) AS QUANTITY2
FROM TABLE1
)
SELECT CASE
WHEN QUANTITY1 <> 0 THEN QUANTITY1
ELSE QUANTITY2
END
FROM CTE
It does work but I feel it can be improved and can be done in minimum steps. How can I improve it?
Edit1: The value of QUANTITY column can be 0 in TABLE1
Edit2: sqlfiddle

For the sum of quantities with CODE = '2' use ELSE 0 in the CASE expression and NULLIF(), so that the result is NULL even if the sum is 0:
SELECT COALESCE(
NULLIF(SUM(CASE WHEN CODE = '2' THEN QUANTITY ELSE 0 END), 0),
SUM(CASE WHEN CODE IN ('5', '6') THEN QUANTITY END)
)
FROM TABLE1
You can use ELSE for quantities with CODE IN ('5', '6') too:
SELECT COALESCE(
NULLIF(SUM(CASE WHEN CODE = '2' THEN QUANTITY ELSE 0 END), 0),
SUM(CASE WHEN CODE IN ('5', '6') THEN QUANTITY ELSE 0 END)
)
FROM TABLE1
See the demo.

If quantity is always greater than 0 in the underlying table, you can use COALESCE():
SELECT COALESCE(SUM(CASE WHEN CODE = '2' THEN QUANTITY END) AS QUANTITY1,
SUM(CASE WHEN CODE IN ('5', '6') THEN QUANTITY END),
0) AS QUANTITY2
FROM TABLE1

Related

Sum rows with same Id based on type and exlude where SUM = 0

I have this table MOVEMENTS:
Id | FatherId | MovementType | Quantity |
=================================================
1 | A | IN | 10 |
2 | A | IN | 5 |
3 | A | OUT | 5 |
4 | B | IN | 10 |
5 | B | OUT | 10 |
6 | C | IN | 5 |
I'm trying to get all the FatherId with the SUM of IN - OUT Movments > 0.
So the result would be:
FatherId | Total |
=========================
A | 10 |
C | 5 |
FatherId = B not showing because
SUM(MovementType = IN) - SUM (MovementType = OUT) = 0
I tried with
SELECT FatherId,
(SELECT (
SUM(CASE WHEN MovementType = 'IN' THEN Quantity ELSE 0 END) -
SUM(CASE WHEN MovementType = 'OUT' THEN Quantity ELSE 0 END)
)) AS Total
FROM MOVEMENTS
GROUP BY FatherId
ORDER BY FatherId
That gives me the result grouped by FatherId, but I'm not able to filter with Total > 0, and also, I'm unable to put this query in a Subquery like:
SELECT * FROM MOVEMENTS WHERE FatherId IN (SELECT ....) OFFSET ... FETCH NEXT ... ROWS ONLY
Is this doable without a stored procedure?
Thank you for any help
Why are you using a subquery? This should do what you want:
SELECT FatherId,
(SUM(CASE WHEN MovementType = 'IN' THEN Quantity ELSE 0 END) -
SUM(CASE WHEN MovementType = 'OUT' THEN Quantity ELSE 0 END)
) AS Total
FROM MOVEMENTS
GROUP BY FatherId
HAVING (SUM(CASE WHEN MovementType = 'IN' THEN Quantity ELSE 0 END) -
SUM(CASE WHEN MovementType = 'OUT' THEN Quantity ELSE 0 END)
) > 0;
You can also simplify the logic to use a single SUM():
SELECT FatherId,
SUM(CASE WHEN MovementType = 'IN' THEN Quantity
WHEN MovementType = 'OUT' THEN - Quantity
ELSE 0
END) AS Total
FROM MOVEMENTS
GROUP BY FatherId
HAVING SUM(CASE WHEN MovementType = 'IN' THEN Quantity
WHEN MovementType = 'OUT' THEN - Quantity
ELSE 0
END) > 0
ORDER BY FatherId;

SQL query sum of total corresponding rows

I have two tables as below. Caseid from first table is referenced in second table along with accidents. What I am trying to get total different accidents for a case type. Below two tables I documented sample data and expected result.
Table case:
caseId CaseType
1 AB
2 AB
3 AB
4 CD
5 CD
6 DE
Table CaseAccidents:
AccidentId caseID AccidentRating
1 1 High
2 1 High
3 1 Medium
4 1 LOW
5 2 High
6 2 Medium
7 2 LOW
8 5 High
9 5 High
10 5 Medium
11 5 LOW
Result should look like:
CaseType TotalHIghrating TotalMediumRating TotalLOWRating
AB 3 2 2
CD 2 1 1
DE 0 0 0
To get the sum of every rating, you can Use a SUM(CASE WHEN) clause, adding 1 by every record that match the rating.
In your question, you have pointed out that you want to see all distinct CaseType, you can get it by using a RIGHT JOIN, this will include all records of case table.
select case.CaseType,
sum(case when caseAccidents.AccidentRating = 'High' then 1 else 0 end) as TotalHighRating,
sum(case when caseAccidents.AccidentRating = 'Medium' then 1 else 0 end) as TotalMediumRating,
sum(case when caseAccidents.AccidentRating = 'LOW' then 1 else 0 end) as TotalLowRating
from caseAccidents
right join case on case.caseId = caseAccidents.caseID
group by case.CaseType;
+----------+-----------------+-------------------+----------------+
| CaseType | TotalHighRating | TotalMediumRating | TotalLowRating |
+----------+-----------------+-------------------+----------------+
| AB | 3 | 2 | 2 |
+----------+-----------------+-------------------+----------------+
| CD | 2 | 1 | 1 |
+----------+-----------------+-------------------+----------------+
| DE | 0 | 0 | 0 |
+----------+-----------------+-------------------+----------------+
Check it: http://rextester.com/MCGJA9193
Have you use case in a select clause before?
select C.CaseType,
sum(case when CA.AccidentRating = 'High' then 1 else 0 end)
from Case C join CaseAccidents CA on C.CaseId = CA.CaseId
group by C.CaseType
Please see this. Sample query of the table and also that result
create table #case(caseid int,casetype varchar(5))
insert into #case (caseid,casetype)
select 1,'AB' union all
select 2,'AB' union all
select 3,'AB' union all
select 4,'CD' union all
select 5,'CD' union all
select 6,'DE'
create table #CaseAccidents(AccidentId int, CaseId int,AccidentRating varchar(10))
insert into #CaseAccidents(AccidentId, CaseId, AccidentRating)
select 1,1,'High' union all
select 2,1,'High' union all
select 3,1,'Medium' union all
select 4,1,'Low' union all
select 5,2,'High' union all
select 6,2,'Medium' union all
select 7,2,'Low' union all
select 8,5,'High' union all
select 9,5,'High' union all
select 10,5,'Medium' union all
select 11,5,'Low'
My script
select c.casetype,
sum(case when ca.AccidentRating='High' then 1 else 0 end) as TotalHighRating,
sum(case when ca.AccidentRating='Medium' then 1 else 0 end) as TotalMediumRating,
sum(case when ca.AccidentRating='Low' then 1 else 0 end) as TotalLowRating
from #case c
Left join #CaseAccidents ca
on c.Caseid=ca.Caseid
group by c.casetype
Hope This could help!
Another approach using Pivot operator
SELECT casetype,
[High],
[Medium],
[Low]
FROM (SELECT c.casetype,
AccidentRating
FROM case c
LEFT JOIN CaseAccidents ca
ON ca.CaseId = c.caseid)a
PIVOT (Count(AccidentRating)
FOR AccidentRating IN ([High],
[Medium],
[Low]) ) p
Try This code once.
select casetype,
sum(case when ca.AccidentRating='High' then 1 else 0 end ) as TotalHIghrating,
sum(case when ca.AccidentRating='Medium' then 1 else 0 end ) as TotalMediumRating ,
sum(case when ca.AccidentRating='Low' then 1 else 0 end ) as TotalLOWRating
from #case c
left join #CaseAccidents ca on c.caseid=ca.CaseId
group by casetype

Count in sql with group by

Table name: mytable
Id username pizza-id pizza-size Quantity order-time
--------------------------------------------------------------
1 xyz 2 9 2 09:00 10/08/2014
2 abc 1 11 3 17:45 13/07/2014
This is mytable which has 6 columns. Id is int, username is varchar, order-time is datetime and rest are of integer datatype.
How to count the number of orders with the following pizza quantities: 1, 2, 3, 4, 5, 6,7 and above 7?
Using a T-SQL query.
It would be very helpful If any one could help to me find the solution.
Try this !
SELECT COUNT(ID),CASE WHEN QUANTITY<7 THEN QUANTITY ELSE 'ABOVE7' END AS QUANTITIES
FROM mytable
GROUP BY CASE WHEN QUANTITY<7 THEN QUANTITY ELSE 'ABOVE7' END
Try
Select CASE WHEN Quantity > 7 THEN 'OVER7' ELSE Cast(quantity as varchar) END Quantity,
COUNT(ID) NoofOrders
from mytable
GROUP BY CASE WHEN Quantity > 7 THEN 'OVER7' ELSE Cast(quantity as varchar) END
or
Select
SUM(Case when Quantity = 1 then 1 else 0 end) Orders1,
SUM(Case when Quantity = 2 then 1 else 0 end) Orders2,
SUM(Case when Quantity = 3 then 1 else 0 end) Orders3,
SUM(Case when Quantity = 4 then 1 else 0 end) Orders4,
SUM(Case when Quantity = 5 then 1 else 0 end) Orders5,
SUM(Case when Quantity = 6 then 1 else 0 end) Orders6,
SUM(Case when Quantity = 7 then 1 else 0 end) Orders7,
SUM(Case when Quantity > 7 then 1 else 0 end) OrdersAbove7
from mytable
If the requirement is like count the number of orders with different pizza quantities and represent count of orders as : 1, 2, 3, 4, 5, 6,7 and consider all above order counts in new category : 'above 7' then you can use window function as:
select case when totalorders < = 7 then cast(totalorders as varchar(10))
else 'Above 7' end as totalorders
, Quantity
from
(
select distinct count(*) over (partition by Quantity order by Quantity asc)
as totalorders,
Quantity
from mytable
) T
order by Quantity
DEMO
Edit: if the requirement is like count the number of orders with pizza quantities: 1, 2, 3, 4, 5, 6,7 and consider all other pizza quantities in new category : 'above 7' then you can write as:
select distinct
count(*) over (
partition by Quantity order by Quantity asc
) as totalorders,
Quantity
from (
select
case when Quantity < = 7 then cast(Quantity as varchar(20))
else 'Above 7' end as Quantity, id
from mytable ) T
order by Quantity
DEMO

Aggregation based on another column values

Assume I have the following table:
+------------+-------------+
| Product_id | customer_id |
+------------+-------------+
| a | c1 |
| a | c2 |
| a | c3 |
| a | c4 |
| b | c1 |
| c | c1 |
| b | c2 |
| d | c2 |
+------------+-------------+
I want to find the number of (a, b, c) products purchases per customer and the number of (a, b, d) products purchases per customer. I tried to use COUNT with GROUP BY but I only managed to the find the number purchases of each customers FIDDLE. Do I need to use CASE WHEN or DECODE? How can I achieve that?
The expected output is something like:
+-------------+-------------+-------------+
| CUSTOMER_ID | ABC_PRODUCT | ABD_PRODUCT |
+-------------+-------------+-------------+
| c1 | 1 | 0 |
| c2 | 0 | 1 |
| c3 | 0 | 0 |
| c4 | 0 | 0 |
+-------------+-------------+-------------+
You can do this with a single aggregation and no subqueries. The key is using a nested case statement with aggregation to count each product for each customer. The following determines whether a customer has each "bundle":
SELECT CUSTOMER_ID,
(case when max(case when product_id = 'a' then 1 else 0 end) +
max(case when product_id = 'b' then 1 else 0 end) +
max(case when product_id = 'c' then 1 else 0 end) = 3
then 1
else 0
end) as ABC,
(case when max(case when product_id = 'a' then 1 else 0 end) +
max(case when product_id = 'b' then 1 else 0 end) +
max(case when product_id = 'd' then 1 else 0 end) = 3
then 1
else 0
end) as ABD
FROM CUSTOMERS_SALES
GROUP BY CUSTOMER_ID;
Now, your question is actually about the number of such purchases. So, I suppose a customer could purchase each item twice, and you would want them counted twice. If so, then the number is the least value of any counts. You can get this as well:
SELECT CUSTOMER_ID,
least(sum(case when product_id = 'a' then 1 else 0 end),
sum(case when product_id = 'b' then 1 else 0 end),
sum(case when product_id = 'c' then 1 else 0 end)
) as ABC,
least(sum(case when product_id = 'a' then 1 else 0 end),
sum(case when product_id = 'b' then 1 else 0 end),
sum(case when product_id = 'd' then 1 else 0 end)
) as ABD
FROM CUSTOMERS_SALES
GROUP BY CUSTOMER_ID;
Please try below query to find customer having the products a, b and c:
SELECT CUSTOMER_ID
FROM CUSTOMERS_SALES
WHERE PRODUCT_ID IN ('a', 'b', 'c')
GROUP BY CUSTOMER_ID
HAVING COUNT(DISTINCT PRODUCT_ID)=3
To get the count try:
SELECT COUNT(*) FROM(
SELECT CUSTOMER_ID
FROM CUSTOMERS_SALES
WHERE PRODUCT_ID IN ('a', 'b', 'd')
GROUP BY CUSTOMER_ID
HAVING COUNT(DISTINCT PRODUCT_ID)=3
)x
Based entirely off #TechDo's example:
SELECT DISTINCT CUSTOMER_ID,
DECODE((SELECT CUSTOMER_ID
FROM CUSTOMERS_SALES CS2
WHERE PRODUCT_ID IN ('A', 'B', 'C')
AND CS2.CUSTOMER_ID = CS.CUSTOMER_ID
GROUP BY CUSTOMER_ID
HAVING COUNT(DISTINCT PRODUCT_ID)=3), NULL, 0, 1) AS ABC_PRODUCT,
DECODE((SELECT CUSTOMER_ID
FROM CUSTOMERS_SALES CS2
WHERE PRODUCT_ID IN ('A', 'B', 'D')
AND CS2.CUSTOMER_ID = CS.CUSTOMER_ID
GROUP BY CUSTOMER_ID
HAVING COUNT(DISTINCT PRODUCT_ID)=3), NULL, 0, 1) AS ABD_PRODUCT
FROM CUSTOMERS_SALES CS
ORDER BY CUSTOMER_ID
SELECT CUSTOMERS_SALES.CUSTOMER_ID,NVL(MAX(abc.CUSTOMER_ID),0) as ABC_PRODUCT ,NVL(MAX(abd.CUSTOMER_ID),0) as ABD_PRODUCT FROM CUSTOMERS_SALES
LEFT JOIN
(SELECT CUSTOMER_ID
FROM CUSTOMERS_SALES
WHERE PRODUCT_ID IN ('a', 'b', 'd')
GROUP BY CUSTOMER_ID
HAVING COUNT(DISTINCT PRODUCT_ID)=3) abd
ON abd.CUSTOMER_ID=CUSTOMERS_SALES.CUSTOMER_ID
LEFT JOIN
(SELECT CUSTOMER_ID
FROM CUSTOMERS_SALES
WHERE PRODUCT_ID IN ('a', 'b', 'c')
GROUP BY CUSTOMER_ID
HAVING COUNT(DISTINCT PRODUCT_ID)=3) abc
ON abc.CUSTOMER_ID=CUSTOMERS_SALES.CUSTOMER_ID
GROUP BY CUSTOMERS_SALES.CUSTOMER_ID
ORDER BY CUSTOMERS_SALES.CUSTOMER_ID;
FIDDLE

Querying Counts on Field Values in a Single Table

I have a single product table with multiple fields which contain user evaluations of various attributes:
product | attr_1_eval | attr_2_eval | attr_3_eval
ABC | Correct | Incorrect | Null
DEF | Incorrect | Null | Null
XYZ | Undetermined | Null | Incorrect
123 | Null | Undetermined | Correct
456 | Incorrect | Correct | Correct
I need to write a query which totals up those attribute evaluations across all products (where not null):
evaluation | correct | incorrect | undetermined
attr_1 | 1 | 2 | 1
attr_2 | 1 | 1 | 1
attr_3 | 2 | 1 | 0
This SQL gets me part way there:
SELECT
SUM(CASE WHEN attr_1_eval = 'Correct' then 1 else 0 END) AS correct,
SUM(CASE WHEN attr_1_eval = 'Incorrect' then 1 else 0 END) AS incorrect,
SUM(CASE WHEN attr_1_eval = 'Undetermined' then 1 else 0 END) AS undetermined,
SUM(CASE WHEN attr_2_eval = 'Correct' then 1 else 0 END) AS correct,
...
FROM product
But it doesn't group attr_1, attr_2.. by rows with error counts in the columns (as in the desired result set above). I'm using Postgres but help in any flavor of SQL would be most welcome.
Could you do 3 unions?
SELECT
'attr_1' AS evaluation,
SUM(CASE WHEN attr_1_eval = 'Correct' then 1 else 0 END) AS correct,
SUM(CASE WHEN attr_1_eval = 'Incorrect' then 1 else 0 END) AS incorrect,
SUM(CASE WHEN attr_1_eval = 'Undetermined' then 1 else 0 END) AS undetermined
FROM product
UNION
SELECT
'attr_2' AS evaluation,
SUM(CASE WHEN attr_2_eval = 'Correct' then 1 else 0 END) AS correct,
SUM(CASE WHEN attr_2_eval = 'Incorrect' then 1 else 0 END) AS incorrect,
SUM(CASE WHEN attr_2_eval = 'Undetermined' then 1 else 0 END) AS undetermined
FROM product
UNION
SELECT
'attr_3' AS evaluation,
SUM(CASE WHEN attr_3_eval = 'Correct' then 1 else 0 END) AS correct,
SUM(CASE WHEN attr_3_eval = 'Incorrect' then 1 else 0 END) AS incorrect,
SUM(CASE WHEN attr_3_eval = 'Undetermined' then 1 else 0 END) AS undetermined
FROM product
It's not the most elegant/efficient solution probably but it should get what you want
It's a little brute force, and I hate the fact that it scans the table three times, but this does appear to get the desired output. I'm sorry I don't know PostGres, but this should work in Oracle:
select
Attribute_name,
Sum (correct) as Correct,
sum (incorrect) as Incorrect,
sum (undetermined) as Undetermined
from
(
select
'attr_1' as Attribute_Name,
decode (attr_1_eval, 'Correct', 1, 0) as correct,
decode (attr_1_eval, 'Incorrect', 1, 0) as incorrect,
decode (attr_1_eval, 'Undetermined', 1, 0) as undetermined
from product
union all
select
'attr_2',
decode (attr_2_eval, 'Correct', 1, 0),
decode (attr_2_eval, 'Incorrect', 1, 0),
decode (attr_2_eval, 'Undetermined', 1, 0)
from product
union all
select
'attr_3',
decode (attr_3_eval, 'Correct', 1, 0),
decode (attr_3_eval, 'Incorrect', 1, 0),
decode (attr_3_eval, 'Undetermined', 1, 0)
from product
)
group by Attribute_Name