MySQL using IF or CASE statement across joined tables - mysql

HI all here is a MySQL problem that uses results from a 2 table join, conditionally assess them and outputs 2 values.
Here is the database structure.
The 1st table gtpro contains
a user ID (column name id)
a samples/year number ie 2, 4 or 12 times/year (column name labSamples__yr)
The 2nd table labresults contains
that same user ID (column name idgtpro)
and a date column for the sample dates (when the samples were provided) column name date
so this query returns an overview of all id's and when were the last samples submitted for that id.
SELECT a.id, a.labSamples__yr, max(b.date) as ndate from gtpro as a
join labresults as b on a.id = b.idgtpro group by a.id
the conditions I want to evaluate looks like this.
a.labSamples__yr = 2 and ndate >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
a.labSamples__yr = 4 and ndate >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)
a.labSamples__yr = 12 and ndate >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)
So if number of samples /year is 2 and the last samle date was more than 6 months ago I want to know the id and latest date of samples for that id.
I tried using CASE and IF statements but can't quite get it right. This was my latest attempt.
select id, ndate,
case when (labSamples__yr = 2 and ndate <= DATE_SUB(CURDATE(), INTERVAL 6 MONTH))is true
then
(SELECT id from gtpro as a join labresults as b on a.id = b.idgtpro where
labSamples__yr = 2 and max(b.date) <= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)) end as id
from (SELECT a.id, a.labSamples__yr, max(b.date) as ndate from gtpro as a
join labresults as b on a.id = b.idgtpro group by a.id) d
this tells me invalid use of group function.
Desperate for a bit of help
EDIT I messed up some of the names in the code above which i have now fixed.

If I understand your question correctly, you should be able to put the conditions in the where clause:
SELECT a.id, a.labSamples__yr, max(b.date) as ndate
from gtpro a join
labresults b
on a.id = b.idgtpro
where (a.labSamples__yr = 2 and b.date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)) or
(a.labSamples__yr = 4 and b.date >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)) or
(a.labSamples__yr = 12 and b.date >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
group by a.id;
That fixes your syntax problem. But, if you want the id with the maximum date, try doing this:
select a.labSamples__yr, max(b.date) as ndate,
substring_index(group_concat(a.id order by b.date desc)) as maxid
from gtpro a join
labresults b
on a.id = b.idgtpro
where (a.labSamples__yr = 2 and b.date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)) or
(a.labSamples__yr = 4 and b.date >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)) or
(a.labSamples__yr = 12 and b.date >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
group by a.labSamples__yr;
Putting a.id in the group by is not going to give you the maximum id of anything.

Is this meant to be valid MySQL? I wasn't aware of "is true" being valid in a CASE statement. In fairness though I'm more familiar with Oracle and SQL Server but nevertheless... does any part of this statement work?
EDIT
Ok, here is what I have edited the code to be:
select id, ndate,
case when (labSamples__yr = 2 and ndate <= DATE_SUB(CURDATE(), INTERVAL 6 MONTH))
then
(SELECT id from bifipro as a join labresults as b on a.id = b.idBifipro where
labSamples__yr = 2 and max(b.date) <= DATE_SUB(CURDATE(), INTERVAL 6 MONTH) where a.id=d.id) end as id
from (SELECT a.id, a.labSamples__yr, max(b.date) as ndate from bifipro as a
join labresults as b on a.id = b.idBifipro group by a.id) d
In your correlated subquery I have added a predicate of "where a.id =
d.id"
I have removed the text "is true" from your case statement (this may
be incorrect but I didnt' think it should be there.

The answer partly inspired by Tomas (sql clarification and syntax clarification) I got rid of the CASE all together. It seems nice and clean to me but I would like to hear any other suggestions
select id, labSamples__yr, ndate from
(SELECT a.id, a.labSamples__yr, max(b.date) as ndate from gtpro as a
join labresults as b on a.id = b.idgtpro group by a.id)d
where (ndate <= DATE_SUB(CURDATE(), INTERVAL 6 MONTH) and labSamples__yr = 2)
or (ndate <= DATE_SUB(CURDATE(), INTERVAL 3 MONTH) and labSamples__yr = 4)
or (ndate <= DATE_SUB(CURDATE(), INTERVAL 1 MONTH) and labSamples__yr = 12)
Thanks for looking but it would still be nice to see a solution using a CASE statement for future reference???

Related

Dynamic , Using a Case Statement in a Where Clause in mysql

I am stucked at a dynamic where clause inside case statement.
WHAT I NEED
When i used this
SELECT col1,col2,col3
FROM Recharge r INNER join ft f ON f.date=r.date
WHERE f.Date LIKE
CASE WHEN f.Date BETWEEN (last_day(curdate() - interval 1 month) + interval 1 day) AND last_day(curdate())
THEN f.Date ELSE f.date between subdate(curdate(),interval 1 month) and (last_day(curdate() - interval 1 month)) END
ORDER BY f.id desc;
The syntax is wrong but when instead it '2022-04%'.
SELECT col1,col2,col3
FROM Recharge r INNER join ft f ON f.date=r.date
WHERE f.Date LIKE
CASE WHEN f.Date BETWEEN (last_day(curdate() - interval 1 month) + interval 1 day) AND last_day(curdate())
THEN f.Date ELSE '2022-04%' END
ORDER BY f.id desc;
It is correct but i want to change dynamically.how can i do it.
I mean that
when i run the query include date of 1-4-2022 to 30-4-2022.
The snapshot my database include data of yesterday in the begin of month i have the issue.
A case statement can only return a value and not a range. If I understand rightly it is only the start date which changes so we only need to decide the start date in the case statement. If the end date also changes we will need a second case statement.
However it looks like a simple test will return the same results
SELECT col1,col2,col3
FROM Recharge r INNER join ft f ON f.date=r.date
WHERE f.Date BETWEEN
curdate() - interval 1 month
AND
last_day(curdate())
ORDER BY f.id desc;
Re-reading your explanation I think that you really want
SELECT col1,col2,col3
FROM Recharge r INNER join ft f ON f.date=r.date
WHERE month(f.date) = month(curdate() - interval 1 day)
AND year(f.date) = year(curdate() - interval 1 day )
ORDER BY f.id desc;
db<>fiddle here

Mysql group_concat with in() function

i'm trying to use group concat with in function on mysql, it didn't give me any syntax error but i'm not sure its working.
i'm trying to do something like that:
select
case
when weekday(ps.date) in (group_concat(vd.valid_days)) then ps.date
when weekday(ps.date+1) in (group_concat(vd.valid_days)) then DATE_ADD(ps.date, INTERVAL 1 DAY)
when weekday(ps.date+2) in (group_concat(vd.valid_days)) then DATE_ADD(ps.date, INTERVAL 2 DAY)
when weekday(ps.date+3) in (group_concat(vd.valid_days)) then DATE_ADD(ps.date, INTERVAL 3 DAY)
when weekday(ps.date+4) in (group_concat(vd.valid_days)) then DATE_ADD(ps.date, INTERVAL 4 DAY)
when weekday(ps.date+5) in (group_concat(vd.valid_days)) then DATE_ADD(ps.date, INTERVAL 5 DAY)
end as valid_date
from
purchase_shopping ps
left join
purchase_region pr on pr.id = ps.idfk_region
left join
valid_weeks_days vd on vd.idfk_region = pr.id
group by ps.id
what i need basically is to return a valid date if certain day is inside a group concat from a left join. I don't know if my logic is wrong or it does not work.
Obs: The group concat function return something like that: (1,2,5), so it's supposed to work with in() function.
Sample with reworked code data:
ps.date = 2018-10-17
weekday(ps.date) = 2
group_concat(vd.valid_days) = (1,4)
then valid_date will be 2018-10-19
Perhaps you simply wants :
select (case when uc.id is not null then 'It has items' end)
from shopping_cart sc left join
user_cart uc
on uc.id = sc.idfk_cart;
You cannot use result of group_concat in same query so you have to use a subquery
select
case
when weekday(date) in valid_days then date
when weekday(date+1) in valid_days then DATE_ADD(date, INTERVAL 1 DAY)
when weekday(date+2) in valid_days then DATE_ADD(date, INTERVAL 2 DAY)
when weekday(date+3) in valid_days then DATE_ADD(date, INTERVAL 3 DAY)
when weekday(date+4) in valid_days then DATE_ADD(date, INTERVAL 4 DAY)
when weekday(date+5) in valid_days then DATE_ADD(date, INTERVAL 5 DAY)
end as valid_date
from (
select ps.date, (group_concat(vd.valid_days)) as valid_days
from
purchase_shopping ps
left join
purchase_region pr on pr.id = ps.idfk_region
left join
valid_weeks_days vd on vd.idfk_region = pr.id
group by ps.id
) as sub_query

mysql join same table different result set

I would like to combine different results from the same table as one big result.
SELECT host_name,stats_avgcpu,stats_avgmem,stats_avgswap,stats_avgiowait
FROM sar_stats,sar_hosts,sar_appgroups,sar_environments
WHERE stats_host = host_id
AND host_environment = env_id
AND env_name = 'Staging 2'
AND host_appgroup = group_id
AND group_name = 'Pervasive'
AND DATE(stats_report_time) = DATE_SUB(curdate(), INTERVAL 1 DAY)
SELECT AVG(stats_avgcpu),AVG(stats_avgmem),AVG(stats_avgswap),AVG(stats_avgiowait)
FROM sar_stats
WHERE stats_id = "stat_id of the first query" and DATE(stats_report_time)
BETWEEN DATE_SUB(curdate(), INTERVAL 8 DAY) and DATE_SUB(curdate(), INTERVAL 1 DAY)
SELECT AVG(stats_avgcpu),AVG(stats_avgmem),AVG(stats_avgswap),AVG(stats_avgiowait)
FROM sar_stats
WHERE stats_id = "stat_id of the first query" and DATE(stats_report_time)
BETWEEN DATE_SUB(curdate(), INTERVAL 31 DAY) and DATE_SUB(curdate(), INTERVAL 1 DAY)
Desired output would be something like ...
host_name|stats_avgcpu|stats_avgmem|stats_avgswap|stats_avgiowait|7daycpuavg|7daymemavg|7dayswapavg|7dayiowaitavg|30daycpuavg|30daymemavg|....etc
SQL Fiddle
http://sqlfiddle.com/#!8/4930b/3
It seems like this is what you want. I updated the first query to use proper ANSI JOIN syntax and then for the additional two queries they were joined via a LEFT JOIN on the stats_host field:
SELECT s.stats_host,
h.host_name,
s.stats_avgcpu,
s.stats_avgmem,
s.stats_avgswap,
s.stats_avgiowait,
s7.7dayavgcpu,
s7.7dayavgmem,
s7.7dayavgswap,
s7.7dayavgiowait,
s30.30dayavgcpu,
s30.30dayavgmem,
s30.30dayavgswap,
s30.30dayavgiowait
FROM sar_stats s
INNER JOIN sar_hosts h
on s.stats_host = h.host_id
INNER JOIN sar_appgroups a
on h.host_appgroup = a.group_id
and a.group_name = 'Pervasive'
INNER JOIN sar_environments e
on h.host_environment = e.env_id
and e.env_name = 'Staging 2'
LEFT JOIN
(
SELECT s.stats_host,
AVG(s.stats_avgcpu) AS '7dayavgcpu',
AVG(s.stats_avgmem) AS '7dayavgmem',
AVG(s.stats_avgswap) AS '7dayavgswap',
AVG(s.stats_avgiowait) AS '7dayavgiowait'
FROM sar_stats s
WHERE DATE(stats_report_time) BETWEEN DATE_SUB(curdate(), INTERVAL 8 DAY) AND DATE_SUB(curdate(), INTERVAL 1 DAY)
GROUP BY s.stats_host
) s7
on s.stats_host = s7.stats_host
LEFT JOIN
(
SELECT s.stats_host,
AVG(s.stats_avgcpu) AS '30dayavgcpu',
AVG(s.stats_avgmem) AS '30dayavgmem',
AVG(s.stats_avgswap) AS '30dayavgswap',
AVG(s.stats_avgiowait) AS '30dayavgiowait'
FROM sar_stats s
WHERE DATE(s.stats_report_time) BETWEEN DATE_SUB(curdate(), INTERVAL 31 DAY) AND DATE_SUB(curdate(), INTERVAL 1 DAY)
GROUP BY s.stats_host
) s30
on s.stats_host = s30.stats_host
WHERE DATE(s.stats_report_time) = DATE_SUB(curdate(), INTERVAL 1 DAY);
see SQL Fiddle with Demo

Optimize a subquery searching for records in another table based on dates not in the past 360 days

Problem:
Find people whose birthdays are tomorrow (table a), who havent got a record with an issue date set in the past 360 days from (table b)
Table a
ID, DOB
Table b
ID, PID, Issued
I've got a query but it's pretty slow, not sure if a join would be quicker - any help appreciated..
SELECT a.ID, a.DOB FROM a
WHERE MONTH(a.DOB)=MONTH(now()) # match month
AND DAYOFMONTH(a.DOB)=DAYOFMONTH(now()+ INTERVAL 1 DAY) # match day of month
AND (
SELECT COUNT(*) FROM b
WHERE b.PID = a.ID
AND b.Issued < DATE_SUB(SYSDATE(), INTERVAL 360 DAY)
) < 1 # hacky subquery to find not issued in past 360 days
SELECT *
FROM A
LEFT JOIN B
ON A.Id = B.PID AND B.Issued >= DATE_SUB(CURDATE(), INTERVAL 360 DAY)
WHERE B.ID IS NULL AND
DATE_ADD(A.DOB, INTERVAL YEAR(CURDATE()) - YEAR(A.DOB) YEAR) =
DATE_ADD(CURDATE(), INTERVAL 1 DAY)

Count another count value but only if it is high enough

I'm trying to pull 2 numbers. One is a total of how many doctors (dr table) have more than 10 answers (answers table) from within 1 month and 75 answers total regardless of the date. The other number is the same thing but for within the last 3 months instead of 1 month.
I used this answer answer below to come up with this query:
SELECT D.name,
count(DISTINCT case when A.created > DATE_SUB(NOW(), INTERVAL 1 MONTH) then A.id end) as '1 month',
count(DISTINCT case when A.created > DATE_SUB(NOW(), INTERVAL 3 MONTH) then A.id end) as '1 quarter',
count(DISTINCT A.id) as total
FROM dr D
JOIN answer A ON A.dr_id=D.id AND A.status=3
GROUP BY D.id
This gives me the raw information I need, but I don't know how to count the counts given by comparing them to the 10 and 75 answers requirements.
Something like this, I think:
SELECT
COUNT(CASE WHEN total >= 75 AND `1month` > 10 THEN name END) AS `10+ per month count`,
COUNT(CASE WHEN total >= 75 AND `1quarter` > 10 THEN name END) AS `10+ per quarter count`
FROM (
SELECT D.name,
count(DISTINCT case when A.created > DATE_SUB(NOW(), INTERVAL 1 MONTH) then A.id end) as `1month`,
count(DISTINCT case when A.created > DATE_SUB(NOW(), INTERVAL 3 MONTH) then A.id end) as `1quarter`,
count(DISTINCT A.id) as total
FROM dr D
JOIN answer A ON A.dr_id=D.id AND A.status=3
GROUP BY D.id
) s
You might need to play w/ this query a bit, but it should give you what you're looking for. Basically take your query and use it as a derived table and summarize it further using group by/having.
;with DrCounts as (
SELECT D.id,
count(DISTINCT case when A.created > DATE_SUB(NOW(), INTERVAL 1 MONTH) then A.id end) as '1month',
count(DISTINCT case when A.created > DATE_SUB(NOW(), INTERVAL 3 MONTH) then A.id end) as '1quarter',
count(DISTINCT A.id) as total
FROM dr D
JOIN answer A ON A.dr_id=D.id AND A.status=3
GROUP BY D.id)
select count(distinct D.id) as Dr1075
from DrCounts D
group by D.Id
having D.total >= 75 and D.1month >= 10
union
select count(distinct D.id) as Dr1075
from DrCounts D
group by D.Id
having D.total >= 75 and D.1quarter >= 10