USE ELSE 0 doesn't work as expected in SQL - sql

I have the following SQL query:
SELECT
modal_text,
COUNT(CASE WHEN ab_group = "control" THEN 1 ELSE 0 END)
FROM
onboarding_modals
GROUP BY
1
ORDER BY
1;
This doesn't work as expected (it will count more than expected), but when I remove the ELSE 0 in aggregate function, it works as expected:
SELECT
modal_text, COUNT(CASE WHEN ab_group = "control" THEN 1 END)
FROM
onboarding_modals
GROUP BY
1
ORDER BY
1;
Could someone explain me why having the ELSE 0 will make it count more data than it should be?
*It will also work if I use ELSE NULL

Because a COUNT(SomeColumn) doesn't count the NULL's in a column.
COUNT(1) or COUNT(*) count the rows.
And so does a COUNT(CASE WHEN x=1 THEN 1 ELSE 0 END)
This has no NULL's to ignore, because it's either 1 or 0.
But a CASE WHEN x=1 THEN 1 END
is just the implicit shorter syntax for
CASE WHEN x=1 THEN 1 ELSE NULL END
So it's normal to COUNT without the ELSE.
COUNT(DISTINCT CASE WHEN x=1 THEN t.ID END)
If you do want to use an ELSE, then do it with a SUM
SUM(CASE WHEN x=1 THEN 1 ELSE 0 END)

Use SUM() instead of COUNT(), as in:
SELECT
modal_text,
SUM(CASE WHEN ab_group = "control" THEN 1 ELSE 0 END)
FROM
onboarding_modals
GROUP BY
1
ORDER BY
1;

Could someone explain me why having the ELSE 0 will make it count more data than it should be?
Becasue COUNT(CASE WHEN ab_group = "control" THEN 1 ELSE 0 END) is different to COUNT(CASE WHEN ab_group = "control" THEN 1 END) let's see a sample below
we can see there will be count when we use count(1) or count(0) except count(null) count function will not be count when the value is null
Query 1:
SELECT COUNT(1)
| COUNT(1) |
|----------|
| 1 |
SELECT COUNT(0)
| COUNT(0) |
|----------|
| 1 |
SELECT COUNT(NULL)
| COUNT(NULL) |
|-------------|
| 0 |
Query 2:
SELECT SUM(1)
| SUM(1) |
|--------|
| 1 |
SELECT SUM(0)
| SUM(0) |
|--------|
| 0 |
SELECT SUM(NULL)
| SUM(NULL) |
|-----------|
| (null) |
Results:

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;

One select statement with multiple Group BY on the same column

I have 'TABLE_A' like this:
TVA Amount InvoiceID
----------------------
22 | 10.00 | inv-12
22 |-14.00 | inv-13
25 | 24.00 | inv-14
25 |-36.00 | inv-15
25 |-25.00 | inv-16
25 | 18.50 | inv-17
24 |-16.50 | inv-18
24 | 10.00 | inv-19
The goal is to make a groupBy TABLE_A.TVA value and by SUM(TABLE_A.Amount) > 0 and SUM(TABLE_A.Amount) <0, to get finally the sum of all positive Amounts grouped by TVA value, and the sum of all negative amounts grouped by their TVA value also
my Query is like this:
SELECT TABLE_A.TVA, TABLE_A.InvoiceID,
SUM(TABLE_A.Amount) AS [PositiveTotalAMount],
SUM(TABLE_A.Amount) AS [NegativeTotalAMount]
FROM TABLE_A
GROUP BY TABLE_A.TVA
HAVING SUM(TABLE_A.TVA) > 0
MY question is how to add the second grouping by on negative values, because here i group only on SUM() > 0
You can use a CASE (Transact-SQL) expression:
SELECT
A.TVA,
A.InvoiceID,
SUM(CASE WHEN A.Amount > 0 THEN A.Amount ELSE 0 END) AS [PositiveTotalAMount],
SUM(CASE WHEN A.Amount < 0 THEN A.Amount ELSE 0 END) AS [NegativeTotalAMount]
FROM
TABLE_A A
GROUP BY
A.TVA,
A.InvoiceID
Also, you must include all columns from the select list that are not aggregated to the GROUP BY list. InvoiceID was missing.
I also use the alias A for TABLE_A to increase readability.
With conditional aggregation:
SELECT
TABLE_A.TVA,
SUM(CASE WHEN TABLE_A.Amount > 0 THEN TABLE_A.Amount ELSE 0 END) AS [PositiveTotalAMount],
SUM(CASE WHEN TABLE_A.Amount < 0 THEN TABLE_A.Amount ELSE 0 END) AS [NegativeTotalAMount]
FROM TABLE_A
GROUP BY TABLE_A.TVA
If you want to include the column TABLE_A.InvoiceID in the grouping then:
SELECT
TABLE_A.TVA,
TABLE_A.InvoiceID,
SUM(CASE WHEN TABLE_A.Amount > 0 THEN TABLE_A.Amount ELSE 0 END) AS [PositiveTotalAMount],
SUM(CASE WHEN TABLE_A.Amount < 0 THEN TABLE_A.Amount ELSE 0 END) AS [NegativeTotalAMount]
FROM TABLE_A
GROUP BY TABLE_A.TVA, TABLE_A.InvoiceID

SQL count of values from different columns

I've got a Dog table. Each dog has Breed and can have 0 to 2 photos. I need to recieve count of photos of all dogs for each breed: table with BreedId and matching PhotosCount. So result table should be:
BreedID|PhotosCount
-------------------
1 |3
-------------------
2 |1
-------------------
This should do the trick:
SELECT BreedID AS B, COUNT(Photo1) + COUNT(Photo2) AS C
FROM Dog
GROUP BY BreedID
COUNT aggregate function simply doesn't take into consideration NULL values. If, for a specific BreedID, all values of either Photo1 or Photo2 are NULL, then COUNT returns 0.
This should work in single scan:
SELECT
BreedID,
SUM(CASE WHEN Photo1 IS NOT NULL THEN 1 ELSE 0 END)
+ SUM(CASE WHEN Photo2 IS NOT NULL THEN 1 ELSE 0 END) [Count]
FROM Table
GROUP BY BreedID
Use Group By and SUM Of Photo1 and Photo2:
Note: If you wants the output for each dog you have to include DogId in group clause.
;WITH T AS
(
SELECT
BreedId,
SUM (CASE ISNULL(Photo1,0) WHEN 1 THEN 1 ELSE 0 END) AS Photo1,
SUM (CASE ISNULL(Photo2,0) WHEN 1 THEN 1 ELSE 0 END) AS Photo2
FROM TableName
Group By BreedId
)
SELECT
BreedId,
SUM(Photo1+Photo2) AS TotalPhoto
FROM T
Or Simply
SELECT
BreedId,
SUM (CASE ISNULL(Photo1,0) WHEN 1 THEN 1 ELSE 0 END + CASE ISNULL(Photo2,0) WHEN 1 THEN 1 ELSE 0 END) AS TotalPhoto
FROM TableName
Group By BreedId
SELECT BreedID AS Breed, COUNT(Photo1) + COUNT(Photo2) AS #ofPhotos
FROM Dog
GROUP BY BreedID;

SQL separate the count of one column

I have a SQL table that contains three columns:
userId
userName
item
and I created this SQL query which will count all the items types of one user:
select
count(ItemID) as 'count of all items types',
userId,
userName
from
userTable
where
ItemID in (2, 3, 4)
and userId = 1
group by
userId, userName
The result will be like this:
+--------+----------+--------------------------+
| userId | userName | count of all items types |
+--------+----------+--------------------------+
| 1 | kim | 25 |
and I am looking for a way to separate the counting of itemes types, so the result should be like this:
+--------+----------+----------------+----------------+-----------------+
| userId | userName | count of item1 | count of item2 | count of item3 |
+--------+----------+----------------+----------------+-----------------+
| 1 | kim | 10 | 10 | 5 |
SELECT
userID,
userName,
SUM(CASE WHEN ItemID = 2 THEN 1 ELSE 0 END) AS count_of_item1,
SUM(CASE WHEN ItemID = 3 THEN 1 ELSE 0 END) AS count_of_item2,
SUM(CASE WHEN ItemID = 4 THEN 1 ELSE 0 END) AS count_of_item3
FROM
My_Table
GROUP BY
userID,
userName
This is called conditional aggregation. Use CASE for this.
With COUNT:
select
count(case when ItemID = 1 then 1 end) as count_item1,
count(case when ItemID = 2 then 1 end) as count_item2,
count(case when ItemID = 3 then 1 end) as count_item3
...
(then 1 could also be anything else except null, e.g. then 'count me'. This works because COUNT counts non-null values and when omitting the ELSE in CASE WHEN you get null. You could also explicitly add else null.)
Or with SUM:
select
sum(case when ItemID = 1 then 1 else 0 end) as count_item1,
sum(case when ItemID = 2 then 1 else 0 end) as count_item2,
sum(case when ItemID = 3 then 1 else 0 end) as count_item3
...
This is how you would do it :
select userId,
username,
SUM(CASE WHEN ItemID = '2' THEN 1 ELSE 0 END) AS Item2-Cnt,
SUM(CASE WHEN ItemID = '3' THEN 1 ELSE 0 END) AS Item3-Cnt,
SUM(CASE WHEN ItemID = '4' THEN 1 ELSE 0 END) AS Item4-Cnt
FROM userTable
GROUP BY userID, userName

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