MySQL get single column value of last record - mysql

I have a query which looks like:
SELECT max(sp.id) as max_id, p.name as player, max(update_time) as last_seen, min(login_time) as first_seen, s.name as last_server,
sum(sp.play_time) as ontime_total,
sum(case when login_time > NOW() - INTERVAL 1 DAY then sp.play_time end) as ontime_day,
sum(case when login_time > NOW() - INTERVAL 7 DAY then sp.play_time end) as ontime_week,
sum(case when login_time > NOW() - INTERVAL 1 MONTH then sp.play_time end) as ontime_month
FROM session_player sp
INNER JOIN players p ON p.id=sp.player_id
INNER JOIN server s ON s.id=sp.server_id
WHERE p.name = ?
The result:
The issue:
Node22 isn't the last server. I am struggling on finding a way to get the server of the last record within this query. How would you solve this issue, if possible without running a second query.
(This query already takes 2-3s seconds depending on the user, if possible I would like to avoid any overhead and in case you see performance optimization possibilities I would appreciate anything.)
This would work, but its performance you can guess (4-5s):
SELECT
MAX( sp.id ) AS max_id, p.name AS player, MAX( update_time ) AS last_seen, MIN( login_time ) AS first_seen,
SUM( sp.play_time ) AS ontime_total,
SUM( CASE WHEN login_time > NOW( ) - INTERVAL 1 DAY THEN sp.play_time END ) AS ontime_day,
SUM( CASE WHEN login_time > NOW( ) - INTERVAL 7 DAY THEN sp.play_time END ) AS ontime_week,
SUM( CASE WHEN login_time > NOW( ) - INTERVAL 1 MONTH THEN sp.play_time END ) AS ontime_month,
(SELECT s.name
FROM session_player sp
JOIN players p ON p.id=sp.player_id
JOIN server s ON s.id=sp.server_id
WHERE p.name = ?
ORDER BY sp.id DESC
LIMIT 1
) as last_server
FROM session_player sp
INNER JOIN players p ON p.id = sp.player_id
INNER JOIN server s ON s.id = sp.server_id
WHERE p.name = ?

After nearly 3 hours of experimenting I got it and even 260 times faster as before:
SELECT MAX(pd.id) AS max_id, pd.name AS player, MAX( pd.update_time ) AS last_seen, MIN( pd.login_time ) AS first_seen,
SUM( pd.play_time ) AS ontime_total,
SUM( CASE WHEN pd.login_time > NOW( ) - INTERVAL 1 DAY THEN pd.play_time END ) AS ontime_day,
SUM( CASE WHEN pd.login_time > NOW( ) - INTERVAL 7 DAY THEN pd.play_time END ) AS ontime_week,
SUM( CASE WHEN pd.login_time > NOW( ) - INTERVAL 1 MONTH THEN pd.play_time END ) AS ontime_month,
(SELECT s.name
FROM session_player sp
INNER JOIN server s ON s.id=sp.server_id
WHERE max(pd.id)=sp.id
) as last_server
FROM (
SELECT sp.id AS id, sp.server_id as server_id, p.name AS name, sp.login_time AS login_time, sp.update_time AS update_time, sp.play_time AS play_time
FROM session_player sp
INNER JOIN players p ON p.id=sp.player_id
WHERE p.name = ?
) as pd

Try this:
SELECT sp.id as max_id, p.name as player, max(update_time) as last_seen,
min(login_time) as first_seen, s.name as last_server,
sum(sp.play_time) as ontime_total,
sum(case when login_time > NOW() - INTERVAL 1 DAY then sp.play_time end) as ontime_day,
sum(case when login_time > NOW() - INTERVAL 7 DAY then sp.play_time end) as ontime_week,
sum(case when login_time > NOW() - INTERVAL 1 MONTH then sp.play_time end) as ontime_month
FROM session_player sp
INNER JOIN players p ON p.id=sp.player_id
INNER JOIN server s ON s.id=sp.server_id
WHERE p.name = ?
group by sp.player_id
order by sp.id desc limit 1

Related

Why query returns empty result set without LIMIT clause?

I have this mysql function:
BEGIN
DECLARE top_tags VARCHAR(100) charset utf8;
SELECT substring_index(group_concat(x.NAME order BY x.tag_score DESC separator ','), ',', 5)
INTO top_tags
FROM (SELECT t.NAME, Sum(r.score) AS tag_score
FROM reputations r
JOIN qanda_tags qt ON qt.qanda_id = r.question_id
JOIN tags t ON t.id = qt.tag_id
WHERE r.owner_id = 1
AND r.date_time > CASE 'all'
WHEN 'WEEK' THEN unix_timestamp(date_sub(Now(), interval 1 week))
WHEN 'MONTH' THEN unix_timestamp(date_sub(now(), interval 1 month))
WHEN 'YEAR' THEN unix_timestamp(date_sub(now(), interval 1 year))
ELSE 1
END
group BY t.NAME ) x;
return top_tags;
end
It returns an empty result set:
And when I add limit 60 clause right after group BY t.NAME, it returns the expected result:
Why really?
Note: limit 61 or more causes no result either. limit 60 or less has the result.
I'm not sure, but would it be more efficient to do the filtering before the group_concat()?
SELECT group_concat(x.NAME order BY x.tag_score DESC separator ',')
INTO top_tags
FROM (SELECT t.NAME, Sum(r.score) AS tag_score
FROM reputations r JOIN
qanda_tags qt
ON qt.qanda_id = r.question_id JOIN
tags t
ON t.id = qt.tag_id
WHERE r.owner_id = 1 AND
r.date_time > (CASE 'all'
WHEN 'WEEK' THEN unix_timestamp(date_sub(Now(), interval 1 week))
WHEN 'MONTH' THEN unix_timestamp(date_sub(now(), interval 1 month))
WHEN 'YEAR' THEN unix_timestamp(date_sub(now(), interval 1 year))
ELSE 1
END)
group BY t.NAME
order by tag_score desc
limit 5
) x;

present and absent in one query using mysql

I have following mysql query:
SELECT m.emp_id AS `Empid`
, d.dt AS `AbsentDate`,
(CASE WHEN p.punch_status IS NULL
THEN 'A'
ELSE p.punch_status
END
) s
FROM ( SELECT DATE(t.added_date) AS dt
FROM pmc_attendance t
WHERE date(t.added_date) >= '2018-08-01'
AND date(t.added_date) < DATE_ADD( '2018-08-31' ,INTERVAL 1 DAY)
GROUP BY DATE(t.added_date)
ORDER BY DATE(t.added_date)
) d
CROSS
JOIN tbl_admin_users m
LEFT
JOIN pmc_attendance p
ON date(p.added_date) >= d.dt
AND date(p.added_date) < d.dt + INTERVAL 1 DAY
AND p.emp_id = m.emp_id
WHERE p.emp_id IS NULL and m.emp_id='000838' group by d.dt
ORDER
BY m.emp_id
, d.dt
this would fetch all the absent records of the employee but i want to show present dates of employees also.
enter image description here
my sql record shows:
Empid AbsentDate
000838 2018-08-05
000838 2018-08-05
000838 2018-08-11
000838 2018-08-12
000838 2018-08-15
000838 2018-08-19
I have use two tables
1. tbl_admin_users- employee data stored
2. pmc_attendance- present records of employees
As mention in my query, I had removed p.emp_id IS NULL condition.
Due to this condition all your record which have p.punch_status is not null get filter out.
So you can try below query
SELECT m.emp_id AS `Empid`
, d.dt AS `AbsentDate`,
(CASE WHEN p.punch_status IS NULL
THEN 'A'
ELSE p.punch_status
END
) s
FROM ( SELECT DATE(t.added_date) AS dt
FROM pmc_attendance t
WHERE date(t.added_date) >= '2018-08-01'
AND date(t.added_date) < DATE_ADD( '2018-08-31' ,INTERVAL 1 DAY)
GROUP BY DATE(t.added_date)
ORDER BY DATE(t.added_date)
) d
CROSS
JOIN tbl_admin_users m
LEFT
JOIN pmc_attendance p
ON date(p.added_date) >= d.dt
AND date(p.added_date) < d.dt + INTERVAL 1 DAY
AND p.emp_id = m.emp_id
WHERE m.emp_id='000838' group by d.dt
ORDER
BY m.emp_id
, d.dt

MySQL join tables with subquery

I have this crazy query below. I want to organize them but don't know how.
Can someone help me run this query? I want to join the 5 sub-query tables below and at the end, the result would be a table displaying line item_id with last5days, last10days, last30days, last60days, last90days.
Thanks a bunch!
Select q1.line_item_id,
q1.domains as 'last5days',
q2.domains as 'last10days',
q3.domains as 'last30days',
q4.dpmains as 'last60days',
q5.domains as 'last90days'
From q1
Join q2 on q1.line_item_id = q2.line_item_id
Join q3 on q1.line_item_id = q3.line_item_id
Join q4 on q1.line_item_id = q4.line_item_id
Join q4 on q1.line_item_id = q5.line_item_id
select q1.line_item_id, count(*) domains
from (
select distinct line_item_id, domain
from rpt_domain_by_campaign
where event_date between DATE_SUB(curdate(), INTERVAL 5 DAY)
and now()
) q1
group by q1.line_item_id
select q1.line_item_id, count(*) domains
from (
select distinct line_item_id, domain
from rpt_domain_by_campaign
where event_date between DATE_SUB(curdate(), INTERVAL 10 DAY)
and now()
) q2
group by q1.line_item_id
select q1.line_item_id, count(*) domains
from (
select distinct line_item_id, domain
from rpt_domain_by_campaign
where event_date between DATE_SUB(curdate(), INTERVAL 30 DAY)
and now()
) q3
group by q1.line_item_id
select q1.line_item_id, count(*) domains
from (
select distinct line_item_id, domain
from rpt_domain_by_campaign
where event_date between DATE_SUB(curdate(), INTERVAL 60 DAY)
and now()
) q4
group by q1.line_item_id
select q1.line_item_id, count(*) domains
from (
select distinct line_item_id, domain
from rpt_domain_by_campaign
where event_date between DATE_SUB(curdate(), INTERVAL 90 DAY)
and now()
) q5
group by q1.line_item_id
Just use sum and case make more easy:
select line_item_id,
Sum( CASE WHEN DATEDIFF(curdate(), event_date) <= 5 then 1 ELSE 0 END ) 'last5days',
Sum( CASE WHEN DATEDIFF(curdate(), event_date) <= 10 then 1 ELSE 0 END ) 'last10days',
Sum( CASE WHEN DATEDIFF(curdate(), event_date) <= 30 then 1 ELSE 0 END ) 'last30days',
Sum( CASE WHEN DATEDIFF(curdate(), event_date) <= 60 then 1 ELSE 0 END ) 'last60days',
Sum( CASE WHEN DATEDIFF(curdate(), event_date) <= 90 then 1 ELSE 0 END ) 'last90days'
from rpt_domain_by_campaign
Group by line_item_id
I recommend using count(distinct...) to avoid the extra layer of nesting in each of your sub-queries.
I also recommend switching the order of sub-queries and using outer joins because not every line item with rows in the past 90 days will also have rows in the past 60 days, etc.
Try something like this:
Select q1.line_item_id,
q1.domains as last90days,
coalesce(q2.domains,0) as last60days,
coalesce(q3.domains,0) as last30days,
coalesce(q4.domains,0) as last10days,
coalesce(q5.domains,0) as last5days
from
(
select line_item_id, count(distinct domain) as domains
from rpt_domain_by_campaign
where event_date between DATE_SUB(curdate(), INTERVAL 90 DAY)
and now()
group by line_item_id
) q1
left outer join
(
select line_item_id, count(distinct domain) as domains
from rpt_domain_by_campaign
where event_date between DATE_SUB(curdate(), INTERVAL 60 DAY)
and now()
group by line_item_id
) q2 on q1.line_item_id = q2.line_item_id
left outer join
(
select line_item_id, count(distinct domain) as domains
from rpt_domain_by_campaign
where event_date between DATE_SUB(curdate(), INTERVAL 30 DAY)
and now()
group by line_item_id
) q3 on q1.line_item_id = q3.line_item_id
left outer join
(
select line_item_id, count(distinct domain) as domains
from rpt_domain_by_campaign
where event_date between DATE_SUB(curdate(), INTERVAL 10 DAY)
and now()
group by line_item_id
) q4 on q1.line_item_id = q4.line_item_id
left outer join
(
select line_item_id, count(distinct domain) as domains
from rpt_domain_by_campaign
where event_date between DATE_SUB(curdate(), INTERVAL 5 DAY)
and now()
group by line_item_id
) q5 on q1.line_item_id = q5.line_item_id
Just use group by max date
select line_item_id,
Sum( CASE WHEN DATEDIFF(curdate(), event_date) <= 5 then 1 ELSE 0 END ) 'last5days',
Sum( CASE WHEN DATEDIFF(curdate(), event_date) <= 10 then 1 ELSE 0 END ) 'last10days',
Sum( CASE WHEN DATEDIFF(curdate(), event_date) <= 30 then 1 ELSE 0 END ) 'last30days',
Sum( CASE WHEN DATEDIFF(curdate(), event_date) <= 60 then 1 ELSE 0 END ) 'last60days',
Sum( CASE WHEN DATEDIFF(curdate(), event_date) <= 90 then 1 ELSE 0 END ) 'last90days'
from
(
select line_item_id, max(event_date) event_date
from
rpt_domain_by_campaign
where event_date < curdate()
group by line_item_id, domain
) a
Group by line_item_id

Same month two different SUMS

I'm trying to get different Sums for same month on same Year, just to get sums by different types. Tried using this code:
SELECT a.invoice_type, year( a.date ) AS Year,
date_format( a.date, '%M' ) AS `month` ,
Sum( x.amount * x.price ) AS sum FROM records x
JOIN paper_invoice a ON x.invoice_id = a.invoice_id
WHERE year( a.date ) = '2012'
GROUP BY a.invoice_type, Year( a.date ) , Month( a.date ) LIMIT 0 , 30
but it gives results in different rows:
http://www.part.lt/img/1505f0f13172922150febede85ddbf0925.png
But I need it to look like:
Year | Month | SUM_GRYNAIS | SUM_PAVEDIMU
2012 | January | 7597.14997705445 | 58740.2800849304
and ETC.
Try this:
SELECT Year, Month,
MAX(CASE WHEN invoice_type = 'GRYNAIS' THEN sum END) As Sum_GRYNAIS
MAX(CASE WHEN invoice_type = 'PAVEDIMU' THEN sum END) As SUM_PAVEDIMU
FROM
(
SELECT a.invoice_type, year( a.date ) AS Year,
date_format( a.date, '%M' ) AS `month` , Sum( x.amount * x.price ) AS sum
FROM records x JOIN paper_invoice a ON x.invoice_id = a.invoice_id
WHERE year( a.date ) = '2012' GROUP BY a.invoice_type, Year( a.date ) ,
Month( a.date ) LIMIT 0 , 30
)
GROUP BY Year, Month
Because each month has a different value. Try Group By on just the field year.
You are basically looking for PIVOT. MySql doesn't have a PIVOT function but you can still accomplish this. Here is an example of a PIVOT in MySql:
http://www.artfulsoftware.com/infotree/queries.php#78

Setting user defined variables with joins

I have a query like below:
Result gives #sold_count:=SUM(I.quantity) = 10, but #sold_count = 0,
so calculations are all 0.
What should be wrong here?
SET #sold_count :=0;
SELECT
#sold_count:=SUM(I.quantity),
#sold_count,I.from_widget,COUNT(from_widget) as order_count,
(#sold_count * buy_price) as ciro,
(#sold_count * list_price) as liste_ciro,
(#sold_count * widget_price) as vitrin_ciro,
P.*
FROM
tbl_products P
LEFT JOIN tbl_order_items I on I.product_id = P.id
WHERE
P.publish_date BETWEEN DATE_SUB( CURDATE( ) ,INTERVAL 3 MONTH ) AND DATE_SUB( CURDATE( ) ,INTERVAL 0 MONTH )
GROUP BY I.from_widget,I.product_id
ORDER BY publish_date DESC
Don't use variables. Just:
SELECT
SUM(I.quantity),
I.from_widget,
COUNT(from_widget) AS order_count,
SUM(I.quantity) * buy_price AS ciro,
SUM(I.quantity) * list_price AS liste_ciro,
SUM(I.quantity) * widget_price AS vitrin_ciro,
P.*
FROM
tbl_products P
LEFT JOIN tbl_order_items I
ON I.product_id = P.id
WHERE
P.publish_date BETWEEN DATE_SUB( CURDATE( ) , INTERVAL 3 MONTH )
AND DATE_SUB( CURDATE( ) , INTERVAL 0 MONTH )
GROUP BY I.from_widget,
I.product_id
ORDER BY publish_date DESC ;
You could also make the query a nested one, if you don't like using SUM(quantity) many times:
SELECT
sum_quantity * buy_price AS ciro,
sum_quantity * list_price AS liste_ciro,
sum_quantity * widget_price AS vitrin_ciro,
tmp.*
FROM
( SELECT
SUM(I.quantity) AS sum_quantity,
I.from_widget,
COUNT(from_widget) AS order_count,
buy_price,
list_price,
widget_price,
P.*
FROM
tbl_products P
LEFT JOIN tbl_order_items I
ON I.product_id = P.id
WHERE
P.publish_date BETWEEN DATE_SUB( CURDATE( ) , INTERVAL 3 MONTH )
AND DATE_SUB( CURDATE( ) , INTERVAL 0 MONTH )
GROUP BY I.from_widget,
I.product_id
) AS tmp
ORDER BY publish_date DESC ;