SQL: find based on previous id - mysql

I have a table like follows:
mysql> select * from tries;
+----+--------+-----------+
| id | person | succeeded |
+----+--------+-----------+
| 1 | 1 | 1 |
| 2 | 1 | 1 |
| 3 | 2 | 0 |
| 4 | 4 | 1 |
| 5 | 2 | 1 |
| 6 | 2 | 0 |
| 7 | 3 | 0 |
| 8 | 3 | 0 |
| 9 | 3 | 0 |
| 10 | 1 | 0 |
| 11 | 4 | 1 |
| 12 | 4 | 1 |
+----+--------+-----------+
I want the people who had (at least) one try that succeeded, following a try that failed (given by 1 and 0, respectively). When I say "follow", I mean the previous attempt by the same person, given by a lower id.
So in this case:
Person 2 succeeded on id = 5, and failed on id = 3, that person's previous try, thus meets the criteria.
Person 1 has no successes that immediately follow failures, thus fails the criteria
Person 3 has no successes, thus fails the criteria
Person 4 has no failures, thus fails the criteria
How would I write such a query?

SELECT t1.person, MIN(t1.id) as SuccessID
FROM tries t1
WHERE t1.succeeded = 1
AND t1.person IN (SELECT t2.person
FROM tries t2
WHERE t2.succeeded = 0
AND t2.id < t1.id)
GROUP BY t1.person

Select ...
From tries As T
Join tries As T2
On T2.id = T.id + 1
And T2.succeeded = 0
Where T.succeeded = 1
If we cannot assume Ids are perfectly contiguous:
Select ...
From tries As T
Join (
Select T1.id, Min(T2.Id) As NextId
From tries As T1
Join tries As T2
On T2.id > T.id
Group By T1.id
) As TriesAndNext
On TriesAndNext.Id = T.Id
Join tries As TNext
On TNext.Id = TriesAndNext.NextId
And TNext.succeeded = 0
Where T.succeeded = 1

Related

How to group values in join

I have two tables. One with main data (messages) and second with stats (messages_open_history).
messages:
id | name
1 | m1
2 | m2
3 | m3
4 | m4
messages_open_history:
id | message_id | opened | date
1 | 1 | 0 | 2019-09-01
2 | 1 | 1 | 2019-09-02
3 | 2 | 0 | 2019-09-01
4 | 2 | 0 | 2019-09-02
5 | 2 | 0 | 2019-09-03
6 | 3 | 1 | 2019-09-01
7 | 3 | 0 | 2019-09-02
8 | 4 | 1 | 2019-09-01
I would like to check if any Message was opened.
id | name | opened
1 | m1 | 1
2 | m2 | 0
3 | m3 | 1
4 | m4 | 1
I have tried:
SELECT m.id, m.name, opened FROM messages m
LEFT JOIN messages_open_history moh
Use exists:
select m.*,
(exists (select 1
from messages_open_history moh
where moh.message_id = m.id and
moh.opened = 1
)
) as ever_opened
from messages m;
This can take advantage of an index on messages_open_history(message_id, opened).
One way is to solve this is using Max() aggregation on opened field with Group By. Now, it seems that opened field has only two values either 0 or 1.
So we can also use LEFT JOIN (with condition opened = 1) with DISTINCT. If the message has not been opened at all, LEFT JOIN will result in NULL. We can then use Coalesce() to consider it as 0.
SELECT DISTINCT m.id, m.name, COALESCE(moh.opened, 0) AS opened
FROM messages AS m
LEFT JOIN messages_open_history AS moh
ON moh.message_id = m.id
AND moh.opened = 1
ORDER BY m.id
Result
| id | name | opened |
| --- | ---- | ------ |
| 1 | m1 | 1 |
| 2 | m2 | 0 |
| 3 | m3 | 1 |
| 4 | m4 | 1 |
View on DB Fiddle
For good performance, please define the following composite index: (message_id, opened) on the messages_open_history table.
If you are using MySQL v 8.0 or higher, another way to get the correct result is by using window function-
SELECT id, name, opened
FROM (SELECT m.id, m.name, moh.opened
,row_number() over(partition by m.name order by moh.opened desc) is_open
FROM messages AS m
JOIN messages_open_history AS moh ON moh.message_id = m.id) open_msg
WHERE is_open = 1
I hope this works for you:
select t1.id, t1.name, max(t2.opened)
from messages t1
join messages_open_history t2 on t1.id = t2.message_id
group by t1.id, t1.name

MySQL: Enumerate and count

I have two tables, table1 and table2.
Example of the table1 table.
^ invoice ^ valid ^
| 10 | yes |
| 11 | yes |
| 12 | no |
Example of the table2 table
^ invoice ^ detail ^
| 10 | A |
| 10 | C |
| 10 | F |
| 11 | A |
| 11 | F |
| 10 | E |
| 12 | A |
Want to select from table 2 all rows that:
Have a valid invoice in table 1
And enumerate:
the detail for each invoice
the invoice
Here the desired result
^ invoice ^ detail ^ ordination ^ ordinationb ^
| 10 | A | 1 | 1 |
| 10 | C | 2 | 1 |
| 10 | F | 3 | 1 |
| 11 | A | 1 | 2 |
| 11 | F | 2 | 2 |
| 10 | E | 4 | 1 |
The sentence should valid for use in phpMyAdmin 4.8.4
Here is the MySQL 8+ way of doing this:
SELECT
t2.Invoice,
t2.`lines`,
ROW_NUMBER() OVER (PARTITION BY t2.Invoice ORDER BY t2.`lines`) line_order,
DENSE_RANK() OVER (ORDER BY t2.Invoice) ordination
FROM table2 t2
WHERE EXISTS (SELECT 1 FROM table1 t1 WHERE t1.Invoice = t2.Invoice AND t1.valid = 'yes');
Demo
If you are using a version of MySQL earlier than 8, then you might have to resort to using session variables. This can lead to an ugly query. If you have a long term need for queries like this one, then I recommending upgrading to MySQL 8+.
Edit:
It just dawned on me that we can use correlated subqueries to simulate both your ROW_NUMBER and DENSE_RANK requirements. Here is one way to do this query in MySQL 5.7 or earlier:
SELECT
t2.Invoice,
t2.detail,
(SELECT COUNT(*) FROM table2 t
WHERE t.Invoice = t2.Invoice AND t.detail <= t2.detail) ordination,
t.dr AS ordinationb
FROM table2 t2
INNER JOIN
(
SELECT DISTINCT
t2.Invoice,
(SELECT COUNT(*)
FROM (SELECT DISTINCT Invoice FROM table2) t
WHERE t.Invoice <= t2.Invoice) dr
FROM table2 t2
) t
ON t.Invoice = t2.Invoice
WHERE EXISTS (SELECT 1 FROM table1 t1 WHERE t1.Invoice = t2.Invoice AND t1.valid = 'yes')
ORDER BY
t2.Invoice,
t2.detail;
Demo

Get a row with min(priority) from two tables

I need to query data from multiple tables, below are the major tables(simplified).
Project
+-----+-------+-------+
| pid | pname | status| //status: 0 = pending, 1 = complete
+-----+-------+-------+
| 1 | Proj1 | 0 |
| 2 | Proj2 | 1 |
| 3 | Proj3 | 0 |
+-----+-------+-------+
Module
+-----+--------+-------+----------+-----------------+
| mid | pid | status| priority |modulecategoryid |
+-----+--------+-------+----------+-----------------+
| 1 | 1 | 1 | 1 | 1 |
| 2 | 1 | 0 | 2 | 3 |
| 3 | 3 | 1 | 1 | 1 |
| 4 | 3 | 0 | 2 | 3 |
| 5 | 3 | 0 | 3 | 5 |
+-----+--------+-------+----------+-----------------+
Task
+----+--------+-------+----------+-----------------+
| id | mid | status| priority | taskcategoryid |
+----+--------+-------+----------+-----------------+
| 1 | 2 | 1 | 2 | 2 |
| 2 | 2 | 0 | 1 | 1 |
| 3 | 4 | 1 | 1 | 2 |
| 4 | 4 | 1 | 2 | 3 |
| 5 | 4 | 0 | 3 | 4 |
| 6 | 5 | 0 | 1 | 1 |
+----+--------+-------+----------+-----------------+
I am trying to get the pending tasks for all the pending projects that can be started first based on the module priority and task priority. i.e. for Proj3, module with priority 1 is completed so i should get first priority pending task for module 2.
I need to get the most prior task for each pending project with modulecategoryid and taskcategoryid for get its related info like this
+-----+--------+-----+------------------+----------------+
| pid | mid | tid | modulecategoryid | taskcategoryid |
+-----+--------+-----+------------------+----------------+
| 1 | 2 | 2 | 3 | 2 |
| 2 | 4 | 5 | 3 | 4 |
+----+---------+-----+------------------+----------------+
I am new to MySql and I have tried query with multiple joins and group it by projectids and min(priority) to get desired result. But columns that are not in group by are fetched randomly from the aggregate.
I have seen this answer SQL Select only rows with Max Value on a Column but that solves the problem for data in only one table.
Shall I get some help on that?
I can post my query if needed but it is getting wrong data.
SQL Select only rows with Max Value on a Column has the right approach. You just need to do it twice.
First create a subquery a showing the highest priority task for each module.
Then create a subquery b showing the highest priority Module for each project.
Then join your three tables and two subqueries together.
Here's a. It shows the highest priority Task id for each Module mid. (http://sqlfiddle.com/#!9/7eb1f3/4/0)
SELECT Task.id, Task.mid
FROM Task
JOIN (
SELECT MAX(priority) priority,
mid
FROM Task
WHERE status = 0
GROUP BY mid
) q ON q.priority = Task.priority AND q.mid = Task.mid
Here's b. It works the same way as a and shows the highest priority Module mid for each Project pid. (http://sqlfiddle.com/#!9/7eb1f3/3/0)
SELECT Module.mid, Module.pid
FROM Module
JOIN (
SELECT MAX(priority) priority,
pid
FROM Module
WHERE status = 0
GROUP BY pid
) q ON q.priority = Module.priority AND q.pid = Module.pid
Then you need a big JOIN to pull everything together. In outline it looks like this.
SELECT Project.pid, Project.pname,
Module.mid, Task.id tid,
Module.modulecategoryid, Task.taskcategoryid
FROM Project
JOIN ( /* the subquery called b */
) b ON Project.pid = b.pid
JOIN Module ON b.mid = Module.mid
JOIN ( /* the subquery called a */
) a ON Module.mid = a.mid
JOIN Task ON a.id = Task.id
WHERE Task.status = 0
The actual query looks like this, with the subqueries put in. (http://sqlfiddle.com/#!9/7eb1f3/2/0)
SELECT Project.pid, Project.pname,
Module.mid, Task.id tid,
Module.modulecategoryid, Task.taskcategoryid
FROM Project
JOIN (
SELECT Module.mid, Module.pid
FROM Module
JOIN (
SELECT MAX(priority) priority, pid
FROM Module
WHERE status = 0
GROUP BY pid
) q ON q.priority = Module.priority
AND q.pid = Module.pid
) b ON Project.pid = b.pid
JOIN Module ON b.mid = Module.mid
JOIN (
SELECT Task.id, Task.mid
FROM Task
JOIN (
SELECT MAX(priority) priority, mid
FROM Task
WHERE status = 0
GROUP BY mid
) q ON q.priority = Task.priority
AND q.mid = Task.mid
) a ON Module.mid = a.mid
JOIN Task ON a.id = Task.id
WHERE Task.status = 0
The secret to this is understanding that subqueries are virtual tables that you can join to each other or to ordinary tables. The skill you need is sorting out the combination of physical and virtual tables you need, and the join sequence.

Joining and nesting queries in mysql

Currently, I'm using this nice query:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(CASE WHEN race_results.place=1 THEN 1 ELSE 0 END) AS times_won_first_place
from users
inner join race_results
where race_results.userid = users.id and race_results.place = 1
group by users.id
order by total_winnings desc
to get this
************************************************
| name | total_winnings | times_won_first_place |
| Bob | 4000 | 4 |
| John | 1000 | 1 |
************************************************
the race_results table looks like this
*******************************************
| id | raceid | userid | place | winnings |
| 1 | 1 | 1 | 1 | 1000 |
| 2 | 1 | 2 | 5 | 50 |
| 3 | 1 | 3 | 6 | 50 |
| 4 | 2 | 1 | 1 | 1000 |
| 5 | 2 | 2 | 3 | 250 |
*******************************************
I would like to include four three more columns for something like this
***************************************************************************
| name | total_winnings | total_races | 1st_place | 2nd_place | 3rd_place |
| Bob | 4000 | 5 | 4 | 0 | 0 |
| John | 1000 | 5 | 1 | 1 | 1 |
***************************************************************************
If I were to do separate queries for the new columns, I'd use
select count(raceid) from race_results where userid = 1
select count(raceid) from race_results where userid = 1 and place = 1
select count(raceid) from race_results where userid = 1 and place = 2
select count(raceid) from race_results where userid = 1 and place = 3
to do separate queries would be easy but with the existing query I had to use CASE just to get the count of times a user won 1st place. (using
count(CASE WHEN race_results.place=2 THEN 1 ELSE 0 END)
returns the same results).
How would I nest these or join them into my existing query to get what I want?
You can do it this way:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(*) AS total_races,
sum(race_results.place = 1) AS times_won_first_place ,
sum(race_results.place = 2) AS times_won_second_place,
sum(race_results.place = 3) AS times_won_third_place
from users
inner join race_results
where race_results.userid = users.id
group by users.id
order by total_winnings desc;
With ANSI standard SQL you could use case expressions inside the sum function but since MySQL (and some other databases) evaluate boolean expressions to 1 for true you can replace the case expression with the just the condition to evaluate and then just sum them.
So instead of CASE WHEN race_results.place=1 THEN 1 ELSE 0 END you can do sum(race_results.place=1) and save some space and typing :)
See this SQL Fiddle for an example.

How to delete unknown number last record (on condition)?

Could you please tell me how to delete unknown number last record (on condition)?
For example, in this situation I want to delete record with id: 6 to 10.
Note: this table and records is not constant.
+----+-----+---------+
| id | url | emailid |
+----+-----+---------+
| 1 | 10 | 1 |
| 2 | 20 | 0 |
| 3 | 30 | 2 |
| 4 | 40 | 0 |
| 5 | 50 | 10 |
| 6 | 60 | 0 |
| 7 | 70 | 0 |
| 8 | 80 | 0 |
| 9 | 90 | 0 |
| 10 | 100 | 0 |
+----+-----+---------+
Thanks...
It seems that you want to delete the last set of records where all the values are 0. This is a bit of a pain. You can find the minimum such id as:
select min(t.id)
from table t
where t.emailid = 0 and
not exists (select 1 from table t2 where t2.id > t.id and t2.emailid <> 0);
The logic is: find all rows where emailid is 0 and there are no subsequent emailids that are not zero.
You can put this into a delete using join:
delete t
from table t cross join
(select min(t.id) as first0id
from table t
where t.emailid = 0 and
not exists (select 1 from table t2 where t2.id > t.id and t2.emailid <> 0)
) tmin
where t.id >= tmin.first0id;
You can use between keyword in your query like this
delete from yourtable where id BETWEEN 6 AND 10
use this query
delete from your_table where id between 6 AND 10
for not being constant you can first store start and end values in variable and then pass in query,example(in php)
$start = 6 ;
$end = 10;
query
"delete from your_table where id between $start AND $end"