2 queries to 1 using 2 different WHERE - mysql

SELECT qurum_id, qurum, COUNT(qurum1) AS I FROM `qurum` AS qur
JOIN (SELECT id, qurum1,input_date FROM DATA ) `aa` ON qur.qurum_id =
aa.qurum1
WHERE DATE_FORMAT(aa.input_date, '%Y') = $year AND DATE_FORMAT(aa.input_date, '%m') < 07
GROUP BY qurum_id
ORDER BY qurum_id
and
SELECT qurum_id, qurum, COUNT(qurum1) AS II FROM `qurum` AS qur2
JOIN (SELECT id, qurum1,input_date FROM DATA ) `bb` ON qur2.qurum_id =
bb.qurum1
WHERE DATE_FORMAT(bb.input_date, '%Y') = $year AND DATE_FORMAT(bb.input_date, '%m') BETWEEN 06 AND 12
GROUP BY qurum_id
ORDER BY qurum_id
How can I join these queries to 1? I need columns like this - qurum_id, qurum, I, II.

You can use conditional aggregation. The trick is to take a conditional count of records depending on the month condition, which is different for your two current queries. Note that the WHERE condition for the year was left alone, because both queries share this condition.
SELECT
qurum_id,
SUM(CASE WHEN DATE_FORMAT(aa.input_date, '%m') < 07
THEN 1 END) AS I,
SUM(CASE WHEN DATE_FORMAT(aa.input_date, '%m') BETWEEN 06 AND 12
THEN 1 END) AS II
FROM qurum AS qur
INNER JOIN (SELECT id, qurum1,input_date FROM DATA ) AS aa
ON qur.qurum_id = aa.qurum1
WHERE DATE_FORMAT(aa.input_date, '%Y') = $year
GROUP BY qurum_id
ORDER BY qurum_id

Related

GROUP BY for each Month and return 0 or NULL if not month exists (MySQL)

SELECT COALESCE(SUM(p.hours),0) as recap
FROM pointage p
WHERE YEAR(date_point) = '2020' AND p.user_id = 1
GROUP BY MONTH(date_point)
I would like sum hours realised for each month but actually in March others month does noot exist because is not present I would like obtain result like (only sum column but for it's an example)
Jan : NULL
Feb : 10
March : 42.75
APR : NULL
MAY : NULL
..
DEC : NULL
AND not only
Feb : 10
March : 42.75
Do you have a solution please?
If you have data for all months in the table -- but just not for that user -- then the simplest (although not most efficient) approach is conditional aggregation:
SELECT MONTH(date_point) as mon,
SUM(CASE WHEN p.user_id = 1 THEN p.hours END) as recap
FROM pointage p
WHERE YEAR(date_point) = 2020
GROUP BY MONTH(date_point) ;
Otherwise, you need a list of the months, which can be generated in many ways -- a calendar table, recursive CTE, explicitly:
select m.mon, sum(p.hours)
from (select 'Jan' as mon, 1 as mm union all
select 'Feb' as mon, 2 as mm union all
. . .
) m left join
pointage p
on p.date_point >= '2020-01-01' and '2021-01-01' and
p.user_id = 1 and
month(p.date_point) = m.mm
group by m.mon
order by min(m.mm);

Optimize subquery in SELECT

My table schema is as follow:
Indexes:
products.id PRIMARY KEY
products.description UNIQUE
expenses.id PRIMARY KEY
expenses.product_id FOREIGN KEY to product.id
My goal is to load
Cost of each product of current month (AS costs_november)
Cost of each product of last month (AS costs_october)
Change in costs of current month compared to last (current month costs - last month costs) (AS costs)
Percentage change of current month costs compared to last (last month costs * 100 / current month costs) (AS percent_diff)
I've managed to code SQL that does exactly that:
SELECT description, (SUM(cost) - IFNULL(
(
SELECT SUM(cost)
FROM expenses
WHERE month = 9 AND year = 2019 AND product_id = e.product_id
GROUP BY product_id
), 0)) AS costs,
SUM(cost) * 100 /
(
SELECT SUM(cost)
FROM expenses
WHERE month = 9 AND year = 2019 AND product_id = e.product_id
GROUP BY product_id
) AS percent_diff,
SUM(cost) AS costs_october,
IFNULL(
(
SELECT SUM(cost)
FROM expenses
WHERE month = 9 AND year = 2019 AND product_id = e.product_id
GROUP BY product_id
), 0) AS costs_september
FROM expenses e
JOIN products p ON (e.product_id = p.id)
WHERE month = 10 AND year = 2019
GROUP BY product_id
ORDER BY product_id;
But is copy-pasting the same subquery three times really the solution? In theory it requires to run four queries per product. Is there a more elegant way?
Appreciate for any help!
I would address this with conditional aggregation:
select
p.description,
sum(case when e.month = 11 then e.cost else 0 end) costs_november,
sum(case when e.month = 10 then e.cost else 0 end) costs_october,
sum(case when e.month = 11 then e.cost else -1 * e.cost end) costs,
sum(case when e.month = 10 then e.cost else 0 end)
* 100
/ nullif(
sum(case when e.month = 11 then e.cost else 0 end),
0
) percent_diff
from expenses e
inner join products p on p.id = e.product_id
where e.year = 2019 and e.month in (10, 11)
goup by e.product_id
You can avoid repeating the same conditional sums by using a subquery (your RDBMS would probably optimize it anyway, but this tends to make the query more readable):
select
description,
costs_november,
costs_october,
costs_november - costs_october costs,
costs_october * 100 / nullif(costs_november, 0) percent_diff
from (
select
p.description,
sum(case when e.month = 11 then e.cost else 0 end) costs_november,
sum(case when e.month = 10 then e.cost else 0 end) costs_october
from expenses e
inner join products p on p.id = e.product_id
where e.year = 2019 and e.month in (10, 11)
goup by e.product_id
) t
You can calculate for all months and all products at one time:
SELECT year, month,
SUM(costs) as curr_month_costs,
LAG(SUM(costs)) OVER (ORDER BY year, month) as prev_month_costs,
(SUM(costs) -
LAG(SUM(costs)) OVER (ORDER BY year, month)
) as diff,
LAG(SUM(costs)) OVER (ORDER BY year, month) * 100 / SUM(costs)
FROM expenses e JOIN
products p
ON e.product_id = p.id
GROUP BY product_id, year, month
ORDER BY year, month, product_id;
You can use a subquery if you want to select only the current month.

Trying to group query by hours

Trying to group my query by hours and have a '0' if nothing is found.
SELECT
tmhours.hours_value,
COALESCE(cc.countingSheep,0) AS countingSheep
FROM time_hours as tmhours
LEFT JOIN (
SELECT count(*) as countingSheep, company_id, `sales_date`
FROM tbl_cc
WHERE `sales_date` BETWEEN '2019-05-01 00:00:00' AND '2019-05-01 23:59:59' AND company_id = '12345' ) as cc on date_format(sales_date, '%H') = tmhours.hours_value
GROUP BY tmhours.hours_value
The time_hours table just contains 01,02,03,04 .... 22, 23
Based on the above query, I am just getting 0's until 07
So:
01 0
02 0
03 0
04 0
05 0
06 0
07 - 57 (the first match in the DB is 07:14:35) - the 57 is the total count, it's not grouping results
08 0
09 0
...
...
22 0
23 0
I've tried removing the group by inside the inner select, tried moving the date_format = hours_value.
Your problem is that you're not grouping the subquery data by the hour, so your subquery is only returning one row (since it has a COUNT in it). Add grouping to the subquery and it should work fine. Note that you don't need grouping in the outer query as you're not doing any aggregation. Also, since you only want one day's data, you can simplify your WHERE condition using the DATE function.
SELECT
tmhours.hours_value,
COALESCE(cc.countingSheep,0) AS countingSheep
FROM time_hours as tmhours
LEFT JOIN (
SELECT count(*) as countingSheep, date_format(sales_date, '%H') AS sales_hour
FROM tbl_cc
WHERE DATE(`sales_date`) = '2019-05-01' AND company_id = '12345'
GROUP BY sales_hour) as cc ON sales_hour = tmhours.hours_value
You have not aggregated function in the outer query so If you need distinct result use DISTINCT (group by can produce unexpected result ) but in your case seems not necessary
insteadd you missed the group by based on the hour in the inner join
SELECT
tmhours.hours_value,
COALESCE(cc.countingSheep,0) AS countingSheep
FROM time_hours as tmhours
LEFT JOIN ( SELECT count(*) as countingSheep, company_id, date_format(sales_date, '%H')
FROM tbl_cc
WHERE `sales_date` BETWEEN '2019-05-01 00:00:00' AND '2019-05-01 23:59:59'
AND company_id = '12345'
GROUP BY company_id , date_format(sales_date, '%H')
) as cc on date_format(sales_date, '%H') = tmhours.hours_value

Optimise MySQL - JOIN vs Nested query

I have been trying to optimise some SQL queries based on the assumption that Joining tables is more efficient than nesting queries. I am joining the same table multiple times to perform a different analysis on the data.
I have 2 tables:
transactions:
id | date_add | merchant_ id | transaction_type | amount
1 1488733332 108 add 20.00
2 1488733550 108 remove 5.00
and a calendar table which just lists dates so that I can create empty records where there are no transactions on particular days:
calendar:
id | datefield
1 2017-03-01
2 2017-03-02
3 2017-03-03
4 2017-03-04
I have many thousands of rows in the transactions table, and I'm trying to get an annual summary of total and different types of transactions per month (i.e 12 rows in total), where
transactions = sum of all "amount"s,
additions = sum of all "amounts" where transaction_type = "add"
redemptions = sum of all "amounts" where transaction_type = "remove"
result:
month | transactions | additions | redemptions
Jan 15 12 3
Feb 20 15 5
...
My initial query looks like this:
SELECT COALESCE(tr.transactions, 0) AS transactions,
COALESCE(ad.additions, 0) AS additions,
COALESCE(re.redemptions, 0) AS redemptions,
calendar.date
FROM (SELECT DATE_FORMAT(datefield, '%b %Y') AS date FROM calendar WHERE datefield LIKE '2017-%' GROUP BY YEAR(datefield), MONTH(datefield)) AS calendar
LEFT JOIN (SELECT COUNT(transaction_type) as transactions, from_unixtime(date_add, '%b %Y') as date_t FROM transactions WHERE merchant_id = 108 GROUP BY from_unixtime(date_add, '%b %Y')) AS tr
ON calendar.date = tr.date_t
LEFT JOIN (SELECT COUNT(transaction_type = 'add') as additions, from_unixtime(date_add, '%b %Y') as date_a FROM transactions WHERE merchant_id = 108 AND transaction_type = 'add' GROUP BY from_unixtime(date_add, '%b %Y')) AS ad
ON calendar.date = ad.date_a
LEFT JOIN (SELECT COUNT(transaction_type = 'remove') as redemptions, from_unixtime(date_add, '%b %Y') as date_r FROM transactions WHERE merchant_id = 108 AND transaction_type = 'remove' GROUP BY from_unixtime(date_add, '%b %Y')) AS re
ON calendar.date = re.date_r
I tried optimising and cleaning it up a little, removing the nested statements and came up with this:
SELECT
DATE_FORMAT(cal.datefield, '%b %d') as date,
IFNULL(count(ct.amount),0) as transactions,
IFNULL(count(a.amount),0) as additions,
IFNULL(count(r.amount),0) as redeptions
FROM calendar as cal
LEFT JOIN transactions as ct ON cal.datefield = date(from_unixtime(ct.date_add)) && ct.merchant_id = 108
LEFT JOIN transactions as r ON r.id = ct.id && r.transaction_type = 'remove'
LEFT JOIN transactions as a ON a.id = ct.id && a.transaction_type = 'add'
WHERE cal.datefield like '2017-%'
GROUP BY month(cal.datefield)
I was surprised to see that the revised statement was about 20x slower than the original with my dataset. Have I missed some sort of logic? Is there a better way to achieve the same result with a more streamlined query, given I am joining the same table multiple times?
EDIT:
So to further explain the results I'm looking for - I'd like a single row for each month of the year (12 rows) each with a column for the total transactions, total additions, and total redemptions in each month.
The first query I was getting a result in about 0.5 sec but with the second I was getting results in 9.5sec.
Looking to your query You could use a single left join with case when
SELECT COALESCE(t.transactions, 0) AS transactions,
COALESCE(t.additions, 0) AS additions,
COALESCE(t.redemptions, 0) AS redemptions,
calendar.date
FROM (SELECT DATE_FORMAT(datefield, '%b %Y') AS date
FROM calendar
WHERE datefield LIKE '2017-%'
GROUP BY YEAR(datefield), MONTH(datefield)) AS calendar
LEFT JOIN
( select
COUNT(transaction_type) as transactions
, sum( case when transaction_type = 'add' then 1 else 0 end ) as additions
, sum( case when transaction_type = 'remove' then 1 else 0 end ) as redemptions
, from_unixtime(date_add, '%b %Y') as date_t
FROM transactions
WHERE merchant_id = 108
GROUP BY from_unixtime(date_add, '%b %Y' ) t ON calendar.date = t.date_t
First I would create a derived table with timestamp ranges for every month from your calendar table. This way a join with the transactions table will be efficient if date_add is indexed.
select month(c.datefield) as month,
unix_timestamp(timestamp(min(c.datefield), '00:00:00')) as ts_from,
unix_timestamp(timestamp(max(c.datefield), '23:59:59')) as ts_to
from calendar c
where c.datefield between '2017-01-01' and '2017-12-31'
group by month(c.datefield)
Join it with the transaactions table and use conditional aggregations to get your data:
select c.month,
sum(t.amount) as transactions,
sum(case when t.transaction_type = 'add' then t.amount else 0 end) as additions,
sum(case when t.transaction_type = 'remove' then t.amount else 0 end) as redemptions
from (
select month(c.datefield) as m, date_format(c.datefield, '%b') as `month`
unix_timestamp(timestamp(min(c.datefield), '00:00:00')) as ts_from,
unix_timestamp(timestamp(max(c.datefield), '23:59:59')) as ts_to
from calendar c
where c.datefield between '2017-01-01' and '2017-12-31'
group by month(c.datefield), date_format(c.datefield, '%b')
) c
left join transactions t on t.date_add between c.ts_from and c.ts_to
where t.merchant_id = 108
group by c.m, c.month
order by c.m

Mysql to select month-wise record even if data not exist

I wrote a query to get month-wise record in user table as follows
SELECT COUNT( `userID` ) AS total, DATE_FORMAT( `userRegistredDate` , '%b' ) AS
MONTH , YEAR( `userRegistredDate` ) AS year
FROM `users`
GROUP BY DATE_FORMAT( FROM_UNIXTIME( `userRegistredDate` , '%b' ) )
Output:
total MONTH year
---------------------------
3 May 2013
2 Jul 2013
--------------------------
Expected Output:
total MONTH year
---------------------------
0 Jan 2013
0 Feb 2013
0 Mar 2013
0 Apr 2013
3 May 2013
0 Jun 2013
2 Jul 2013
--------------------------
I need to show the record even if data not exist. How to do this?
I won't say much about efficiency as I have not tested it against other methods but without having a temp table this seem a fair way to go.
SELECT COUNT(u.userID) AS total, m.month
FROM (
SELECT 'Jan' AS MONTH
UNION SELECT 'Feb' AS MONTH
UNION SELECT 'Mar' AS MONTH
UNION SELECT 'Apr' AS MONTH
UNION SELECT 'May' AS MONTH
UNION SELECT 'Jun' AS MONTH
UNION SELECT 'Jul' AS MONTH
UNION SELECT 'Aug' AS MONTH
UNION SELECT 'Sep' AS MONTH
UNION SELECT 'Oct' AS MONTH
UNION SELECT 'Nov' AS MONTH
UNION SELECT 'Dec' AS MONTH
) AS m
LEFT JOIN users u
ON MONTH(STR_TO_DATE(CONCAT(m.month, ' 2013'),'%M %Y')) = MONTH(u.userRegistredDate)
AND YEAR(u.userRegistredDate) = '2013'
GROUP BY m.month
ORDER BY 1+1;
If you make the union based on a date format you can even reduce the work and load on the query.
SELECT COUNT(u.userID) AS total, DATE_FORMAT(merge_date,'%b') AS month, YEAR(m.merge_date) AS year
FROM (
SELECT '2013-01-01' AS merge_date
UNION SELECT '2013-02-01' AS merge_date
UNION SELECT '2013-03-01' AS merge_date
UNION SELECT '2013-04-01' AS merge_date
UNION SELECT '2013-05-01' AS merge_date
UNION SELECT '2013-06-01' AS merge_date
UNION SELECT '2013-07-01' AS merge_date
UNION SELECT '2013-08-01' AS merge_date
UNION SELECT '2013-09-01' AS merge_date
UNION SELECT '2013-10-01' AS merge_date
UNION SELECT '2013-11-01' AS merge_date
UNION SELECT '2013-12-01' AS merge_date
) AS m
LEFT JOIN users u
ON MONTH(m.merge_date) = MONTH(u.userRegistredDate)
AND YEAR(m.merge_date) = YEAR(u.userRegistredDate)
GROUP BY m.merge_date
ORDER BY 1+1;
Live DEMO of both queries.
You may need a table to hold every "month" record. A temp table can do the trick:
drop table if extists temp_months;
create temporary table temp_months
month date,
index idx_date(month);
insert into temp_months
values ('2013-01-31'), ('2013-02-28'), ...
And now, you can left join your data with this newly created temp table:
SELECT
COUNT( `userID` ) AS total,
DATE_FORMAT( m.month , '%b' ) AS
MONTH ,
YEAR( m.month ) AS year
FROM
months as m
left join `users` as u on m.month = last_day(FROM_UNIXTIME(`userRegistredDate`, '%b' )
GROUP BY
last_day(m.month);
Notice that you can put the temp table creation (and fill) in a stored procedure.
I use last_day for simplicity, but you are free to use any date in the month that you like, if you join it correctly.
Hope this helps