MySQL use date() in CASE statement - mysql

I am trying to use the date() function in MySQL to use only the date from a datetime format to create cases for total number of orders based on day of the week. However, when I run this query, I get a value of '0' for each case.
select
(CASE datetime WHEN date(datetime) = '2020-06-22' THEN count(order_id) ELSE 0 END) AS 'Mon'
,(CASE datetime WHEN date(datetime) = '2020-06-23' THEN count(order_id) ELSE 0 END) AS 'Tue'
,(CASE datetime WHEN date(datetime) = '2020-06-24' THEN count(order_id) ELSE 0 END) AS 'Wed'
,(CASE datetime WHEN date(datetime) = '2020-06-25' THEN count(order_id) ELSE 0 END) AS 'Thu'
,(CASE datetime WHEN date(datetime) = '2020-06-26' THEN count(order_id) ELSE 0 END) AS 'Fri'
from table;
I confirmed I do get my desired ouput for an individual date when I run the following query.
select count(order_id)
from table
where date(datetime) = '2020-06-22';

tray a query like this:
select
sum( if( weekday(datetime) = 0,1,0)) as 'Mon',
sum( if( weekday(datetime) = 1,1,0)) as 'Tue',
sum( if( weekday(datetime) = 2,1,0)) as 'Wed',
sum( if( weekday(datetime) = 3,1,0)) as 'Thu',
sum( if( weekday(datetime) = 4,1,0)) as 'Fri',
sum( if( weekday(datetime) = 5,1,0)) as 'Sat',
sum( if( weekday(datetime) = 6,1,0)) as 'Sun'
;
and you can use a
WHERE datetime BETWEEN '2020-06-22' AND '2020-06-26'
if you want to get a range of dates

MySQL has a nice feature where boolean values are treated as numbers, with 1 for true and 0 for false. So you can express the logic as:
select sum( weekday(datetime) = 0 ) as Mon,
sum( weekday(datetime) = 1 ) as Tue,
sum( weekday(datetime) = 2 ) as Wed,
sum( weekday(datetime) = 3 ) as Thu,
sum( weekday(datetime) = 4 ) as Fri,
sum( weekday(datetime) = 5 ) as Sat,
sum( weekday(datetime) = 6 ) as Sun
Or:
select sum( date(datetime) = '2020-06-22' ) as Mon,
sum( date(datetime) = '2020-06-23' ) as Tue,
sum( date(datetime) = '2020-06-24' ) as Wed,
sum( date(datetime) = '2020-06-25' ) as Thu,
sum( date(datetime) = '2020-06-26' ) as Fri,
sum( date(datetime) = '2020-06-27' ) as Sat,
sum( date(datetime) = '2020-06-28' ) as Sun
Your query doesn't work because it is an aggregation query (the COUNT()) but you have unaggregated columns in the SELECT. MySQL should be returning a parsing error -- and the most recent version does.
I also strongly discourage you from using single quotes to delimit columns. Here are three reasons:
Your column names are fine and don't need to be escaped.
Single quotes should only be used for string and date constants.
Backticks can be used if the identifiers do need to be escaped.

Related

SQL how to aggregate by date and multiple criteria

I have this table:
id
amount
method
date
01
10
A
2022-01-24 12:27:14.440
02
80
A
2022-01-24 12:27:14.440
01
20
D
2022-02-24 12:27:14.440
01
10
D
2022-02-24 12:27:14.440
02
20
D
2022-02-24 12:27:14.440
03
30
D
2022-02-24 12:27:14.440
and I want this:
method
amount_sum_jan
n_transaction_jan
n_customer_jan
amount_sum_feb
n_transaction_feb
n_customer_feb
A
10
2
2
0
0
0
D
0
0
0
80
4
3
This is a table with 7 column and rows equal to the number of methods.
AMOUNT: sum of amount in one month of one method
N_TRANSACTIONS: number of transaction in one month with one method
N_CUSTOMER: number of customers (id) who used that method in one month
Can I get it with just one query?
You want to aggregate your data by method and have separate columns for January data and February data. You get this with conditional aggregation (CASE expression inside the aggregate function),
select
method,
sum(case when month(date) = 1 then amount else 0 end) as amount_sum_jan,
count(case when month(date) = 1 then 1 end) as n_transaction_jan,
count(distinct case when month(date) = 1 then id end) as n_customer_jan,
sum(case when month(date) = 2 then amount else 0 end) as amount_sum_feb,
count(case when month(date) = 2 then 1 end) as n_transaction_feb,
count(distinct case when month(date) = 2 then id end) as n_customer_feb
from mytable
group by method
order by method;
It is called pivot, and would for fix dates look like this.
An aggregation of the method for the year and month, and you can COUNT or SUM your number
select
`method`,
SUM(CASE WHEN EXTRACT( YEAR_MONTH FROM `date` ) = '202201' then `amount` ELSE 0 END) amount_sum_jan,
SUM(CASE WHEN EXTRACT( YEAR_MONTH FROM `date` ) = '202201' then 1 ELSE 0 END) n_transaction_jan,
COUNT(DISTINCT CASE WHEN EXTRACT( YEAR_MONTH FROM `date` ) = '202201' then `d` ELSE 0 END) n_customer_jan,
SUM(CASE WHEN EXTRACT( YEAR_MONTH FROM `date` ) = '202202' then `amount` ELSe 0 END) amount_sum_feb,
SUM(CASE WHEN EXTRACT( YEAR_MONTH FROM `date` ) = '202202' then 1 ELSe 0 END) n_transaction_feb,
COUNT(DISTINCT CASE WHEN EXTRACT( YEAR_MONTH FROM `date` ) = '202202' then `d` ELSe 0 END) n_customer_feb
from tab1
GROUP BY `method`
http://www.sqlfiddle.com/#!9/31d8ef/10
much more interesting would be to make that dynamic

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.

BAD FIELD ERROR in WHERE DATE CLAUSE for MySQL

Why am I getting an error with the following code:
SELECT
Hour,
SUM( CASE col WHEN '1' THEN data ELSE 0 END ) AS 'Sun',
SUM( CASE col WHEN '2' THEN data ELSE 0 END ) AS 'Mon',
SUM( CASE col WHEN '3' THEN data ELSE 0 END ) AS 'Tues',
SUM( CASE col WHEN '4' THEN data ELSE 0 END ) AS 'Wed',
SUM( CASE col WHEN '5' THEN data ELSE 0 END ) AS 'Thur',
SUM( CASE col WHEN '6' THEN data ELSE 0 END ) AS 'Fri',
SUM( CASE col WHEN '7' THEN data ELSE 0 END ) AS 'Sat'
FROM (
SELECT
HOUR(arrPurch.PurchaseDate) as HOUR,
DAYOFWEEK(arrPurch.PurchaseDate) as col,
SUM(ROUND(arrPurch.Credits * 1.85 + arrPurch.Price,0)) AS Data
FROM praisecharts_main_new.arrangementPurchases AS arrPurch
INNER JOIN praisecharts_main_new.catalog_dev_arrangements
AS catDEVarr ON arrPurch.ArrangementID = catDEVarr.ArrangementID
INNER JOIN praisecharts_main_new.members
ON arrPurch.MemberID = members.MemberID
GROUP BY HOUR(arrPurch.PurchaseDate), DAYOFWEEK(arrPurch.PurchaseDate)
) AS stats
WHERE
Hour BETWEEN 0 AND 23
-- AND arrPurch.PurchaseDate BETWEEN (CURDATE() - INTERVAL 29 DAY)
-- AND (CURDATE() - INTERVAL 1 DAY)
-- AND arrPurch.PurchaseDate > (CURDATE() - INTERVAL 1 DAY)
GROUP BY Hour WITH ROLLUP
When I don't try to PIVOT the results with the CASE clauses, I don't get an error. Here is an example of a query that works:
SELECT
DAYOFWEEK(arrPurch.PurchaseDate) AS DayWeek,
FORMAT(SUM(arrPurch.Credits*1.85+arrPurch.Price)/4,0) AS `c.Rev`
FROM
praisecharts_main_new.arrangementPurchases AS arrPurch
INNER JOIN praisecharts_main_new.catalog_dev_arrangements AS catDEVarr ON
arrPurch.ArrangementID = catDEVarr.ArrangementID
INNER JOIN praisecharts_main_new.members
ON arrPurch.MemberID = members.MemberID
WHERE arrPurch.PurchaseDate BETWEEN (CURDATE() - INTERVAL 29 DAY)
AND (CURDATE() - INTERVAL 1 DAY)
GROUP BY DayWeek WITH ROLLUP;
LIMIT 14
I was able to find the answer. I moved the WHERE clause up to the embedded SELECT cause above, rather than at the bottom. Here is an example of the code that works.
SELECT
Hour,
SUM( CASE col WHEN '1' THEN data ELSE 0 END ) AS 'Sun',
SUM( CASE col WHEN '2' THEN data ELSE 0 END ) AS 'Mon',
SUM( CASE col WHEN '3' THEN data ELSE 0 END ) AS 'Tues',
SUM( CASE col WHEN '4' THEN data ELSE 0 END ) AS 'Wed',
SUM( CASE col WHEN '5' THEN data ELSE 0 END ) AS 'Thur',
SUM( CASE col WHEN '6' THEN data ELSE 0 END ) AS 'Fri',
SUM( CASE col WHEN '7' THEN data ELSE 0 END ) AS 'Sat'
FROM (
SELECT
HOUR(arrPurch.PurchaseDate) as HOUR,
DAYOFWEEK(arrPurch.PurchaseDate) as col,
SUM(ROUND((arrPurch.Credits * 1.85 + arrPurch.Price)/4,0)) AS data
FROM praisecharts_main_new.arrangementPurchases AS arrPurch
INNER JOIN praisecharts_main_new.catalog_dev_arrangements
AS catDEVarr ON arrPurch.ArrangementID = catDEVarr.ArrangementID
INNER JOIN praisecharts_main_new.members
ON arrPurch.MemberID = members.MemberID
WHERE arrPurch.PurchaseDate BETWEEN (CURDATE() - INTERVAL 29 DAY)
AND (CURDATE() - INTERVAL 1 DAY)
GROUP BY HOUR(arrPurch.PurchaseDate),
DAYOFWEEK(arrPurch.PurchaseDate)
) AS stats
WHERE
Hour BETWEEN 0 AND 23
GROUP BY Hour WITH ROLLUP

Combining database queries

How can these SQL-queries to extract statistics from my database be combined for better performance?
$total= mysql_query("SELECT COUNT(*) as number, SUM(order_total) as sum FROM history");
$month = mysql_query("SELECT COUNT(*) as number, SUM(order_total) as sum FROM history WHERE date >= UNIX_TIMESTAMP(DATE_ADD(CURDATE(),INTERVAL -30 DAY))");
$day = mysql_query("SELECT COUNT(*) as number, SUM(order_total) as sum FROM history WHERE date >= UNIX_TIMESTAMP(CURDATE())");
If you want to all the data in a single query, you have two choices:
Use a UNION query (as sugested by bishop in his answer)
Tweak a query to get what you need in a single row
I'll show option 2 (option 1 has been already covered).
Note: I'm using user variables (that stuff in the init subquery) to avoid writing the expressions again and again. Also, to filter the aggregate data, I'm using case ... end expressions.
select
-- Your first query:
count(*) as number, sum(order_total) as `sum`
-- Your second query:
, sum(case when `date` <= #prev_date then 1 else 0 end) as number_prev
, sum(case when `date` <= #prev_date then order_total else 0 end) as sum_prev
-- Your third query:
, sum(case when `date` <= #cur_date then 1 else 0 end) as number_cur
, sum(case when `date` <= #cur_date then order_total else 0 end) as sum_cur
from (
select #cur_date := unix_timestamp(curdate())
, #prev_date := unix_timestamp(date_add(curdate(), interval -30 day))
) as init
, history;
Hope this helps
Since the queries have the same column structure, you can ask MySQL to combine them with the UNION operation:
(SELECT 'total' AS kind, COUNT(*) as number, SUM(order_total) as sum FROM history~
UNION
(SELECT 'by-month' AS kind, COUNT(*) as number, SUM(order_total) as sum FROM history WHERE date <= UNIX_TIMESTAMP(DATE_ADD(CURDATE(),INTERVAL -30 DAY)))
UNION
(SELECT 'by-day' AS kind, COUNT(*) as number, SUM(order_total) as sum FROM history WHERE date <= UNIX_TIMESTAMP(CURDATE()))

Oracle to SQLServer conversion

I have the following query in oracle so dont know how to convert into SQL Server.
WITH start_date AS
(
SELECT TO_DATE ( '01-Jan-2010'
, 'DD-Mon-YYYY'
) AS start_date
FROM dual
)
SELECT m.task_name
, COUNT (CASE WHEN TO_CHAR (d.task_date, 'DD') = '01' THEN 1 END) AS Day_1
, COUNT (CASE WHEN TO_CHAR (d.task_date, 'DD') = '02' THEN 1 END) AS Day_2
, COUNT (CASE WHEN TO_CHAR (d.task_date, 'DD') = '03' THEN 1 END) AS Day_3
...
, COUNT (CASE WHEN TO_CHAR (d.task_date, 'DD') = '31' THEN 1 END) AS Day_31
FROM task_master m
JOIN task_detail d ON m.task_id = d.task_id
JOIN start_date s ON d.task_date >= s.start_date
AND d.task_date < ADD_MONTHS (s.start_date, 1)
GROUP BY m.task_name
;
Have a look at using
DATEPART to check the day of month (use d or dd)
and
CONVERT(DATETIME,'01-Jan-2010')
for the Date
and
DATEADD to add the date use DATEADD (datepart ,number,date )