mysql Return duplicates and exclude lowest id - mysql

I am trying list all the rows from my query that will return all the duplicates next to each other so I can then grab their id's but i also want to exclude the id with the lowest number from the results. How can I go about doing that with my query.
My Query
SELECT
a.tail_number,
min(a.id),
b.aircraft_id
from aircraft a
left join jobs b on a.id = b.aircraft_id
where
a.active = 1 and b.aircraft_id is null
group by a.tail_number having count(*) > 1
The current Output
tail_number min(a.id) aircraft_id tail_count
125TH 4429 NULL 7
362FX 4223 NULL 7
439FL 4221 NULL 7
453FX 4220 NULL 7
455FX 4259 NULL 7
The output im trying to achieve
tail_number min(a.id) aircraft_id tail_count
125TH 4429 NULL 1
125TH 4430 NULL 1
125TH 4431 NULL 1
125TH 4432 NULL 1
362FX 4223 NULL 1
362FX 4224 NULL 1
362FX 4225 NULL 1
362FX 4226 NULL 1

Join with a subquery that gets the lowest ID for each tail number, and then exlude that from the results in the ON condition.
SELECT a.tail_number, a.id
FROM aircraft AS a
JOIN (SELECT tail_number, MIN(id) AS minid
FROM aircraft
WHERE active = 1
GROUP BY tail_number
HAVING COUNT(*) > 1) AS m ON a.tail_number = m.tail_number AND a.id != m.minid
LEFT JOIN jobs AS j ON a.id = j.aircraft_id
WHERE j.aircraft_id IS NULL
ORDER BY a.tail_number, a.id
I've moved the checks for active = 1 and COUNT(*) > 1 into the subquery as well, since there's no longer any grouping in the main query.

Related

Getting difference of a column from same table

I have a table with data that is similar to this table below
id
session_id
user_id
action
log_time
1
1
3
join
1642645048
2
1
3
left
1642645048
3
1
3
join
1642645552
4
1
3
left
1642646072
5
1
3
join
1642646632
6
1
3
left
1642646736
7
1
5
join
1642647083
8
1
5
join
1642649879
9
1
5
left
1642649951
10
1
5
join
1642650112
11
1
5
join
1642650159
12
1
5
join
1642651005
log_time is saved as a unix time
Question: Is it possible to retrieve the total amount of time that a user was in a session?
So it would do something like total_participation = ("1st left" - "1st join") + ("2nd left" - "2nd join") + ("3rd left - "3rd join")
I've already got the difference between first join and last left time by doing the following query:
SELECT s1.session_id as 'Id',
u.name AS 'Participant',
IFNULL(TIME_FORMAT(SEC_TO_TIME(s2.time_log - s1.time_log), '%Hh %im %ss'), 0) AS 'TotalParticipation'
FROM tblSessionLog AS s1
LEFT JOIN tblSessionLog AS s2 ON (
s2.id = (
SELECT id
FROM tblSessionLog
WHERE action = 'left'
AND user_id = s1.user_id
AND id > s1.id
ORDER BY time_log DESC
LIMIT 1
)
)
LEFT JOIN tblUser AS u ON u.id = s1.user_id -- used only to get participant name
WHERE s1.action = 'join'
GROUP BY s1.session_id, s1.user_id
ORDER BY s1.session_id, s1.user_id;
But I can't seem to get how to remove the time in between the participant have left and join backed again. Or is that not possible on SQL and should be handled on backend code?
Not exactly a copy but more or less, the actual data is like this sample SQL Fiddle here: http://sqlfiddle.com/#!9/2d8f6c/1/0
Update:
#Akina's solution work well when the data is consistent which is very much appreciated but I found out it will not be suitable on my case as there are instances where it is possible to have more than one join action before having left action, as well as no left action after join action. I updated the example table above to further show the actual data. Updated the sample fiddle as well.
Any leads will be really appreciated. And apologies as for some reason I need to do this on MySQL without the help of backend code. Thanks!
Perhaps something like this:
WITH cte1 AS(
SELECT user_id, action, time_log,
ROW_NUMBER() OVER (PARTITION BY user_id, action ORDER BY time_log) AS rn
FROM tblSessionLog
WHERE action='join'),
cte2 AS(
SELECT user_id, action, time_log,
ROW_NUMBER() OVER (PARTITION BY user_id, action ORDER BY time_log) AS rn
FROM tblSessionLog
WHERE action='left')
SELECT *,
cte2.time_log-cte1.time_log
FROM cte1
LEFT JOIN cte2
ON cte1.user_id=cte2.user_id
AND cte1.rn=cte2.rn;
Which on your current data will return the following results:
user_id
action
time_log
rn
user_id
action
time_log
rn
cte2.time_log-cte1.time_log
3
join
1642645048
1
3
left
1642645048
1
0
3
join
1642645552
2
3
left
1642646072
2
520
3
join
1642646632
3
3
left
1642646736
3
104
5
join
1642647083
1
5
left
1642649951
1
2868
5
join
1642649879
2
NULL
NULL
NULL
NULL
NULL
5
join
1642650112
3
NULL
NULL
NULL
NULL
NULL
5
join
1642650159
4
NULL
NULL
NULL
NULL
NULL
5
join
1642651005
5
NULL
NULL
NULL
NULL
NULL
With two generated common table expressions (cte), each result from cte assigned with ROW_NUMBER() then LEFT JOIN them with matching user_id and the generated ROW_NUMBER(). As your sample data only have 1 left for user_id=5 then it only paired for the first found join for the same user_id and returned NULL for the rest. If a left action was added for user_id=5 afterwards, it will occupy as the left action for the join action that is NULL. For example, if we add:
INSERT INTO tblSessionLog (id, session_id, user_id, action, time_log)
VALUES (13, 1, 5, 'left', 1642652005);
INSERT INTO tblSessionLog (id, session_id, user_id, action, time_log)
VALUES (14, 1, 5, 'left', 1642652085);
then the results will be something like this:
user_id
action
time_log
rn
user_id
action
time_log
rn
cte2.time_log-cte1.time_log
3
join
1642645048
1
3
left
1642645048
1
0
3
join
1642645552
2
3
left
1642646072
2
520
3
join
1642646632
3
3
left
1642646736
3
104
5
join
1642647083
1
5
left
1642649951
1
2868
5
join
1642649879
2
5
left
1642652005
2
2126
5
join
1642650112
3
5
left
1642652085
3
1973
5
join
1642650159
4
NULL
NULL
NULL
NULL
NULL
5
join
1642651005
5
NULL
NULL
NULL
NULL
NULL
One thing to take note is that the ROW_NUMBER() I generated here is order by time_log and not by id (or which data was inserted first). If you wish to pair them by which data comes first, you can simply change the ORDER BY time_log to ORDER BY id on both of the ROW_NUMBER() assignment.
Demo fiddle

MySQL Select first entry of GROUP after custom ORDER BY

I'm trying to fetch the first entry of each group after the custom ORDER BY but don't know how to select that first entry of each group. The groups should be ordered by the #team if exist, otherwise other team else NULL.
SELECT t1.*
FROM tbl t1
INNER JOIN tbl2 t2 ON t2.group_id = t1.group
WHERE t2.region = #region
ORDER BY
CASE
WHEN team=#team THEN 1
WHEN team is NOT NULL THEN 2
WHEN team is NULL THEN 3
END
Content of tbl
id group team
1 1 AA
2 1 BB
3 2 AA
4 2 CC
5 3 BB
6 3 NULL
7 4 NULL
Expected result when #team=AA
id group team
1 1 AA
3 2 AA
5 3 BB
7 4 NULL
Expected result when #team=BB
id group team
2 1 BB
3 2 AA
5 3 BB
7 4 NULL
Use your custom ordering logic with ROW_NUMBER:
WITH cte AS (
SELECT t1.*, ROW_NUMBER() OVER (PARTITION BY t1.`group`
ORDER BY CASE WHEN team = #team THEN 1
WHEN team IS NOT NULL THEN 2
ELSE 3 END) rn
FROM tbl t1
INNER JOIN tbl2 t2 ON t2.group_id = t1.`group`
WHERE t2.region = #region
)
SELECT id, `group`, team
FROM cte
WHERE rn = 1;
Side note: Avoid naming your table columns GROUP, as this is a reserved MySQL keyword, and therefore must always be escaped in backticks.

How to select a column value depending if id is even or odd

having a table structure of id and a name:
create table Mytable (
id integer not null,
name varchar(30) not null,
unique(id)
);
insert into Mytable (id,name) values
(1 , 'one'),
(2 , 'two'),
(3 , 'three'),
(4 , 'four'),
(6 , 'six');
How may I get a mix of even and odd rows in a result table like:
even | odd
-----------
null one '0 is not in Mytable so it puts null value
two three
four null '5 and 6 are not in Mytable so it puts null value
six null
I was trying to first get the following as a template and use it later as
a dictionary:
SELECT MIN(id-1) as id,MAX(id-1) as col
FROM Mytable
GROUP BY FLOOR((id+1)/2);
I get:
id col
0 1
2 3
5 5
But I do not know how to continue
For MySQL Version <= 5.7, You can use the below query
Query 1:
SELECT
MAX(CASE WHEN m.id % 2 = 0 THEN name END) AS even,
MAX(CASE WHEN m.id % 2 = 1 THEN name END) AS odd
FROM
(
SELECT
(SELECT MAX(id) FROM mytable) AS maxid,
#rn := #rn + 1 AS rn,
(SELECT IF((#rn * 2) <= maxid, #rn, NULL)) AS rid
FROM
mytable
JOIN
(SELECT #rn := -1) AS var
) AS t
JOIN
mytable m ON FLOOR(m.id/2) = t.rid
GROUP BY rid;
Result 1:
even | odd
:--- | :----
null | one
two | three
four | null
six | null
Demo 1:
db fiddle
Query 2:
After confirmation based on #Madhur Bhaiya comment. If there is no row for id = 8 and 9 then it will show null, null.
SELECT
MAX(CASE WHEN m.id % 2 = 0 THEN name END) AS even,
MAX(CASE WHEN m.id % 2 = 1 THEN name END) AS odd
FROM
(
SELECT
(SELECT MAX(id) FROM mytable) AS maxid,
#rn := #rn + 1 AS rn,
(SELECT IF((#rn * 2) <= maxid, #rn, NULL)) AS rid
FROM
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t
JOIN
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t2
JOIN
(SELECT #rn := -1) var -- currently it will return 1..100, if needed more add joins based on your needs
) AS t
LEFT JOIN
mytable m ON FLOOR(m.id/2) = t.rid
GROUP BY rid HAVING rid IS NOT NULL;
Result 2:
even | odd
:------ | :-----
null | one
two | three
four | null
six | null
null | null
null | eleven
null | null
null | null
sixteen | null
Demo 2:
db fiddle
For MySQL Version > 8.0, You can use #Nick query but if you need null, null like Result 2 mentioned for <= v5.7 then add LEFT JOIN with ORDER BY clause.
Query:
with recursive maxid as (
select max(id) as id from Mytable)
, cte as (
select 0 as rid
union all
select rid + 1
from cte
cross join maxid
where (rid + 1) * 2 <= maxid.id)
select max(case when m.id % 2 = 0 then name end) as even,
max(case when m.id % 2 = 1 then name end) as odd
from cte
left join Mytable m on floor(m.id / 2) = cte.rid
group by rid order by rid;
Result:
even | odd
:------ | :-----
null | one
two | three
four | null
six | null
null | null
null | eleven
null | null
null | null
sixteen | null
Demo: db fiddle
Credits: Thanks to #Nick, #Madhur Bhaiya for the fiddle and the logic used to create this query.
Here's a CTE based query that will work in SQL Server and MySQL > v8.0 (with the addition of the keyword recursive before maxid). It generates a list of rows that encompasses the pairs of MyTable values (in the sample, this is 0,1,2,3) and then JOINs that to Mytable to extract the even/odd column values:
with maxid as (
select max(id) as id from Mytable)
, cte as (
select 0 as rid
union all
select rid + 1
from cte
cross join maxid
where (rid + 1) * 2 <= maxid.id)
select max(case when m.id % 2 = 0 then name end) as even,
max(case when m.id % 2 = 1 then name end) as odd
from cte
join Mytable m on m.id / 2 = cte.rid
group by rid
Output:
even odd
one
two three
four
six
Demo on dbfiddle

Mysql join on null

I have 2 tables, each with 3 columns to join with.
table A
c1 c2 c3
10 NULL NULL
10 NULL 1
10 1 NULL
table B
c1 c2 c3
10 NULL NULL
10 NULL 1
10 1 NULL
I would like to join them so that NULL = NULL, so
SELECT * FROM a JOIN b ON a.c1 = b.c1 AND a.c2 = b.c2 AND a.c3 = b.c3
I would like it to join on NULL should match NULL. So that in the end I'm getting the 3 records:
table A+B
c1 c2 c3 c1 c2 c3
10 NULL NULL 10 NULL NULL
10 NULL 1 10 NULL 1
10 1 NULL 10 1 NULL
is this possible somehow? I have tried also with IFNULL but did'n get the results what I expect. I would be grateful if you could point me to the right direction. Many thanks!
Use the NULL-safe equality operator:
SELECT *
FROM a JOIN
b
ON a.c1 <=> b.c1 AND a.c2 <=> b.c2 AND a.c3 <=> b.c3;
However, with your sample data, a join on the first column is sufficient:
SELECT *
FROM a JOIN
b
ON a.c1 = b.c1 ;

Grouping data into ranges

I have a query that returns the counts from a database. Sample output of the query:
23
14
94
42
23
12
The query:
SELECT COUNT(*)
FROM `submissions`
INNER JOIN `events`
ON `submissions`.event_id = `events`.id
WHERE events.user_id IN (
SELECT id
FROM `users`
WHERE users.created_at IS NOT NULL
GROUP BY `events`.id
Is there a way to easily take the output and split it into pre-defined ranges of values (0-100, 101-200, etc), indicating the number of rows that fall into a particular range?
Use a case expression in select clause.
SELECT `events`.id ,
case when COUNT(`events`.id) between 0 and 100 then '0 - 100'
when COUNT(`events`.id) between 100 and 200 then '100 - 200'
end as Range
FROM `submissions`
INNER JOIN `events`
ON `submissions`.event_id = `events`.id
WHERE events.user_id IN (
SELECT id
FROM `users`
WHERE users.created_at IS NOT NULL
GROUP BY `events`.id
Use conditional count by leveraging SUM() aggregate.
If you need your ranges in columns
SELECT SUM(CASE WHEN n BETWEEN( 0 AND 100) THEN 1 ELSE 0 END) '0-100',
SUM(CASE WHEN n BETWEEN(101 AND 200) THEN 1 ELSE 0 END) '101-200'
-- , add other ranges here
FROM (
SELECT COUNT(*) n
FROM submissions s JOIN events e
ON s.event_id = e.id JOIN users u
ON e.user_id = u.id
WHERE u.created_at IS NOT NULL
GROUP BY e.id
) q
Sample output
+-------+---------+
| 0-100 | 101-200 |
+-------+---------+
| 2 | 3 |
+-------+---------+
1 row in set (0.01 sec)
If you'd rather have it as a set you can do
SELECT CONCAT(r.min, '-', r.max) `range`,
SUM(n BETWEEN r.min AND r.max) count
FROM (
SELECT COUNT(*) n
FROM submissions s JOIN events e
ON s.event_id = e.id JOIN users u
ON e.user_id = u.id
WHERE u.created_at IS NOT NULL
GROUP BY e.id
) q CROSS JOIN (
SELECT 0 min, 100 max
UNION ALL
SELECT 101, 200
-- add other ranges here
) r
GROUP BY r.min, r.max
Sample output
+---------+-------+
| range | count |
+---------+-------+
| 0-100 | 2 |
| 101-200 | 3 |
+---------+-------+
2 rows in set (0.01 sec)