SELECT
EXTRACT( DAY FROM date) AS day,
EXTRACT( MONTH FROM date) AS month, who, sum ( wsd ) AS total FROM weekly
WHERE season = '08'
AND status = 1
AND who = 'NAME SURNAME'
GROUP BY day, month
day month who total
12 8 NAME SURNAME 18
I am getting totals of "wsd" column for every person in "who" column per day. If one person doesn't have records in table for a day, we can not see that name in the results as expected.
But i want to see that records too, with date and name with "0" in total column.
How can i do it with mysql only?
For this case, you can move the conditional logic from the where to the aggregation -- assuming that you have data for someone on every day:
SELECT EXTRACT( DAY FROM date) AS day, EXTRACT( MONTH FROM date) AS month, who,
sum(case when status = 1 AND who = 'NAME SURNAME' then wsd else 0 end) AS total
FROM weekly
WHERE season = '08'
GROUP BY day, month;
Did you try Coalesce or Isnull for inserting default values(in your case 0) ?
Related
I want to count the distinct number of fd_id over the time between today and yesterday, between today and 3 days ago, between today and 5 days ago, between today and 7 days ago, between today and 15 days ago, between today and 30 days ago.
My data table looks like the following:
user_id. fd_id. date
1. 123a. 20201010
1. 123a. 20201011
1. 124a. 20201011
...
and the desired result is of the following format:
user_id count_fd_id_1d count_fd_id_3d ... count_fd_id_30d
Specifically, I know I can do the following 6 times and join them together (some column bind method):
select user_id, count(distinct fd_id) as count_fd_id_1d
from table
where date <= today and date >= today-1 (#change this part for different dates)
select user_id, count(distinct fd_id) as count_fd_id_3d
from table
where date <= today and date >= today-3 (#change this part for different dates)
...
I am wondering how I may do this in one shot without running almost identical code for 6 times.
You can use conditional aggregation:
select user_id,
count(distinct case when date >= current_date - 1 day and date < current_date then fd_id end) as cnt_1d,
count(distinct case when date >= current_date - 3 day and date < current_date then fd_id end) as cnt_3d,
...
from mytable
goup by user_id
You can play around with the date expressions to set the ranges you want. The above works on entire days, and does not include the current day.
If the date column in the the table really does look like that (not in date/datetime format), I think you need to use STR_TO_DATE() to convert it to date format then uses DATEDIFF to check the date differences. Consider this example query:
SELECT user_id,
MAX(CASE WHEN ddiff=1 THEN cn END) AS count_fd_id_1d,
MAX(CASE WHEN ddiff=2 THEN cn END) AS count_fd_id_2d,
MAX(CASE WHEN ddiff=3 THEN cn END) AS count_fd_id_3d,
MAX(CASE WHEN ddiff=4 THEN cn END) AS count_fd_id_4d,
MAX(CASE WHEN ddiff=5 THEN cn END) AS count_fd_id_5d
FROM (SELECT user_id,
DATEDIFF(CURDATE(), STR_TO_DATE(DATE,'%Y%m%d')) ddiff,
COUNT(DISTINCT fd_id) cn
FROM mytable
GROUP BY user_id, ddiff) A
GROUP BY user_id;
At the moment, if you check date value simply by using direct subtraction, you'll get incorrect result. For example:
*your current date value - how many days:
'20201220' - 30 = '20201190' <-- this is not correct.
*if you convert the date value and using the same subtraction:
STR_TO_DATE('20201220','%Y%m%d') - 30 = '20201190' <-- still get incorrect.
*convert date value then uses INTERVAL for the date subtraction:
STR_TO_DATE('20201220','%Y%m%d') - INTERVAL 30 DAY = '2020-11-20'
OR
DATE_SUB(STR_TO_DATE('20201220','%Y%m%d'),INTERVAL 30 DAY) = '2020-11-20'
*IF your date column is storing standard date format value, then omit STR_TO_DATE
'2020-12-20' - INTERVAL 30 DAY = '2020-11-20'
OR
DATE_SUB('2020-12-20',INTERVAL 30 DAY) = '2020-11-20'
Check out more date manipulation in MySQL.
For the question, I made a fiddle with a bunch of testing.
From this database, I want to select those data which payment_date is current month but the service_date is any month except this month.
Database sample
One way to check if 2 dates are in the same month is with the use of the function last_day(), because if for 2 dates the last day of their month is the same then they are in the same month:
select *
from tablename
where last_day(payment_date) = last_day(curdate())
and last_day(service_date) <> last_day(curdate())
If you want the results for any month and not for the current month only:
select *
from tablename
where last_day(payment_date) <> last_day(service_date)
I have a table event, where i have records with a field end_date, so my problem is i want to fetch number of records, grouping month wise, where end_date should with in that month only, so for example:
If a record have end_date as 2013-01-01 00:00:00 then it should be counted in January 2013, and i am not able to do that. I am unable to put that where condition, how to do tell database that end_date should be between the month for which it is currently grouping.
SELECT COUNT(*) AS 'count', MONTH(created) AS 'month', YEAR(created) AS 'year' FROM event WHERE is_approved =1 GROUP BY YEAR(created), MONTH(created)
Please help me out.
EDIT :
Data say i have is like:
Record name end_date
record_1 2013-11-01 00:00:00
record_2 2013-11-30 00:00:00
record_3 2013-12-01 00:00:00
record_4 2013-12-04 00:00:00
record_5 2013-12-06 00:00:00
record_6 2013-12-10 00:00:00
...many more
Result Expected is:
Count month year
2 11 2013
4 12 2013
....so on
Try this:
SELECT COUNT(1) AS 'count', MONTH(end_date) AS 'month', YEAR(end_date) AS 'year'
FROM event
WHERE is_approved = 1
GROUP BY EXTRACT(YEAR_MONTH FROM end_date);
OR
SELECT COUNT(1) AS 'count', MONTH(end_date) AS 'month', YEAR(end_date) AS 'year'
FROM event
WHERE is_approved = 1
GROUP BY YEAR(end_date), MONTH(end_date);
::EDIT::
1. end date is greater than that particular month - Simply add where condition in your query and pass particular month in format of YYYYMM instead of 201411
2. event is started - Add one more where condition to check whether the created date is less then current date
SELECT COUNT(1) AS 'count', MONTH(end_date) AS 'month', YEAR(end_date) AS 'year'
FROM event
WHERE is_approved = 1 AND
EXTRACT(YEAR_MONTH FROM end_date) > 201411 AND
DATE(created) <= CURRENT_DATE()
GROUP BY EXTRACT(YEAR_MONTH FROM end_date);
OR
SELECT COUNT(1) AS 'count', MONTH(end_date) AS 'month', YEAR(end_date) AS 'year'
FROM event
WHERE is_approved = 1 AND
EXTRACT(YEAR_MONTH FROM end_date) > 201411 AND
DATE(created) <= CURRENT_DATE()
GROUP BY YEAR(end_date), MONTH(end_date);
The count is aggregated based on the month and year so if you are spanning years, you wont have Jan 2013 mixed with Jan 2014, hence pulling those values too and that is the same basis of the group by.
As for your criteria, that all goes in the WHERE clause. In this case, I did anything starting with Jan 1, 2013 and ending Dec 31, 2014 via 'yyyy-mm-dd' standard date recognized format. That said, and the structure of the table you provided, I am using the "end_date" column.
SELECT
YEAR(end_date) AS EventYear,
MONTH(end_Date) AS EventMonth,
COUNT(*) AS EventCount
FROM
event
WHERE is_approved = 1
and end_date between '2013-01-01' and '2014-12-31'
GROUP BY
YEAR(end_date),
MONTH(end_Date)
Now, if you want them to have the most recent events on the top, I would put the year and month descending so 2014 is listed first, then 2013, etc and months within them as December (month 12), before the others.
GROUP BY
YEAR(end_date) DESC,
MONTH(end_Date) DESC
Your criteria could be almost anything from as simple as just a date change, approved status, or even get counts per account status is so needed, such as (and these are just EXAMPLES if you had such code status values)
SUM( is_approved = 0 ) as PendingEvent,
SUM( is_approved = 1 ) as ApprovedEvent,
SUM( is_approved = 2 ) as CancelledEvent
Per comment feedback.
For different date ranges, ignore the between clause and change the WHERE to something like
WHERE end_date > '2014-08-01' or all after a date...
where end_date < '2014-01-01' or all before a date...
They will still group by month / year. If you wanted based on a start date of the event, just change that column in instead, or do IN ADDITION to the others.
MySQL has a bunch of date and time functions that can help you with that. For example:
MONTH() Return the month from the date passed
or
YEAR() Return the year
So you can just get the month and year of your dates. And group your results by them.
SELECT
COUNT(*) cnt
,MONTH(end_date) month
,YEAR(end_date) year
FROM events
GROUP BY month, year
Result :
cnt month year
2 11 2013
4 12 2013
Update:
For filtering only the records that have an end_date greater than a particular month AND have already started, you just need to add a WHERE clause. For example, if the particular month were February 2015:
SELECT
COUNT(*) cnt
,MONTH(end_date) month
,YEAR(end_date) year
FROM events
WHERE end_date >= '2015-03-01'
AND created < NOW()
GROUP BY month, year
Alternatively, the first part of the WHERE clause can be rewritten in the following way, which is probably more comfortable to use if you have to pass the year and month as distinct parameters.
...
WHERE (YEAR(end_date) > 2015
OR (YEAR(end_date) = 2015 AND MONTH(end_date) > 02))
AND created...
SELECT COUNT(*) AS 'count', MONTH(created) AS 'month', YEAR(created) AS 'year' FROM event WHERE is_approved =1 and month(created) = "the month u want" and year(created) = "the year you want" group by GROUP BY YEAR(created), MONTH(created)
you will need to pull the month and year... i could help with that but not sure how you are getting it but months would be 01/02/03 ect and year is 2013/2014/2015 ect
I have a MySQL DB as such:
Date Customer_ID
How can I turn it into:
Customer_ID | Count_Visits_Past_Week | Count_Visits_Past_Month | Count_Visits_Past_90days | Count_Total
Note : Count_Total =sum of the other three counts
Thanks
The first step is to determine the demarcation points for the specified date ranges.
There's several questions to answer here: did you want to compare just the DATE ('yyyy-mm-dd') and disregard any time component?
By "past week", does that mean within the last seven days, or does it mean so far since the previous Sunday, or does it mean the last last full week, from Sunday through Saturday.
For "past month", does that mean the previous whole month, from the first through the end of the month? Or does it mean that if the query is run on the 20th of the month, we want dates since the 20th of the previous month up until today? Or yesterday?
Once we know the points in time that begin and end each specified period, relative to today's date, we can build expressions that evaluate to those dates.
For example, "past week" could be represented as the most recent seven day period:
DATE(NOW())-INTERVAL 1 WEEK -thru- DATE(NOW())
And "past month" can be represented as the same "day of month" (e.g. 17th) of the immediately preceding month up until today:
DATE(NOW())-INTERVAL 1 MONTH -thru- DATE(NOW())
That's really the first step, to determine the begin and end dates of each specified period.
Once we have that, we can move on to building a query that gets a "count" of rows with a date column that falls within each period.
The "trick" is to use conditional tests in expressions in the SELECT list of the query. We'll use those conditional tests to return a 1 if the row is to be included in the "count", and return 0 or NULL if the row should be excluded.
I prefer to use the SUM() aggregate function to get the "count". But it's also possible to use COUNT() aggregate. (If we use COUNT(), we need to use an expression that returns NULL when the row is to be excluded. I prefer to return a 1 or 0; I think it makes debugging easier.
Here's an outline of what a "count" query would look like.
SELECT t.Customer_Id
, SUM(IF( <some_condition> ,1,0) AS Count_something
, SUM(IF( <other_condition> ,1,0) AS Count_something_else
FROM mytable t
GROUP BY t.Customer_Id
When <some_condition> is true, we return a 1, otherwise we return 0.
To test the conditional expressions, it's often easiest to avoid doing the aggregation, and just return the individual rows:
That way, we can see which individual rows are going to be included in each "count".
For example:
SELECT t.Customer_ID
, t.date
, IF(t.date BETWEEN DATE(NOW())-INTERVAL 1 WEEK AND DATE(NOW()),1,0)
AS visit_past_week
, IF(t.date BETWEEN DATE(NOW())-INTERVAL 1 MONTH AND DATE(NOW()),1,0)
AS visit_past_month
FROM mytable t
ORDER BY t.date, t.Customer_Id
That query doesn't return the "count", it just returns the results of the expressions, which can be useful in testing. And of course we want to test the expressions that return the beginning and ending date of each period:
SELECT DATE(NOW()) - INTERVAL 1 WEEK AS past_week_begin
, DATE(NOW()) AS past_week_end
With this approach, the same row can be included in multiple "counts" with one query and one pass through the table.
Note that the expressions inside the SUM() aggregate in the query below are taking advantage of a convenient shorthand, an expression evaluated as a boolean will return 1 if TRUE, 0 if false, or a NULL.
To use the COUNT aggregate, we need to insure that the expression being aggregated returns a non-NULL when the row is to be "counted", and a NULL when the row is to be excluded from the count. So we use the convenient NULLIF function to return NULL if the value returned by the expression is a zero.
SELECT t.Customer_ID
, COUNT(NULLIF( t.date BETWEEN DATE(NOW())-INTERVAL 1 WEEK AND DATE(NOW()),0))
AS Count_Visits_Past_Week
, COUNT(NULLIF( t.date BETWEEN DATE(NOW())-INTERVAL 1 MONTH AND DATE(NOW()),0))
AS Count_Visits_Past_Month
, COUNT(NULLIF( t.date BETWEEN DATE(NOW())-INTERVAL 90 DAY AND DATE(NOW()),0))
AS Count_Visits_Past_90days
, COUNT(NULLIF( t.date BETWEEN DATE(NOW())-INTERVAL 1 WEEK AND DATE(NOW()),0))
+ COUNT(NULLIF( t.date BETWEEN DATE(NOW())-INTERVAL 1 MONTH AND DATE(NOW()),0))
+ COUNT(NULLIF( t.date BETWEEN DATE(NOW())-INTERVAL 90 DAY AND DATE(NOW()),0))
AS Count_Total
FROM mytable t
GROUP BY t.Customer_Id
NOTE: NULLIF(a,b) is a convenient shorthand for IF a IS NULL THEN return b ELSE return a
Returning the Count_Total is a bit odd, since it's got the potential to "count" the same row multiple times... but the value it returns should match the total of the individual counts.
I think this will give you what you want.
select customer_id,
sum(case when splitter = 'week' then num_visits else 0 end) as visits_this_week,
sum(case when splitter = 'month' then num_visits else 0 end) as visits_this_month,
sum(case when splitter = '90days' then num_visits else 0 end) as visits_last_90days,
sum(num_visits) as total
from (select customer_id, 'week' as splitter, count(*) as num_visits
from tbl
where extract(week from date) = extract(week from sysdate())
and extract(year from date) = extract(year from sysdate())
group by customer_id
union all
select customer_id, 'month' as splitter, count(*) as num_visits
from tbl
where extract(month from date) = extract(month from sysdate())
and extract(year from date) = extract(year from sysdate())
group by customer_id
union all
select customer_id, '90days' as splitter, count(*) as num_visits
from tbl
where date between date_sub(sysdate(), interval 90 day) and
sysdate()) x
group by customer_id
sql fiddle example: http://sqlfiddle.com/#!2/a762c/12/0
I have a large number of records with a transaction datetime field going back several years. I would like to do a comparative analysis between the same timespan this year and last. How can I group by week over a 3 month range?
I'm running into problems using the YEARWEEK and WEEK functions because of the day the year 2012 starts of versus the day 2011 starts on.
Given that I have records with datetimes everyday from Jan 1st to the current day, and records with the same datetimes from the prior year, how can I group by week so the output is sums with dates like: 01/01/2011, 01/08/2011, 01/15/2011, etc., and 01/01/2012, 01/08/2012, 01/15/2012, etc.?
My query so far is as follows:
SELECT
DATE_FORMAT(A.transaction_date, '%Y-%m-%d') as date,
ROUND(sum(A.quantity), 3) AS quantity,
ROUND(sum(A.total_amount), 3) AS amount,
A.product_code,
D.fuel_type_code,
D.fuel_type_name,
C.customer_code,
C.customer_name
FROM
cl_transactions AS A
INNER JOIN
card AS B ON A.card_number=B.card_number
INNER JOIN
customer AS C ON B.customer_code=C.customer_code
INNER JOIN
fuel_type AS D ON A.fuel_type=D.fuel_type_code
WHERE
((A.transaction_date >= DATE_FORMAT(NOW() - INTERVAL 3 MONTH, '%Y-%m-01')) OR (A.transaction_date - INTERVAL 1 YEAR >= DATE_FORMAT(NOW() - INTERVAL 15 MONTH, '%Y-%m-01') AND A.transaction_date <= NOW() - INTERVAL 1 YEAR))
GROUP BY
A.transaction_date, fuel_type_code;
I would essentially like something that achieves the following pseudo-query:
GROUP BY
STARTING FROM THE OLDEST DATE (A.transaction_date + INTERVAL 6 DAY)
I started with an inner query using sqlvariables to build out from/to ranges for this year and last year of each respective start of year/month/day (ex: 2012-01-01 and 2011-01-01 respectively). From that, I'm also pre-formatting the date for final output so you have ONE master date basis for display reflecting that of whatever the "this year" week would be.
From that, I do a join to the transaction table where the transaction date is BETWEEN the respective start of current week and start of next week. Since date/time stamps include hour minute, 2012-01-01 by itself is implied as 12:00:00am (midnight) of the day. and between will go UP TO 7 days later 12:00:00 am. And that date will become the start date of the following week.
So, by joining on the date being between EITHER last yr or this yr time period, its the same group qualification. So the field selection does a ROUND( SUM( IF() )) per respective last year or this year. if the incoming transaction date is LESS than the current year's week start, then it must be a record from prior year, otherwise its for the current year. So, respectively, add the value itself, or zero as it applies.
So now, you have the group by. The week that it qualified for was already prepared from the inner query via "ThisYearWeekOf" formatted column, regardless of the otherwise computed "YEARWEEK()" or "WEEK()". The date ranges took care of that qualification for us.
Finally, I added the fuel-type as a join and included that as the group by. You have to group by all non-aggregate columns for proper SQL, although MySQL lets you get by by just grabbing the first entry for the given group if it is NOT so specified in group by.
To close, I DID include the information for the customer as you didn't have it in the group by and did not appear to be applicable... it would just arbitrarily grab one. However, I've added it to the group by, so now your records will show at the per customer level, per product and fuel type, how much sales and quantity between this year and last.
SELECT
JustWeekRange.ThisYearWeekOf,
CTrans.product_code,
FT.fuel_type_code,
FT.fuel_type_name,
C.customer_code,
C.customer_name,
ROUND( SUM( IF( CTrans.transaction_date < JustWeekRange.ThisYrWeekStart, CTrans.Quantity, 0 )), 3) as LastYrQty,
ROUND( SUM( IF( CTrans.transaction_date < JustWeekRange.ThisYrWeekStart, CTrans.total_amount, 0 )), 3) as LastYrAmt,
ROUND( SUM( IF( CTrans.transaction_date < JustWeekRange.ThisYrWeekStart, 0, CTrans.Quantity )), 3) as ThisYrQty,
ROUND( SUM( IF( CTrans.transaction_date < JustWeekRange.ThisYrWeekStart, 0, CTrans.total_amount )), 3) as ThisYrAmt,
FROM
( SELECT
DATE_FORMAT(#ThisYearDate, '%Y-%m-%d') as ThisYearWeekOf,
#LastYearDate as LastYrWeekStart,
#ThisYearDate as ThisYrWeekStart,
#LastYearDate := date_add( #LastYearDate, interval 7 day ) LastYrStartOfNextWeek,
#ThisYearDate := date_add( #ThisYearDate, interval 7 day ) ThisYrStartOfNextWeek
FROM
(select #ThisYearDate := '2012-01-01',
#LastYearDate := '2011-01-01' ) sqlvars,
cl_transactions justForLimit
HAVING
ThisYrWeekStart < '2012-04-01'
LIMIT 15 ) JustWeekRange
JOIN cl_transactions AS CTrans
ON CTrans.transaction_date BETWEEN
JustWeekRange.LastYrWeekStart AND JustWeekRange.LastYrStartOfNextWeek
OR CTrans.transaction_date BETWEEN
JustWeekRange.ThisYrWeekStart AND JustWeekRange.ThisYrStartOfNextWeek
JOIN fuel_type FT
ON CTrans.fuel_type = FT.fuel_type_code
JOIN card
ON CTrans.card_number = card.card_number
JOIN customer AS C
ON card.customer_code = C.customer_code
GROUP BY
JustWeekRange.ThisYearWeekOf,
CTrans.product_code,
FT.fuel_type_code,
FT.fuel_type_name,
C.customer_code,
C.customer_name