I'm trying to create a script that would give me the fiscal weeks based upon reference dates of Start Week and End Week
For example, assume that I have table columns with StartWeek and EndWeek such that:
StartWeek | EndWeek
----------|---------
20190603 | 20190609
20190610 | 20190616
20190617 | 20190623
20190624 | 20190630
20190701 | 20190707
...
I would like a script to generate the following:
FiscalDateStart | FiscalDateEnd | NumOfDays | WeekDayStart | WeekDayEnd
----------------|----------------|------------|--------------|-----------
2019-06-05 | 2019-06-09 | 5 | Wednesday | Sunday
2019-06-10 | 2019-06-16 | 7 | Monday | Sunday
2019-06-17 | 2019-06-23 | 7 | Monday | Sunday
2019-06-24 | 2019-06-30 | 7 | Monday | Sunday
2019-07-01 | 2019-07-02 | 2 | Monday | Tuesday
I have found the following logic on Stack:
DECLARE #StartDate DATE = '2019-06-05'
, #EndDate DATE = '2019-07-02'
SET DATEFIRST 1
SET LANGUAGE French;
SET NOCOUNT ON;
select *
from
(Select
WeekNum = Dense_Rank() over (Partition By FY,FM Order By FW)
,StartDate = Min(D) over (Partition By FY,FM,FW )
,EndDate = Max(D) over (Partition By FY,FM,FW )
,NumOfDays = DATEDIFF(DAY, Min(D) over (Partition By FY,FM,FW ), Max(D) over (Partition By FY,FM,FW )) + 1
From
(
/* Establish calendar rules/functions for Fiscal Year (FY), Fiscal Month (FM), Fiscal Week (FW), & Fiscal Day (D) */
Select FY = DatePart(Year,#StartDate)-1+sum(case when convert(varchar(5),#StartDate,101)=convert(varchar(5),D,101) then 1 else 0 end) over (Order By D)
,FM = sum(case when DatePart(Day,D)=DatePart(Day,#StartDate) then 1 else 0 end) over (Order By D)
,FW = sum(case when DatePart(WeekDay,D)=1 then 1 else 0 end) over (Order By D)
,D
From (
/* Generate Fiscal Day (D) rule based on #StartDate & #EndDate */
Select Top (DateDiff(DAY,#StartDate,#EndDate)+1)
D = DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#StartDate)
From master..spt_values n1,master..spt_values n2
) A1
) A
--Order By D
) a
group by
WeekNum, StartDate, EndDate, NumOfDays
The issue is that we're forcing the #StartDate in the above query to be as of 2019-06-05. However, I need the above script to generate the same results, but based upon the #StartDate as of 2019-06-03.
Any help would be appreciated.
Related
CREATE TABLE visitors (
Date DATE,
visitor VARCHAR(20));
INSERT INTO visitors VALUES
("2019-10-01", "v1"),
("2019-10-01", "v2"),
("2019-10-01", "v3"),
("2019-10-02", "v2"),
("2019-10-03", "v2"),
("2019-10-03", "v4"),
("2019-10-03", "v5");
please find Find number_of_visitors_per_date, number_of_visitors gained comparing with previous day, and number_of_visitors lost comparing with previous day. The expected results for the above table should be:
Date | number_of_visitors | number_of_visitors_gained | number_of_visitors_lost
2019-10-01 | 3 | 3 | 0
2019-10-02 | 1 | 0 | 2
2019-10-03 | 3 | 2 | 0
Obviously, the most challenging part is how to get the last two columns. Note, since there is no previous day for the first day, the number_of_visitors_gained is the total number of visitors in the first day, and the number_of_visitors_lost is 0.
If you RDBMS supports window functions, you can aggregate, and then use lag():
select
date,
number_of_visitors,
case
when lag(number_of_visitors) over(order by date) is null
then number_of_visitors
when lag(number_of_visitors) over(order by date) < number_of_visitors
then number_of_visitors - lag(number_of_visitors) over(order by date)
else 0
end number_of_visitors_gained,
case when lag(number_of_visitors) over(order by date) > number_of_visitors
then lag(number_of_visitors) over(order by date) - number_of_visitors
else 0
end number_of_visitors_lost
from (
select date, count(*) number_of_visitors
from visitors
group by date
) t
order by date
Demo on DB Fiddle:
date | number_of_visitors | number_of_visitors_gained | number_of_visitors_lost
:--------- | -----------------: | ------------------------: | ----------------------:
2019-10-01 | 3 | 3 | 0
2019-10-02 | 1 | 0 | 2
2019-10-03 | 3 | 2 | 0
You are looking for aggregation using lead() and lag() window functions:
select date, count(*) as visitors_on_day,
sum(case when prev_vdate is null or prev_vdate <> date - interval 1 day
then 1 else 0
end) as gained_visitors,
sum(case when next_vdate <> date + interval 1 day
then 1 else 0
end) as lost_visitors
from (select v.*,
lag(date) over (partition by visitor) as prev_vdate,
lead(date) over (partition by visitor) as next_vdate
from visitors v
) t
group by date
order by date;
I need some help querying my calendar/dates table
Scenario:
I have a "calendar" table with dates, user will set his available dates, usually day by day. So my table looks like this:
+------+------------+---------------------+---------------------+
| ID | user_id | start_date | end_date |
+------+------------+---------------------+---------------------+
| 1 | 1 | 2016-09-01 08:00:00 | 2016-09-01 16:00:00 |
| 2 | 1 | 2016-09-03 08:00:00 | 2016-09-03 16:00:00 |
| 3 | 1 | 2016-09-04 08:00:00 | 2016-09-04 16:00:00 |
| 3 | 1 | 2016-09-05 08:00:00 | 2016-09-05 16:00:00 |
+------+------------+---------------------+---------------------+
This means user 1 is available on the 1st, 3rd, 4th and 5th.
Lets say I want to query the table and find if user is available from date 2016-09-01 08:00:00 to 2016-09-05 16:00:00, this query must return zero rows since the user is not available on the 2nd of September. But if query from date 2016-09-03 08:00:00 to 2016-09-05 16:00: 00 then it will return these 3 rows.
Hope someone can help me with this
This could be one way (for a single user).
Note #endDate and #startDate are the supplied date fields to search.
SELECT
*
FROM your_table
WHERE EXISTS (
SELECT
user_id
FROM your_table
WHERE start_date >= #startDate
AND start_date <= #endDate
AND user_id = 1
GROUP BY user_id
HAVING SUM((DATEDIFF(end_date,start_date)+1)) = DATEDIFF(#endDate,#startDate)+1
)
AND start_date >= #startDate
AND start_date <= #endDate
AND user_id = 1
Note:
If the supplied date range falls within any range bounded by start_date and end_date (exclusive) then it won't work.
Since SUM((DATEDIFF(end_date,start_date)+1)) = DATEDIFF(#endDate,#startDate)+1 won't be equal in this case. Condition
In this case, you need to stay within the required boundary. Here the boundary is demarcated by the smaller value of end_date and #endDate and the larger value of start_date and #startDate.
Suppose, you have the following record (only one)
start_date = 2016-09-01 and end_date=2016-09-05.
And #startDate=2016-09-02 , #endDate=2016-09-04
Now check the above condition will fail for this set of data.
In this case you need to adopt the following query:
SELECT
*
FROM your_table
WHERE EXISTS (
SELECT
user_id
FROM your_table
WHERE end_date >= #startDate
AND start_date <= #endDate
AND user_id = 1
GROUP BY user_id
HAVING SUM((DATEDIFF(LEAST(end_date,#endDate),GREATEST(start_date,#startDate))+1)) = DATEDIFF(#endDate,#startDate)+1
)
AND end_date >= #startDate
AND start_date <= #endDate
AND user_id = 1
Assuming the periods in the table are not overlapping, you can count the number of days. The days in the period are then:
select sum(datediff(least($period_end, end_date),
greatest($period_start, start_date)
) + 1
)
from t
where $period_start <= end_date and
$period_end >= start_date;
You can then get a flag by comparing to the number of days:
select (case when sum(datediff(least($period_end, end_date),
greatest($period_start, start_date)
) + 1
) =
datediff($period_end, $period_start) + 1
then 1 else 0
end) as IsAvailableForAllDays
from t
where $period_start <= end_date and
$period_end >= start_date;
I have a table with :
user_id | order_date
---------+------------
12 | 2014-03-23
12 | 2014-01-24
14 | 2014-01-26
16 | 2014-01-23
15 | 2014-03-21
20 | 2013-10-23
13 | 2014-01-25
16 | 2014-03-23
13 | 2014-01-25
14 | 2014-03-22
A Active user is someone who has logged in last 12 months.
Need output as
Period | count of Active user
----------------------------
Oct-2013 - 1
Jan-2014 - 5
Mar-2014 - 10
The Jan 2014 value - includes Oct -2013 1 record and 4 non duplicate record for Jan 2014)
You can use a variable to calculate the running total of active users:
SELECT Period,
#total:=#total+cnt AS `Count of Active Users`
FROM (
SELECT CONCAT(MONTHNAME(order_date), '-', YEAR(order_date)) AS Period,
COUNT(DISTINCT user_id) AS cnt
FROM mytable
GROUP BY Period
ORDER BY YEAR(order_date), MONTH(order_date) ) t,
(SELECT #total:=0) AS var
The subquery returns the number of distinct active users per Month/Year. The outer query uses #total variable in order to calculate the running total of active users' count.
Fiddle Demo here
I've got two queries that do the thing. I am not sure which one's the fastest. Check them aginst your database:
SQL Fiddle
Query 1:
select per.yyyymm,
(select count(DISTINCT o.user_id) from orders o where o.order_date >=
(per.yyyymm - INTERVAL 1 YEAR) and o.order_date < per.yyyymm + INTERVAL 1 MONTH) as `count`
from
(select DISTINCT LAST_DAY(order_date) + INTERVAL 1 DAY - INTERVAL 1 MONTH as yyyymm
from orders) per
order by per.yyyymm
Results:
| yyyymm | count |
|---------------------------|-------|
| October, 01 2013 00:00:00 | 1 |
| January, 01 2014 00:00:00 | 5 |
| March, 01 2014 00:00:00 | 6 |
Query 2:
select DATE_FORMAT(order_date, '%Y-%m'),
(select count(DISTINCT o.user_id) from orders o where o.order_date >=
(LAST_DAY(o1.order_date) + INTERVAL 1 DAY - INTERVAL 13 MONTH) and
o.order_date <= LAST_DAY(o1.order_date)) as `count`
from orders o1
group by DATE_FORMAT(order_date, '%Y-%m')
Results:
| DATE_FORMAT(order_date, '%Y-%m') | count |
|----------------------------------|-------|
| 2013-10 | 1 |
| 2014-01 | 5 |
| 2014-03 | 6 |
The best thing I could do is this:
SELECT Date, COUNT(*) as ActiveUsers
FROM
(
SELECT DISTINCT userId, CONCAT(YEAR(order_date), "-", MONTH(order_date)) as Date
FROM `a`
ORDER BY Date
)
AS `b`
GROUP BY Date
The output is the following:
| Date | ActiveUsers |
|---------|-------------|
| 2013-10 | 1 |
| 2014-1 | 4 |
| 2014-3 | 4 |
Now, for every row you need to sum up the number of active users in previous rows.
For example, here is the code in C#.
int total = 0;
while (reader.Read())
{
total += (int)reader['ActiveUsers'];
Console.WriteLine("{0} - {1} active users", reader['Date'].ToString(), reader['ActiveUsers'].ToString());
}
By the way, for the March of 2014 the answer is 9 because one row is duplicated.
Try this, but thise doesn't handle the last part: The Jan 2014 value - includes Oct -2013
select TO_CHAR(order_dt,'MON-YYYY'), count(distinct User_ID ) cnt from [orders]
where User_ID in
(select User_ID from
(select a.User_ID from [orders] a,
(select a.User_ID,count (a.order_dt) from [orders] a
where a.order_dt > (select max(b.order_dt)-365 from [orders] b where a.User_ID=b.User_ID)
group by a.User_ID
having count(order_dt)>1) b
where a.User_ID=b.User_ID) a
)
group by TO_CHAR(order_dt,'MON-YYYY');
This is what I think you are looking for
SET #cnt = 0;
SELECT Period, #cnt := #cnt + total_active_users AS total_active_users
FROM (
SELECT DATE_FORMAT(order_date, '%b-%Y') AS Period , COUNT( id) AS total_active_users
FROM t
GROUP BY DATE_FORMAT(order_date, '%b-%Y')
ORDER BY order_date
) AS t
This is the output that I get
Period total_active_users
Oct-2013 1
Jan-2014 6
Mar-2014 10
You can also do COUNT(DISTINCT id) to get the unique Ids only
Here is a SQL Fiddle
This question already has answers here:
How to populate a table with a range of dates?
(10 answers)
Closed 8 years ago.
Today is 20th Aug 2013.
I want to generate 20 rows which will contain dates from 1st to 20th (whatever would be the current date) of the month by using mysql query.
Count should always start from 1st date of the month and till current date... output would be like, only one column and multiple rows till current date like given below..
Current month
8/1/13 12:00 AM
8/2/13 12:00 AM
8/3/13 12:00 AM
8/4/13 12:00 AM
8/5/13 12:00 AM
8/6/13 12:00 AM
8/7/13 12:00 AM
8/8/13 12:00 AM
8/9/13 12:00 AM
8/10/13 12:00 AM
8/11/13 12:00 AM
8/12/13 12:00 AM
8/13/13 12:00 AM
8/14/13 12:00 AM
8/15/13 12:00 AM
8/16/13 12:00 AM
8/17/13 12:00 AM
8/18/13 12:00 AM
8/19/13 12:00 AM
8/20/13 12:00 AM
I tried following query but is of no use. Can you please help to find some other workaround for this?
DECLARE #startDate DATETIME=CAST(MONTH(GETDATE()) AS VARCHAR) + '/' + '01/' + + CAST(YEAR(GETDATE()) AS VARCHAR) -- mm/dd/yyyy
DECLARE #endDate DATETIME= GETDATE() -- mm/dd/yyyy
;WITH Calender AS
(
SELECT #startDate AS CalanderDate
UNION ALL
SELECT CalanderDate + 1 FROM Calender
WHERE CalanderDate + 1 <= #endDate
)
SELECT [Date] = CONVERT(VARCHAR(10),CalanderDate,25)
FROM Calender
OPTION (MAXRECURSION 0)
This too would work by dynamically building a result set of all days, but can work against any existing table you have that has as least 31 days (max for any given month).
select
#curDay := date_add( #curDay, interval 1 day ) as CalendarDay
from
( select #curDay := date_add( DATE_FORMAT(NOW(),
'%Y-%m-01'), interval -1 day) ) sqlvars,
AnyTableInYourDatabaseWithAtLeast31Records
where
#curDay <= now()
limit
31
The first part of select #curDay builds whatever the current day is, gets to the first of the month, then subtracts 1 day from it giving you the last day of the previous month. Then, the outer select #curDay := keeps updating itself by adding 1 day as the CalendarDay result column. Since its a join to "any table" in your database, it will keep grabbing a MAX of 31 records, but only return where the date is less than or current to now.
Fiddle at
http://www.sqlfiddle.com/#!2/28466/1
CREATE TABLE CALENDAR(DATE1 DATETIME);
INSERT INTO CALENDAR VALUES ('2013/8/1 12:00:00');
INSERT INTO CALENDAR VALUES ('2013/8/2 12:00:00');
INSERT INTO CALENDAR VALUES ('2013/8/3 12:00:00');
INSERT INTO CALENDAR VALUES ('2013/8/4 12:00:00');....
SELECT DISTINCT DATE1 FROM Calender where MONTH(DATE1)=MONTH(NOW()) and DAYOFMONTH(DATE1) <=DAYOFMONTH(NOW())
This gives the output
I like to use tally tables for these sorts of problems, they tend to be pretty fast:
DECLARE #startDate DATETIME= CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(GETDATE())-1),GETDATE()),101);
WITH
N0 as (SELECT 1 as n UNION ALL SELECT 1)
,N1 as (SELECT 1 as n FROM N0 AS t1 CROSS JOIN N0 AS t2)
,N2 as (SELECT 1 as n FROM N1 AS t1 CROSS JOIN N1 AS t2)
,N3 as (SELECT 1 as n FROM N2 AS t1 CROSS JOIN N2 AS t2)
,N4 as (SELECT 1 as n FROM N3 AS t1 CROSS JOIN N3 AS t2)
,N5 as (SELECT 1 as n FROM N4 AS t1 CROSS JOIN N4 AS t2)
,N6 as (SELECT 1 as n FROM N5 AS t1 CROSS JOIN N5 AS t2)
,nums as (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as num FROM N6)
SELECT DATEADD(day,num-1,#startDate) as theDate
FROM nums
WHERE num <= DATEDIFF(day,#startDate,GETDATE()) + 1
You could try calling this Stored Procedure;
DELIMITER $$
CREATE PROCEDURE `test`.`GenerateDates` ()
BEGIN
DECLARE Days INTEGER;
DECLARE Count INTEGER;
SET Days = DATEDIFF(NOW(),CONCAT(YEAR(NOW()),'-',MONTH(NOW()),'-01'));
SET Count = 0;
DROP TEMPORARY TABLE IF EXISTS tempDates;
CREATE TEMPORARY TABLE tempDates
(
YourDate Date,
PRIMARY KEY(YourDate)
);
WHILE (Count <= Days) DO
INSERT INTO tempDates (YourDate) VALUES
(DATE_FORMAT(DATE_ADD(CONCAT(YEAR(NOW()),'-',MONTH(NOW()),'-01'), INTERVAL Count DAY),'%Y-%m-%d'));
SET Count = Count + 1;
END WHILE;
SELECT * FROM tempDates;
END
:) ... or if a table of 31 integers seems like a stretch, how about a table of 10...
SELECT * FROM ints;
+---+
| i |
+---+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
+---+
SELECT DATE_FORMAT(CURDATE(),'%Y-%m-01')+INTERVAL i2.i*10+i1.i DAY x FROM ints i1, ints i2 HAVING x <= NOW();
+------------+
| x |
+------------+
| 2013-08-01 |
| 2013-08-02 |
| 2013-08-03 |
| 2013-08-04 |
| 2013-08-05 |
| 2013-08-06 |
| 2013-08-07 |
| 2013-08-08 |
| 2013-08-09 |
| 2013-08-10 |
| 2013-08-11 |
| 2013-08-12 |
| 2013-08-13 |
| 2013-08-14 |
| 2013-08-15 |
| 2013-08-16 |
| 2013-08-17 |
| 2013-08-18 |
| 2013-08-19 |
| 2013-08-20 |
+------------+
(still not sure why you'd do this in MySQL)
How should I convert number to date?
for example:-
I would like to enter number as 31, it should find which all months has date as 31 and it should show out put as below for current year.
Date Day
31-01-2013 Thursday
31-03-2013 Sunday
And how should I convert number to date.
I'm not sure if this is exactly what you want to do, but if you create a calendar table first then it's a very simple query:
select [Date], [Day]
from dbo.Calendar
where YearNumber = 2013 and DayNumber = 31
You could use the day() function to compare the day in your date column to the value that you provide. Then you can use datename() to get the weekday:
declare #val int = 31
select dt, datename(dw, dt)
from yourtable
where day(dt) = #val
See SQL Fiddle with Demo
SQL Fiddle
MS SQL Server 2008 Schema Setup:
Query 1:
declare #Day int;
set #Day = 31
select D.D,
datename(weekday, D.D) as DOW
from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) as M(M)
cross apply (select dateadd(day,
#Day - 1,
dateadd(month,
M.M - 1,
dateadd(year,
year(getdate()) - 1,
cast('00010101' as date))))) as D(D)
where day(D.D) = #Day
Results:
| D | DOW |
--------------------------
| 2013-01-31 | Thursday |
| 2013-03-31 | Sunday |
| 2013-05-31 | Friday |
| 2013-07-31 | Wednesday |
| 2013-08-31 | Saturday |
| 2013-10-31 | Thursday |
| 2013-12-31 | Tuesday |