Get the start date of a MySQL calendar week (weekyear) - mysql

All date format in European (yyyy-mm-dd).
I am trying find the corresponding start date for a week reported by the weekofyear(this is, week starting on Monday).
set #date0 ='2012-12-31';
set #date1 ='2013-01-01';
select weekofyear(#date0), weekofyear(#date1);
> 1, 1
SQL Fidle 1
This is, both dates are in the first week of 2013.
However, if I try to extract the year and weekofyear I will get different results (which means I need to find a different strategy):
set #date0 ='2012-12-31';
select year(#date0), weekofyear(#date0);
>2012,1
set #date1 ='2013-01-01';
select year(#date1), weekofyear(#date1);
>2013,1
SQl Fidle 2
If I manually consult the calender I can see to which year they belong (2013).
this big case will output the week start date
set #date1 ='2012-01-01';
select
case when weekofyear(#date1) <> weekofyear(date_sub(#date1 , interval 1 day)) then #date1
when weekofyear(#date1) <> weekofyear(date_sub(#date1 , interval 2 day)) then date_sub(#date1 , interval 1 day)
when weekofyear(#date1) <> weekofyear(date_sub(#date1 , interval 3 day)) then date_sub(#date1 , interval 2 day)
when weekofyear(#date1) <> weekofyear(date_sub(#date1 , interval 4 day)) then date_sub(#date1 , interval 3 day)
when weekofyear(#date1) <> weekofyear(date_sub(#date1 , interval 5 day)) then date_sub(#date1 , interval 4 day)
when weekofyear(#date1) <> weekofyear(date_sub(#date1 , interval 6 day)) then date_sub(#date1 , interval 6 day)
else date_sub(#date1 , interval 6 day) end as week_start_date;
>2011-12-26
SQL Fidle 3
and this big case will also generate the combo year-week
set #date1 ='2012-12-31';
select
case when weekofyear(#date1) <> weekofyear(date_add(#date1 , interval 1 day)) then concat(year(#date1),'-', if(weekofyear(#date1) < 10, '0','') ,weekofyear(#date1))
when weekofyear(#date1) <> weekofyear(date_add(#date1 , interval 2 day)) then concat(year(date_add(#date1 , interval 1 day)),'-', if(weekofyear(#date1) < 10, '0','') ,weekofyear(#date1))
when weekofyear(#date1) <> weekofyear(date_add(#date1 , interval 3 day)) then concat(year(date_add(#date1 , interval 2 day)),'-', if(weekofyear(#date1) < 10, '0','') ,weekofyear(#date1))
when weekofyear(#date1) <> weekofyear(date_add(#date1 , interval 4 day)) then concat(year(date_add(#date1 , interval 3 day)),'-', if(weekofyear(#date1) < 10, '0','') ,weekofyear(#date1))
when weekofyear(#date1) <> weekofyear(date_add(#date1 , interval 5 day)) then concat(year(date_add(#date1 , interval 4 day)),'-', if(weekofyear(#date1) < 10, '0','') ,weekofyear(#date1))
when weekofyear(#date1) <> weekofyear(date_add(#date1 , interval 6 day)) then concat(year(date_add(#date1 , interval 5 day)),'-', if(weekofyear(#date1) < 10, '0','') ,weekofyear(#date1))
else concat(year(date_add(#date1 , interval 6 day)),'-', if(weekofyear(#date1) < 10, '0','') ,weekofyear(#date1)) end as week_of_year;
> 2013-01
SQL Fidle 4
Now i either need to find an more elegant way of do it, or find a suitable strategy to include this in the group by condition. I was thinking in add an extra column to the table being grouped and -- after add an index --, group by week_of_year or by the week_start_date.
Does someone experienced have a better idea/strategy?
Notes: this is to be used in a database with over half million users, to Analise a certain action they perform and the group by condition will take other parameters (such as, but not limited to, demographics).

Related

To make date changes with if condition in mysql

for this my query is =
SELECT SalesDate,COUNT(Shape) as pcs,
ROUND(SUM(TotalAmount),2) as amount,
ROUND(SUM(Carat),2) as carat,
ROUND(ROUND(SUM(TotalAmount),2)/ROUND(SUM(Carat),2),2) as avgprice
from `tbl_sales`
WHERE IF((SalesDate = CURDATE() - INTERVAL 1 DAY) = null, SalesDate=CURDATE() - INTERVAL 2 DAY,SalesDate= CURDATE() - INTERVAL 1 DAY)
so this is my response
so in If condition I want to make sure that if the data in yesterday data is null or 0 then it will take day before yesterday
Perhaps something like this:
SELECT SalesDate,COUNT(Shape) as pcs,
ROUND(SUM(TotalAmount),2) as amount,
ROUND(SUM(Carat),2) as carat,
ROUND(ROUND(SUM(TotalAmount),2)/ROUND(SUM(Carat),2),2) as avgprice
FROM `tbl_sales`
GROUP BY SalesDate
HAVING SalesDate = CASE WHEN (SalesDate = CURDATE() - INTERVAL 1 DAY)=0
THEN CURDATE() - INTERVAL 2 DAY
WHEN (SalesDate = CURDATE() - INTERVAL 1 DAY)=1
AND amount IS NULL
THEN CURDATE() - INTERVAL 2 DAY
ELSE CURDATE() - INTERVAL 1 DAY END;
When you do (SalesDate = CURDATE() - INTERVAL 1 DAY) it will return false=0 and true=1. Therefore doing (SalesDate = CURDATE() - INTERVAL 1 DAY) = NULL, although it should be .. IS NULL instead of .. = NULL.. either way, it won't work. Let's inspect the CASE expression in HAVING part.
If it return 0 means there's no matching with date specified, then take 2 days before:
CASE WHEN (SalesDate = CURDATE() - INTERVAL 1 DAY)=0
THEN CURDATE() - INTERVAL 2 DAY
If it has match for the date checking and return 1 BUT with NULL amount, then take 2 days before as well:
WHEN (SalesDate = CURDATE() - INTERVAL 1 DAY)=1
AND amount IS NULL
THEN CURDATE() - INTERVAL 2 DAY
Else take yesterday date:
ELSE CURDATE() - INTERVAL 1 DAY END;
Demo fiddle
You need to cast your column to date:
SELECT SalesDate,COUNT(Shape) as pcs,
ROUND(SUM(TotalAmount),2) as amount,
ROUND(SUM(Carat),2) as carat,
ROUND(ROUND(SUM(TotalAmount),2)/ROUND(SUM(Carat),2),2) as avgprice
from `tbl_sales`
WHERE IF((CAST(SalesDate AS date) = CURDATE() - INTERVAL 1 DAY), CAST(SalesDate AS date) = CURDATE() - INTERVAL 2 DAY, CAST(SalesDate AS date) = CURDATE() - INTERVAL 1 DAY)

how best to combine multiple select statements to create single dataset for report

I currently have 3 select statements that pull user login info for day shift, night shift and saturday. i want to combine all these into single query/dataset i can then use to pull single report/chart in our BI tool.
Running these each individually gives my correct output but want to combine so can then generate report/graph with each overlayed.
Any help is appreciated.
SELECT
DATE(users_logins.login_time - INTERVAL 7 HOUR) AS Date,
COUNT(DISTINCT users_logins.user_id) AS `Number of Unique Users DayShift`
FROM users_logins
WHERE TIME(users_logins.login_time - INTERVAL 7 HOUR) >= '04:00:00'
AND TIME(users_logins.login_time - INTERVAL 7 HOUR) < '16:30:00'
AND WEEKDAY(DATE(users_logins.login_time - INTERVAL 7 HOUR)) BETWEEN 0 AND 4
GROUP BY 1
ORDER BY Date DESC
SELECT
DATE(users_logins.login_time - INTERVAL 7 HOUR) AS Date,
COUNT(DISTINCT users_logins.user_id) AS `Number of Unique Users NightShift`
FROM users_logins
WHERE TIME(users_logins.login_time - INTERVAL 7 HOUR) >= '16:30:00'
AND TIME(users_logins.login_time - INTERVAL 7 HOUR) < '21:00:00'
AND WEEKDAY(DATE(users_logins.login_time - INTERVAL 7 HOUR)) BETWEEN 0 AND 4
GROUP BY 1
ORDER BY Date DESC
SELECT
DATE(users_logins.login_time - INTERVAL 7 HOUR) AS Date,
COUNT(DISTINCT users_logins.user_id) AS `Number of Unique Users Saturday`
FROM users_logins
WHERE TIME(users_logins.login_time - INTERVAL 7 HOUR) >= '04:00:00'
AND TIME(users_logins.login_time - INTERVAL 7 HOUR) < '21:00:00'
AND WEEKDAY(DATE(users_logins.login_time - INTERVAL 7 HOUR)) = 5
GROUP BY 1
ORDER BY Date DESC
Use conditional aggregation. The idea is to move the conditions to within aggregate expressions. I would also recommend offsetting the date in a subquery, so there is no need to repeat the expression in the query.
So:
SELECT
DATE(login_time) AS Date,
COUNT(DISTINCT
CASE WHEN TIME(login_time) >= '04:00:00'
AND TIME(login_time) < '16:30:00'
AND WEEKDAY(DATE(login_time)) BETWEEN 0 AND 4
THEN user_id
END
) AS `Number of Unique Users DayShift`,
COUNT(DISTINCT
CASE WHEN TIME(login_time) >= '16:30:00'
AND TIME(login_time) < '21:00:00'
AND WEEKDAY(DATE(login_time)) BETWEEN 0 AND 4
THEN user_id
END
) AS `Number of Unique Users NightShift`,
COUNT(DISTINCT
CASE WHEN TIME(login_time) >= '04:00:00'
AND TIME(login_time) < '21:00:00'
AND WEEKDAY(DATE(login_time)) = 5
THEN user_id
END
) AS `Number of Unique Users DayShift`
FROM (SELECT login_time - INTERVAL 7 HOUR login_time, user_id FROM users_logins) u
GROUP BY 1
ORDER BY Date DESC
You can use UNION for this purpose, I believe you'll need to put the ORDER BY at the end instead of in each SELECT :
SELECT
DATE(users_logins.login_time - INTERVAL 7 HOUR) AS Date,
COUNT(DISTINCT users_logins.user_id) AS `Number of Unique Users DayShift`
FROM users_logins
WHERE TIME(users_logins.login_time - INTERVAL 7 HOUR) >= '04:00:00'
AND TIME(users_logins.login_time - INTERVAL 7 HOUR) < '16:30:00'
AND WEEKDAY(DATE(users_logins.login_time - INTERVAL 7 HOUR)) BETWEEN 0 AND 4
GROUP BY 1
UNION
SELECT
DATE(users_logins.login_time - INTERVAL 7 HOUR) AS Date,
COUNT(DISTINCT users_logins.user_id) AS `Number of Unique Users NightShift`
FROM users_logins
WHERE TIME(users_logins.login_time - INTERVAL 7 HOUR) >= '16:30:00'
AND TIME(users_logins.login_time - INTERVAL 7 HOUR) < '21:00:00'
AND WEEKDAY(DATE(users_logins.login_time - INTERVAL 7 HOUR)) BETWEEN 0 AND 4
GROUP BY 1
UNION
SELECT
DATE(users_logins.login_time - INTERVAL 7 HOUR) AS Date,
COUNT(DISTINCT users_logins.user_id) AS `Number of Unique Users Saturday`
FROM users_logins
WHERE TIME(users_logins.login_time - INTERVAL 7 HOUR) >= '04:00:00'
AND TIME(users_logins.login_time - INTERVAL 7 HOUR) < '21:00:00'
AND WEEKDAY(DATE(users_logins.login_time - INTERVAL 7 HOUR)) = 5
GROUP BY 1
ORDER BY Date DESC

Retrieving Data from database on quaterly basis condition But it should be based on current year data only

I had tried this code:
Its works also fine,but the issue is, if current month is feb and fire this query then it considers past 3 months from now and hence starts from past year i.e 2012 nov or dec say i want only current year data,if it is feb now and i fire this query then it should only show jan and feb records.
SELECT CROEmailId,
(
SELECT COUNT(LeadId)
FROM LeadStatus
WHERE DATE(`LeadTime`)> DATE_SUB(now(),
INTERVAL 3 MONTH
)
AND Generated=1 and AssignedTo=a.CROEmailId)
AS 'NEW LEAD',(
SELECT COUNT(LeadId)
FROM LeadHistory
WHERE DATE(UpdatedAt)> DATE_SUB(now(),
INTERVAL 3 MONTH
) AND AssignedTo=a.CROEmailId)
AS 'Lead Updated',
(
SELECT SUM(TotalEmails)
FROM MailJobs
WHERE DATE(CompletedAt)> DATE_SUB(now(),
INTERVAL 3 MONTH
)
AND MailFrom=a.CROEmailId)
AS 'Email Uploaded',
(
SELECT SUM(TotalSent)
FROM MailJobs
WHERE DATE(CompletedAt)> DATE_SUB(now(),
INTERVAL 3 MONTH)
AND MailFrom=a.CROEmailId
)
AS 'Email Sent',
(
SELECT SUM(NetTotal)
FROM Invoice
WHERE Status='PAID'
AND DATE(CreatedAt)> DATE_SUB(now(), INTERVAL 3 MONTH)
AND CROEmailId=a.CROEmailId)
AS 'Payment Today' FROM CustomersManager a;
Try change
DATE_SUB(now(), INTERVAL 3 MONTH)
to
IF(MONTH(CURDATE()) < 4, DATE_FORMAT(CURDATE(), '%Y-01-01'), CURDATE() - INTERVAL 3 MONTH)
in all subqueries.
SELECT CROEmailId,
(SELECT COUNT(LeadId)
FROM LeadStatus
WHERE DATE(`LeadTime`)> IF(MONTH(CURDATE()) < 4, DATE_FORMAT(CURDATE(), '%Y-01-01'), CURDATE() - INTERVAL 3 MONTH)
AND Generated=1
AND AssignedTo=a.CROEmailId) AS 'NEW LEAD',
(SELECT COUNT(LeadId)
FROM LeadHistory
WHERE DATE(UpdatedAt)> IF(MONTH(CURDATE()) < 4, DATE_FORMAT(CURDATE(), '%Y-01-01'), CURDATE() - INTERVAL 3 MONTH)
AND AssignedTo=a.CROEmailId) AS 'Lead Updated',
(SELECT SUM(TotalEmails)
from MailJobs
WHERE DATE(CompletedAt)> IF(MONTH(CURDATE()) < 4, DATE_FORMAT(CURDATE(), '%Y-01-01'), CURDATE() - INTERVAL 3 MONTH)
AND MailFrom=a.CROEmailId) AS 'Email Uploaded',
(SELECT SUM(TotalSent)
FROM MailJobs
WHERE DATE(CompletedAt)> IF(MONTH(CURDATE()) < 4, DATE_FORMAT(CURDATE(), '%Y-01-01'), CURDATE() - INTERVAL 3 MONTH)
AND MailFrom=a.CROEmailId) AS 'Email Sent',
(SELECT SUM(NetTotal)
FROM Invoice
WHERE Status='PAID'
AND DATE(CreatedAt)> IF(MONTH(CURDATE()) < 4, DATE_FORMAT(CURDATE(), '%Y-01-01'), CURDATE() - INTERVAL 3 MONTH)
AND CROEmailId=a.CROEmailId) AS 'Payment Today'
FROM CustomersManager a;
use this in your query to find record filter by year
YEAR( '20013-12-12' )
example,
SELECT * FROM TABLE WHERE YEAR(DATE_FIELD) = 2013

using CASE expression in the WHERE clause and BETWEEN operator

I'm trying to fetch some rows from my table based on some condition as follow:
SELECT * FROM MyTable WHERE Date BETWEEN
CASE dayofweek(curdate())
when 1 then curdate() AND adddate(curdate(), interval 6 day)
when 2 then subdate(curdate(), interval 1 day) AND adddate(curdate(), interval 5 day)
when 3 then subdate(curdate(), interval 2 day) AND adddate(curdate(), interval 4 day)
when 4 then subdate(curdate(), interval 3 day) AND adddate(curdate(), interval 3 day)
when 5 then subdate(curdate(), interval 4 day) AND adddate(curdate(), interval 2 day)
when 6 then subdate(curdate(), interval 5 day) AND adddate(curdate(), interval 1 day)
when 7 then subdate(curdate(), interval 6 day) AND curdate()
END
but for some reason it doesn't work. it gives me a syntax error instead. how should I accomplish something like this?
Try this instead,
SELECT *
FROM MyTable
WHERE 1 =
CASE dayofweek(curdate())
when 1 then Date BETWEEN curdate() AND adddate(curdate(), interval 6 day)
when 2 then Date BETWEEN subdate(curdate(), interval 1 day) AND adddate(curdate(), interval 5 day)
when 3 then Date BETWEEN subdate(curdate(), interval 2 day) AND adddate(curdate(), interval 4 day)
when 4 then Date BETWEEN subdate(curdate(), interval 3 day) AND adddate(curdate(), interval 3 day)
when 5 then Date BETWEEN subdate(curdate(), interval 4 day) AND adddate(curdate(), interval 2 day)
when 6 then Date BETWEEN subdate(curdate(), interval 5 day) AND adddate(curdate(), interval 1 day)
when 7 then Date BETWEEN subdate(curdate(), interval 6 day) AND curdate()
END
The CASE() statement ,in this scenario, will only return two possible values: 1 and 0.
A CASE returns a value, not an expression. You must repeat the CASE statement for each side of the BETWEEN:
SELECT * FROM MyTable
WHERE Date BETWEEN
CASE dayofweek(curdate())
when 1 then curdate()
when 2 then subdate(curdate(), interval 1 day)
... etc
END
AND -- this is the "AND" for the BETWEEN values
CASE dayofweek(curdate())
when 1 then adddate(curdate(), interval 6 day)
when 2 then adddate(curdate(), interval 5 day)
... etc
END

Anyway to make Union statements smaller?

Is there anyway I can make this statement smaller? for efficiency, the whole idea is to get
todays, this month and last months amount of "quotes".
SELECT COUNT(QuoteDate) AS today
FROM database
WHERE QuoteDate >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY)
UNION
SELECT COUNT(QuoteDate) AS this_month
FROM database
WHERE QuoteDate >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH)
UNION
SELECT COUNT(QuoteDate) AS last_month
FROM database
WHERE QuoteDate >= DATE_SUB(CURRENT_DATE(), INTERVAL 2 MONTH)
Thanks
Couldn't you just use an OR statement?
SELECT COUNT(QuoteDate) AS today FROM database
WHERE
QuoteDate >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY)
OR
QuoteDate >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH)
OR
QuoteDate >= DATE_SUB(CURRENT_DATE(), INTERVAL 2 MONTH)
Untested:
SELECT
sum(if(quotedate >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY), 1, 0)) AS today,
sum(if(quotedate >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH), 1, 0)) AS this_month,
sum(if(quotedate <= DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH), 1, 0)) AS last_month
FROM database
WHERE QuoteDate >= DATE_SUB(CURRENT_DATE(), INTERVAL 2 MONTH)
The idea is to move the selection made in the where clause to the field clause and do the calculation there.
Note, that the third clause is reversed, as you might only want those entries which are older than 1 month.