Trying to group query by hours - mysql

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

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);

Mysql Use of MAX in a subquery

Why can't I use that subquery which returns a group error ?
SELECT hs.dateFin, hs.codeAdherent, hs.codeArticle
FROM hs
WHERE hs.codeFamilleArticle IN ('CNI', 'COT', 'ABO', 'ABOW',
'CNIW', 'O&T', 'EPH', 'TAX')
AND codeAdherent != 0
AND MAX(hs.dateFin) BETWEEN '2017-01-01'
AND '2017-12-31'
GROUP BY hs.codeAdherent
The same data exists for 2018-01-01 and 2018-12-31 but I only want to get the ones that end in 2017.
Here under a sample of table which contains 140000 raws (not all columns are showed).
codeAdherent A has data for 2018, 2017, 2016.
codeAdherent B has data for2018, 2017
codeAdherent C only for 2017.
If I do a select on 2017 I get all three codeAdherent, then the MAX BETWEEN will exclude A and B... But that's doesn't work
You can use NOT EXISTS to check if no record exists for 2018:
SELECT dateFin, codeAdherent, codeArticle
FROM hs AS t
WHERE codeFamilleArticle IN ('CNI', 'COT', 'ABO', 'ABOW', 'CNIW', 'O&T', 'EPH', 'TAX')
AND codeAdherent != 0
-- filter 2017 rows
AND dateFin >= '2017-01-01'
AND dateFin < '2018-01-01'
-- filter rows where 2018 data does not exist
AND NOT EXISTS (
SELECT 1
FROM hs
WHERE codeAdherent = t.codeAdherent
AND dateFin >= '2018-01-01'
)
You can do it like this:
HAVING YEAR(MAX(hs.dateFin)) = 2017
You cannot use aggregate functions like Max() inside Where clause. You can simply modify your where condition to include dates in year 2017 only, and then determine Max() date after group by.
SELECT MAX(hs.dateFin), hs.codeAdherent, hs.codeArticle
FROM hs
WHERE hs.codeFamilleArticle IN ('CNI', 'COT', 'ABO', 'ABOW',
'CNIW', 'O&T', 'EPH', 'TAX')
AND hs.codeAdherent != 0
WHERE hs.dateFin BETWEEN '2017-01-01'
AND '2017-12-31'
GROUP BY hs.codeAdherent, hs.codeArticle

2 queries to 1 using 2 different WHERE

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

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

Need help on mysql date range query

Below query I am using in my search
I have date range in search, when I searching data between '01/05/2012' AND '31/05/2012' date range but query return all record set.
SELECT t.created date, t.saleid sale_id, u.id user_id, NULL merchant_name, cheque_number cheque_number, u.first_name name, (
SELECT company_name
FROM users
WHERE id = u.my_charity_id)charity_name, t.campaignname website, t.campaignid campaign_id, t.mysoko_discount discount_percentage, t.discount, t.salecommission sale_commission, t.salevalue total_sale_value, t.salestatus sale_status
FROM `transaction` AS `t` , `users` AS `u`
WHERE DATE_FORMAT( t.created, '%d/%m/%Y' )
BETWEEN '01/05/2012' AND '31/05/2012'
AND t.user_id = u.id
LIMIT 0 , 30
I am looking for date range search data but result return all data from table.
date sale_id user_id
2012-04-19 00:00:00 20253305 45
2012-04-11 00:00:00 20253306 68
2012-04-23 00:00:00 20253307 68
2012-04-25 00:00:00 20253308 45
2012-04-27 00:00:00 20253309 45
2012-04-29 00:00:00 20253310 68
2012-04-30 00:00:00 20253311 45
2012-05-01 00:00:00 20253312 45
2012-05-03 00:00:00 20253313 68
2012-04-18 00:00:00 20253314 4
My search is from date range txn search (from / to date)
INPUT date format: DD/MM/YYYY
Do this:
WHERE t.created BETWEEN '2012-05-01 00:00:00' AND '2012-05-31 23:59:59' ...
MySQL will interpret '2012-05-31 23:59:59' as the appropriate DATETIME or TIMESTAMP type, allowing you to take advantage of an index on t.created.
Your problem is that your current query specifies that the string representation of t.created be between the strings '01/05/2012' and '31/05/2012'. Since you string format day-month-year, any date whose day component is between '02' and '30', inclusive, will match. (And the first of any month of May or later will match, etc.)
You could try filtering rows like this:
WHERE t.created >= '2012-05-01'
AND t.created < '2012-06-01'
If the month is specified as an argument, you could use the following calculations in the WHERE clause:
WHERE t.created >= #monthdate
AND t.created < #monthdate + INTERVAL 1 MONTH
One option:
WHERE MONTH(t.created) = 5 AND YEAR(t.created) = 2012
SELECT t.created date, t.saleid sale_id, u.id user_id, NULL merchant_name, cheque_number cheque_number, u.first_name name, (
SELECT company_name
FROM users
WHERE id = u.my_charity_id)charity_name, t.campaignname website, t.campaignid campaign_id, t.mysoko_discount discount_percentage, t.discount, t.salecommission sale_commission, t.salevalue total_sale_value, t.salestatus sale_status
FROM `transaction` AS `t` , `users` AS `u`
WHERE DATE_FORMAT( t.created, '%d/%m/%Y' )
BETWEEN STR_TO_DATE('01/05/2012','%d,%m,%Y') AND STR_TO_DATE('31/05/2012','%d,%m,%Y')
AND t.user_id = u.id
LIMIT 0 , 30