I want to get the difference between two sequential values from my table.
| id | count |
| 1 | 1 |
| 2 | 7 |
| 3 | 9 |
| 4 | 3 |
| 5 | 7 |
| 6 | 9 |
For example the difference between
id2-id1 = 6,
id3-id2 = -2,
...
How can I do it? SELECT SUM(id(x+1) - id(x)) FROM table1
You can use a subquery to find count for the preceding id.
In case there are no gaps in the ID column:
SELECT CONCAT(t.`id` ,' - ', t.`id` - 1) AS `IDs`
, t.`count` - (SELECT `count`
FROM `tbl`
WHERE `id` = t.`id` - 1) AS `Difference`
FROM `tbl` t
WHERE t.`id` > 1
SQLFiddle
In case there are gaps in the IDcolumn.
First solution, using ORDER BY <...> DESC with LIMIT 1:
SELECT CONCAT(t.id ,' - ', (SELECT `id` FROM tbl WHERE t.id > id ORDER BY id DESC LIMIT 1)) AS IDs
, t.`count` - (SELECT `count`
FROM tbl
WHERE t.id > id
ORDER BY id DESC
LIMIT 1) AS difference
FROM tbl t
WHERE t.id > 1;
SQLFiddle
Second solution, using another subquery to find count with the MAX(id) less than current id:
SELECT CONCAT(t.id ,' - ', (SELECT MAX(`id`) FROM tbl WHERE id < t.id)) AS IDs
, t.`count` - (SELECT `count`
FROM tbl
WHERE `id` = (SELECT MAX(`id`)
FROM tbl
WHERE id < t.id)
) AS difference
FROM tbl t
WHERE t.id > 1;
SQLFiddle
P.S. : First column, IDs, is just for readability, you can omit it or change completely, if it is necessary.
If you know that the ids have no gaps, then just use a join:
select t.*, (tnext.count - t.count) as diff
from table t join
table tnext
on t.id = tnext.id - 1;
If you just want the sum of the differences, then that is the same as the last value minus the first value (all the intermediate values cancel out in the summation). You can do this with limit:
select last.count - first.count
from (select t.* from table order by id limit 1) as first cross join
(select t.* from table order by id desc limit 1) as last;
Try this:
SELECT MAX(count)-MIN(count) diff WHERE id IN(1,2)
Or this way
SELECT 2*STD(count) diff WHERE id IN(1,2)
This works even if ids have distances between them:
SELECT *,
((SELECT value FROM example e2 WHERE e2.id > e1.id ORDER BY id ASC LIMIT 1) - value) as diff
FROM example e1;
Related
I have a database table setup like this:
id | code | group_id | status ---
---|-------|---------|------------
1 | abcd1 | group_1 | available
2 | abcd2 | group_1 | available
3 | adsd3 | group_1 | available
4 | dfgd4 | group_1 | available
5 | vfcd5 | group_1 | available
6 | bgcd6 | group_2 | available
7 | abcd7 | group_2 | available
8 | ahgf8 | group_2 | available
9 | dfgd9 | group_2 | available
10 | qwer6 | group_2 | available
In the example above, each group_id has 5 total rows (arbitrary for example, total rows will be dynamic and vary), I need to remove every row that matches available in status except for 2 of them (which 2 does not matter, as long as there are 2 of them remaining)
Basically every unique group_id should only have 2 total rows with status of available. I am able to do a simple SQL query to remove all of them, but struggling to come up with a SQL query to remove all except for 2 ... please helppppp :)
If code is unique, you can use subqueries to keep the "min" and "max"
DELETE FROM t
WHERE t.status = 'available'
AND (t.group_id, t.code) NOT IN (
SELECT group_id, MAX(code)
FROM t
WHERE status = 'available'
GROUP BY group_id
)
AND (t.group_id, t.code) NOT IN (
SELECT group_id, MIN(code)
FROM t
WHERE status = 'available'
GROUP BY group_id
)
Similarly, with an auto increment id:
DELETE FROM t
WHERE t.status = 'available'
AND t.id NOT IN (
SELECT MAX(id) FROM t WHERE status = 'available' GROUP BY group_id
UNION
SELECT MIN(id) FROM t WHERE status = 'available' GROUP BY group_id
)
I reworked the subquery into a UNION instead in this version, but the "AND" format would work just as well too. Also, if "code" was unique across the whole table, the NOT IN could be simplified down to excluding the group_id as well (though it would still be needed in the subqueries' GROUP BY clauses).
Edit: MySQL doesn't like subqueries referencing tables being UPDATEd/DELETEd in the WHERE of the query doing the UPDATE/DELETE; in those cases, you can usually double-wrap the subquery to give it an alias, causing MySQL to treat it as a temporary table (behind the scenes).
DELETE FROM t
WHERE t.status = 'available'
AND t.id NOT IN (
SELECT * FROM (
SELECT MAX(id) FROM t WHERE status = 'available' GROUP BY group_id
UNION
SELECT MIN(id) FROM t WHERE status = 'available' GROUP BY group_id
) AS a
)
Another alternative, I don't recall if MySQL complains as much about joins in DELETE/UPDATE....
DELETE t
FROM t
LEFT JOIN (
SELECT MIN(id) AS minId, MAX(id) AS maxId, 1 AS keep_flag
FROM t
WHERE status = 'available'
GROUP BY group_id
) AS tKeep ON t.id IN (tKeep.minId, tKeep.maxId)
WHERE t.status = 'available'
AND tKeep.keep_flag IS NULL
To keep the min and max ids, I think a join is the simplest solution:
DELETE t
FROM t LEFT JOIN
(SELECT group_id, MIN(id) as min_id, MAX(id) as max_id
FROM t
WHERE t.status = 'available'
GROUP BY group_id
) tt
ON t.id IN (tt.min_id, tt.max_id)
WHERE t.status = 'available' AND
tt.group_id IS NULL;
If the column "id" is the PRIMARY KEY or a UNIQUE KEY, then we could use a correlated subquery to get the second lowest value for a particular group_id.
We could then use that to identify rows for group_id that have higher values of the "id" column.
A query something like this:
SELECT t.`id`
, t.`group_id`
FROM `setup_like_this` t
WHERE t.`status` = 'available'
AND t.`id`
> ( SELECT s.`id`
FROM `setup_like_this` s
WHERE s.`status` = 'available'
AND s.`group_id` = t.`group_id`
ORDER
BY s.`id`
LIMIT 1,1
)
We test that as a SELECT first, to examine the rows that are returned. When we are satisfied this query is returning the set of rows we want to delete, we can replace SELECT ... FROM with DELETE t.* FROM to convert it to a DELETE statement to remove the rows.
Error 1093 encountered converting to DELETE statement.
One workaround is to make the query above into a inline view, and then join to the target table
DELETE q.*
FROM `setup_like_this` q
JOIN ( -- inline view, query from above returns `id` of rows we want to delete
SELECT t.`id`
, t.`group_id`
FROM `setup_like_this` t
WHERE t.`status` = 'available'
AND t.`id`
> ( SELECT s.`id`
FROM `setup_like_this` s
WHERE s.`status` = 'available'
AND s.`group_id` = t.`group_id`
ORDER
BY s.`id`
LIMIT 1,1
)
) r
ON r.id = q.id
select id, code, group_id, status
from (
select id, code, group_id, status
, ROW_NUMBER() OVER (
PARTITION BY group_id
ORDER BY id DESC) row_num
) rownum
from a
) q
where rownum < 3
I have a MySQL table tbl below:
now I run the following query:
SELECT DAY_NUMBER, WEEK_NUMBER, MAX(total_volume)
FROM `tbl`
GROUP BY WEEK_NUMBER
I get the following output:
Everything looks good except for the DAY_NUMBER. The DAY_NUMBER does not reflect the TOTAL_VOLUME from which I query the MAX(TOTAL_VOLUME). If you look at WEEK_NUMBER 31, the MAX(TOTAL_VOLUME) 777 has DAY_NUMBER 7 but the query has 5.
My goal is to have DAY_NUMBER reflect MAX(TOTAL_VOLUME) value DAY_NUMBER.
could you please tell me how do I fix it?
With NOT EXISTS:
SELECT t.*
FROM tbl t
WHERE NOT EXISTS (
SELECT 1 FROM tbl
WHERE WEEK_NUMBER = t.WEEK_NUMBER AND total_volume > t.total_volume
)
Use like this,
SELECT DAY_NUMBER, WEEK_NUMBER, total_volume
FROM `tbl` WHERE total_volume IN(SELECT MAX(total_volume)
FROM `tbl`
GROUP BY WEEK_NUMBER)
You will get Desired output
Do not use GROUP BY for a query where you should be filtering.
I recommend a correlated subquery:
select t.*
from tbl t
where t.total_volume = (select max(t2.total_volume)
from tbl t2
where t2.week_number = t.week_number
);
Alternatively, you can use window functions in MySQL 8+:
select t.*
from (select t.*,
row_number() over (partition by week_number order by volume desc) as seqnum
from tbl t
) t
where seqnum = 1;
With join
drop table if exists t;
create table t
(day_number int, week_number int, value int);
insert into t values
(1,1,10),(2,1,20),
(10,2,20),(11,2,10);
select t.day_number,t.week_number,t.value
from t
join
(select week_number,max(value) m
from t
group by week_number
) w on w.week_number = t.week_number and w.m = t.value;
+------------+-------------+-------+
| day_number | week_number | value |
+------------+-------------+-------+
| 2 | 1 | 20 |
| 10 | 2 | 20 |
+------------+-------------+-------+
2 rows in set (0.00 sec)
In the event of a draw you will get all days which match the max value.
I have a SQL table thus:
username | rank
a | 0
b | 2
c | 5
d | 4
e | 5
f | 7
g | 1
h | 12
I want to use a single select statement that returns all rows that have rank greater than the value of user e's rank.
Is this possible with a single statement?
Yes it is possible.
SELECT * FROM MyTable
WHERE rank > (SELECT Rank FROM MyTable WHERE username = 'e')
or you can also use self-join for the same
SELECT t1.* FROM MyTable t1
JOIN MyTable t2
ON t1.Rank > t2.Rank
AND t2.username = 'e';
See this SQLFiddle
You can use subquery
SELECT * FROM `table` WHERE `rank` > (
SELECT `rank` FROM `table` WHERE `username` ='b' LIMIT 1)
This is just an edit script of #UweB.. It will return the max rank even if there are multiple rows for username='e'
SELECT *
FROM tbl
WHERE
tbl.rank > (
SELECT max(rank) FROM tbl WHERE username = 'e')
SELECT *
FROM tbl
WHERE
tbl.rank > (
SELECT rank FROM tbl WHERE username = 'e'
);
Note that this will only work if the sub-select (the part in brackets) returns a single value (one row, one column so to speak) only.
I have a table tbl_patient and I want to fetch last 2 visit of each patient in order to compare whether patient condition is improving or degrading.
tbl_patient
id | patient_ID | visit_ID | patient_result
1 | 1 | 1 | 5
2 | 2 | 1 | 6
3 | 2 | 3 | 7
4 | 1 | 2 | 3
5 | 2 | 3 | 2
6 | 1 | 3 | 9
I tried the query below to fetch the last visit of each patient as,
SELECT MAX(id), patient_result FROM `tbl_patient` GROUP BY `patient_ID`
Now i want to fetch the 2nd last visit of each patient with query but it give me error
(#1242 - Subquery returns more than 1 row)
SELECT id, patient_result FROM `tbl_patient` WHERE id <(SELECT MAX(id) FROM `tbl_patient` GROUP BY `patient_ID`) GROUP BY `patient_ID`
Where I'm wrong
select p1.patient_id, p2.maxid id1, max(p1.id) id2
from tbl_patient p1
join (select patient_id, max(id) maxid
from tbl_patient
group by patient_id) p2
on p1.patient_id = p2.patient_id and p1.id < p2.maxid
group by p1.patient_id
id11 is the ID of the last visit, id2 is the ID of the 2nd to last visit.
Your first query doesn't get the last visits, since it gives results 5 and 6 instead of 2 and 9.
You can try this query:
SELECT patient_ID,visit_ID,patient_result
FROM tbl_patient
where id in (
select max(id)
from tbl_patient
GROUP BY patient_ID)
union
SELECT patient_ID,visit_ID,patient_result
FROM tbl_patient
where id in (
select max(id)
from tbl_patient
where id not in (
select max(id)
from tbl_patient
GROUP BY patient_ID)
GROUP BY patient_ID)
order by 1,2
SELECT id, patient_result FROM `tbl_patient` t1
JOIN (SELECT MAX(id) as max, patient_ID FROM `tbl_patient` GROUP BY `patient_ID`) t2
ON t1.patient_ID = t2.patient_ID
WHERE id <max GROUP BY t1.`patient_ID`
There are a couple of approaches to getting the specified resultset returned in a single SQL statement.
Unfortunately, most of those approaches yield rather unwieldy statements.
The more elegant looking statements tend to come with poor (or unbearable) performance when dealing with large sets. And the statements that tend to have better performance are more un-elegant looking.
Three of the most common approaches make use of:
correlated subquery
inequality join (nearly a Cartesian product)
two passes over the data
Here's an approach that uses two passes over the data, using MySQL user variables, which basically emulates the analytic RANK() OVER(PARTITION ...) function available in other DBMS:
SELECT t.id
, t.patient_id
, t.visit_id
, t.patient_result
FROM (
SELECT p.id
, p.patient_id
, p.visit_id
, p.patient_result
, #rn := if(#prev_patient_id = patient_id, #rn + 1, 1) AS rn
, #prev_patient_id := patient_id AS prev_patient_id
FROM tbl_patients p
JOIN (SELECT #rn := 0, #prev_patient_id := NULL) i
ORDER BY p.patient_id DESC, p.id DESC
) t
WHERE t.rn <= 2
Note that this involves an inline view, which means there's going to be a pass over all the data in the table to create a "derived tabled". Then, the outer query will run against the derived table. So, this is essentially two passes over the data.
This query can be tweaked a bit to improve performance, by eliminating the duplicated value of the patient_id column returned by the inline view. But I show it as above, so we can better understand what is happening.
This approach can be rather expensive on large sets, but is generally MUCH more efficient than some of the other approaches.
Note also that this query will return a row for a patient_id if there is only one id value exists for that patient; it does not restrict the return to just those patients that have at least two rows.
It's also possible to get an equivalent resultset with a correlated subquery:
SELECT t.id
, t.patient_id
, t.visit_id
, t.patient_result
FROM tbl_patients t
WHERE ( SELECT COUNT(1) AS cnt
FROM tbl_patients p
WHERE p.patient_id = t.patient_id
AND p.id >= t.id
) <= 2
ORDER BY t.patient_id ASC, t.id ASC
Note that this is making use of a "dependent subquery", which basically means that for each row returned from t, MySQL is effectively running another query against the database. So, this will tend to be very expensive (in terms of elapsed time) on large sets.
As another approach, if there are relatively few id values for each patient, you might be able to get by with an inequality join:
SELECT t.id
, t.patient_id
, t.visit_id
, t.patient_result
FROM tbl_patients t
LEFT
JOIN tbl_patients p
ON p.patient_id = t.patient_id
AND t.id < p.id
GROUP
BY t.id
, t.patient_id
, t.visit_id
, t.patient_result
HAVING COUNT(1) <= 2
Note that this will create a nearly Cartesian product for each patient. For a limited number of id values for each patient, this won't be too bad. But if a patient has hundreds of id values, the intermediate result can be huge, on the order of (O)n**2.
Try this..
SELECT id, patient_result FROM tbl_patient AS tp WHERE id < ((SELECT MAX(id) FROM tbl_patient AS tp_max WHERE tp_max.patient_ID = tp.patient_ID) - 1) GROUP BY patient_ID
Why not use simply...
GROUP BY `patient_ID` DESC LIMIT 2
... and do the rest in the next step?
I have this table
id fruit
---------
1 apple
2 banana <--
3 apple
4 apple
5 apple
6 apple
7 banana <----
8 apple
9 banana
10 apple
And I want to select rows until 2 bananas are found, like
SELECT id FROM table_fruit UNTIL number_of_bananas = 2
So the result would be 1,2,3,4,5,6,7
How could I achieve this?
thanks
I wish I could give credits to all of you who answered my question. I'v tested all of them, and they all work perfectly (got the expected result).
Though answers of Devart and ypercube seem a little bit complex and difficult for me to understand.
And since AnandPhadke was the first one provided a working solution, I'll choose his answer as accepted.
You guys are awesome, thanks!
Try this query -
SELECT id, fruit FROM (
SELECT
b.*, #b:=IF(b.fruit = 'banana', 1, 0) + #b AS banana_number
FROM
bananas b,
(SELECT #b := 0) t
ORDER BY id) t2
WHERE
banana_number < 2 OR banana_number = 2 AND fruit = 'banana'
SQLFiddle demo
select * from tables where id <=
(
select id from (
select id from tables where fruit='banana'
order by id limit 2) a order by id desc limit 1
)
SQLFIDDLE DEMO
#Devart's answer is perfect but it's an alternative option to we can use:
SELECT * FROM table_fruit WHERE id <=
(
SELECT id FROM
(SELECT id FROM table_fruit WHERE fruit='banana' ORDER BY id LIMIT 2) a
ORDER BY ID DESC LIMIT 1
);
Or using MAX
SELECT * FROM table_fruit WHERE id <=
(
SELECT MAX(id) FROM
(SELECT id FROM table_fruit WHERE fruit='banana' ORDER BY id LIMIT 2) a
);
See this SQLFiddle
select * from table_fruit where id <=
(
select max(id) from
(select id from table_fruit where fruit='banana' order by id limit 2) t
)
If there are less than 2 rows with 'banana', this will return all rows of the table:
SELECT t.*
FROM table_fruit AS t
JOIN
( SELECT MAX(id) AS id
FROM
( SELECT id
FROM table_fruit
WHERE fruit = 'banana'
ORDER BY id
LIMIT 1 OFFSET 1
) AS lim2
) AS lim
ON t.id <= lim.id
OR lim.id IS NULL ;