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
Related
This question already has answers here:
How can I SELECT rows with MAX(Column value), PARTITION by another column in MYSQL?
(22 answers)
Closed 2 years ago.
I have three tables, e.g. fruits:
+----+--------+---------+
| id | type | variety |
+----+--------+---------+
| 1 | orange | 5 |
| 2 | orange | 7 |
| 3 | apple | 1 |
| 4 | apple | 0 |
+----+--------+---------+
containers:
+----+--------+
| id | year |
+----+--------+
| 1 | 2015 |
| 2 | 2020 |
| 3 | 2020 |
| 4 | 2018 |
+----+--------+
and inclusion:
+----+----------+---------+
| id | fruit_id | cont_id |
+----+----------+---------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 3 | 2 |
| 5 | 3 | 3 |
| 6 | 3 | 4 |
+----+----------+---------+
I need to select "newest" container for each fruit variety if there is any:
+----+--------+----------+------+
| id | type | variety | year |
+----+--------+----------+------+
| 1 | orange | 5 | 2020 |
| 2 | orange | 7 | 2015 |
| 3 | apple | 1 | 2020 |
| 4 | apple | 0 | NULL |
+----+--------+----------+------+
I'm trying something like
SELECT * FROM `fruits`
LEFT JOIN (SELECT * FROM `containers`
JOIN `inclusion` ON `inclusion`.`cont_id` = `containers`.`id`
WHERE `fruit_id` = `fruits`.`id`
ORDER BY `year` DESC LIMIT 1
) `tops` ON `tops`.`fruit_id` = `fruits`.`id`;
but it says
ERROR 1054 (42S22): Unknown column 'fruits.id' in 'where clause'
is there any way to get the required result?
I'm using mariadb my now, but migration to mysql could happen, so I need a solution working on both servers.
What if I also add cnt_type table:
+----+---------+
| id | type |
+----+---------+
| 1 | box |
| 2 | package |
+----+---------+
and containers would include type:
+----+--------+------+
| id | year | type |
+----+--------+------+
| 1 | 2015 | 1 |
| 2 | 2020 | 1 |
| 3 | 2020 | 2 |
| 4 | 2018 | 2 |
+----+--------+------+
so I need to extract top-year of each container type including each fruit variety?
+----+--------+----------+----------+------+
| id | type | variety | cnt_type | year |
+----+--------+----------+----------+------+
| 1 | orange | 5 | box | 2020 |
| 1 | orange | 5 | package | NULL |
| 2 | orange | 7 | box | 2015 |
| 2 | orange | 7 | package | NULL |
| 3 | apple | 1 | box | 2020 |
| 3 | apple | 1 | package | 2020 |
| 4 | apple | 0 | box | NULL |
| 4 | apple | 0 | package | NULL |
+----+--------+----------+----------+------+
In this case combination type-year for each container should be unique.
In Maria 10.2+ and MySQL 8+ window functions can solve this:
WITH x AS (
SELECT
*,
ROW_NUMBER() OVER(PARTITION BY f.type, f.variety, ct.cnt_type ORDER BY c.year DESC) rn
FROM
fruits f
INNER JOIN inclusion i on f.id = i.fruit_id
LEFT JOIN containers c on c.id = i.container_id
LEFT JOIN cnt_type ct on c.type = ct.id
)
SELECT * FROM x WHERE rn = 1
I'm not entirely sure I got your requirement on the last line, because you have multiple things called "type" and you talked about fruit types and varieties in the first instance, then you stoped talking about varieties in the second instance.. but you can change the columns in the PARTITION BY if you need. Easiest way to "debug" the requirement is to remove the WHERE rn = 1 and just run the query, then you'll see the rn column having 2, 3, 4 maybe.. If you adjust the PARTITION BY it will behave like the rows are being counted up and everything in the partition that is the same will have a counter that increments as the years descend (so for my query with a partition of f.type, f.variety, ct.cnt_type if you have 5 rows where all those rows have the same values for f.type, f.variety, ct.cnt_type, then the rn will increment from 1..5 as the years descend. If it's wrong and only f.type, f.variety should be considered "the group within which the rn counts up" then make the partition that instead
Because the most recent year is the one where rn = 1 that's what we pick to give the latest
If you end up stuck with MySQL 5.7 (and no window functions) you can use this query (which will also work on MariaDB). The problem can be resolved to a fairly simple selection of MAX(containers.year) grouped by all columns in fruit and the container type. Note a CROSS JOIN of fruits to cnt_type is required to ensure that all fruit/container combinations are included in the output:
SELECT f.id, f.type, f.variety,
ct.type AS cnt_type,
MAX(c.year) AS year
FROM fruits f
CROSS JOIN cnt_type ct
LEFT JOIN inclusion i ON i.fruit_id = f.id
LEFT JOIN containers c ON c.id = i.cont_id AND c.type = ct.id
GROUP BY f.id, f.type, f.variety, ct.type
Output:
id type variety cnt_type year
1 orange 5 box 2020
1 orange 5 package null
2 orange 7 box 2015
2 orange 7 package null
3 apple 1 box 2020
3 apple 1 package 2020
4 apple 0 box null
4 apple 0 package null
Demo on db-fiddle
You can use window functions such as DENSE_RANK() for MariaDB 10.2+ in order to pick the latest records grouped by fruit id's as follows
WITH f AS
(
SELECT f.*, c.year, DENSE_RANK() OVER (PARTITION BY f.id ORDER BY c.year DESC) AS dr
FROM fruits f
LEFT JOIN inclusion i
ON i.fruit_id = f.id
LEFT JOIN containers c
ON c.id = i.cont_id
)
SELECT id , type, variety, year
FROM f
WHERE dr = 1
Demo
Update : If you need to expand the results as adding that table(cnt_type) in the last update, then replace the query with the following one
WITH f AS
(
SELECT f.*, cn.type AS cnt_type, c.year,
DENSE_RANK() OVER (PARTITION BY f.type, f.variety, cn.type ORDER BY c.year DESC)
AS dr
FROM fruits f
LEFT JOIN inclusion i
ON i.fruit_id = f.id
CROSS JOIN cnt_type cn
LEFT JOIN containers c
ON c.id = i.cont_id AND c.type = cn.id
)
SELECT id , type, variety, cnt_type, year
FROM f
WHERE dr = 1
ORDER BY id, type, variety, cnt_type
Demo
where returning rows are multiplexed due to each rows of cnt_type table through use of
CROSS JOIN.
let me show table structure first then my question
suppose table A
id | url |
1 | page
2 | home
3 | product
4 | sell
and table b
id |user_id | table a id
1 | 1 | 1
the output which I want like
id | url | ispermission
1 | page | 1
2 | home | 0
3 | product | 0
4 | sell | 0
(value 1 )in ispermission mean that it map with user as you can see in table b other mean it does not have mapping
below is my query
select p.id,p.url,if(r.user_id=4,1,0) ispermission from
`table a` p
left join `table b` r on p.id=r.url_id
group by p.id,p.url order by ispermission DESC
this query is working great but in some case it got failed the result let conside that failed case the output which i m getting like (note this result is without groupby)
id | url | ispermission
1 | page | 0
1 | page | 1
2 | home | 0
3 | product | 0
4 | sell | 0
by using group by i m getting result like
id | url | ispermission
1 | page | 0
2 | home | 0
3 | product | 0
4 | sell | 0
Are you looking for something like this?
SELECT
T1.ID,
T1.Url,
COUNT(T2.[table a id]) Impression
FROM TableA T1
LEFT JOIN TableB T2 ON T1.ID = T2.[table a id]
GROUP BY T1.ID,T1.Url
Using LEFT JOIN with CASE expression:
select a.id, a.url,
case when b.tableAid is not null then 1 else 0 end as ispermission
from TableA a
left join TableB b on b.tableAid = a.id
Demo on db<>fiddle
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.
I don't know how to explain the scenario using words. So am writing the examples:
I have a table named tblType:
type_id | type_name
---------------------
1 | abb
2 | cda
3 | edg
4 | hij
5 | klm
And I have another table named tblRequest:
req_id | type_id | user_id | duration
-------------------------------------------
1 | 4 | 1002 | 20
2 | 1 | 1002 | 60
3 | 5 | 1008 | 60
....
So what am trying to do is, fetch the SUM() of duration for each type, for a particular user.
This is what I tried:
SELECT
SUM(r.`duration`) AS `duration`,
t.`type_id`,
t.`type_name`
FROM `tblRequest` AS r
LEFT JOIN `tblType` AS t ON r.`type_id` = t.`type_id`
WHERE r.`user_id` = '1002'
GROUP BY r.`type_id`
It might return something like this:
type_id | type_name | duration
-------------------------------
1 | abb | 60
4 | hij | 20
It works. But the issue is, I want to get 0 as value for other types that doesn't have a row in tblRequest. I mean I want the output to be like this:
type_id | type_name | duration
-------------------------------
1 | abb | 60
2 | cda | 0
3 | edg | 0
4 | hij | 20
5 | klm | 0
I mean it should get the rows of all types, but 0 as value for those type that doesn't have a row in tblRequest
You could perform the aggregation on tblRequest and only then join it, using a left join to handle missing rows and coalesce to convert the nulls to 0s:
SELECT t.type_id, type_name, COALESCE(sum_duration, 0) AS duration
FROM tblType t
LEFT JOIN (SELECT type_id, SUM(duration) AS sum_duration
FROM tblRequest
WHERE user_id = '1002'
GROUP BY type_id) r ON t.type_id = r.type_id
Select a.type_id, isnull(sum(b.duration), 0)
From tblType a Left Outer Join tblRequest b
ON a.type_id = b.type_id and b.user_id = 1002
Group by a.type_id
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