sql to sum rows of booleans into rows - sql

I'm trying to work out if it's possible to do the following transformation in SQL:
+--------+--------+--------+
|POLICY_1|POLICY_2|POLICY_3|
+--------+--------+--------+
|T |T |F |
+--------+--------+--------+
|F |T |F |
+--------+--------+--------+
|T |T |T |
+--------+--------+--------+
Is it possible to query this table and end up with a result set that looks like:
+------+-----+
|POLICY|COUNT|
+------+-----+
|1 |2 |
+------+-----+
|2 |3 |
+------+-----+
|3 |1 |
+------+-----+
I'm wondering in general SQL terms, but incase it matters I'm using Postgres (9.2)

Union All, Aggregate and CASE Version
select 1 as POLICY, SUM(case when POLICY_1 = 'T' THEN 1 ELSE 0 end) as COUNT
from POLICIES
union all
select 2 as POLICY, SUM(case when POLICY_2 = 'T' THEN 1 ELSE 0 end) as COUNT
from POLICIES
union all
select 3 as POLICY, SUM(case when POLICY_3 = 'T' THEN 1 ELSE 0 end) as COUNT
from POLICIES
*Unpivot Version: *(MicroSoft T-SQL specific)
If you insist, to convert a row to columns, you can use PIVOT/UNPIVOT functionality.
SELECT ROW_NUMBER() OVER (ORDER BY PolicyName) AS Row, *
FROM ( select SUM(CASE WHEN Policy_1 = 'T' THEN 1 ELSE 0 END) as Policy_1,
SUM(CASE WHEN Policy_2 = 'T' THEN 1 ELSE 0 END) as Policy_2,
SUM(CASE WHEN Policy_3 = 'T' THEN 1 ELSE 0 END) as Policy_3
from POLICIES
)p
UNPIVOT ( T_Count FOR PolicyName in ([Policy_1], [Policy_2], [Policy_3]))unpvt
Unnest version ( postgre sql specific )All credits go to Francis, topic caster, i just post it here.
select
UNNEST((select array_agg(generate_series)
from generate_series(1,3))) as policy_name,
UNNEST(array[
sum(case when policy_1 = 't' then 1 else 0 end),
sum(case when policy_2 = 't' then 1 else 0 end),
sum(case when policy_3 = 't' then 1 else 0 end)
]) as count from POLICY

As suggested,
here is the UNNEST version for Postgresql:
select
UNNEST((select array_agg(generate_series) from generate_series(1,3))) as policy_name,
UNNEST(array[
sum(case when policy_1 = 't' then 1 else 0 end),
sum(case when policy_2 = 't' then 1 else 0 end),
sum(case when policy_3 = 't' then 1 else 0 end)
]) as count from POLICY

Related

USE ELSE 0 doesn't work as expected in 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:

HIVE Pivot and count

I have a table that I am trying to figure out how to pivot and count based on these.
This example may not be very suitable, but the result is exactly what I want.
Example input:
name |chinese|math|english
tom |A |A |B
tom |B |A |C
peter|B |C |C
peter|A |B |C
Example output:
name |object |A|B|C
tom |chinese|1|1|0
tom |math |2|0|0
tom |english|0|1|1
peter|chinese|1|1|0
peter|math |0|1|1
peter|english|0|0|2
Use UNION ALL with aggregation.
Demo:
with your_table as (
select stack(4,
'tom' ,'A','A','B',
'tom' ,'B','A','C',
'peter','B','C','C',
'peter','A','B','C'
) as (name,chinese,math,english)
)
select name, 'chinese' as object,
count(case when chinese='A' then 1 end) as A,
count(case when chinese='B' then 1 end) as B,
count(case when chinese='C' then 1 end) as C
from your_table
group by name
UNION ALL
select name, 'math' as object,
count(case when math='A' then 1 end) as A,
count(case when math='B' then 1 end) as B,
count(case when math='C' then 1 end) as C
from your_table
group by name
UNION ALL
select name, 'english' as object,
count(case when english='A' then 1 end) as A,
count(case when english='B' then 1 end) as B,
count(case when english='C' then 1 end) as C
from your_table
group by name;
Result:
name object a b c
peter chinese 1 1 0
tom chinese 1 1 0
peter math 0 1 1
tom math 2 0 0
peter english 0 0 2
tom english 0 1 1

Conditional count when case = 1, DB2

I'm currently trying to figure out the best way to do a conditional count as an alias in DB2 for Iseries. The below values represent job statuses where a job can be created, completed and cancelled so any one job will possibly have multiple status codes attached to it.
However, for my final value, I'm trying to get a count of jobs that only have the created status so that I can show how many are still open jobs. Basically looking for cases where the count for the created case = 1, but the below fails at the '='
SELECT
COUNT(CASE A1.JOB WHEN = 'CREATED' THEN 1 END) AS CREATED,
COUNT(CASE A1.JOB WHEN = 'CANCELLED' THEN 1 END) AS CANCELLED,
COUNT(CASE WHEN A1.JOB 'CREATED' = 1 then 1 END) AS OPEN
FROM SCHEMA.TABLE A1;
sample data and results:
Job ID | Status_code
-------------------------
123 'CREATED'
123 'COMPLETED'
521 'CREATED'
521 'CANCELLED'
645 'CREATED'
Results:
JOB | CREATED | CANCELLED | OPEN
-------------------------------------------
123 1 0 0
521 1 1 0
645 1 0 1
Assuming that the only "close" status is 'CANCELLED', you can use not exists like this:
select count(*)
from schema.table t
where t.status_code = 'CREATED' and
not exists (select 1
from schema.table t2
where t2.job = t.job and
t2.status_code in ('CANCELLED', 'COMPLETED', 'DELETED')
);
If you want multiple counts, then filtering like this does not work. So aggregate by job first:
select sum(is_created) as num_created,
sum(is_cancelled) as num_cancelled,
sum(is_created * (1 - is_cancelled) * (1 - is_completed) * (1 - is_deleted)) as open
from (select job,
max(case when status_code = 'CREATED' then 1 else 0 end) as is_created,
max(case when status_code = 'CANCELLED' then 1 else 0 end) as is_cancelled,
max(case when status_code = 'COMPLETED' then 1 else 0 end) as is_completed,
max(case when status_code = 'DELETED' then 1 else 0 end) as is_deleted
from t
group by job
) j
The following returns the result you need:
WITH TAB (Job_ID, JOB) AS
(
VALUES
(123, 'CREATED')
, (123, 'COMPLETED')
, (521, 'CREATED')
, (521, 'CANCELLED')
, (645, 'CREATED')
)
SELECT
Job_ID
, COUNT(CASE A1.JOB WHEN 'CREATED' THEN 1 END) AS CREATED
, COUNT(CASE A1.JOB WHEN 'CANCELLED' THEN 1 END) AS CANCELLED
, CASE
WHEN NULLIF(COUNT(1), 0) = COUNT(CASE A1.JOB WHEN 'CREATED' then 1 END)
THEN 1
ELSE 0
END AS OPEN
FROM TAB A1
GROUP BY JOB_ID;
With conditional aggregation:
SELECT
JobID,
MAX(CASE Status_code WHEN 'CREATED' THEN 1 ELSE 0 END) AS CREATED,
MAX(CASE Status_code WHEN 'CANCELLED' THEN 1 ELSE 0 END) AS CANCELLED,
MIN(CASE WHEN Status_code <> 'CREATED' THEN 0 ELSE 1 END) AS OPEN
FROM tablename
GROUP BY JobID
See the demo.
Results:
> JobID | CREATED | CANCELLED | OPEN
> ----: | ------: | --------: | ---:
> 123 | 1 | 0 | 0
> 521 | 1 | 1 | 0
> 645 | 1 | 0 | 1
Assuming valid close status is either "COMPLETED" or "CANCELLED", you can try following SQL.
SELECT
A1.JobID,
sum(CASE WHEN A1.Status_code = 'CREATED' THEN 1 ELSE 0 END) AS CREATED,
sum(CASE WHEN A1.Status_code = 'CANCELLED' THEN 1 ELSE 0 END) AS CANCELLED,
(
SUM(CASE WHEN A1.Status_code = 'CREATED' THEN 1 ELSE 0 END)
- sum(CASE WHEN A1.Status_code = 'CANCELLED' THEN 1 ELSE 0 END)
- sum(CASE WHEN A1.Status_code = 'COMPLETED' THEN 1 ELSE 0 END)
) AS OPEN
FROM SCHEMA.TABLE A1
GROUP BY A1.JobID

Build String depending on Column values [Oracle SQL]

I have this table:
ID | UNIT| CODE
2 | A | bit0
2 | A | bit2
1 | B | bit2
2 | B | bit7
1 | B | bit5
1 | C | bit7
I wonder how can I group the bits depending on the ID and UNIT? For example, the output for the source table above would be:
ID|UNIT| CODE
2 | A |00000101
1 | B |00100100
1 | C |10000000
2 | B |10000000
is a CASE statement + concatenating 1's and 0's the best option here? I really don't think so but that's the only solution I can find at the moment.
Thanks!
You could use the bit_or aggregation function in MySQL, but it is not available in Oracle.
If you never have more than one bit set, you could do
select id, unit,
sum(case when code = 'bit0' then 1
when code = 'bit1' then 2
when code = 'bit2' then 4
when code = 'bit3' then 8
when code = 'bit4' then 16
when code = 'bit5' then 32
when code = 'bit6' then 64
when code = 'bit7' then 128
else 0
end)
from table t
group by id, unit;
But that's not really a satisfying answer.
Instead, you need to spread the values out, aggregate, and bring them back. Here is a method where the result is a string:
select id, unit,
(max(case when code = 'bit0' then 1 else 0 end) ||
max(case when code = 'bit1' then 1 else 0 end) ||
max(case when code = 'bit2' then 1 else 0 end) ||
max(case when code = 'bit3' then 1 else 0 end) ||
max(case when code = 'bit4' then 1 else 0 end) ||
max(case when code = 'bit5' then 1 else 0 end) ||
max(case when code = 'bit6' then 1 else 0 end) ||
max(case when code = 'bit7' then 1 else 0 end)
)
from table t
group by id, unit;
And here is the method with the result as an integer:
select id, unit,
(max(case when code = 'bit0' then 1 else 0 end) +
max(case when code = 'bit1' then 2 else 0 end) +
max(case when code = 'bit2' then 4 else 0 end) +
max(case when code = 'bit3' then 8 else 0 end) +
max(case when code = 'bit4' then 16 else 0 end) +
max(case when code = 'bit5' then 32 else 0 end) +
max(case when code = 'bit6' then 64 else 0 end) +
max(case when code = 'bit7' then 128 else 0 end)
)
from table t
group by id, unit;
You can see the latter work here at SQL 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