Select a single result from groups by the min of two values - mysql

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 |
+----+-------+---------+--------+--------+

Related

How do I get the first n records, per foreign key with MySQL?

I have the following table:
+----+-----------+------+
| id | table2_id | type |
+----+-----------+------+
| 1 | 100 | A |
| 2 | 100 | B |
| 3 | 100 | C |
| 4 | 100 | A |
| 5 | 250 | A |
+----+-----------+------+
I need a select statement that would get all the records before the first occurrence of type C, per table2_id.
So I want records 1, 2, and 5
I'd do this in code with a loop, but I need to do it in MySQL specifically.
If you are running MySQL 8.0, you can do this with window functions:
select *
from (
select t.*,
min(case when type = 'C' then id end) over(partition by table2_id) min_id
from mytable t
) t
where min_id is null or id < min_id
In all versions, you could use not exists:
select t.*
from mytable t
where not exists (
select 1
from mytable t1
where t1.table2_id = t.table2_id and t1.id <= t.id and t1.type = 'C'
)

Select row by column id AND include rows that contain the same value from another column if null

I am trying to get a row by ID but also include rows that have the same value in other column but not including them if the value is null or empty.
Data:
+------+------+------+
|ID GROUP |AREA |
+------+------+------+
| 1 | A | AA |
+------+------+------+
| 2 | | AA |
+------+------+------+
| 3 | A | AA |
+------+------+------+
| 4 | B | AA |
+------+------+------+
| 5 | | BB |
+------+------+------+
| 6 | A | AA |
+------+------+------+
| 7 | B | BB |
+------+------+------+
| 8 | | AA |
+------+------+------+
What I have now:
SELECT * WHERE ID = 1 AND AREA = "AA"
Which returns:
+------+------+------+
| 1 | A | AA |
+------+------+------+
But I also want to get all the rows that contain GROUP "A":
+------+------+------+
| 1 | A | AA |
+------+------+------+
| 3 | A | AA |
+------+------+------+
| 6 | A | AA |
+------+------+------+
But would need to return just the ID requested if the "GROUP" column is Null.
SELECT * WHERE ID = 2 AND AREA = "AA"
+------+------+------+
| 2 | | AA |
+------+------+------+
I've tried everything I can think of. Different joins and sub-queries, but I can't seem to make this work.
You can try to write the condition in the subquery, then do self-join by the subquery.
JOIN condition on AREA and GROUP columns, if GROUP is null addition to check id.
CREATE TABLE T(
ID INT,
`GROUP` VARCHAR(5),
AREA VARCHAR(5)
);
INSERT INTO T VALUES ( 1 , 'A' , 'AA' );
INSERT INTO T VALUES ( 2 , NULL , 'AA' );
INSERT INTO T VALUES ( 3 , 'A' , 'AA' );
INSERT INTO T VALUES ( 4 , 'B' , 'AA' );
INSERT INTO T VALUES ( 5 , NULL , 'BB' );
INSERT INTO T VALUES ( 6 , 'A' , 'AA' );
INSERT INTO T VALUES ( 7 , 'B' , 'BB' );
INSERT INTO T VALUES ( 8 , NULL , 'AA' );
Query 1:
SELECT t1.*
FROM T t1 INNER JOIN (
SELECT *
FROM T
WHERE ID = 2 AND AREA = "AA"
) t2 ON t1.AREA = t2.AREA and t1.GROUP = t2.GROUP or (t2.GROUP is null and t1.id = t2.id)
Results:
| ID | GROUP | AREA |
|----|--------|------|
| 2 | (null) | AA |
Query 2:
SELECT t1.*
FROM T t1 INNER JOIN (
SELECT *
FROM T
WHERE ID = 1 AND AREA = "AA"
) t2 ON t1.AREA = t2.AREA and t1.GROUP = t2.GROUP or (t2.GROUP is null and t1.id = t2.id)
Results:
| ID | GROUP | AREA |
|----|-------|------|
| 1 | A | AA |
| 3 | A | AA |
| 6 | A | AA |
I am not quite sure I understand you fully, but if you want to get ID by GROUP, perhaps you can try
select distinct ID
from TABLE
group by GROUP
Try this:
SELECT * FROM `table_name` WHERE `ID` = 2 AND `AREA` = 'AA' AND `GROUP` IN (SELECT `GROUP` FROM `table_name` WHERE `ID` = 2);
PS: don't forget to change table_name by the table you're fetching records from.
I'd do this with a UNION
SELECT
ID
,`GROUP`
,AREA
FROM
your_table
WHERE ID = 2
UNION
SELECT
ID
,`GROUP`
,AREA
FROM
your_table
WHERE
`GROUP` = (SELECT `GROUP` FROM your_table WHERE ID = 2)
AND NULLIF(`GROUP`, '') IS NOT NULL;

group similar successive records that they are not separated by other deferent records - Mysql

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;

mysql: group surrounding records by same field value

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 |
+-------+------+------+

cumulative average in MySQL

I have a table with id and values shown below. is it possible to get another column which takes the value divided by the cumulative average as we go down the row?
original table : t1
+----+----------------------+
| id | Val |
+----+---------------------+-
| 1 | NULL |
| 2 | 136 |
| 3 | 42 |
table i want to get
+----+---------------------+-----------------------------+
| id | Val | VAL/(AVG(VAL) ) |
+----+---------------------+-----------------------------+
| 1 | NULL | NULL |
| 2 | 136 | 136/((136+0)/2)=2.000 |
| 3 | 42 | 42/((42+136+0)/3)=0.708 |
here is my query:
SELECT t1.id, t1.Val, Val/AVG(t1.Val)
FROM followers t1
JOIN followers t2
ON t2.id <= t1.id
group by t1.id;
however i get this instead:
+----+---------------------+----------------------+
| id | Val | VAL/(AVG(VAL) ) |
+----+---------------------+----------------------+
| 1 | NULL | NULL |
| 2 | 136 | 1.0000 |
| 3 | 42 | 1.0000 |
seems like AVG(Val) returns the same value from the col Val.
I was hoping to do something similar to this link here but instead of sum i want average.
MySQL SELECT function to sum current data
I re-implemented the edits and took rows with NULL into account:
+----+---------------------+---------------------+
| id | Val | VAL/(AVG(VAL) ) |
+----+---------------------+----------------------+
| 1 | NULL | NULL |
| 2 | 136 | 1.0000 |<---need this to = 2.000
| 3 | 42 | 0.4719 |<---need this to = 0.708
SELECT t1.id, t1.Val, t1.Val/(SUM(t2.Val)/(t1.id)) AS C
FROM followers t1
JOIN followers t2
ON t2.id <= t1.id
group by t1.id;
I think you want t2.val in the avg():
SELECT t1.id, t1.Val, t1.Val/AVG(t2.Val)
FROM followers t1 JOIN
followers t2
ON t2.id <= t1.id
group by t1.id;
EDIT:
Mike Brand is correct that the above is a lousy way to do what you want. In MySQL, you can do the same using variables:
select t.id, t.val,
(case when (#n := #n + 1) is null then null
when (#cumval := #cumval + val) is null then null
else t.val / (#cumval / #n)
end)
from followers t cross join
(select #n := 0, #cumval := 0) vars
order by t.id;
This might misbehave with NULL values of val, but it gives the idea for a faster way to do the calculation in MySQL.