I have a list of rows and want to group them by ID and a certain start-value. (Say value=1 in the below-given example)
Group-by value
Set of rows - MySQL & Presto
-----------
ID | value
-----------
A | 1
A | 2
B | 1
B | 2
B | 5
B | 1
B | 2
C | 1
C | 3
C | 4
C | 1
D | 1
D | 8
D | 1
-----------
Expected Output :
-----------
ID | Value
-----------
A | 1,2
B | 1,2,5
B | 1,2
C | 1,3,4
C | 1
D | 1,8
D | 1
-----------
Actual Output :
-----------
ID | Value
-----------
A | 1,2
B | 1,2,5,1,2
C | 1,3,4,1
D | 1,8,1
-----------
Without a real id is really hard. So I introduce rowNumber:
SELECT (#row := #row + 1) AS rowNumber, ID,value
FROM myTable
CROSS JOIN (SELECT #row := 0) AS dummy
Then i add 2 columns with max Value:
SELECT a.*
FROM customTable as a
LEFT OUTER JOIN myTable b ON a.id = b.id AND a.value < b.value
WHERE b.id IS NULL
After this i need to use group_concat(value) and group by.
Group by has 2 condition, id and another custom boolean field:
CASE
WHEN l1.rowNumber <= l2.rowNumber THEN 0
ELSE 1
END
FINAL QUERY:
SELECT ct1.id, group_concat(ct1.value) as Value
FROM (
SELECT (#cnt := #cnt + 1) AS rowNumber, ID, value
FROM myTable
CROSS JOIN (SELECT #cnt := 0) AS dummy
) AS ct1
JOIN (
SELECT a.*
FROM (
SELECT (#row := #row + 1) AS rowNumber, ID, value
FROM myTable
CROSS JOIN (SELECT #row := 0) AS dummy
) AS a
LEFT OUTER JOIN myTable b ON a.id = b.id AND a.value < b.value
WHERE b.id IS NULL
) AS ct2 ON ct2.ID = ct1.id
GROUP BY ct1.id,
CASE
WHEN ct1.rowNumber <= ct2.rowNumber THEN 0
ELSE 1
END
You can test Here.
This only works with MySQL 5.6 or above
Related
Let's assume I have two columns: letters and numbers in a table called tbl;
letters numbers
a 1
b 2
c 3
d 4
Doing a cartesian product will lead to :
a 1
a 2
a 3
a 4
b 1
b 2
b 3
b 4
c 1
c 2
c 3
c 4
d 1
d 2
d 3
d 4
Write a query that reverts the cartesian product of these two columns back to the original table.
I tried multiple methods from using ROWNUM to selecting distinct values and joining them (which leads me back to the cartesian product)
SELECT DISTINCT *
FROM (SELECT DISTINCT NUMBERS
FROM TBL
ORDER BY NUMBERS) AS NB
JOIN (SELECT DISTINCT LETTERS
FROM TBL
ORDER BY LETTERS) AS LT1
which led me back to the cartesian product....
This is a version that works with 5.7.
SELECT `numbers`,`letters` FROM
(SELECT `numbers`,
#curRank := #curRank + 1 AS rank
FROM Table1 t, (SELECT #curRank := 0) r
GROUP By `numbers`
ORDER BY `numbers`) NB1
INNER JOIN
(SELECT `letters`,
#curRank1 := #curRank1 + 1 AS rank
FROM (
Select `letters` FROM Table1 t
GROUP By `letters`) t2, (SELECT #curRank1 := 0) r
ORDER BY `letters`) LT1 ON NB1.rank = LT1.rank;
https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=cc17c2cfeff049edc73e437e5e4fd892
As Raymond and Ankit pointed out you have to know which order have the letters and even the order of the numbers has to be defined prior or else you never get a correct answer.
Another way of writing this:
SELECT numbers
, letters
FROM
( SELECT DISTINCT numbers
, #curRank := #curRank + 1 rank
FROM Table1 t
, (SELECT #curRank := 0) r
ORDER
BY numbers
) NB1
JOIN
( SELECT letters
, #curRank1 := #curRank1 + 1 rank
FROM
( SELECT DISTINCT letters
FROM Table1 t
) t2
, (SELECT #curRank1 := 0) r
ORDER
BY letters
) LT1
ON NB1.rank = LT1.rank;
If you are sure that the order will never be destroyed and is deterministic, You can use dense_rank() analytic function to achieve it back -
SELECT LT1.LETTERS, NB.NUMBERS
FROM (SELECT DISTINCT NUMBERS
FROM TBL
ORDER BY NUMBERS) AS NB
JOIN (SELECT DISTINCT LETTERS, RN
FROM (SELECT LETTERS, DENSE_RANK() OVER (ORDER BY LETTERS) RN
FROM TBL
ORDER BY LETTERS) T) AS LT1
ON NB.NUMBERS = LT1.RN
Here is the fiddle
Perhaps this is oversimplifying the problem, but it should be seen that this, or some variation of it, would suffice...
SELECT * FROM my_table;
+---------+---------+
| letters | numbers |
+---------+---------+
| a | 1 |
| a | 2 |
| a | 3 |
| a | 4 |
| b | 1 |
| b | 2 |
| b | 3 |
| b | 4 |
| c | 1 |
| c | 2 |
| c | 3 |
| c | 4 |
| d | 1 |
| d | 2 |
| d | 3 |
| d | 4 |
+---------+---------+
16 rows in set (0.00 sec)
SELECT x.*
, #i:=#i+1 numbers
FROM
( SELECT DISTINCT letters
FROM my_table
) x
, (SELECT #i:=0) vars
ORDER
BY letters;
+---------+---------+
| letters | numbers |
+---------+---------+
| a | 1 |
| b | 2 |
| c | 3 |
| d | 4 |
+---------+---------+
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
is there a way to reverse an entire column?
Example:
ID ColX ColY ColZ
0 001 010 100
1 002 020 200
2 003 030 300
shall be:
ID ColX ColY ColZ
0 003 030 300
1 002 020 200
2 001 010 100
So the Column ID shall be reversed, the record with the last ID shall be the first, the second last the second first and so far.
The newest value has ID = 0 and the oldest ID = n, and this must be exactly reversed, else I cannot insert new records.
You can do it using variables:
SELECT t2.ID, ColX, ColY, ColZ
FROM (SELECT ID, ColX, ColY, ColZ,
#row_number := #row_number + 1 AS rn
FROM mytable
CROSS JOIN (SELECT #row_number := 0) AS var
ORDER BY ID) AS t1
INNER JOIN (
SELECT ID, #rn := #rn + 1 AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0) AS var
ORDER BY ID DESC) AS t2
ON t1.rn = t2.rn
ORDER BY t2.ID
Demo here
If you want to UPDATE then you can use the above query in an UPDATE statement like this:
UPDATE mytable AS t
INNER JOIN(
SELECT ID, ColX, ColY, ColZ,
#row_number := #row_number + 1 AS rn
FROM mytable
CROSS JOIN (SELECT #row_number := 0) AS var
ORDER BY ID) AS t1 ON t.ID = t1.ID
INNER JOIN (
SELECT ID, #rn := #rn + 1 AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0) AS var
ORDER BY ID DESC) AS t2 ON t1.rn = t2.rn
SET t.ID = t2.ID
Demo here
The above will work irrespective of the values of ID column.
If you don't have gaps in your ID, then you can use this select query:
SELECT
max_id-ID AS ID,
ColX,
ColY,
ColZ
FROM
mytable CROSS JOIN (SELECT MAX(ID) AS max_id FROM mytable) m
ORDER BY
ID
or this update query (but it will work only if ID is not a primary key):
UPDATE
mytable m1 CROSS JOIN (SELECT MAX(ID) as max_id FROM mytable) m
SET
m1.ID = m.max_id - m1.ID
if it's a primary key you could use this:
UPDATE
mytable m1 CROSS JOIN (SELECT MAX(ID) as max_id FROM mytable) m
INNER JOIN mytable m2 ON m1.ID = m.max_id - m2.ID
SET
m1.ColX = m2.ColX,
m1.ColY = m2.ColY,
m1.ColZ = m2.ColZ
(please see it here)
but if your ID column has gaps (e.g. 0, 1, 2, 5, 6) you need a different approach.
SELECT
#x - t.id AS new_id,
t.*
FROM tab t,
( SELECT #x:= max(id+1) FROM tab ) m;
MariaDB [tmp]> SELECT
-> #x - t.id AS new_id,
-> t.*
-> FROM tab t,
-> ( SELECT #x:= max(id+1) FROM tab ) m;
+--------+----+--------+---------------------+
| new_id | id | action | date |
+--------+----+--------+---------------------+
| 7 | 1 | 2 | 2015-09-24 15:28:30 |
| 6 | 2 | 4 | 2015-09-24 15:29:26 |
| 5 | 3 | 2 | 2015-09-24 15:30:01 |
| 4 | 4 | 3 | 2015-09-24 15:30:55 |
| 3 | 5 | 1 | 2015-09-24 16:07:25 |
| 2 | 6 | 5 | 2015-09-24 16:10:25 |
| 1 | 7 | 4 | 2015-09-24 16:29:26 |
+--------+----+--------+---------------------+
7 rows in set (0.00 sec)
Table Structure
Table 1
-------------
code | name
-------------
1 | abc
2 | xyz
Table 2
-------------------------------
code | table1_code | data
-------------------------------
1 | 1 | a
2 | 1 | b
3 | 1 | c
4 | 1 | d
5 | 1 | e
6 | 2 | f
7 | 2 | g
Now Expected Result:
Result
table1_code | name | data_col1 | data_col2 | data_col3
------------------------------------------------------------------
1 abc a b c
2 xyz f g -
What i have tried so far
SELECT a.code AS table1_code,
a.NAME,
b.data_col
FROM table1 AS a
LEFT OUTER JOIN table2 AS b
ON a.code = b.table1_code
kindly help me to alter the query to get above output,
i want data upto 3 columns only.
To do this you need a way to determine what the first three items in each group should be. The way to do this is to add a row number that restarts for each new group. This would be easy in any database that supports window function, but MySQL doesn't so it gets a bit more complicated as you have to use user defined variables to do the ranking.
In the query below ranking of data is done in the derived table used as source and then a conditional aggregation is performed so that you get one row per group:
SELECT
code,
name,
MAX(CASE WHEN rank = 1 THEN data END) data_col1,
MAX(CASE WHEN rank = 2 THEN data END) data_col2,
MAX(CASE WHEN rank = 3 THEN data END) data_col3
FROM (
SELECT
a.code,
a.name,
b.data,
(
CASE a.name
WHEN #grp THEN #row := #row + 1
ELSE #row := 1 AND #grp := a.name
END
) + 1 AS rank
FROM table1 AS a
LEFT OUTER JOIN table2 AS b ON a.code = b.table1_code
, (SELECT #row := 0, #grp := '') r
ORDER BY a.code, a.name asc
) src
GROUP BY code, name;
Sample SQL Fiddle
I have Table A
============
| id | val |
=====+======
| 1 | abc |
| 1 | def |
| 2 | ghi |
| 2 | jkl |
============
I have Table B
============
| id | val2 |
=====+======
| 1 | rty |
| 1 | vbn |
| 2 | uio |
| 2 | zxc |
============
I want to display the two tables like this..
===================
| id | val | val2 |
=====+=====+=======
| 1 | abc | rty |
| 1 | def | vbn |
| 2 | ghi | uio |
| 2 | jkl | zxc |
===================
my problem is i am having redundancy..
Yes, you have a problem because you don't have a proper join key. You can do this by using variables to create one. Something like this will work for the data you provide:
select min(id), max(aval), max(bval)
from ((select id, val as aval, NULL as bval, #rna := #rna + 1 as seqnum
from tablea a cross join (select #rna := 0)
) union all
(select id, NULL val, #rnb := #rnb + 1 as seqnum
from tableb b cross join (select #rnb := 0)
)
) ab
group by seqnum;
I like Gordon's approach, since it doesn't assume the same number of rows in each table, but here's a JOIN version:
SELECT a.id,a.val,b.val2
FROM (SELECT #row_number:=#row_number+1 AS row_number
,id,val
FROM Table1 a cross join (select #row_number := 0) b
)a
JOIN (SELECT #row_number2:=#row_number2+1 AS row_number2
,id,val2
FROM Table2 a cross join (select #row_number2 := 0) b
)b
ON a.Row_Number = b.Row_Number2
AND a.id = b.id
Here's a working version of his UNION version:
SELECT Row_Number,ID,MAX(Val) AS Val,MAX(Val2) AS Val2
FROM (SELECT #row_number:=#row_number+1 AS row_number
,id,val,NULL as Val2
FROM Table1 a cross join (select #row_number := 0) b
UNION ALL
SELECT #row_number2:=#row_number2+1 AS row_number
,id,NULL,val2
FROM Table2 a cross join (select #row_number2 := 0) b
)sub
GROUP BY Row_Number,ID
Demo of both: SQL Fiddle
You can simply do this using INNER JOIN. See my query below:
SELECT A.id,val,val2 FROM
(SELECT
#row_number:=#row_number+1 AS RowNumber,
id,
val
FROM TableA, (SELECT #row_number:=0) AS t ORDER BY val) AS A
INNER JOIN
(SELECT
#row_number:=#row_number+1 AS RowNumber,
id,
val2
FROM TableB, (SELECT #row_number:=0) AS t ORDER BY val2) AS B
ON A.RowNumber=B.RowNumber