How to Sum "case when" clause after left join without duplication - mysql

How do I get the SUM of an amount in a CASE WHEN clause?
Table real:
id
name
goal
year
10
ronaldo
5
2022
10
ronaldo
5
2022
11
messi
5
2022
11
messi
5
2022
10
ronaldo
10
2021
11
messi
10
2021
Table target:
id
name
goal
year
10
ronaldo
10
2022
11
messi
10
2022
10
ronaldo
10
2021
11
messi
10
2021
I tried inner join, but the result was wrong:
id
name
real 2022
target 2022
real 2021
target 2021
10
ronaldo
20
30
20
30
11
messi
20
30
20
30
Desired result:
id
name
real 2022
target 2022
real 2021
target 2021
10
ronaldo
10
10
10
10
11
messi
10
10
10
10
<?php
$sql = $pdo->prepare("SELECT *,
SUM( case when YEAR(real.year) = YEAR(CURDATE()) then real.goal else 0 end) AS goal_now,
SUM( case when YEAR(real.year) = YEAR(CURDATE() - INTERVAL 1 YEAR) then real.goal else 0 end) AS goal_then,
SUM( case when YEAR(target.year) = YEAR(CURDATE()) then target.goal else 0 end) AS goal_target,
SUM( case when YEAR(target.year) = YEAR(CURDATE() - INTERVAL 1 YEAR) then target.goal else 0 end) AS goal_target_then
FROM real
left join target
on id_real = id_target
group by real.id_real
having
real.id_real LIKE '1%'
");
$sql->execute();
while($data = $sql->fetch()){
?>

select
id,
name,
sum(real_goal_now) as real_goal_now,
sum(real_goal_then) as real_goal_then,
sum(target_goal_now) as target_goal_now,
sum(target_goal_then) as target_goal_then
from
(
select
a.id,
a.name,
case
when a.year = year(curdate()) then a.goal else 0 end as real_goal_now,
case
when a.year = year(curdate() - interval 1 year) then a.goal else 0 end as real_goal_then,
case
when b.year = year(curdate()) then b.goal else 0 end as target_goal_now,
case
when b.year = year(curdate() - interval 1 year) then b.goal else 0 end as target_goal_then
from
(
select
id, name, year, sum(goal) goal
from
real
group by
id, year) a,
(
select
id, name, year, sum(goal) goal
from
target
group by
id, year) b
where
a.id = b.id
and a.year = b.year
group by
a.id, a.year) c
group by
id;

<?php
$sql = $pdo->prepare("SELECT *,
SUM( case when YEAR(real.year) = YEAR(CURDATE()) then real.goal else 0 end) AS goal_now,
SUM( case when YEAR(real.year) = YEAR(CURDATE() - INTERVAL 1 YEAR) then real.goal else 0 end) AS goal_then
FROM real
left join ( select *,
SUM( case when YEAR(target.year) = YEAR(CURDATE()) then target.goal else 0 end) AS goal_target,
SUM( case when YEAR(target.year) = YEAR(CURDATE() - INTERVAL 1 YEAR) then target.goal else 0 end) AS goal_target_then
from target
group by id_target ) as t
on real.id_real = t.id_target
group by real.id_real
having
real.id_real LIKE '1%'
");
$sql->execute();
while($data = $sql->fetch()){
?>

Related

How to count number of nights booked per month with check-in and check-out dates having different months in SQL?

I have a dataset like the below dataset. I want to find the number of nights each id was occupied per month. For some rows, the check-in and checkout dates are in different months. I want to know how to write a query to have the occupancy per month. For example, for id=1, check-in: 2020-01-26 and checkout date: 2020-03-02. How can I have a table that shows January occupancy: 6, Feb occupancy: 29, and March occupancy: 1
id
check-in
checkout
1
2020-01-26
2020-03-02
2
2020-04-01
2020-04-20
3
2020-06-29
2020-07-03
The outcome should be like this:
id
Month
Occupancy
1
Jan
06
1
Feb
29
1
Mar
01
2
Apr
19
3
Jun
02
3
Jul
02
first, you need a numbers table or tally table , after you can easily to it using this query :
select c.id,
case when m.id <> 0
then adddate(last_day(adddate(checkin_date, interval m.id -1 month)),interval 1 day)
else checkin_date
end as Checkin_date,
case when last_day(adddate(checkin_date, interval m.id month)) > checkout_date
then checkout_date
else last_day(adddate(checkin_date, interval m.id month))
end checout_date,
datediff(case when last_day(adddate(checkin_date, interval m.id month)) > checkout_date
then checkout_date
else last_day(adddate(checkin_date, interval m.id month)) end,
case when m.id <> 0
then last_day(adddate(checkin_date, interval m.id -1 month))
else adddate(checkin_date, interval -1 day) end
) daysdiff
from checkins c
join numbers m on m.id <= period_diff(date_format(checkout_date, "%Y%m"),date_format(checkin_date, "%Y%m"))
order by c.id, checkin_date
this is works for any gap (for more than 1 year)
you can usedate_format to show month :
select
date_format(case when m.id <> 0
then adddate(last_day(adddate(checkin_date, interval m.id -1 month)),interval 1 day)
else checkin_date
end, '%Y %M') as month_year
,sum(datediff(case when last_day(adddate(checkin_date, interval m.id month)) > checkout_date
then checkout_date
else last_day(adddate(checkin_date, interval m.id month)) end,
case when m.id <> 0
then last_day(adddate(checkin_date, interval m.id -1 month))
else adddate(checkin_date, interval -1 day) end
)) Occupancy
from checkins c
join numbers m on m.id <= period_diff(date_format(checkout_date, "%Y%m"),date_format(checkin_date, "%Y%m"))
group by date_format(case when m.id <> 0
then adddate(last_day(adddate(checkin_date, interval m.id -1 month)),interval 1 day)
else checkin_date
end, '%Y %M')
order by month_year
month_year | Occupancy
:------------ | --------:
2020 April | 20
2020 February | 29
2020 January | 6
2020 July | 3
2020 June | 2
2020 March | 2
db<>fiddle here
If I understand correctly, you want month-wise aggregated result of occupied inventory.
You can try below simple aggregate query as based on 'Group by' clause then add more criteria logic based on your need if required
select monthname(check_in) as 'Month', sum(dayofyear(check_out) - dayofyear(check_in)) as 'Occupied_days'
from inventory
where year(check_in)=year(check_out)
group by 1;
Note: Above query will work only for dataset where check_in & check_out happened within same year.
Check sample query output here in Fiddle

Using count(*) .. Over(*) in mysql

My data looks like the following,
requestedDate Status
2020-04-21 APPROVED
2020-04-23 APPROVED
2020-04-27 PENDING
2020-05-21 PENDING
2020-06-01 APPROVED
I would like to extarct a report that looks like the following where the count is by status and month.
Status StatusCount Month MonthCount CountTotal
APPROVED 2 APR 3 5
PENDING 1 MAY 1 5
APPROVED 1 JUN 1 5
My sql looks like the following,
select distinct
status,
count(status) over (partition by status) as total_by_status,
CASE
WHEN Month(requestedDate) = 1 THEN 'JAN'
WHEN Month(requestedDate) = 2 THEN 'FEB'
WHEN Month(requestedDate) = 3 THEN 'MAR'
WHEN Month(requestedDate) = 4 THEN 'APR'
WHEN Month(requestedDate) = 5 THEN 'MAY'
WHEN Month(requestedDate) = 6 THEN 'JUN'
WHEN Month(requestedDate) = 7 THEN 'JUL'
WHEN Month(requestedDate) = 8 THEN 'AUG'
WHEN Month(requestedDate) = 9 THEN 'SEP'
WHEN Month(requestedDate) = 10 THEN 'OCT'
WHEN Month(requestedDate) = 11 THEN 'NOV'
WHEN Month(requestedDate) = 12 THEN 'DEC'
END AS myMONTH,
count(Month(requestedDate)) over (partition by Month(requestedDate)) as total_by_month,
count(*) over () as Totals
from Reports
where
requestedDate between DATE_SUB(CURDATE(), INTERVAL 120 DAY) and date(CURDATE())
order by 1;
The output for that looks like,
status total_by_status myMONTH total_by_month Totals
APPROVED 3 APR 3 5
APPROVED 3 JUN 1 5
PENDING 2 APR 3 5
PENDING 2 MAY 1 5
dbfiddle
First you need a valid aggregation query. Then you can use window functions on top of it (here, you would typically compute window sums of the counts).
I would write this as:
select
status,
count(*) status_count,
date_format(requestedDate, '%b') requested_month
sum(count(*)) over(partition by year(requestedDate), month(requestedDate)) month_count,
sum(count(*)) over() total_count
from reports
where requestedDate between current_date - interval 120 day and current_date
group by status, year(requestedDate), month(requestedDate), date_format(requestedDate, '%b')
Since it is just for last 120 days (last years same month wouldnt occur) so we can also use distinct instead of group by), something like below:
select distinct status,
count(*) over (partition by status) as total_by_status,
date_format(requestedDate, '%b') mymonth,
count(Month(requestedDate)) over (partition by Month(requestedDate)) as total_by_month,
count(*) over () as total_by_month
from reports
where requestedDate between current_date - interval 120 day and current_date
order by status, mymonth
Demo

MYSQL Subtracting two SELECT Queries

Using MYSQL, I have written two big SELECT queries combined by a UNION, to get 2 rows, where the first row is the count for the current month, and the second row is the count for the previous month. The Query is as follows:
select * from
(select count(*) as type1 from table_x where nationality_id = 23 and month(START_DATE) = month(now())) as t1,
(select count(*) as type2 from table_x where nationality_id = 24 and month(START_DATE) = month(now())) as t2,
(select count(*) as type3 from table_x where nationality_id = 25 and month(START_DATE) = month(now())) as t3,
(select count(*) as type4 from table_x where nationality_id = 26 and month(START_DATE) = month(now())) as t4
UNION
select * from
(select count(*) as type1 from table_x where nationality_id = 23 and month(START_DATE) = month(now() - INTERVAL 1 MONTH)) as t1,
(select count(*) as type2 from table_x where nationality_id = 24 and month(START_DATE) = month(now() - INTERVAL 1 MONTH)) as t2,
(select count(*) as type3 from table_x where nationality_id = 25 and month(START_DATE) = month(now() - INTERVAL 1 MONTH)) as t3,
(select count(*) as type4 from table_x where nationality_id = 26 and month(START_DATE) = month(now() - INTERVAL 1 MONTH)) as t4
I want to add a third row, which is the difference between row 2 and row 1.
How can I do this with my current query?
You are obviously doing a compare between current and prior month. So, I would start with my inner pre-query aggregate getting only those transactions >= the first of the prior month AND the records within the nationality IDs you are looking for.
The inner date_sub() of DAYOFMONTH() -1 day gives you the first of the CURRENT month. By subtracting one more month, gives you the first of the LAST month.
Now you can aggregate the totals per nationality compared to current month or not. That inner query gives you all begin and end counts. Now that is wrapped to the outer and you can get all the counts in addition to the differences... Obviously you can change the column names respectively.
select
PQ.*,
PQ.ThisMonth23 - PQ.LastMonth23 = Diff23,
PQ.ThisMonth24 - PQ.LastMonth24 = Diff24,
PQ.ThisMonth25 - PQ.LastMonth25 = Diff25,
PQ.ThisMonth26 - PQ.LastMonth26 = Diff26
from
( select
sum( case when t.Nationality_id = 23 and month( t.StartDate ) = month( now()) then 1 else 0 end ) ThisMonth23,
sum( case when t.Nationality_id = 24 and month( t.StartDate ) = month( now()) then 1 else 0 end ) ThisMonth24,
sum( case when t.Nationality_id = 25 and month( t.StartDate ) = month( now()) then 1 else 0 end ) ThisMonth25,
sum( case when t.Nationality_id = 26 and month( t.StartDate ) = month( now()) then 1 else 0 end ) ThisMonth26,
sum( case when t.Nationality_id = 23 and month( t.StartDate ) != month( now()) then 1 else 0 end ) LastMonth23,
sum( case when t.Nationality_id = 24 and month( t.StartDate ) != month( now()) then 1 else 0 end ) LastMonth24,
sum( case when t.Nationality_id = 25 and month( t.StartDate ) != month( now()) then 1 else 0 end ) LastMonth25,
sum( case when t.Nationality_id = 26 and month( t.StartDate ) != month( now()) then 1 else 0 end ) LastMonth26
from
table_x t
where
t.StartDate >= date_sub( date_sub( t.StartDate, interval DAYOFMONTH( t.StartDate ) -1 DAY ), interval 1 MONTH )
AND t.Nationality_id IN ( 23, 24, 25, 26 )
) PQ
I would just add that your query might be getting more than you think... You are asking for ALL records Ex: January REGARDLESS of the year, compared to ALL records December REGARDLESS of the year because all you are qualifying is based on the MONTH() and no YEAR() consideration. I am explicitly querying back only current and prior month.

MYSQL Count with INNER JOIN not working

I have two tables
1) outreach
id profile_id url
-------------------------
1 2 www.test.com
2 3 www.google.com
3 4 www.example.com
4 2 www.test2.com
5 2 www.test3.com
6 2 www.test4.com
2). outreach_links
id outreach_id start_date created_at cost status
-----------------------------------------------------------------------
1 1 2016-12-01 00:00:00 2016-12-07 00:00:00 100.00 Approved
2 1 2016-12-02 00:00:00 2016-12-09 00:00:00 120.00 Approved
3 1 NUll 2016-12-28 00:00:00 20.00 Pending
4 1 2016-12-05 00:00:00 2016-12-10 00:00:00 35.00 Approved
5 1 2016-12-07 00:00:00 2016-12-13 00:00:00 10.00 Approved
6 2 2016-12-10 00:00:00 2016-12-15 00:00:00 10.00 Pending
7 2 2016-12-13 00:00:00 2016-12-18 00:00:00 10.00 Approved
8 2 2016-12-01 00:00:00 2016-12-28 00:00:00 10.00 Pending
9 2 2016-12-04 00:00:00 2016-12-21 00:00:00 10.00 Approved
10 2 2016-12-09 00:00:00 2016-12-22 00:00:00 15.00 Pending
I am trying to do a count by Month/Year and I thought its working but I think its not working because of "profile_id" issue here is my query:
select monthname(date) as Month, year(date) as Year, month(date) as Mn, UNIX_TIMESTAMP(CONCAT(year(date),"-",month(date),"-","01")) as tt,
(select count(*) from outreach_links where year(outreach_links.created_at) = year and month(outreach_links.created_at) = month and status = "Pending" and created_at>="2016-12-01 00:00:00" and created_at<="2016-12-31 00:00:00") as pp,
(select count(*) from outreach_links where year(outreach_links.start_date) = year and month(outreach_links.start_date) = month and status = "Approved" and start_date>="2016-12-01 00:00:00" and start_date<="2016-12-31 00:00:00") as aa,
(select sum(cost) from outreach_links where year(outreach_links.start_date) = year and month(outreach_links.start_date) = month and status = "Approved" and start_date>="2016-12-01 00:00:00" and start_date<="2016-12-31 00:00:00") as cc
from
(select year(outreach_links.created_at) as year, month(outreach_links.created_at) as month, outreach_links.created_at as date
from outreach_links
inner join outreach on outreach.id = outreach_links.outreach_id
where outreach_links.created_at>="2016-12-01 00:00:00" and outreach_links.created_at<="2016-12-31 00:00:00" and outreach.profile_id=2
union
select year(outreach_links.start_date) as year, month(outreach_links.start_date) as month, outreach_links.start_date as date
from outreach_links
inner join outreach on outreach.id = outreach_id
where start_date>="2016-12-01 00:00:00" and start_date<="2016-12-31 00:00:00" and outreach.profile_id=2 ) t1
group by year, month
order by date
So I am doing a date range from "2016-12-01 00:00:00" to "2016-12-31 00:00:00" these could be any date range inputed by the user , and try to do a count based on outreach.profile_id = 2 , my output is wrong its counting everything for all profile_ids, I am not sure why
Note: this is just a sample of the tables , there might be more records and the user inputed Date range from to could be different , I want to group them by Month / Year
here is my output: ( its counting ALL records)
array:1 [▼
0 => {#394 ▼
+"Month": "December"
+"Year": "2016"
+"Mn": "12"
+"tt": "1480568400.000000"
+"pp": "4"
+"aa": "6"
+"cc": "285.00"
}
]
Which is wrong it should count only for profile_id=2 , here is the DESIRED output I want:
array:1 [▼
0 => {#394 ▼
+"Month": "December"
+"Year": "2016"
+"Mn": "12"
+"tt": "1480568400.000000"
+"pp": "1"
+"aa": "4"
+"cc": "265.00"
}
]
As you can see the 3 counts are wrong they suppose to be:
"pp": "1"
"aa": "4"
"cc": "265.00"
here is what I am looking for:
1). **"pp" is Total Pending** Count when status="Pending" based on created_at
2). **"aa" is Total Approved** Count when status="Approved" based on start_date
3). **"cc" is Total Cost** Sum of All cost when Status="Approved" and based on start_date
4). Group by Month & Year of the user imputed Date Range
here is a SQLFIDDLE >> http://sqlfiddle.com/#!9/87dfa8/1
can you please help me fix it?
Thanks
I think you want something like this:
SELECT MONTHNAME(d.date) AS Month
, YEAR(d.date) AS Year
, MONTH(d.date) AS Mn
, SUM(IF(l.status = 'Pending' AND l.created_at >= d.date AND l.created_at < d.date + INTERVAL 1 MONTH ,1 ,0)) AS pp
, SUM(IF(l.status = 'Approved' AND l.start_date >= d.date AND l.start_date < d.date + INTERVAL 1 MONTH ,1 ,0)) AS aa
, SUM(IF(l.status = 'Approved' AND l.start_date >= d.date AND l.start_date < d.date + INTERVAL 1 MONTH ,l.cost,0)) AS cc
FROM ( SELECT '2016-12-01' + INTERVAL 0 MONTH AS date ) d
JOIN outreach o
ON o.profile_id = 2
LEFT
JOIN outreach_links l
ON l.outreach_id = o.id
AND ( ( l.start_date >= d.date + INTERVAL 0 MONTH
AND l.start_date < d.date + INTERVAL 1 MONTH
)
OR ( l.created_at >= d.date + INTERVAL 0 MONTH
AND l.created_at < d.date + INTERVAL 1 MONTH
)
)
GROUP BY d.date
EDIT
To specify the end date of the range along with the start date, the inline view d can return two date values. And the outer query can reference the second date value, in this example d.end_date, as well as the start of the range d.date.
SELECT MONTHNAME(d.date) AS Month
, YEAR(d.date) AS Year
, MONTH(d.date) AS Mn
, SUM(IF(l.status = 'Pending' AND l.created_at >= d.date AND l.created_at < d.end_date,1 ,0)) AS pp
, SUM(IF(l.status = 'Approved' AND l.start_date >= d.date AND l.start_date < d.end_date,1 ,0)) AS aa
, SUM(IF(l.status = 'Approved' AND l.start_date >= d.date AND l.start_date < d.end_date,l.cost,0)) AS cc
FROM ( SELECT '2016-12-01' + INTERVAL 0 MONTH AS date
, '2016-12-16' + INTERVAL 0 MONTH AS end_date
) d
JOIN outreach o
ON o.profile_id = 2
LEFT
JOIN outreach_links l
ON l.outreach_id = o.id
AND ( ( l.start_date >= d.date
AND l.start_date < d.end_date
)
OR ( l.created_at >= d.date
AND l.created_at < d.end_date
)
)
GROUP BY d.date, d.end_date

Calculate percentage and total after create categories mysql

I've this query
SELECT
trage,
CASE trage
WHEN '<18' THEN SUM(CASE WHEN AGE <18 THEN 1 ELSE 0 END)
WHEN '18-24' THEN SUM(CASE WHEN AGE >= 18 AND AGE <= 24 THEN 1 ELSE 0 END)
WHEN '25-34' THEN SUM(CASE WHEN AGE >= 25 AND AGE <= 34 THEN 1 ELSE 0 END)
WHEN '35-44' THEN SUM(CASE WHEN AGE >= 35 AND AGE <= 44 THEN 1 ELSE 0 END)
WHEN '45-54' THEN SUM(CASE WHEN AGE >= 45 AND AGE <= 54 THEN 1 ELSE 0 END)
WHEN '>=55' THEN SUM(CASE WHEN AGE >= 55 THEN 1 ELSE 0 END)
END Total
FROM
( SELECT
t_personne.pers_date_naissance,
t_personne.pers_date_inscription,
TIMESTAMPDIFF(Year, t_personne.pers_date_naissance, t_personne.pers_date_inscription)
- CASE
WHEN MONTH(t_personne.pers_date_naissance) > MONTH(t_personne.pers_date_inscription)
OR (MONTH(t_personne.pers_date_naissance) = MONTH(t_personne.pers_date_inscription)
AND DAY(t_personne.pers_date_naissance) > DAY(t_personne.pers_date_inscription))
THEN 1 ELSE 0
END AS AGE
FROM t_personne
) AS Total
CROSS JOIN
( SELECT '<18' trage UNION ALL
SELECT '18-24' UNION ALL
SELECT '25-34' UNION ALL
SELECT '35-44' UNION ALL
SELECT '45-54' UNION ALL
SELECT '>=55'
)a
GROUP BY trage
ORDER BY FIELD(trage, '<18', '18-24', '25-34', '35-44', '45-54', '>=55')
it give a table with two columns trage and Total for all categories
How to add a column percentage with a line TOTAL for the column Total and %
Thanks for your help
For the time being, you can't do this. To support this MySQL needs Window Function support which it still doesn't have. If you need functions like these I would recommend switching to PostgreSQL.
Also take a look at this question: MySql using correct syntax for the over clause