how to create pivots to avoid ID duplicates? - mysql

I Have the following table(´information´)
ID | value
1 | A
1 | B
2 | A
3 | A
3 | B
3 | C
4 | A
How can I use a query to render the following table?
ID | A | B | C |
1 X x null
2 X null null
3 X X X
4 X null null
I have the following code:
select id, CASE WHEN value= 'A' THEN 'X'
ELSE NULL END AS A,
CASE WHEN value= 'B' THEN 'X'
ELSE NULL END AS B,
CASE WHEN value= 'C' THEN 'X'
ELSE NULL END AS C
FROM information group by id;
but I ot one row per Id as well.

You can use conditional aggregation:
SELECT id,
MAX(CASE WHEN value= 'A' THEN 'X' END) AS A,
MAX(CASE WHEN value= 'B' THEN 'X' END) AS B,
MAX(CASE WHEN value= 'C' THEN 'X' END) AS C
FROM information
GROUP BY id;
Demo here

You just need aggregation functions:
select id,
MAX(CASE WHEN value = 'A' THEN 'X' END) AS A,
MAX(CASE WHEN value = 'B' THEN 'X' END) AS B,
MAX(CASE WHEN value = 'C' THEN 'X' END) AS C
FROM information
group by id;

Related

SQL - sum of every combination

I have table with 3 columns : A, B and C. These columns can be true or false.
I want to get count of every possible combination.
Sample data:
CREATE TABLE `myTable` (
`id` mediumint(8) unsigned NOT NULL auto_increment,
`A` mediumint default NULL,
`B` mediumint default NULL,
`C` mediumint default NULL,
PRIMARY KEY (`id`)
) AUTO_INCREMENT=1;
INSERT INTO `myTable` (`A`,`B`,`C`) VALUES (0,0,1),(1,1,0),(0,0,0),(1,1,0),(1,0,0),(1,0,1),(0,0,1),(1,1,1),(0,1,0),(1,1,1);
INSERT INTO `myTable` (`A`,`B`,`C`) VALUES (1,0,1),(0,1,0),(1,1,1),(0,0,1),(1,0,0),(0,0,0),(0,0,1),(1,1,0),(0,0,0),(1,1,0);
INSERT INTO `myTable` (`A`,`B`,`C`) VALUES (1,1,0),(0,1,0),(1,1,1),(0,0,0),(1,1,0),(1,0,1),(1,1,1),(1,0,1),(1,1,1),(1,1,1);
INSERT INTO `myTable` (`A`,`B`,`C`) VALUES (0,1,0),(1,0,0),(0,1,0),(0,0,0),(0,0,0),(1,0,0),(1,0,1),(1,1,1),(0,0,1),(0,0,0);
INSERT INTO `myTable` (`A`,`B`,`C`) VALUES (1,1,1),(0,0,1),(1,1,0),(1,1,0),(1,0,0),(0,0,1),(0,1,1),(1,0,1),(1,0,0),(1,1,0);
INSERT INTO `myTable` (`A`,`B`,`C`) VALUES (1,1,1),(0,0,0),(1,0,1),(1,0,0),(1,0,0),(1,0,0),(0,0,1),(1,1,1),(0,1,1),(1,1,0);
INSERT INTO `myTable` (`A`,`B`,`C`) VALUES (0,1,1),(0,1,1),(0,1,0),(0,0,0),(0,1,0),(0,1,1),(0,1,1),(0,1,1),(0,1,0),(0,1,0);
INSERT INTO `myTable` (`A`,`B`,`C`) VALUES (0,1,1),(0,0,1),(0,1,0),(1,1,0),(0,0,0),(1,1,1),(1,1,0),(0,1,1),(1,0,1),(1,0,0);
INSERT INTO `myTable` (`A`,`B`,`C`) VALUES (0,1,0),(1,1,1),(0,1,0),(1,1,0),(1,0,1),(1,1,0),(0,1,0),(0,1,0),(0,1,0),(0,1,0);
INSERT INTO `myTable` (`A`,`B`,`C`) VALUES (1,1,0),(0,1,0),(1,1,1),(0,0,0),(1,0,0),(1,1,0),(1,0,1),(0,0,1),(1,0,1),(1,0,0);
Example result (from sample data):
combination: count
none: 11
A: 12
B: 17
C: 10
AB: 16
BC: 9
AC: 11
ABC: 14
Is this possible in one query? (MySQL)
This appears to be a simple count and Group by.
SELECT A, B, C, count(*)
FROM MyTable
GROUP BY A, B, C;
DEMO:
If you want you can show the string of values combined use concat and case...
SELECT concat(case when A = 1 then 'A' else '' end,
case when B = 1 then 'B' else '' end,
case when C = 1 then 'C' else '' end) as Combination
, count(*)
FROM MyTable
GROUP BY A, B, C
ORDER BY Combination;
or as Paul Spiegel shows in comments:
SELECT concat(left('A', A), left('B', B), left('C', C)) as Combination
, count(*)
FROM MyTable
GROUP BY A, B, C
ORDER BY Combination;
Giving us:
+----+-------------+----------+
| | Combination | count(*) |
+----+-------------+----------+
| 1 | | 11 |
| 2 | A | 12 |
| 3 | AB | 16 |
| 4 | ABC | 14 |
| 5 | AC | 11 |
| 6 | B | 17 |
| 7 | BC | 9 |
| 8 | C | 10 |
+----+-------------+----------+
Assuming your combinations are where those columns' values are TRUE, you're just looking at a group by over those 3 columns. The case logic is just there to present the combo in a single column; you could easily replace "case...end" with "A, B, C" to get the same result with those columns showing their values separately.
select
case
when A = 1 and B = 1 and C = 1 then 'ABC'
when A = 1 and B = 1 and C = 0 then 'AB'
when A = 1 and B = 0 and C = 1 then 'AC'
when A = 0 and B = 1 and C = 1 then 'BC'
when A = 1 and B = 0 and C = 0 then 'A'
when A = 0 and B = 1 and C = 0 then 'B'
when A = 0 and B = 0 and C = 1 then 'C'
else 'oops, this should not happen'
end as `Combo`
--, sum(sumThing) as `sum` --amended to count per question edit
, count(*) as `count`
from myTable
where A = true
or B = true
or C = true
group by A, B, C
Use conditional count
SELECT
COUNT(CASE WHEN A=1 THEN 1 END) AS A,
COUNT(CASE WHEN B=1 THEN 1 END) AS B,
COUNT(CASE WHEN C=1 THEN 1 END) AS C,
COUNT(CASE WHEN A=1 AND B=1 THEN 1 END) AS AB,
COUNT(CASE WHEN A=1 AND C=1 THEN 1 END) AS AC,
COUNT(CASE WHEN B=1 AND C=1 THEN 1 END) AS BC,
COUNT(CASE WHEN A=1 AND B=1 AND C=1 THEN 1 END) AS ABC,
COUNT(CASE WHEN A<>1 AND B<>1 AND C<>1 THEN 1 END) AS None
FROM table1;
You can use a simple subquery for each combination and union them:
SELECT "none", COUNT(*) FROM mytable WHERE A = 0 AND B = 0 AND C = 0
UNION
SELECT "A", COUNT(*) FROM mytable WHERE A = 1 AND B = 0 AND C = 0
UNION
SELECT "B", COUNT(*) FROM mytable WHERE A = 0 AND B = 1 AND C = 0
UNION
SELECT "C", COUNT(*) FROM mytable WHERE A = 0 AND B = 0 AND C = 1
UNION
SELECT "AB", COUNT(*) FROM mytable WHERE A = 1 AND B = 1 AND C = 0
UNION
SELECT "BC", COUNT(*) FROM mytable WHERE A = 0 AND B = 1 AND C = 1
UNION
SELECT "AC", COUNT(*) FROM mytable WHERE A = 1 AND B = 0 AND C = 1
UNION
SELECT "ABC", COUNT(*) FROM mytable WHERE A = 1 AND B = 1 AND C = 1

How to check each value in particular group and set flag status?

Assume I have a Table "Emp"
Dpt_ID | E_ID | E_Status
A1 | 00200 | H
A1 | 00200 | M
A1 | 00400 | H
A1 | 00400 | M
A2 | 00300 | L
A2 | 00300 | M
A2 | 00400 | L
A2 | 00400 | L
Data needs to grouped according to Dpt_ID, If any E_ID under that Dpt_ID has status =H, then Set Flag=High. Similarly, if any E_ID under that Dpt_ID has status =M, then Set Flag=Medium else Low.
Currently I have written following query, but with group by function it checks only 1st row :
SELECT Dpt_ID,E_ID,E_Status,(CASE
WHEN E_Status = "H" THEN 'High'
WHEN E_Status = "M" THEN 'Medium'
ELSE 'Low'
END) AS Flag
FROM Emp
group by Dpt_ID
How do I check each value in Dpt_ID group to see if it's H/M/L ?
E.g. Target should be displayed as
Dpt_ID | Flag
A1 | High
A2 | Medium
The problem here is that you can't use MAX() directly because you have arbitrary char values, one option is assigning a weight to your values so you can use MAX(), and then get the flag using the weight values:
SELECT Dpt_ID,
CASE MAX(CASE E_Status WHEN 'H' THEN 2 WHEN 'M' THEN 1 ELSE 0 END)
WHEN 2 THEN 'High' WHEN 1 THEN 'Medium' ELSE 'Low' END AS Flag
FROM Emp
group by Dpt_ID
You are close. You can do:
SELECT Dpt_ID, E_ID, E_Status,
(CASE WHEN MAX(E_Status = 'H') THEN 'High'
WHEN MAX(E_Status = 'M') THEN 'Medium'
ELSE 'Low'
END) AS Flag
FROM Emp
GROUP BY Dpt_ID;

SQL Aggregation with SUM, GROUP BY and JOIN (many-to-many)

Here's an example Table layout:
TABLE_A: TABLE_B: TABLE_A_B:
id | a | b | c id | name a_id | b_id
--------------------- --------- -----------
1 | true | X | A 1 | A 1 | 1
2 | true | Z | null 2 | B 1 | 2
3 | false | X | null 3 | C 2 | 2
4 | true | Y | Q 4 | 1
5 | false | null | null 4 | 2
5 | 1
Possible Values:
TABLE_A.a: true, false
TABLE_A.b: X, Y, Z
TABLE_A.c: A, B, C, ... basically arbitrary
TABLE_B.name: A, B, C, ... basically arbitrary
What I want to achieve:
SELECT all rows from TABLE_A
SUM(where a = true),
SUM(where a = false),
SUM(where b = 'X'),
SUM(where b = 'Y'),
SUM(where b = 'Z'),
SUM(where b IS NULL),
and also get the SUMs for all distinct TABLE_A.c values.
and also get the SUMs for all those TABLE_A_B relations.
The result for the example Table above should look like:
aTrue | aFalse | bX | bY | bZ | bNull | cA | cQ | cNull | nameA | nameB | nameC
-------------------------------------------------------------------------------
3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 3 | 3 | 3 | 0
What I've done so far:
SELECT
SUM(CASE WHEN a = true THEN 1 ELSE 0 END) AS aTrue,
SUM(CASE WHEN b = false THEN 1 ELSE 0 END) AS aFalse,
SUM(CASE WHEN b = 'X' THEN 1 ELSE 0 END) AS bX,
...
FROM TABLE_A
What's my problem?
Selecting column TABLE_A.a and TABLE_A.b is easy, because there's a fixed number of possible values.
But I can't figure out how to count the distinct values of TABLE_A.c. And basically the same problem for the JOINed TABLE_B, because the number of values within TABLE_B is unknown and can change over time.
Thanks for your help! :)
EDIT1: New (preferred) SQL result structure:
column | value | sum
----------------------------
TABLE_A.a | true | 3
TABLE_A.a | false | 2
TABLE_A.b | X | 2
TABLE_A.b | Y | 1
TABLE_A.b | Z | 1
TABLE_A.b | null | 1
TABLE_A.c | A | 1
TABLE_A.c | Q | 1
TABLE_A.c | null | 3
TABLE_B.name | A | 3
TABLE_B.name | B | 3
TABLE_B.name | C | 0
From your original request of rows as a simulated pivot. By doing a SUM( logical condition ) basically returns 1 if true, 0 if false. So, since the column "a" is true or false, simple sum of "a" or NOT "a" (for the false counts -- NOT FALSE = TRUE). Similarly, your "b" column, so b='X' = true counted as 1, else 0.
In other sql engines, you might see it as SUM( case/when ).
Now, since your table counts don't rely on each other, they can be separate SUM() into their own sub-alias query references (pqA and pqB for pre-queryA and pre-queryB respectively). Since no group by, they will each result in a single row. With no join will create a Cartesian, but since 1:1 ratio, will only return a single record of all columns you want.
SELECT
pqA.*, pqB.*
from
( SELECT
SUM( ta.a ) aTrue,
SUM( NOT ta.a ) aFalse,
SUM( ta.b = 'X' ) bX,
SUM( ta.b = 'Y' ) bY,
SUM( ta.b = 'Z' ) bZ,
SUM( ta.b is null ) bNULL,
SUM( ta.c = 'A' ) cA,
SUM( ta.c = 'Q' ) cQ,
SUM( ta.c is null ) cNULL,
COUNT( distinct ta.c ) DistC
from
table_a ta ) pqA,
( SELECT
SUM( b.Name = 'A' ) nameA,
SUM( b.Name = 'B' ) nameB,
SUM( b.Name = 'C' ) nameC
from
table_a_b t_ab
join table_b b
ON t_ab.b_id = b.id ) pqB
This option gives your second (preferred) output
SELECT
MAX( 'TABLE_A.a ' ) as Basis,
CASE when a then 'true' else 'false' end Value,
COUNT(*) finalCnt
from
TABLE_A
group by
a
UNION ALL
SELECT
MAX( 'TABLE_A.b ' ) as Basis,
b Value,
COUNT(*) finalCnt
from
TABLE_A
group by
b
UNION ALL
SELECT
MAX( 'TABLE_A.c ' ) as Basis,
c Value,
COUNT(*) finalCnt
from
TABLE_A
group by
c
UNION ALL
SELECT
MAX( 'TABLE_B.name ' ) as Basis,
b.Name Value,
COUNT(*) finalCnt
from
table_a_b t_ab
join table_b b
ON t_ab.b_id = b.id
group by
b.Name
I think You will need to build dynamic query as you don't know possible values for column C in table A. So you can write store procedure where you can get list of distinct value for Column C in one variable and by using "Do WHILE" you can construct your dynamic query.
Please let me know if you need more help in detail
Dynamic SQL

Convert SQL rows to columns

This is my table:
id | num | comment
---+-----+--------
3 | 10 | hello
3 | 20 | pls
3 | 30 | respond
7 | 10 | leet
7 | 20 | hax
7 | 30 | zor
How can I query it out in this manner:
id | first | second | third
---+-------+--------+--------
3 | hello | pls | respond
7 | leet | hax | zor
In the event that the num column does not reliably always start at 10 and ascend by 10 you can use the following to establish a row number that restarts at each change in ID, that way you can use the rownumbers in conjunction with conditional aggregation to show each comment. The following would do so for up to 10 comments per ID, and the NUM column does not have to be 10/20/30/40/50/60/70/80/90 (it could be anything).
If the NUM column reliably starts at 10 and ascends by 10, this question has been asked and answered: How to pivot rows into columns (custom pivoting)
select id,
max(case when row_number = 1 then comment else null end) as c01,
max(case when row_number = 2 then comment else null end) as c02,
max(case when row_number = 3 then comment else null end) as c03,
max(case when row_number = 4 then comment else null end) as c04,
max(case when row_number = 5 then comment else null end) as c05,
max(case when row_number = 6 then comment else null end) as c06,
max(case when row_number = 7 then comment else null end) as c07,
max(case when row_number = 8 then comment else null end) as c08,
max(case when row_number = 9 then comment else null end) as c09,
max(case when row_number = 10 then comment else null end) as c10
from(
select #row_number := case when #prev_val = id then #row_number+1 else 1 end as row_number,
id,
comment,
#prev_val:=id as prev_val
from tbl, (select #row_number:=0,#prev_val:='') x
order by id, num) x
group by id
order by id

Mysql get sub list with count

I have table data in this form.
it w count
i1 a 2
i1 b 3
i2 a 1
i3 c 2
i4 b 1
When I query for a-b pair for each item (it) and respective count. If certain item (it) doesn't have both a and b then it is ignored (like i3). I need output in the form
it a b
i1 2 3
i2 1 0
i4 0 1
Any ideas how to solve this?
And I cannot store each word in different column. There are many words and mXn matrix of such a matrix is sparse.
Based on Saharsh's answer we could do it by
SELECT * FROM (SELECT a.it, SUM(CASE WHEN a.w = 'a' THEN a.count ELSE 0 END) AS a,
SUM(CASE WHEN a.w = 'b' THEN a.count ELSE 0 END) AS b
FROM tableA a WHERE a.w IN ('a', 'b') GROUP BY a.it) WHERE a>0 OR b>0
Is there a better way it terms of handling data with million rows?
You can do this with conditional aggregation:
select it, max(case when w = 'a' then count else 0 end) as a,
max(case when w = 'b' then count else 0 end) as b
from table t
where w in ('a', 'b')
group by it;
Try this:
SELECT a.it,
SUM(CASE WHEN a.w = 'a' THEN a.count ELSE 0 END) AS a,
SUM(CASE WHEN a.w = 'b' THEN a.count ELSE 0 END) AS b
FROM tableA a
WHERE a.w IN ('a', 'b')
GROUP BY a.it;
Check this SQL FIDDLE DEMO
OUTPUT
| IT | A | B |
|----|---|---|
| i1 | 2 | 3 |
| i2 | 1 | 0 |
| i4 | 0 | 1 |