SQL Query simplification (EXPLAIN COST 58) - mysql

Can someone help in simplifying below query. Cost of it shows as 58.
b.dueDate and b.tID are composite key.
a.tID is primary key and foreign key between table 1 and 2.
SELECT test.tID, test.sor_acct_id, test.pmt, test.status FROM ct.tab1 a,
(SELECT a.tID, a.sor_acct_id, b.dueDate, b.amt, b.status, a.pmt,
Row_number() OVER ( partition BY a.tID ORDER BY b.dueDate DESC) AS rn
FROM ct.tab1 a
INNER JOIN ct.tab2 b
ON a.tID = b.tID
WHERE a.tID IN (SELECT a.tID
FROM ct.tab1 a
INNER JOIN
ct.tab2 b
ON a.tID =
b.tID
WHERE a.status = 'E'
AND a.pmt IS NOT NULL
AND a.pmt <> '{}'
AND b.dueDate > CURRENT_DATE - 4
AND b.dueDate < CURRENT_DATE)
AND b.dueDate > CURRENT_DATE - 1
) test WHERE rn = 1
AND test.status IN ( 'X', 'Z' )
AND a.tID = test.tID

Maybe you would change:
WHERE a.tID IN (SELECT a.tID ....
into:
join ((SELECT a.tID FROM ct.tab1 a ....) t on t.tID=a.tID

tID is tab1's primary key. So when you say you are looking for tab1 records the tID of which is found in a set of tab1 records with status E, you could just as well simply say: I'm looking for tab1 records with status E.
What your query does is: Show all tab1 records with their final tab2 status provided ...
the tab1 pmt is not null and not '{}'
the tab1 status is E
the final tab2 status is X or Z
the final tab2 status is due today or in the future
there exists at least one tab2 record due in the last three days for the tab1 record
The query:
SELECT
t1.tID,
t1.sor_acct_id,
t1.pmt,
t2.status
FROM ct.tab1 t1
join
(
SELECT
tID,
status,
ROW_NUMBER() OVER (PARTITION BY tID ORDER BY dueDate DESC) AS rn
FROM ct.tab2
WHERE dueDate > CURRENT_DATE - 1
) t2 ON t2.tID = tab1.tID AND t2.rn = 1 AND t2.status IN ('X', 'Z')
WHERE t1.status = 'E'
AND t1.pmt IS NOT NULL
AND t1.pmt <> '{}'
and t1.tID IN
(
SELECT tID
FROM ct.tab2
WHERE dueDate > CURRENT_DATE - 4
AND dueDate < CURRENT_DATE
);

Related

Group SQL data and split into columns

I have a table with the following columns, where the timestamp column indicates the date the user viewed a product, and the purchase column if the view generated a purchase:
id
userId
productId
timestamp
purchase
1
2
4
2022-01-07
0
2
2
4
2022-01-10
1
3
2
4
2022-01-12
0
4
2
4
2022-01-16
1
How to group the data by userId and productId in order to return as follows:
id
userId
productId
firstView
lastView
firstPurchase
1
2
4
2022-01-07
2022-01-16
2022-01-10
For the firstView and listView columns I know I should use Min() and Max(), but the firstPurchase column I can't display. I tried using CASE when column purchase = 1 but it didn't work.
You need to use MIN(CASE WHEN purchase = 1 THEN "timestamp" ELSE NULL END), like this:
create table your_table (id int, userId int, productId int, "timestamp" date, purchase int)
insert into your_table (id, userId, productId, "timestamp", purchase)
values
(1,2,4,'2022-01-07 00:00:00',0),
(2,2,4,'2022-01-10 00:00:00',1),
(3,2,4,'2022-01-12 00:00:00',0),
(4,2,4,'2022-01-16 00:00:00',1);
SELECT
userId,
productId,
MIN(timestamp) AS firstView,
MAX(timestamp) AS lastView,
MIN(CASE WHEN purchase = 1 THEN "timestamp" ELSE NULL END) AS firstPurchase
FROM your_table
GROUP BY
userId,
productId;
p.s. please don't call a column "timestamp" :)
Try this:
SELECT t1.id, t1.userId, t1.productId, t1.timestamp,
(SELECT t3.timestamp
FROM yourtable t3
WHERE t3.userId = t1.userId
AND t3.productId = t1.productId
AND NOT EXISTS
(SELECT 1
FROM yourtable t4
WHERE t4.userId = t3.userId
AND t4.productId = t3.productId
AND t4.timestamp > t3.timestamp
)
) as lastView,
(SELECT t3.timestamp
FROM yourtable t3
WHERE t3.userId = t1.userId
AND t3.productId = t1.productId
AND t3.purchase = 1
AND NOT EXISTS
(SELECT 1
FROM yourtable t4
WHERE t4.userId = t3.userId
AND t4.productId = t3.productId
AND t4.timestamp < t3.timestamp
AND t4.purchase = 1
)
) as firstPurchase
FROM yourtable t1
WHERE NOT EXISTS
(SELECT 1 FROM yourtable t2
WHERE t1.userId = t2.userId
AND t1.productId = t2.productId
AND t2.timestamp < t1.timestamp)
See Sql Fiddle

Mysql - reorder / reset position key

I have a MySQL table with position key (65,000 records). I deleted, updated some rows in the middle of the table. Now I have, for example, something like this in the position 1 - 6 - 2 - 9
id
category
position
1
1
1
2
1
6
3
2
2
4
2
9
I want to reset / reorder the position key
id
category
position
1
1
1
2
1
2
3
2
1
4
2
2
How can I reset position where category = 1
and where category = 2?
Try this:
UPDATE source_table
JOIN ( SELECT id, ROW_NUMBER() OVER (PARTITION BY category ORDER BY position) position
FROM source_table ) subquery USING (id)
SET source_table.position = subquery.position
mysql> SHOW VARIABLES LIKE "%version%"; 5.7.24 – sagittarius
UPDATE source_table
JOIN ( SELECT t1.id, COUNT(t2.id) position
FROM source_table t1
JOIN source_table t2 ON t1.category = t2.category
AND t1.position >= t2.position
GROUP BY t1.id ) subquery USING (id)
SET source_table.position = subquery.position;
fiddle
if position is duplicated everything crashes – sagittarius
UPDATE source_table
JOIN ( SELECT t1.id, COUNT(t2.id) position
FROM source_table t1
JOIN source_table t2 ON t1.category = t2.category
AND ( t1.position > t2.position
OR ( t1.position = t2.position
AND t1.id >= t2.id ))
GROUP BY t1.id ) subquery USING (id)
SET source_table.position = subquery.position;
fiddle
UPDATE source_table t1
INNER JOIN
(
SELECT id,category,position, ROW_NUMBER() OVER (PARTITION BY category
ORDER BY position) position2
FROM source_table
) t2
ON t2.id = t1.id
SET
t1.position = t2.position2
I think this code is easy to understand and apply.

MySQL query add duration to previous record

I like to add event duration to a previous record every time a new record gets added.
This is what I have
ID EventType EventTime EventDuration
-------------------------------------
1 TypeA 10:20 NULL
2 TypeB 09:30 NULL
3 TypeC 08:00 NULL
This is what I want to achieve:
ID EventType EventTime EventDuration
-------------------------------------
1 TypeA 10:20 00:50
2 TypeB 09:30 01:30
3 TypeC 08:00 ...
4 ... ...
When a new records gets added (with ID, EventType and EventTime), the duration of the previous record (timediff between TypeB and Type A) should be added to the previous record in column EventDuration.
What I have so far is:
SELECT
id, EventTime,
timestampdiff(minute,
(SELECT EventTime
FROM TableName t2
WHERE t2.id < t1.id ORDER BY t2.id DESC LIMIT 1),EventTime)
AS EventDuration
FROM records t1
WHERE id = ....<this is where I get stuck, this should be a query that identifies the ID of latest EventTime record>
Any suggestions?
(I am running MySQL 5.6.39)
If you are running MySQL 8.0, you can use window functions for this:
update mytable t
inner join (
select id, timediff(eventTime, lag(eventTime) over(order by eventTime)) event_duration
from mytable t
) t1 on t1.id = t.id
set t.event_duration = t1.event_duration
If you want to update only the last but 1 record, you can order by and limit in the subquery (or in the outer query):
update mytable t
inner join (
select id, timediff(eventTime, lag(eventTime) over(order by eventTime)) event_duration
from mytable t
order by id desc
limit 1, 1
) t1 on t1.id = t.id
set t.event_duration = t1.event_duration
In earlier versions, one option is to emulate lag() with a window function:
update mytable t
inner join (
select
id,
timediff(
(select min(eventTime) from mytable t1 where t1.eventTime > t.eventTime),
eventTime
) event_duration
from mytable t
) t1 on t1.id = t.id
set t.event_duration = t1.event_duration

MySQl Query to select count until you hit a specific value

I have this table e.g.:
Id StatusDate Status
1 20-08-2014
1 15-08-2014
1 09-08-2014 P
2 17-08-2014
1 10-08-2014
2 12-08-2014
2 06-07-2014 P
1 30-07-2014
2 02-07-2014
2 01-07-2014 P
...... and so on
I want to select count by ID where status is blank until I hit the first 'P' in ascending order of date group by ID. So my results will be like this.
ID Count
1 3
2 2
Try it out. Not tested
SELECT t1.ID, count(*) FROM table t1
WHERE t1.StatusDate >= (SELECT MAX(t2.StatusDate) FROM table t2
WHERE t1.ID = t2.ID AND t2.Status = 'P')
GROUP BY t1.ID
Assuming your table name is StatusTable This will work:
SELECT
ID,
COUNT(*) AS `Count`
FROM StatusTable AS st
WHERE
st.Status = ''
AND st.StatusDate > (
SELECT st2.StatusDate
FROM `StatusTable` AS st2
WHERE st.ID = st2.ID
AND st2.Status = 'P'
ORDER BY st2.StatusDate DESC
LIMIT 1
)
GROUP BY st.ID
ORDER BY st.ID
One option is to use a JOIN and COUNT rows which have a lower statusdate value, like this:
SELECT t1.id, SUM(CASE WHEN t1.statusdate > t2.statusdate THEN 1 ELSE 0 END) AS mycount
FROM t t1 JOIN (
SELECT id, MIN(statusdate) statusdate
FROM t
WHERE status = 'P'
GROUP BY id
) t2
ON t1.id = t2.id
GROUP BY t1.id
Working Demo: http://sqlfiddle.com/#!2/d9d91/2

check 2 primary key in one table

I have table psc_Pro_ProfessorPositions(ProfessorID,PositionID,StartDate,EndDate). It have 2 primary key is ProfessorID,PositionID.
I want to check ProfessorID,PositionID not in table to insert.I wrote like this:
insert into CoreUIs.dbo.psc_Pro_ProfessorPositions
(
ProfessorID,PositionID,StartDate,EndDate
)
select a.MaQuanLy,b.MaQuanLy,convert(smalldatetime,NgayHieuLuc),convert(smalldatetime,NgayHetHieuLuc)
from inserted
inner join GiangVien a on a.MaGiangVien = inserted.MaGiangVien
inner join ChucVu b on b.MaChucVu = inserted.MaChucVu
where a.MaQuanLy not in (select ProfessorID from CoreUIs.dbo.psc_Pro_ProfessorPositions)
and b.MaQuanLy not in (select PositionID from CoreUIs.dbo.psc_Pro_ProfessorPositions)
But it's wrong.Can help me?Thanks all.
;WITH x AS
(
SELECT TeacherID, ClassID, ClassStuID, s = [SUM],
rn = ROW_NUMBER() OVER (PARTITION BY TeacherID ORDER BY ClassID)
FROM dbo.TB1
)
SELECT TeacherID, ClassID, ClassStuID,
[SUM] = CASE rn WHEN 1 THEN s ELSE NULL END
FROM x
ORDER BY TeacherID, [SUM] DESC;
You can employ CTEs with ROW_NUMBER()OVER() to identify the first row of each TeacherID:
; with a as (
select * from TB1
union
select * from TB2
)
, b as (
select *, r=ROW_NUMBER()over(partition by a.TeacherID order by a.TeacherID,
a.ClassID, a.ClassStuID) from a
)
select b.TeacherID, b.ClassID, b.ClassStuID
, [SUM]=case b.r when 1 then b.[SUM] else null end
from b
order by b.TeacherID, b.r
go
Result: