I have a table with a following structure and data:
id | type | title
--------------------------
1 | 1 | test 1
2 | 1 | test 2
3 | 2 | test 3
4 | 2 | test 4
5 | 1 | test 5
I need to group neighbor rows with the same type field values.
So the result should be like:
type |
------
1 |
2 |
1 |
Thanks in advance.
this should do the trick.. using user defined variables.
SELECT
type
FROM(
SELECT
type,
if(#a = type, #b, #b := #b + 1) as grouping_col,
#a := type
FROM testing
JOIN (select #a := 1, #b := 0) as temp
) as t
GROUP BY grouping_col;
SQL FIDDLE to play with
Here's one way - although a solution using variables will scale better...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,type INT NOT NULL
,title VARCHAR(12) NOT NULL
);
INSERT INTO my_table VALUES
(1,1,'test 1'),
(3,1,'test 2'),
(4,2,'test 3'),
(7,2,'test 4'),
(9,1,'test 5');
SELECT * FROM my_table;
+----+------+--------+
| id | type | title |
+----+------+--------+
| 1 | 1 | test 1 |
| 3 | 1 | test 2 |
| 4 | 2 | test 3 |
| 7 | 2 | test 4 |
| 9 | 1 | test 5 |
+----+------+--------+
SELECT a.id start
, MIN(c.id) End
, a.type
FROM
( SELECT x.*,COUNT(*) rank FROM my_table x JOIN my_table y ON y.id <= x.id GROUP BY x.id) a
LEFT
JOIN
( SELECT x.*,COUNT(*) rank FROM my_table x JOIN my_table y ON y.id <= x.id GROUP BY x.id) b
ON b.type = a.type
AND b.rank = a.rank - 1
LEFT
JOIN
( SELECT x.*,COUNT(*) rank FROM my_table x JOIN my_table y ON y.id <= x.id GROUP BY x.id) c
ON c.type = a.type
AND c.rank >= a.rank
LEFT
JOIN
( SELECT x.*,COUNT(*) rank FROM my_table x JOIN my_table y ON y.id <= x.id GROUP BY x.id) d
ON d.type = a.type
AND d.rank = c.rank + 1
WHERE b.id IS NULL
AND c.id IS NOT NULL
AND d.id IS NULL
GROUP
BY a.id;
+-------+------+------+
| start | End | type |
+-------+------+------+
| 1 | 3 | 1 |
| 4 | 7 | 2 |
| 9 | 9 | 1 |
+-------+------+------+
Related
I have a table with Transactions, amongst whose columns are id, created_at, and company_id. I'd like to group the four first transactions of every company and return the created_at values of each transaction on each row.
In other words, I want each row of my output to correspond to the four first transactions of each company (so grouping by company_id) with columns showing me the company_id and the created_at of each of those four transactions.
How do I do that?
Sample data:
id | company_id | created_at
---------------------------------
1123 | abcd | 10/12/2015
8291 | abcd | 10/14/2015
9012 | abcd | 10/15/2015
9540 | abcd | 10/16/2015
10342 | abcd | 10/21/2015
10456 | abcd | 10/22/2015
2301 | efgh | 10/13/2015
4000 | efgh | 11/01/2015
4023 | efgh | 11/03/2015
6239 | efgh | 11/08/2015
7500 | efgh | 11/14/2015
Sample output:
company_id | created_at_1 | created_at_2 | created_at_3 | created_at_4
--------------------------------------------------------------------------
abcd | 10/12/2015 | 10/14/2015 | 10/15/2015 | 10/16/2015
efgh | 10/13/2015 | 11/01/2015 | 11/03/2015 | 11/08/2015
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,company_id VARCHAR(12) NOT NULL
,created_at DATE NOT NULL
);
INSERT INTO my_table VALUES
( 1123,'abcd','2015/10/12'),
( 8291,'abcd','2015/10/14'),
( 9012,'abcd','2015/10/15'),
( 9540,'abcd','2015/10/16'),
(10342,'abcd','2015/10/21'),
(10456,'abcd','2015/10/22'),
( 2301,'efgh','2015/10/13'),
( 4000,'efgh','2015/11/01'),
( 4023,'efgh','2015/11/03'),
( 6239,'efgh','2015/11/08'),
( 7500,'efgh','2015/11/14');
SELECT x.*
FROM my_table x
JOIN my_table y
ON y.company_id = x.company_id
AND y.created_at <= x.created_at
GROUP
BY x.id
HAVING COUNT(*) <= 4
ORDER
BY company_id
, created_at;
+------+------------+------------+
| id | company_id | created_at |
+------+------------+------------+
| 1123 | abcd | 2015-10-12 |
| 8291 | abcd | 2015-10-14 |
| 9012 | abcd | 2015-10-15 |
| 9540 | abcd | 2015-10-16 |
| 2301 | efgh | 2015-10-13 |
| 4000 | efgh | 2015-11-01 |
| 4023 | efgh | 2015-11-03 |
| 6239 | efgh | 2015-11-08 |
+------+------------+------------+
A solution with variables will be orders of magnitude faster, e.g...
SELECT a.id
, a.company_id
, a.created_at
FROM
( SELECT x.*
, CASE WHEN #prev = x.company_id THEN #i:=#i+1 ELSE #i:=1 END i, #prev:=x.company_id prev
FROM my_table x
, (SELECT #i:=1,#prev:=null) vars
ORDER
BY x.company_id
, x.created_at
) a
WHERE i <= 4;
One possible way is the following:
select company_id,
min(created_at) as created_at_1,
(select created_at from t where company_id=t1.company_id order by created_at limit 1 offset 1) as created_at_2,
(select created_at from t where company_id=t1.company_id order by created_at limit 1 offset 2) as created_at_3,
(select created_at from t where company_id=t1.company_id order by created_at limit 1 offset 3) as created_at_4
from t as t1
group by company_id
EDIT:
Another possibility (inspired by this answer) is:
select company_id,
min(created_at) as created_at_1,
min(case r when 2 then created_at else null end) as created_at_2,
min(case r when 3 then created_at else null end) as created_at_3,
min(case r when 4 then created_at else null end) as created_at_4
from (
select company_id, created_at,
(case company_id when #curType
then #curRank := #curRank + 1
else #curRank := 1 and #curType := company_id end)+1 as r
from t, (select #curRank := 0, #curType := '') f
order by company_id, created_at
) as o
where r <= 4
group by company_id
Could by like this maybe?
SELECT S.company_id,
A.created_at created_at_1,
B.created_at created_at_2,
C.created_at created_at_3,
D.created_at created_at_4
FROM sample S
LEFT JOIN sample A on S.company_id = A.company_id AND A.id NOT IN(S.id)
LEFT JOIN sample B on S.company_id = B.company_id AND B.id NOT IN(S.id, A.id)
LEFT JOIN sample C on S.company_id = C.company_id AND C.id NOT IN(S.id, A.id, B.id)
LEFT JOIN sample D on S.company_id = D.company_id AND D.id NOT IN(S.id, A.id, B.id, C.id)
GROUP BY S.company_id
http://sqlfiddle.com/#!9/c577e/3
It might not be very efficient, though.
And they are not in order, because your American date format is not good to be sorted. Better switch to TIMESTAMP format.
I have a table that looks like this:
+----+-------+---------+--------+--------+
| id | meta1 | meta2 | value1 | value2 |
+----+-------+---------+--------+--------+
| 1 | foo | bar | 0.1 | 0.01 |
| 1 | baz | quux | 0.2 | 0.01 |
| 1 | lorem | ipsum | 0.1 | 0.05 |
| 2 | dolor | sit | 0.2 | 0.02 |
| 2 | amet | eos | 0.3 | 0.02 |
| 3 | clita | corpora | 0.5 | 0.03 |
+----+-------+---------+--------+--------+
I am trying to extract one (complete) row for each id with the lowest value1 and in the case that there are equal value1s, falling back to the lowest value2.
The query should result in a result set like this:
+----+-------+---------+--------+--------+
| id | meta1 | meta2 | value1 | VALUE2 |
+----+-------+---------+--------+--------+
| 1 | foo | bar | 0.1 | 0.01 |
| 2 | dolor | sit | 0.2 | 0.02 |
| 3 | clita | corpora | 0.5 | 0.03 |
+----+-------+---------+--------+--------+
I started by attempting the following query:
SELECT
t1.*
FROM
test t1
INNER JOIN
(SELECT
id, MIN(value1) minValue1
FROM
test
GROUP BY id) t2 ON t1.id = t2.id
AND t1.value1 = t2.minValue1;
But this doesn't 'break the tie' for id '1' and I end up with two of those records. I have tried adding HAVING clauses and additional subqueries and am lost beyond this initial step. Help much appreciated.
You want all records for which no better record (ie. with lower value1 or same value1 and lower value2) exists:
select *
from mytable
where not exists
(
select *
from mytable better
where better.id = mytable.id
and
(
better.value1 < mytable.value1
or
(better.value1 = mytable.value1 and better.value2 < mytable.value2)
)
);
You can use not exists:
SELECT t.*
FROM test t
WHERE NOT EXISTS (SELECT 1
FROM test t2
WHERE t2.id = t.id AND
(t2.value1 < t.value1 OR
(t2.value1 = t.value1 and t2.value2 < t.value2) )
);
An alternative method is to use variables:
select t.*
from (select t.*,
(#rn := if(#i = id, #rn + 1,
if(#i := id, 1, 1)
)
) as rn
from test t cross join
(select #rn := 0, #i := -1) params
order by id, value1, value2
) t
where rn = 1;
Consider the following...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL
,meta1 VARCHAR(12)
,meta2 VARCHAR(12)
,value1 DECIMAL(5,2)
,value2 DECIMAL(5,2)
);
INSERT INTO my_table VALUES
(1 ,'foo','bar',0.1,0.09),
(1 ,'baz','quux',0.2,0.08),
(1 ,'lorem','ipsum',0.1,0.07),
(2 ,'dolor','sit',0.2,0.06),
(2 ,'amet','eos',0.3,0.05),
(3 ,'clita','corpora',0.5,0.04);
SELECT a.*
FROM my_table a
JOIN
( SELECT x.id
, x.value1
, MIN(x.value2) min_value2
FROM my_table x
JOIN
( SELECT id
, MIN(value1) min_value1
FROM my_table
GROUP
BY id
) y
ON y.id = x.id
AND y.min_value1 = x.value1
GROUP
BY x.id
, x.value1
) b
ON b.id = a.id
AND b.value1 = a.value1
AND b.min_value2 = a.value2;
+----+-------+---------+--------+--------+
| id | meta1 | meta2 | value1 | value2 |
+----+-------+---------+--------+--------+
| 1 | lorem | ipsum | 0.10 | 0.07 |
| 2 | dolor | sit | 0.20 | 0.06 |
| 3 | clita | corpora | 0.50 | 0.04 |
+----+-------+---------+--------+--------+
you can extend this indefinitely, but at some point an laternative solution may become easier to manage...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL
,meta1 VARCHAR(12)
,meta2 VARCHAR(12)
,value1 DECIMAL(5,2)
,value2 DECIMAL(5,2)
);
INSERT INTO my_table VALUES
(1 ,'foo' ,'bar' ,0.1,0.09),
(1 ,'baz' ,'quux' ,0.2,0.08),
(1 ,'lorem','ipsum' ,0.1,0.07),
(2 ,'dolor','sit' ,0.2,0.06),
(2 ,'amet' ,'eos' ,0.3,0.05),
(3 ,'clita','corpora',0.5,0.04),
(1 ,'bar' ,'foo' ,0.1,0.07);
SELECT a.*
FROM my_table a
JOIN
(
SELECT q.id
, q.value1
, q.value2
, MIN(q.meta1) min_meta1
FROM my_table q
JOIN
(
SELECT x.id
, x.value1
, MIN(x.value2) min_value2
FROM my_table x
JOIN
( SELECT id
, MIN(value1) min_value1
FROM my_table
GROUP
BY id
) y
ON y.id = x.id
AND y.min_value1 = x.value1
GROUP
BY x.id
, x.value1
) r
ON r.id = q.id
AND r.value1 = q.value1
AND r.min_value2 = q.value2
GROUP
BY q.id
, q.value1
, q.value2
) b
ON b.id = a.id
AND b.value1 = a.value1
AND b.value2 = a.value2
AND b.min_meta1 = a.meta1;
+----+-------+---------+--------+--------+
| id | meta1 | meta2 | value1 | value2 |
+----+-------+---------+--------+--------+
| 2 | dolor | sit | 0.20 | 0.06 |
| 3 | clita | corpora | 0.50 | 0.04 |
| 1 | bar | foo | 0.10 | 0.07 |
+----+-------+---------+--------+--------+
In MySQL, how to group similar successive records that they are not separated by other deferent records
id | type |
1 | 1 |
2 | 1 |
3 | 1 |
4 | 5 |
5 | 1 |
6 | 8 |
7 | 1 |
8 | 3 |
9 | 3 |
10 | 3 |
the result should be like this :
id | type |
3 | 1 |
4 | 5 |
5 | 1 |
6 | 8 |
8 | 1 |
10 | 3 |
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,type INT NOT NULL
,x INT NOT NULL
);
INSERT INTO my_table VALUES
(1,1,1),
(2,1,1),
(3,1,1),
(4,5,5),
(5,1,1),
(6,8,8),
(7,1,1),
(8,1,1);
SELECT MIN(c.id) id
, a.type
, a.x
FROM my_table a
LEFT
JOIN my_table b
ON b.id + 1 = a.id
AND b.type = a.type
AND b.x = a.x
LEFT
JOIN my_table c
ON c.id >= a.id
AND c.type = a.type
AND c.x = a.x
LEFT
JOIN my_table d
ON d.id - 1 = c.id
AND d.type = a.type
AND d.x = a.x
WHERE b.id IS NULL
AND c.id IS NOT NULL
AND d.id IS NULL
GROUP
BY a.id;
+------+------+---+
| id | type | x |
+------+------+---+
| 3 | 1 | 1 |
| 4 | 5 | 5 |
| 5 | 1 | 1 |
| 6 | 8 | 8 |
| 8 | 1 | 1 |
+------+------+---+
You just need a standard group by:
select count(*) id, type, `group`
from mytable
group by type, `group`
Note that you'll need to delimit the reserved word "group "
Your question is rather ill-formed. I think the following might be close to what you want. It identifies the groups based on consecutive type values by assigning a new grp column that identifies the consecutive groups.
select type, max(id) as maxid, count(*) as numingroup
from (select t.*,
(#grp := if(#type = type, #grp, if(#type := type, #grp + 1, #grp + 1))) as grp
from my_table t cross join
(select #grp := 0, #type := -1)x
order by id
) t
group by grp, type;
I have a select result like this:
ID | DATE
----------------
10 | 2014-07-23
7 | 2014-07-24
8 | 2014-07-24
9 | 2014-07-24
1 | 2014-07-25
2 | 2014-07-25
6 | 2014-07-25
3 | 2014-07-26
4 | 2014-07-27
5 | 2014-07-28
The result above is ordered by date. Now, I want to select the one previous row before:
2 | 2014-07-25
Which is:
1 | 2014-07-25
In case I don't know the exact ID and the conditional code must be compatible with if I want to select a previous row of:
3 | 2014-07-26
Which is:
6 | 2014-07-25
What condition should I use?
UPDATE
Tried this:
SET #rank=0;
SELECT #rank:=#rank+1 AS rank, t1.*
FROM table t1
Then I got this:
RANK | ID | DATE
----------------
1 | 10 | 2014-07-23
2 | 7 | 2014-07-24
3 | 8 | 2014-07-24
4 | 9 | 2014-07-24
5 | 1 | 2014-07-25
6 | 2 | 2014-07-25
7 | 6 | 2014-07-25
8 | 3 | 2014-07-26
9 | 4 | 2014-07-27
10 | 5 | 2014-07-28
Then I tried this:
SET #rank=0;
SELECT #rank:=#rank+1 AS rank, t1.*
FROM table t1
WHERE rank < 3;
I got this error: Unknown column 'rank' in 'where clause'.
Here's one way...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(ID INT NOT NULL PRIMARY KEY
,DATE DATE NOT NULL
);
INSERT INTO my_table VALUES
(10 ,'2014-07-23'),
(7 ,'2014-07-24'),
(8 ,'2014-07-24'),
(9 ,'2014-07-24'),
(1 ,'2014-07-25'),
(2 ,'2014-07-25'),
(6 ,'2014-07-25'),
(3 ,'2014-07-26'),
(4 ,'2014-07-27'),
(5 ,'2014-07-28');
SELECT a.id
, a.date
, b.id b_id
, b.date b_date
FROM
( SELECT x.*
, COUNT(*) rank
FROM my_table x
JOIN my_table y
ON (y.date < x.date)
OR (y.date = x.date AND y.id <= x.id)
GROUP
BY x.date
, x.id
) a
LEFT
JOIN
( SELECT x.*
, COUNT(*) rank
FROM my_table x
JOIN my_table y
ON (y.date < x.date)
OR (y.date = x.date AND y.id <= x.id)
GROUP
BY x.date
, x.id
) b
ON b.rank = a.rank - 1;
+----+------------+------+------------+
| id | date | b_id | b_date |
+----+------------+------+------------+
| 10 | 2014-07-23 | NULL | NULL |
| 7 | 2014-07-24 | 10 | 2014-07-23 |
| 8 | 2014-07-24 | 7 | 2014-07-24 |
| 9 | 2014-07-24 | 8 | 2014-07-24 |
| 1 | 2014-07-25 | 9 | 2014-07-24 |
| 2 | 2014-07-25 | 1 | 2014-07-25 |
| 6 | 2014-07-25 | 2 | 2014-07-25 |
| 3 | 2014-07-26 | 6 | 2014-07-25 |
| 4 | 2014-07-27 | 3 | 2014-07-26 |
| 5 | 2014-07-28 | 4 | 2014-07-27 |
+----+------------+------+------------+
... but you can also do this (quicker) with variables.
You can add a row id to the select like this
SELECT #rowid:=#rowid+1 as rowid,
t1.* FROM yourdatabase.tablename t1, (SELECT #rowid:=0) as rowids;
Then you can run a simple query to get the lower rowid from the input.
This uses a sub query that joins the table against itself, where on one side it is the date you are checking and matching against smaller dates. It uses MAX to get the highest smaller date.
This is then joined against another sub query that gets the highest ID for each date, which also joins against the table itself to get the other details from that row.
SELECT table.*
FROM table
INNER JOIN
(
SELECT MAX(a.date) AS latest_prev_date
FROM table1 a
INNER JOIN table1 b
ON a.date > b.date
WHERE a.date = '2014-07-26'
) sub0
ON table.date = sub0.latest_prev_date
INNER JOIN
(
SELECT date, MAX(ID) AS latest_prev_id
FROM table1
GROUP BY date
) sub1
ON table.ID = sub1.latest_prev_id
AND sub1.date = sub0.latest_prev_date
if you want to use a user_defined_variable this is a way to do it.
SELECT
tab.id, temp.id, temp.date
FROM
(
SELECT
#A:=#A + 1 AS rank_col, t.date, t.id
FROM
myTable t
CROSS JOIN (SELECT #A:=0) join_table
) AS tab
LEFT JOIN
(
SELECT
#B:=#B + 1 AS rank_col, t2 . *
FROM myTable t2
CROSS JOIN (SELECT #B:=0) join_table1
) temp ON temp.rank_col = tab.rank_col - 1;
DEMO
Given the table structure below, how do I select only rows having the same root and level values for a given name and user_id?
Category
+----+---------+------+------+-------+
| id | user_id | name | root | level |
+----+---------+------+------+-------+
| 1 | 10 | wzq | 1 | 1 |
| 2 | 11 | xyz | 2 | 1 |
| 3 | 10 | xyz | 2 | 2 |
| 4 | 10 | xyz | 2 | 2 |
+----+---------+------+------+-------+
What I have now also selects rows that do not have the same root and level
SELECT `c`.`id`, `c`.`user_id`, `c`.`name`, `c`.`root`, `c`.`level`
FROM `category` `c`
WHERE c.id IN (SELECT d.id FROM category AS d WHERE c.root=d.root AND c.level=d.level ) AND c.user_id = 10 AND c.name = 'xyz'
ORDER BY `c`.`id`
For the above example only the 3rd and 4th rows should be returned
Try this one for select only rows having the same root and level simply root=level
SELECT `id`, `user_id`, `name`, `root`, `level`
FROM `category`
WHERE root=level AND user_id = 10 AND `name` = 'xyz'
ORDER BY `id`
SELECT x.*
FROM my_table x
JOIN my_table y
ON y.user_id = x.user_id
AND y.root = x.root
AND y.level = x.level
AND y.id <> x.id
WHERE x.user_id = 10
try this:
SELECT *
FROM
(
SELECT root, level
FROM category
WHERE name = 'xyz' AND user_id = 10
) x JOIN category c ON x.root = c.root