Is it possible to display months between two dates in sql using a single select statement without using temp tables [duplicate] - sql

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Months between two dates
I have date range as [start date] = '2012-09-01' and [finish date] = '2014-01-01'
Now, I need to display months between two dates..using a single select statement
Expected Output :
9
10
11
12
1
2
3
.
.
.
12
1
How can I do this?

DECLARE #StartDate DATETIME,
#EndDate DATETIME;
SELECT #StartDate = '20120901'
,#EndDate = '20140101';
;WITH MONTHS (date)
AS
(
SELECT #StartDate
UNION ALL
SELECT DATEADD(MONTH,1,date)
FROM MONTHS
WHERE DATEADD(MONTH,1,date)<=#EndDate
)
SELECT MONTH(date) AS MONTH FROM MONTHS
Result:
MONTH
-----------
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
1
(17 row(s) affected)
EDIT:
As per your updated requirement you can achieve this using following query:
DECLARE #StartDate DATETIME,
#EndDate DATETIME;
SELECT #StartDate = '20120901'
,#EndDate = '20140101';
SELECT MONTH(DATEADD(MONTH, x.number, #StartDate)) AS Months
FROM master.dbo.spt_values x
WHERE x.type = 'P'
AND x.number <= DATEDIFF(MONTH, #StartDate, #EndDate);
Result:
Months
-----------
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
1
(17 row(s) affected)

You can user table variables also:
DECLARE #startdt DATETIME, #enddt DATETIME
SELECT #startdt = '2012-09-01', #enddt = '2014-01-01'
DECLARE #Months TABLE (Months INT)
INSERT INTO #Months VALUES (MONTH(#startdt))
WHILE #startdt < #enddt
BEGIN
SET #startdt = DATEADD(MONTH,1,#startdt)
INSERT INTO #Months VALUES (MONTH(#startdt))
END
SELECT * FROM #Months

Related

Count last 10 data for each minute

I'm trying to write a SQL Query that has to return how many transactions were made in the last 10 minutes for each minute. Like:
1 minute ago : 2 transactions
2 minutes ago : 1 transaction
...
9 minutes ago : 3 transactions
10 minutes ago : 4 transactions
I was trying to do this query:
DECLARE #N int = 10;
DECLARE #NOW DATETIME = GETDATE();
WITH numbers( num ) AS (
SELECT 1 UNION ALL
SELECT 1 + num FROM numbers WHERE num < #N )
SELECT num AS minute,
(
SELECT COUNT(*) AS RESULTS
FROM [ApiTransactions]
WHERE [DateUtc] > DATEADD(year, -1, #NOW)
GROUP BY DATEPART(minute, DATEADD(minute, -num, #NOW))
)
FROM numbers;
I still don't know if the logic is right. What I know is that I'm receiving the error:
Each GROUP BY expression must contain at least one column that is not an outer reference.
Why I'm having this error? Is there a better way to do the query?
You don't need a numbers table for this, unless you need to fill in times with no transactions. I would start with this:
SELECT DATEADD(minute, DATEDIFF(minute, 0, DateUtc), 0) as the_minute, COUNT(*)
FROM ApiTransactions
WHERE DateUtc > DATEADD(minute, -10, DateUtc)
GROUP BY DATEADD(minute, DATEDIFF(minute, 0, DateUtc), 0)
ORDER BY the_minute;
You can use this one:
SELECT DATEDIFF(minute,[DateUtc],GETDATE()) as minutes_ago,
COUNT(*) tran_count
FROM ApiTransactions
WHERE DATEDIFF(second,[DateUtc],GETDATE()) <= 600 -- 10 minutes ago
GROUP BY DATEDIFF(minute,[DateUtc],GETDATE())
Output:
minutes_ago tran_count
0 2
1 3
2 10
3 15
4 4
5 9
6 12
7 6
8 13
9 4
10 2
I managed to solve my problem in two different ways:
DECLARE #N int = 10;
DECLARE #NOW DATETIME = GETDATE();
WITH numbers( num ) AS (
SELECT 1 UNION ALL
SELECT 1 + num FROM numbers WHERE num < #N )
SELECT num-1 AS minute,
(
SELECT COUNT(*) AS RESULTS
FROM [ApiTransactions]
WHERE [DateUtc] > DATEADD(minute, -num, #NOW) AND [DateUtc] < DATEADD(minute, -num+1, #NOW)
)
FROM numbers;
that has as output:
minutes_ago amount
0 4 (most recents. still being updated)
1 0
2 2
3 3
4 1
5 2
6 1
7 2
8 1
9 3
and with:
DECLARE #NOW DATETIME = GETDATE();
SELECT CONVERT(int, DATEDIFF(second, [DateUtc], #NOW)/60) as TimePassed, COUNT([TypeCode]) as Amount
FROM [ApiTransactions]
WHERE [DateUtc] >= DATEADD (minute, -10 , #NOW)
GROUP BY CONVERT(int, DATEDIFF(second, [DateUtc], #NOW)/60)
ORDER BY CONVERT(int, DATEDIFF(second, [DateUtc], #NOW)/60)
that has as output:
minutes_ago amount
0 4 (most recents. still being updated)
2 2
3 3
4 1
5 2
6 1
7 2
8 1
9 3

Get Quarter and Year between two dates

I'd like to retrieve the list of years and quarters between two dates.
For example, from 25/12/2015 to 06/30/2017, the result should look like:
Year Quarter
2015 4
2016 1
2016 2
2016 3
2016 4
2017 1
2017 2
2017 3
You can use a tally table to do this.
declare #start date='2015-12-25';
declare #end date = '2017-06-30';
select distinct year(dateadd(day,rnum,#start)) yr,
datepart(quarter,dateadd(day,rnum,#start)) qtr
from (select row_number() over(order by (select null)) as rnum
from master..spt_values) t
where dateadd(day,rnum,#start) <= #end;
If you need to span more than 6 years... virtually identical to vkp (he's so fast!)
Declare #Date1 date = '2015-12-25'
Declare #Date2 date = '2017-06-30'
Select Distinct
[Year] =DatePart(YEAR,D)
,[Quarter]=DatePart(QUARTER,D)
From (
Select Top (DateDiff(DD,#Date1,#Date2)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#Date1)
From master..spt_values n1,master..spt_values n2
) A
Returns
Year Quarter
2015 4
2016 1
2016 2
2016 3
2016 4
2017 1
2017 2
Declare #StartDate Date='2016-01-01'
, #EndDate Date='2017-05-01'
DECLARE #Date TABLE
(
[Year] INT,
[Quarter] INT
)
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #Date
([Year],
[Quarter])
SELECT DATEPART(YEAR,#StartDate) AS [Year],
CASE WHEN DATEPART(MM,#StartDate) BETWEEN 1 AND 3 THEN 1
WHEN DATEPART(MM,#StartDate) BETWEEN 4 AND 6 THEN 2
WHEN DATEPART(MM,#StartDate) BETWEEN 7 AND 9 THEN 3
WHEN DATEPART(MM,#StartDate) BETWEEN 10 AND 12 THEN 4
END AS [Quarter]
SET #StartDate = DATEADD(DAY,1,#StartDate)
END
SELECT DISTINCT [Year],[Quarter] FROM #Date

Ordering dates for 4 weeks

I am writing a SQL query that pulls some information for the last 4 weeks.
I am seeing two issues with my results (see below).
First problem is that when I look back four weeks, the range should be August 10 - September 6. When I order by 'Day of the Month', the dates in September get moved to the top of the results when they should actually be at the end. So my results should start from 10 (august) and end at 6 (September).
Second problem is I'm missing a few random dates (3, 4, 13, 27).
Day of the Month Number of X
1 125
2 77
5 5
6 23
10 145
11 177
12 116
14 2
15 199
16 154
17 134
18 140
19 154
21 8
22 166
23 145
24 151
25 107
26 79
28 3
29 151
30 163
31 147
Here a general version of my query:
DECLARE #startDate datetime, #endDate datetime;
SET #startDate = dateadd(day, -28, GETDATE());
SET #endDate = dateadd(day, -1, GETDATE());
Select DATEPArt(dd, Time) AS 'Day of the Month', count(*) AS ' Number of X'
from SomeTable ST
where Time >= #startDate
AND Time < #endDate
group by DATEPArt(dd, Time)
order by 'Day of the Month'
For the first problem you can order by date to get the correct date order. I use convert to get a time-free date so that the entries group correctly.
DECLARE #StartDate datetime, #EndDate datetime;
SET #StartDate = DATEADD(day, -28, GETDATE());
SET #EndDate = DATEADD(day, -1, GETDATE());
SELECT
DATEPART(dd, Convert(date, Time)) AS 'Day of the Month', COUNT(*) AS ' Number of X'
FROM SomeTable ST
WHERE Time >= #StartDate
AND Time < #EndDate
GROUP BY Convert(date, Time)
ORDER BY Convert(date, Time)
As for the missing days, this is more complicated as the data needs to be there for the group-by to work.
One option is to create a temporary table with all the dates in, then join in the data. This will still leave a row where the join does not find any data, and can get a "zero" count.
DECLARE #StartDate datetime, #EndDate datetime;
SET #StartDate = DATEADD(day, -28, GETDATE());
SET #EndDate = DATEADD(day, -1, GETDATE());
--Create temporary table with all days between the two dates
;WITH d(d) AS
(
SELECT DATEADD(DAY, n, DATEADD(DAY, DATEDIFF(DAY, 0, #StartDate), 0))
FROM ( SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
FROM sys.all_objects ORDER BY [object_id] ) AS n
)
--Join in our query to the table temporary table
SELECT
DATEPART(dd, d.d) AS 'Day of the Month',
COUNT(Time) AS ' Number of X'
FROM d LEFT OUTER JOIN SomeTable ST ON DATEPART(dd, d.d) = DATEPART(dd, Time)
AND Time >= #StartDate
AND Time < #EndDate
GROUP BY d.d
ORDER BY d.d

SQL - creating a list of custom dates between two dates

I am having trouble compiling a query than can do the following:
I have a table which has a startDate and endDate [tblPayments]
I have a column which stores a specific paymentDay [tblPayments]
Data
paymentID startDate endDate paymentDay
1 2016-01-01 2016-12-31 25
2 2015-06-01 2016-06-30 16
I am trying to generate a SELECT query which will split this specific table into separate lines based on the amount of months between these two dates, and set the paymentDay as the day for these queries
Example Output
paymentID expectedDate
1 2016-01-25
1 2016-02-25
1 2016-03-25
1 2016-04-25
1 2016-05-25
1 2016-06-25
1 2016-07-25
1 2016-08-25
1 2016-09-25
1 2016-10-25
1 2016-11-25
1 2016-12-25
2 2015-06-16
2 2015-07-16
2 2015-08-16
2 2015-09-16
2 2015-10-16
2 2015-11-16
2 2015-12-16
2 2016-01-16
2 2016-02-16
2 2016-03-16
2 2016-04-16
2 2016-05-16
I have found a query which will select the months between these dates but its adapting it to my table above, and multiple startDates and endDates I am struggling with
spliting the months
declare #start DATE = '2015-01-01'
declare #end DATE = '2015-12-31'
;with months (date)
AS
(
SELECT #start
UNION ALL
SELECT DATEADD(MM,1,date)
from months
where DATEADD(MM,1,date)<=#end
)
select Datename(MM,date) from months
This query is limited to just one startDate and endDate, so I haven't expanded it to change the DAY of the date.
Use a date table and a simple inner join
DECLARE #tblPayments table (paymentID int identity(1,1), startDate date, endDate date, paymentDay int)
INSERT #tblPayments VALUES
('2016-01-01', '2016-12-31', 25),
('2015-06-01', '2016-06-30', 16)
;WITH dates AS -- Build date within the range of startDate and endDate
(
SELECT MIN(startDate) AS Value, MAX(endDate) AS MaxDate FROM #tblPayments
UNION ALL
SELECT DATEADD(DAY, 1, Value), MaxDate
FROM dates WHERE DATEADD(DAY, 1, Value) <= MaxDate
)
SELECT pay.paymentID, dates.Value AS expectedDate
FROM
#tblPayments pay
INNER JOIN dates ON
dates.Value BETWEEN pay.startDate AND pay.endDate
AND DAY(dates.Value) = paymentDay
OPTION (maxrecursion 0)
I would create an in memory calendar table and then perform a simple query by joining to that:
-- Create a table with all the dates between the min and max dates in the
-- data table
DECLARE #Calendar TABLE
(
[CalendarDate] DATETIME
)
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SELECT #StartDate = MIN(startdate), #EndDate = MAX(enddate) FROM YourDataTable
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #Calendar (CalendarDate)
SELECT #StartDate
SET #StartDate = DATEADD(dd, 1, #StartDate)
END
-- Join to return only dates between the start and end date that match the Payment Day
SELECT D.PaymentId, C.CalendarDate FROM YourDataTable D
INNER JOIN #Calendar C ON C.CalendarDate BETWEEN D.StartDate AND D.EndDate
AND DATEPART(day, C.CalendarDate) = D.PaymentDay

Check if given month+date is present in the data containing range of month+date

Here is my query:
DECLARE #MM INT -- Current month
DECLARE #DD INT -- Current date
SET #MM = 1 -- For testing, set it to January
SET #DD = 1 -- For testing, set it to 01
SELECT xxxID, xxxFK, StartMonth, StartDate, StopMonth, StopDate, NULL AS OKorNOT
FROM xxxTable
ORDER BY xxxFK
And here is the data:
xxxID xxxFK StartMonth StartDate StopMonth StopDate OKorNOT
---------------- ----------- ----------- ----------- ----------- ----------- -----------
8 2287 11 15 1 2 NULL
4 2290 2 1 2 21 NULL
2 2306 9 15 10 31 NULL
3 2306 1 3 1 20 NULL
9 2661 11 15 1 3 NULL
10 2661 5 5 5 31 NULL
5 3778 6 2 9 5 NULL
6 3778 1 1 3 31 NULL
7 3778 5 10 5 31 NULL
1 3778 12 10 12 31 NULL
I need to populate OKorNot column with 1/0 depending on whether the given month-date lies between StartMonth-StartDate and StopMonth-StopDate. This is SQL Server 2000 by the way.
EDIT
The thing here to note that is that there are no years stored in the data and the months-dates may start in, say Nov-15 and end in Jan-15 so on Dec-31 and Jan-1 this case should return true.
Using integer operations only and an imaginary 384-days calendar
Since your dates are combinations of month and day, I tried to create an integer for every such combination, an integer that is unique and also preserves order. To have calculations as simple as possible, we invent a new calendar where all months have exactly 32 days and we act as if our dates are from this calendar. Then to get how many days have past since 1st of January, we have the formula:
DaysPast = 32 * month + day
(OK, it should be 32 * (month-1) + (day-1) but this way it's simpler and we only want to compare dates relatively to one another, not to January 1st. And the result is still unique for every date).
Therefore, we first calculate the DaysPast for our check date:
SET #CHECK = 32 * #MM + #DD
Then, we calculate the DaysPast for all dates (both start and stop ones) in our table:
( SELECT *
, (32 * StartMonth + StartDate) AS Start
, (32 * StopMonth + StopDate ) AS Stop
FROM xxxTable
) AS temp
Then, we have two cases.
First case, when Start = (8-Feb) and Stop = (23-Nov).
Then, the first condition #CHECK BETWEEN Start AND Stop will be true and the dates between Start and Stop will be OK.
The second condition will be False, so no more dates will be OK.
Second case, when Start = (23-Nov) and Stop = (8-Feb). :
Then, the first condition #CHECK BETWEEN Start AND Stop will be false because Start is bigger than Stop so no dates can match this condition.
The second condition Stop < Start will be true, so we also test if
#CHECK is NOT BETWEEN (9-Feb) AND (22-Nov)
to match the dates that are before (9-Feb) or after (22-Nov).
DECLARE #CHECK INT
SET #CHECK = 32 * #MM + #DD
SELECT *
, CASE WHEN
#CHECK BETWEEN Start AND Stop
OR ( Stop < Start
AND #CHECK NOT BETWEEN Stop+1 AND Start-1
)
THEN 1
ELSE 0
END
AS OKorNOT
FROM
( SELECT *
, (32 * StartMonth + StartDate) AS Start
, (32 * StopMonth + StopDate ) AS Stop
FROM xxxTable
) AS temp
ORDER BY xxxFK
It would be easier if you'd stored dates as, well, dates...
Anyway, something like this. I haven't tested. And you need to deal with year boundary which I've done
SELECT
xxxID, xxxFK, StartMonth, StartDate, StopMonth, StopDate,
CASE
WHEN
FullStart <= FullStop AND
DATEADD(month, #MM-1, DATEADD(day, #DD-1, 0)) BETWEEN FullStart AND FullStop
THEN 1
WHEN
FullStart > FullStop AND
DATEADD(month, #MM-1, DATEADD(day, #DD-1, 0)) BETWEEN
FullStart AND DATEADD(year, 1, FullStop)
THEN 1
ELSE 0
END AS OKOrNot
FROM
(
SELECT
xxxID, xxxFK, StartMonth, StartDate, StopMonth, StopDate,
DATEADD(month, StartMonth-1, DATEADD(day, StartDate-1, 0)) AS FullStart,
DATEADD(month, StopMonth-1, DATEADD(day, StopDate-1, 0)) AS FullStop
FROM xxxTable
) foo
ORDER BY xxxFK
edit: added "-1" to all values: if we're already Jan don't add another month...
SELECT *
FROM xxxTable
WHERE (StartMonth < StopMonth OR StartMonth = StopMonth AND StartDate<=StopDate)
AND (#MM > StartMonth OR #MM = StartMonth AND #DD >= StartDate)
AND (#MM < StopMonth OR #MM = StopMonth AND #DD <= StopDate)
OR (StartMonth > StopMonth OR StartMonth = StopMonth AND StartDate>StopDate)
AND ((#MM > StartMonth OR #MM = StartMonth AND #DD >= StartDate)
OR (#MM < StopMonth OR #MM = StopMonth AND #DD <= StopDate))