SQL - sum of every combination - mysql

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

Related

Sum of repeating values in successive rows

I have a table like this:
id col1 col2 col3
10 1 3
9 1 2 3
8 2 3
7 2 3
6 1 2
5 3
Each column has one value only or null. Eg. Col1 has 1 or empty. Col2 has 2 or empty.
I'd like to get the sum of repeating values only between two successive rows.
so the result would look like this:
I need to get the sum of total repeating values in each row.
id col1 col2 col3 Count
10 1 3 2 (shows the repeating values between id10 & id9 rows)
9 1 2 3 2 (shows the repeating values between id9 & id8 rows)
8 2 3 1
7 2 1
6 1 2 0
5 3
I googled and tried some queries I found on the web but couldn't get the right result. Thanks in advance for your help.
To further clarify, for example:
id10 row has (1,,3) and id9 row has (1,2,3). so there is two values repeating. so count is 2.
If the ids are consecutive and there are no gaps, you can do it with a self join:
select
t.*,
coalesce((t.col1 = tt.col1), 0) +
coalesce((t.col2 = tt.col2), 0) +
coalesce((t.col3 = tt.col3), 0) count
from tablename t left join tablename tt
on tt.id = t.id - 1
See the demo.
Results:
| id | col1 | col2 | col3 | count |
| --- | ---- | ---- | ---- | ----- |
| 10 | 1 | | 3 | 2 |
| 9 | 1 | 2 | 3 | 2 |
| 8 | | 2 | 3 | 1 |
| 7 | | 2 | | 1 |
| 6 | 1 | 2 | | 0 |
| 5 | | | 3 | 0 |
And if there are gaps...
SELECT a.id
, a.col1
, a.col2
, a.col3
, COALESCE(a.col1 = b.col1,0) + COALESCE(a.col2 = b.col2,0) + COALESCE(a.col3 = b.col3,0) n
FROM
( SELECT x.*
, MIN(y.id) y_id
FROM my_table x
JOIN my_table y
ON y.id > x.id
GROUP
BY x.id
) a
LEFT
JOIN my_table b
ON b.id = a.y_id;
Were you to restructure your schema, then you could do something like this instead...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL
,val INT NOT NULL
,PRIMARY KEY(id,val)
);
INSERT INTO my_table VALUES
(10,1),
(10,3),
( 9,1),
( 9,2),
( 9,3),
( 8,2),
( 8,3),
( 7,2),
( 7,3),
( 6,1),
( 6,2),
( 5,3);
SELECT a.id
, COUNT(b.id) total
FROM
( SELECT x.*
, MIN(y.id) next
FROM my_table x
JOIN my_table y
ON y.id > x.id
GROUP
BY x.id
, x.val
) a
LEFT
JOIN my_table b
ON b.id = a.next
AND b.val = a.val
GROUP
BY a.id;
+----+-------+
| id | total |
+----+-------+
| 5 | 0 |
| 6 | 1 |
| 7 | 2 |
| 8 | 2 |
| 9 | 2 |
+----+-------+
You can use :
select t1_ID, t1_col1,t1_col2,t1_col3, count
from
(
select t1.id as t1_ID, t1.col1 as t1_col1,t1.col2 as t1_col2,t1.col3 as t1_col3, t2.*,
case when t1.col1 = t2.col1 then 1 else 0 end +
case when t1.col2 = t2.col2 then 1 else 0 end +
case when t1.col3 = t2.col3 then 1 else 0 end as count
from tab t1
left join tab t2
on t1.id = t2.id + 1
order by t1.id
) t3
order by t1_ID desc;
Demo
If there are gaps between id values for the next row, you could have user defined variables to explicitly assign values to rows in their natural ordering in the table. Rest logic remains the same as already answered. You would do an inner join between current row number and next row number to get the col1,col2 and col3 values and use coalesce for computation of count.
select derived_1.*,
coalesce((derived_1.col1 = derived_2.col1), 0) +
coalesce((derived_1.col2 = derived_2.col2), 0) +
coalesce((derived_1.col3 = derived_2.col3), 0) count
from (
select #row := #row + 1 as row_number,t1.*
from tablename t1,(select #row := 0) d1
) derived_1
left join (
select *
from (
select #row2 := #row2 + 1 as row_number,t2.*
from tablename t2,(select #row2 := 0) d2
) d3
) derived_2
on derived_1.row_number + 1 = derived_2.row_number;
Demo: https://www.db-fiddle.com/f/wAzb67zSEfbZKg5RywQvC8/1

MySQL: Multiple Running Totals from Different Subqueries

When I run a single query using the following formula to have the first column give back the month/year, the second give back the number of people signing per month, and the third give back the running total of signers, it works great:
SET #runtot1:=0;
SELECT
1rt.MONTH,
1rt.1signed,
(#runtot1 := #runtot1 + 1rt.1signed) AS 1rt
FROM
(SELECT
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 791796 THEN s.id ELSE NULL END),0) AS 1signed
FROM table1 s
JOIN table2 m ON s.id = m.id AND m.current = "Yes"
WHERE STR_TO_DATE(s.datecontacted,'%m/%d/%Y') > '2015-03-01'
GROUP BY MONTH
ORDER BY MONTH) AS 1rt
With the query above, I get the following results table, which would be exactly what I want if I only needed to count one thing:
MONTH 1signed 1rt
2015-03 0 0
2015-04 1 1
2015-05 0 1
2015-08 1 2
2015-10 1 3
2015-11 1 4
2016-01 0 4
2016-02 0 4
But I can't figure out how to do that with multiple subqueries since I need this to happen for multiple columns at the same time. For example, I was attempting things like this (which doesn't work):
SET #runtot1:=0;
SET #runtot2:=0;
select
DATE_FORMAT(STR_TO_DATE(s1.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
t1.1signed,
(#runtot1 := #runtot1 + t1.1signed) AS 1rt,
t2.2signed,
(#runtot2 := #runtot2 + t2.2signed) AS 2rt
from
(select
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 791796 THEN s.id ELSE NULL END),0) AS 1signed
from table1 s
left join table2 m ON m.id = s.id
where m.current = "Yes"
GROUP BY MONTH
ORDER BY MONTH) as T1,
(select
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 846346 THEN s.id ELSE NULL END),0) AS 2signed
from table1 s
left join table2 m ON m.id = s.id
where m.current = "Yes"
GROUP BY MONTH
ORDER BY MONTH) as T2,
table1 s1
LEFT JOIN table2 m1 ON m1.id = s1.id AND m1.current = "Yes"
WHERE STR_TO_DATE(s1.datecontacted,'%m/%d/%Y') > '2015-03-01'
GROUP BY DATE_FORMAT(STR_TO_DATE(s1.datecontacted,'%m/%d/%Y'),'%Y-%m')
ORDER BY DATE_FORMAT(STR_TO_DATE(s1.datecontacted,'%m/%d/%Y'),'%Y-%m')
That blew up my results badly -- I also tried LEFT JOINs to get those two next each other, but that didn't work either.
Here's a SQL Fiddle with a few values with the query at the top that works, but not the query needed to look like the idea below.
If the multiple subquery version of the code worked, below would be the ideal end-result:
MONTH 1signed 1rt 2signed 2rt
2015-03 0 0 1 1
2015-04 1 1 0 1
2015-05 0 1 1 2
2015-08 1 2 0 2
2015-10 1 3 0 2
2015-11 1 4 0 2
2016-01 0 4 0 2
2016-02 0 4 1 3
Just trying to figure out a way to get counts by month and rolling totals since March 2015 for two different survey questions using the same query. Any help would be greatly appreciated!
Your attempt was actually pretty close. I just got rid of S1 and joined the two subqueries together on their MONTH columns:
SET #runtot1:=0;
SET #runtot2:=0;
select
T1.MONTH,
t1.1signed,
(#runtot1 := #runtot1 + t1.1signed) AS 1rt,
t2.2signed,
(#runtot2 := #runtot2 + t2.2signed) AS 2rt
from
(select
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 791796 THEN s.id ELSE NULL END),0) AS 1signed
from table1 s
left join table2 m ON m.id = s.id
where m.current = "Yes" and STR_TO_DATE(s.datecontacted,'%m/%d/%Y') > '2015-03-01'
GROUP BY MONTH
ORDER BY MONTH) as T1,
(select
DATE_FORMAT(STR_TO_DATE(s.datecontacted,'%m/%d/%Y'),'%Y-%m') AS MONTH,
IFNULL(COUNT(DISTINCT CASE WHEN s.surveyid = 846346 THEN s.id ELSE NULL END),0) AS 2signed
from table1 s
left join table2 m ON m.id = s.id
where m.current = "Yes" and STR_TO_DATE(s.datecontacted,'%m/%d/%Y') > '2015-03-01'
GROUP BY MONTH
ORDER BY MONTH) as T2
WHERE
T1.MONTH=T2.MONTH
GROUP BY T1.MONTH
ORDER BY T1.MONTH
I haven't tested Strawberry's solution, which looks more elegant. But I thought you'd like to know that your approach (solving the running totals individually, then joining the results together) would have worked too.
It seems that you're after something like this...
The data set:
DROP TABLE IF EXISTS table1;
CREATE TABLE table1
( id INT NOT NULL
, date_contacted DATE NOT NULL
, survey_id INT NOT NULL
, PRIMARY KEY(id,survey_id)
);
DROP TABLE IF EXISTS table2;
CREATE TABLE table2
(id INT NOT NULL PRIMARY KEY
,is_current TINYINT NOT NULL DEFAULT 0
);
INSERT INTO table1 VALUES
(1,"2015-03-05",846346),
(2,"2015-04-15",791796),
(2,"2015-05-04",846346),
(3,"2015-06-07",791796),
(3,"2015-06-08",846346),
(4,"2015-08-02",791796),
(5,"2015-10-15",791796),
(6,"2015-11-25",791796),
(6,"2016-01-02", 11235),
(6,"2016-02-06",846346);
INSERT INTO table2 (id,is_current) VALUES
(1,1),
(2,1),
(3,0),
(4,1),
(5,1),
(6,1);
The query:
SELECT x.*
, #a:=#a+a rt_a
, #b:=#b+b rt_b
FROM
( SELECT DATE_FORMAT(date_contacted,'%Y-%m') month
, SUM(survey_id = 791796) a
, SUM(survey_id = 846346) b
FROM table1 x
JOIN table2 y
ON y.id = x.id
WHERE y.is_current = 1
GROUP
BY month
) x
JOIN (SELECT #a:=0,#b:=0) vars
ORDER
BY month;
+---------+------+------+------+------+
| month | a | b | rt_a | rt_b |
+---------+------+------+------+------+
| 2015-03 | 0 | 1 | 0 | 1 |
| 2015-04 | 1 | 0 | 1 | 1 |
| 2015-05 | 0 | 1 | 1 | 2 |
| 2015-08 | 1 | 0 | 2 | 2 |
| 2015-10 | 1 | 0 | 3 | 2 |
| 2015-11 | 1 | 0 | 4 | 2 |
| 2016-01 | 0 | 0 | 4 | 2 |
| 2016-02 | 0 | 1 | 4 | 3 |
+---------+------+------+------+------+

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

How to update in MySql columns

There are 5 columns X and A, B, C, D in table t. Columns A, B, C, D are varchar. and column X has to show us how many of the next row repeating characters. I need help to update column X.
Example :
|ID | X | A | B | C | D |
=========================
| 4 | 1 | 7 | J | 7 | Q |
| 3 | 2 | K | Q | 8 | 8 |
| 2 | 3 | 7 | 8 | 9 | J | next row X=3
| 1 | 0 | 7 | J | 8 | K | 0 default
ID-1 is the first and X is zero by default and from there begin calculations ID-2 and X=3 because we have ID-1 "7" "J" and "8" and the next row ID-2 have the combination "7 8 9 J" in ID-1 there "7 J 8 -" and X should be 3. Values ​​of X can be between 0 and 4. ID-3, X=2 "- - 8 8" because in ID-2 have the combination "7 8 9 J" and i have 8 - twice in "K Q 8 8".
Added : 08/06
http://sqlfiddle.com/#!2/4586e/1
CREATE TABLE tmp
(
id int,
alnum CHAR(1),
cnt int,
PRIMARY KEY (id, alnum)
);
INSERT INTO tmp (id, alnum, cnt) SELECT id, A, 1 FROM tab ON DUPLICATE KEY UPDATE cnt = cnt + 1;
INSERT INTO tmp (id, alnum, cnt) SELECT id, B, 1 FROM tab ON DUPLICATE KEY UPDATE cnt = cnt + 1;
INSERT INTO tmp (id, alnum, cnt) SELECT id, C, 1 FROM tab ON DUPLICATE KEY UPDATE cnt = cnt + 1;
INSERT INTO tmp (id, alnum, cnt) SELECT id, D, 1 FROM tab ON DUPLICATE KEY UPDATE cnt = cnt + 1;
UPDATE tab INNER JOIN (
SELECT t1.id AS id, SUM(t2.cnt) AS sum
FROM tmp t1 INNER JOIN tmp t2 ON t1.id + 1 = t2.id
AND t1.alnum = t2.alnum
GROUP BY t1.id
) tmp3 ON tab.id = tmp3.id + 1
SET tab.X = tmp3.sum;
SELECT * FROM tab WHERE X > 4;
Original answer : 08/05
How about using tmp table which stores for each A, B, C, D columns into single rows. This makes us easy to calculate X value.
Below code assumes id is sequencial value. If not, please let me know there is another query for it.
CREATE TABLE tab
(
id INT,
X INT,
A CHAR(1),
B CHAR(1),
C CHAR(1),
D CHAR(1)
);
INSERT INTO tab VALUES (1, 0, '7', 'J', '8', 'K');
INSERT INTO tab VALUES (2, 0, '7', '8', '9', 'J');
INSERT INTO tab VALUES (3, 0, 'K', 'Q', '8', '8');
INSERT INTO tab VALUES (4, 0, '7', 'J', '7', 'Q');
CREATE TABLE tmp
(
id INT,
alnum CHAR(1)
);
INSERT INTO tmp SELECT id, A FROM tab;
INSERT INTO tmp SELECT id, B FROM tab;
INSERT INTO tmp SELECT id, C FROM tab;
INSERT INTO tmp SELECT id, D FROM tab;
UPDATE tab INNER JOIN (
SELECT t1.id AS id, COUNT(*) AS cnt
FROM tmp t1 INNER JOIN tmp t2 ON t1.id + 1 = t2.id
AND t1.alnum = t2.alnum
GROUP BY t1.id
) tmp3 ON tab.id = tmp3.id + 1
SET tab.X = tmp3.cnt;
mysql> SELECT * FROM tab ORDER BY id DESC;
+------+------+------+------+------+------+
| id | X | A | B | C | D |
+------+------+------+------+------+------+
| 4 | 1 | 7 | J | 7 | Q |
| 3 | 2 | K | Q | 8 | 8 |
| 2 | 3 | 7 | 8 | 9 | J |
| 1 | 0 | 7 | J | 8 | K |
+------+------+------+------+------+------+
4 rows in set (0.00 sec)
The X values in your example are from the "next" row, not the previous. Assuming you have an auto-incrementing id, you can calculate this information by looking at the next row. You can generate a query to do the calculation:
select ((case when t1.A in (tnext.A, tnext.B, tnext.C, tnext.D) then 1 else 0 end) +
(case when t1.B in (tnext.A, tnext.B, tnext.C, tnext.D) then 1 else 0 end) +
(case when t1.C in (tnext.A, tnext.B, tnext.C, tnext.D) then 1 else 0 end) +
(case when t1.D in (tnext.A, tnext.B, tnext.C, tnext.D) then 1 else 0 end)
) as X, t1.*
from (select t.*, (select max(id) from table t2 where t2.id > t.id) as nextid
from table t
) t1 left outer join
t tnext
on tnext.id = t1.nextid;
Depending on the database you are using, this code can be simplified and expressed differently. Also, the specific update syntax might depend on the database.

mysql selecting a union where values in one don't appear in the other

sorry for the poorly titled post.
Say I have the following table:
C1 | C2 | c3
1 | foo | x
2 | bar | y
2 | blaz | z
3 | something| y
3 | hello | z
3 | doctor | x
4 | name | y
5 | continue | x
5 | yesterday| z
6 | tomorrow | y
I'm trying to come up w/ a sql statement which performs the following union:
1st retrieval retrieves all records w/ c3 = 'y'
2nd retrieval retrieves the first instance of a record where c3 <> 'y' and the result is not in the previous union
So, for the result, I should see:
C1 | C2
1 | foo
2 | bar
3 | something
4 | name
5 | continue
6 | tomorrow
So two questions: 1: Am I totally smoking crack where I think I can do this, and 2: (assuming I can), how do I do this?
Try this one:
SELECT a.C1, a.C2
FROM MyTable a
WHERE a.C3 = 'y'
UNION
SELECT b.C1, b.C2
FROM MyTable b
WHERE b.C3 <> 'y' AND
b.C1 not in
(
SELECT c.C1
FROM MyTable c
WHERE c.C3 = 'y'
)
UPDATE 1
by the way, why is that there is only one record of 5 in your desired result? where, in fact, there could be two.
SEE FOR DEMO 1
OR
SELECT g.C1, MIN(g.C2) C2
FROM
(SELECT a.C1, a.C2
FROM MyTable a
WHERE a.C3 = 'y'
UNION
SELECT b.C1, b.C2
FROM MyTable b
WHERE b.C3 <> 'y' AND
b.C1 not in
(
SELECT c.C1
FROM MyTable c
WHERE c.C3 = 'y'
)
) g
GROUP BY g.C1
SEE FOR DEMO 2 (yields same result with your desired result)
DEMO # Sql Fiddle.
select *
from table1
where c3 = 'y'
union all
(select table1.*
from table1
left join table1 t1
on table1.c1 = t1.c1
and t1.c3 = 'y'
where table1.c3 <> 'y'
and t1.c1 is null
-- The meaning of first becomes clear here
order by table1.c3, table1.c2
limit 1)
Note: foo is not in a list because it is marked as x.
Try this:
SELECT C1, C2
FROM Table1
Where C3 = 'y'
UNION
(
SELECT C1, C2
FROM Table1
Where C3 <> 'y' ORDER BY C1 LIMIT 1
)
ORDER BY C1