MySQL fetching rows based on condition within condition - mysql

I have the following below
---------------------------------
| Id | Value | Flag |
---------------------------------
| 1 | 23 | 0 |
---------------------------------
| 1 | 24 | 1 |
---------------------------------
| 2 | 30 | 0 |
---------------------------------
I require a query, which should fetch the last two rows. The condition here is IF two rows have the same Id then the output should fetch the row which has flag = 1 . Where as If there is only one row for an Id, then the row with Flag = 0 should be fetched. Thus the output for the given requirement will be
---------------------------------
| 1 | 24 | 1 |
---------------------------------
| 2 | 30 | 0 |

SELECT
*
FROM yourTable yt1
WHERE Flag = (SELECT MAX(Flag) FROM yourTable yt2 WHERE yt1.Id = yt2.Id)
GROUP BY Id

Other answers looks correct to me. This is a different approach:
SELECT t1.*
FROM
yourtable t1 LEFT JOIN yourtable t2
ON t1.Id = t2.Id AND t1.Flag=0 AND t2.Flag=1
WHERE
t2.Id IS NULL
Please see fiddle here.

I prefer an uncorellated approach...
SELECT x.*
FROM my_table x
JOIN
( SELECT id,MAX(flag) max_flag FROM my_table GROUP BY id ) y
ON y.id = x.id
AND y.max_flag = x.flag;

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'
)

Duplicate and get the last item from the mysql table

table 1 t1
+----+----------+
| id | name |
+----+----------+
| 1 | free |
| 2 | basic |
| 3 | advanced |
+----+----------+
table 2 t2
+----+-------+------+
| id | t1_fk | cost |
+----+-------+------+
| 1 | 2 | 1650 |
| 3 | 3 | 2000 |
| 4 | 2 | 550 |
+----+-------+------+
I want to get the output of t2 table but without duplicates. I was able to get this using GROUP BY function. Also i need the last item on the duplicate (i got stuck here).
Here's what i tried and it didn't work.
SELECT id cost FROM t2 GROUP BY t1_fk ORDER BY MAX(id) DESC
any help
On MySQL 8+, we can use ROW_NUMBER here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY t1_fk ORDER BY id DESC) rn
FROM t2
)
SELECT id, t1_fk, cost
FROM cte
WHERE rn = 1;
On earlier versions of MySQL, one canonical way to handle this would be to use a join to a subquery which finds the max id value for each t1_fk:
SELECT a.id, a.t1_fk, a.cost
FROM t2 a
INNER JOIN
(
SELECT t1_fk, MAX(id) AS max_id
FROM t2
GROUP BY t1_fk
) b
ON a.t1_fk = b.t1_fk AND a.id = b.max_id;

How to track previous row status count

I want to calculate count of order status changes within different states.
My Orderstatus table:
| id |ordr_id| status |
|----|-------|------------|
| 1 | 1 | pending |
| 2 | 1 | processing |
| 3 | 1 | complete |
| 4 | 2 | pending |
| 5 | 2 | cancelled |
| 6 | 3 | processing |
| 7 | 3 | complete |
| 8 | 4 | pending |
| 9 | 4 | processing |
Output I want:
| state | count |
|----------------------|-------|
| pending->processing | 2 |
| processing->complete | 2 |
| pending->cancelled | 1 |
Currently I'm fetching the results by SELECT order_id,GROUP_CONCAT(status) as track FROM table group by order_id and then process the data in php to get the output. But is that possible in query itself ?
Use lag():
select prev_status, status, count(*)
from (select t.*,
lag(status) over (partition by order_id order by status) as prev_status
from t
) t
group by prev_status, status;
LAG() is available in MySQL starting with version 8.
Note that you can filter out the first status for each order by putting where prev_status is not null in the outer query.
Your version is not quite correct, because it does not enforce the ordering. It should be:
SELECT order_id,
GROUP_CONCAT(status ORDER BY id) as track
EDIT:
In earlier versions of MySQL, you can use a correlated subquery:
select prev_status, status, count(*)
from (select t.*,
(select t2.status
from t t2
where t2.order_id = t.order_id and t2.id < t.id
order by t2.id desc
limit 1
) as prev_status
from t
) t
group by prev_status, status;
If id column ensure the sequence of records, you can use self join to achieve your requirement as below-
SELECT A.Status +'>'+ B.Status, COUNT(*)
FROM OrderStatus A
INNER JOIN OrderStatus B
ON A.id = B.id -1
WHERE B.Status IS NOT NULL
GROUP BY A.Status +'>'+ B.Status
With a join of the 3 status change types to the grouping of the table that you already did:
select c.changetype, count(*) counter
from (
select 'pending->processing' changetype union all
select 'processing->complete' union all
select 'pending->cancelled'
) c inner join (
select
group_concat(status order by id separator '->') changestatus
from tablename
group by ordr_id
) t on concat('->', t.changestatus, '->') like concat('%->', changetype, '->%')
group by c.changetype
See the demo.
Results:
> changetype | counter
> :------------------- | ------:
> pending->cancelled | 1
> pending->processing | 2
> processing->complete | 2
...or just a simple join...
SELECT CONCAT(a.status,'->',b.status) action
, COUNT(*) total
FROM my_table a
JOIN my_table b
ON b.ordr_id = a.ordr_id
AND b.id = a.id + 1
GROUP
BY action;
+----------------------+-------+
| action | total |
+----------------------+-------+
| pending->cancelled | 1 |
| pending->processing | 2 |
| processing->complete | 2 |
+----------------------+-------+
Note that this relies on the fact that ids are contiguous.

MySQL select from specific ID until match condition

Given this table:
+----+-----------+--------+
| id | condition | values |
+----+-----------+--------+
| 1 | a | 1 |
+----+-----------+--------+
| 2 | a | 2 |
+----+-----------+--------+
| 3 | a | 3 |
+----+-----------+--------+
| 4 | a | 4 |
+----+-----------+--------+
| 5 | b | 5 |
+----+-----------+--------+
| 6 | b | 6 |
+----+-----------+--------+
How can I get a new table that begins on id=3 (including) and goes until condition = b (excluding):
+----+-----------+--------+
| id | condition | values |
+----+-----------+--------+
| 3 | a | 3 |
+----+-----------+--------+
| 4 | a | 4 |
+----+-----------+--------+
added fiddle: http://sqlfiddle.com/#!2/9882f7
Basically I want a table between a matching first condition (over a specific column - id) and a second one (over a different column - condition)
You need to stop thinking of SQL data as having any order. Think of SQL data in sets; you have to search by values, not by positions.
SELECT t1.*
FROM t AS t1
JOIN (
SELECT MIN(id) AS id FROM t
WHERE id >= 3 AND `condition` = 'b'
) AS t2
WHERE t1.id >= 3 AND t1.id < t2.id
ORDER BY t1.id
Something like this:
select t.*
from table t
where id >= 3 and id < (select min(t2.id) from table t2 where t2.condition = 'b');
EDIT:
This query works fine on the SQL Fiddle:
select t.*
from t
where id >= 3 and id < (select min(t2.id) from t t2 where t2.condition = 'b');
If I understand what you are asking for, I believe this will work for you:
SELECT id, condition, values
FROM tableName
WHERE id > 2
AND condition != b
ORDER BY id
I hope that works for you.

How can I get last inserted values for a user?

I have this MYSQL table...
+----+---------+-----------+---------------+------------+---------------------+
| id | user | val1 | val2 | val3 | last_modified_date |
+----+---------+-----------+---------------+------------+---------------------+
| 1 | 0001 | 1 | 1 | 1 | 2014-03-31 16:53:29 |
| 2 | 0100 | 1 | 1 | 1 | 2014-04-01 10:32:50 |
| 3 | 0200 | 1 | 0 | 0 | 2014-04-01 10:34:13 |
| 4 | 0200 | 1 | 1 | 1 | 2014-04-01 14:43:47 |
+----+---------+-----------+---------------+------------+---------------------+
What I'm trying to achieve is getting all the last (in order of time) set of values created for the users.
In the example given we insert 0 for user 0200 but after 4 hours we insert 1. I want to be able to get the last value inserted (1).
I have this query:
select *
from table
where val3 = 1
group by user
order by last_modified_date desc;
And a more generic one
select * from table group by user order by last_modified_date desc;
These queries seems to be working fine for the example table. Are they correct for every case? How can I tell "select all the users that have val3 = 1 as their last inserted value"?
You can use one of these two methods:
Option 1 - get the last record for each user and filter for t.val3:
SELECT t.*
FROM table t
JOIN (
SELECT MAX(last_modified_date) as last_modified, user
FROM table
GROUP BY user
) as tt ON t.user = tt.user AND t.last_modified_date = tt.last_modified_date
GROUP BY user
HAVING val3 = 1
Option 2 - get all the records for t.val3 = 1 and remove those which are not last:
SELECT t.*
FROM table t
LEFT JOIN table tt ON tt.user = t.user AND tt.last_modified_date > t.last_modified_date
WHERE t.val3 = 1
AND tt.id IS NULL
Try this
select * from table as t1
where val3 = 1
and last_modified_date =
(select max(last_modified_date) as last_modified_date from table as t2
where t1.user=t2.user and t2.val3 = 1 );