I have a MySQL database containing discounts. A simplified version looks like this:
id | start (UNIX timestamp) | end (UNIX timestamp)
45 | 1384693200 | 1398992400
68 | 1386018000 | 1386277200
263 | 1388530800 | 1391209200
A discount can last a few days, a few months, or even a few years. I'm looking for a way to select a unique list of months where (future) discounts are valid.
If there is:
a discount which starts in november 2013 and ends in april 2014
a discount which starts in december 2013 and ends in the same month
a discount which starts in january 2014 and ends one month later
a discount which starts in june 2014 and ends the same month
The output should be:
- December (2013)
- January (2014)
- February (2014)
- March (2014)
- April (2014)
- June (2014)
November 2013 is not shown because it is in the past. May 2014 is not shown because there is no discount in that month.
Can somebody help?
Thanks in advance!
Create a table containing a sequence of numbers from 0 to a number of month you could ever require, and join this table to your table.
This is example how to get a list of years+months separately for each id
SELECT id,
year( start + interval x month ) year,
month( start + interval x month ) month
FROM
numbers n
JOIN
(
SELECT id,
from_unixtime( start ) start,
from_unixtime( end ) end
FROM Table1
) q
ON n.x <= period_diff( date_format( q.end, '%Y%m' ),date_format( q.start, '%Y%m' ))
ORDER BY id, year, month ;
Demo --> http://www.sqlfiddle.com/#!9/d7cfc/4
If you want to combine years+months for all id, skip id column and use GROUP BY
SELECT year( start + interval x month ) year,
month( start + interval x month ) month
FROM
numbers n
JOIN
(
SELECT id,
from_unixtime( start ) start,
from_unixtime( end ) end
FROM Table1
) q
ON n.x <= period_diff( date_format( q.end, '%Y%m' ),date_format( q.start, '%Y%m' ))
GROUP BY year, month
ORDER BY year, month ;
If you want to skip past years and months, add WHERE year >= current year AND month >= current month, this is a trivial change. Also add another WHERE end < current-unix-time in the subquery to filter out unwanted past rows.
Related
I'm attempting to write a query that finds the user's work anniversary for the current month and considers a leap year as well (don't get an idea how to manage within the query)
Table "emp_detail":
emp_no
join_date
1
2002-06-10
2
2022-06-25
3
2020-02-29
4
2002-02-15
5
2011-02-01
So far I have tried the below query:
SELECT no,
join_date
CASE WHEN DATEADD(YY,DATEDIFF(yy,join_date,GETDATE()),join_date) < GETDATE()
THEN DATEDIFF(yy,join_date,GETDATE())
ELSE DATEDIFF(yy,join_date,GETDATE()) - 1
END AS 'anniversary'
FROM emp_detail
WHERE 'status' = 'active'
HAVING MONTH(join_date) = 06/07/08 -- ...so on
EDIT:
Expected output:
For FEBRUARY month current year 2022
emp_no
join_date
anniversary_date
3
2020-02-29
2022-02-28 (Here, want get 29 Feb 2020 leap year record with non leap year 2022)
4
2002-02-15
2022-02-15
5
2011-02-01
2022-02-01
Looking for a way to display employees with anniversary dates coming up at the start of the current month considering the leap year.
Am I going in the right direction? Any help would be great.
Most (all?) SQL engines already handle year arithmetic involving leap days the way you want: folding the leap day to the final day of February.
So, computing the employee's join_date + INTERVAL x YEAR will handle '2020-02-29' correctly. To compute that interval in MySQL/MariaDB for the current year, you may use TIMESTAMPDIFF compute the difference between EXTRACTed years yourself:
SELECT emp_no,
join_date,
join_date +
INTERVAL (EXTRACT(YEAR FROM CURDATE()) -
EXTRACT(YEAR FROM join_date)) YEAR
AS "anniversary_date_this_year",
....
You can split your problem into two steps:
filtering your "join_date" values using the current month
changing the year to your "join_date"
getting the minimum value between your updated "join_date" and the last day for that date (>> this will handle leap years kinda efficiently wrt other solutions that attempt to check for specific years every time)
WITH cte AS (
SELECT emp_no,
join_date,
STR_TO_DATE(CONCAT_WS('-',
YEAR (CURRENT_DATE()),
MONTH(join_date ),
DAY (join_date )),
'%Y-%m-%d') AS join_date_now
FROM tab
WHERE MONTH(join_date) = MONTH(CURRENT_DATE())
AND YEAR(join_date) < YEAR(CURRENT_DATE())
)
SELECT emp_no,
join_date,
LEAST(join_date_now, LAST_DAY(join_date_now)) AS anniversary_date
FROM cte
Check the demo here
Note: in the demo, since you want to look at February months and we are in July, the WHERE clause will contain an additional -5 while checking the month.
You can make use of extract function in MySQL
select * from emp_detail where extract( month from (select now())) = extract( month from join_date) and extract( year from (select now())) != extract( year from join_date);
The above query will display all employees whose work anniversary is in the current month.
For the below table:
The above query will display the following rows.
The following query also considers leap year.
If the employee has joined on Feb-29 in a leap year and the current year is a non-leap year, then the query displays Anniversary Date as 'currentYear-Feb-28'
If the employee has joined on Feb-29 in a leap year and the current year is also a leap year, then the query displays Anniversay Date as 'currentYear-Feb-29'
select empId ,
case
when ( ( extract(year from (select now()))%4 = 0 and extract(year from (select now()))%100 != 0 ) or extract(year from (select now())) % 400 = 0 ) then
cast( concat( extract(year from (select now())), '-', extract( month from join_date),'-', extract( day from join_date) ) as date)
when ( ( (extract(year from join_date) % 4 = 0 and extract(year from join_date)%100 != 0) or extract( year from join_date)%400 = 0) and extract(month from join_date) =2
and extract(day from join_date) = 29 ) then
cast( concat( cast( extract(year from (select now())) as nchar), '-02-28') as date)
else cast( concat( extract(year from (select now())), '-', extract( month from join_date),'-', extract( day from join_date) ) as date)
end as AnniversaryDate
from emp_detail
where extract(year from join_date) != extract(year from (select now()));
Emp_detail data
For this data the query will show the following rows
Further if you want to filter the date to current month only, you can make use of extract function.
I need to query data with count and sum by multiple date ranges and I am looking for a faster query than what I am doing now.
I have a transaction table with a date and amount. I need to present a table with a count of transactions and total amount by date ranges of today, yesterday, this week, last week, this month, last month. Currently I am doing sub queries, is there a better way?
select
(select count(date) from transactions where date between ({{today}})) as count_today,
(select sum(amount) from transactions where date between ({{today}})) as amount_today,
(select count(date) from transactions where date between ({{yesterday}})) as count_yesterday,
(select sum(amount) from transactions where date between ({{yesterday}})) as amount_yesterday,
(select count(date) from transactions where date between ({{thisweek}})) as count_thisweek,
(select sum(amount) from transactions where date between ({{thisweek}})) as amount_thisweek,
etc...
Is there a better way?
although you have a marked solution, I have another that will probably simplify your query even further using MySQL variables so you don't have to mis-type / calculate dates and such...
Instead of declaring variables up front, you can do them inline as a select statement, then use them as if they were columns in another table. Since it is created as a single row, there is no Cartesian result. First the query, then I'll describe the computations on it.
select
sum( if( t.date >= #today AND t.date < #tomorrow, 1, 0 )) as TodayCnt,
sum( if( t.date >= #today AND t.date < #tomorrow, amount, 0 )) as TodayAmt,
sum( if( t.date >= #yesterday AND t.date < #today, 1, 0 )) as YesterdayCnt,
sum( if( t.date >= #yesterday AND t.date < #today, amount, 0 )) as YesterdayAmt,
sum( if( t.date >= #FirstOfWeek AND t.date < #EndOfWeek, 1, 0 )) as WeekCnt,
sum( if( t.date >= #FirstOfWeek AND t.date < #EndOfWeek, amount, 0 )) as WeekAmt
from
transations t,
( select #today := curdate(),
#yesterday := date_add( #today, interval -1 day ),
#tomorrow := date_add( #today, interval 1 day ),
#FirstOfWeek := date_add( #today, interval +1 - dayofweek( #today) day ),
#EndOfWeek := date_add( #FirstOfWeek, interval 7 day ),
#minDate := least( #yesterday, #FirstOfWeek ) ) sqlvars
where
t.date >= #minDate
AND t.date < #EndOfWeek
Now, the dates. Since the #variables are prepared in sequence, you can think of it as an inline program to set the variables. Since they are a pre-query, they are done first and available for the duration of the rest of the query as previously stated. So to start, I am working with whatever "curdate()" is which gets the date portion only without respect to time. From that, subtract 1 day (add -1) to get the beginning of yesterday. Add 1 day to get Tomorrow. Then, the first of the week is whatever the current date is +1 - the actual day of week (you will see shortly). Add 7 days from the first of the week to get the end of the week. Finally, get whichever date is the LEAST between a yesterday (which COULD exist at the end of the prior week), OR the beginning of the week.
Now look at today for example... Feb 23rd.
Sun Mon Tue Wed Thu Fri Sat Sun
21 22 23 24 25 26 27 28
Today = 23
Yesterday = 22
Tomorrow = 24
First of week = 23 + 1 = 24 - 3rd day of week = 21st
End of Week = 21st + 7 days = 28th.
Why am I doing a cutoff of the dates stripping times? To simplify the SUM() condition for >= AND <. If I stated some date = today, what if your transactions were time-stamped. Then you would have to extract the date portion only to qualify. By this approach, I can say that "Today" count and amount is any date >= Feb 23 at 12am midnight AND < Feb 24th 12 am midnight. This is all time inclusive Feb 23rd up to 11:59:59pm hence LESS than Feb 24th (tomorrow).
Similar consideration for yesterday is all inclusive UP TO but not including whatever "today" is. Similarly for the week range.
Finally the WHERE clause is looking for the earliest date as the range so it does not have to run through the entire database of transactions to the end.
Lastly, if you ever wanted the counts and totals for a prior week / period, whatever, you could just extrapolate and change
#today := '2015-01-24'
and the computations will be AS IF the query was run ON THAT DATE.
Similar if you cared to alter such as for a month, you could compute the first of the month to the first of a following month for MONTHLY totals.
Hope you enjoy this flexible solution to you.
Yes, you can use aggregate functions on conditional expressions, like so:
SELECT SUM(IF(date between ({{today}})), 1, 0) AS count_today
, SUM(IF(date between ({{today}})), amount, 0) AS amount_today
, ...
I'm trying to get all posts from the 12 last month, group by month. I have a quite correct query:
SELECT MONTH(time) as mois, YEAR(time) as annee, count(*) as nbre
FROM touist_stories
WHERE time >= DATE_SUB(now() + INTERVAL 1 MONTH, INTERVAL 2 YEAR)
group by MONTH(time)
order by YEAR(time) DESC, MONTH(time) DESC
But one month is always missing : november 2012
I tryied to add
+ INTERVAL 1 MONTH
to now() but it still missing... How can I get the 12 last month and not the 11 ones please?
Thanks
To get one year ago, here's a technique I've used in the past. Using #mysql variables, create a date based on the first day of a given month/year (via now()), then subtract 12 months. This example will get from Oct 1, 2012 to current -- which will include current Oct 2013. To exclude that, just add to where clause where I re-added 1 year so it goes from Oct 1, 2012 at 12:00:00 am to LESS THEN Oct 1, 2013 12:00:00.
SELECT
MONTH(time) as mois,
YEAR(time) as annee,
count(*) as nbre
FROM
touist_stories,
( select #lastYear := date_add( DATE_FORMAT(NOW(),
'%Y-%m-01'), interval -11 month) ) sqlvar
WHERE
time >= #lastYear
group by
MONTH(time)
order by
YEAR(time) DESC,
MONTH(time) DESC
Revised to make it go 11 months back (to November per example), and include UP TO AND INCLUDING all Current October activity.
For realy want on year data use 11 MONTH not 12
SELECT time
FROM touist_stories
WHERE time
BETWEEN
date_sub(Now(), INTERVAL 11 MONTH)
AND
Now();
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
I wrote a query that returns monthly sales.
SELECT
count(O.orderid) as Number_of_Orders,
concat (MonthName(FROM_UNIXTIME(O.`date`)),' - ',year(FROM_UNIXTIME(O.date))) as Ordered_Month,
sum(O.total) as TotalAmount,
Month(FROM_UNIXTIME(O.`date`)) as Month_of_Year,
year(FROM_UNIXTIME(O.date)) as Sale_Year
FROM orders O
group by Month_of_Year, Sale_Year
order by Sale_Year DESC,Month_of_Year DESC
I would like to make it group for a custom date like
instead of 1st to 1st, it should group for 10th -10th of every month.
Not sure how to group it that way!
because you are dealing with a time "shift", you'll have to do that math in your equation to "fake it out". Something like
SELECT
count(O.orderid) as Number_of_Orders,
concat(
MonthName( Date_Sub( FROM_UNIXTIME(O.`date`), INTERVAL 10 DAY )),
' - ',
Year( Date_Sub( FROM_UNIXTIME(O.date), INTERVAL 10 DAY) )
) as Ordered_Month,
sum(O.total) as TotalAmount,
Month( Date_Sub( FROM_UNIXTIME(O.`date`), INTERVAL 10 DAY )) as Month_of_Year,
Year( Date_Sub( FROM_UNIXTIME(O.date), INTERVAL 10 DAY )) as Sale_Year
FROM
orders O
group by
Month_of_Year,
Sale_Year
order by
Sale_Year DESC,
Month_of_Year DESC
So, in essence, you are taking the dates ex: March 11-31 + April 1-10 and subtracting "10 days" from them... so for the query, they will look like March 1-31, and April 11-30 will appear like April 1-20 + May, etc for rest of each year...
Not tested.
group by Month_of_Year, ceil(day(o.`date`)/10), Sale_Year
This is a better idea in order to avoid having 4 groups but just 3
select
month(my_date) as your_month,
year(my_date) as your_year,
case
when day(my_date) <= 10 then 1
when day(my_date) between 11 and 20 then 2
else 3 end as decade,
count(*) as total
from table
group by
your_month,your_year,decade
Adapt it to your needs.