How can I select data from last 13 months? - sql

I have two tables with exactly same structure
table 1 - Data_2020 --> This is an static table which has data from year 2020 and is not being updated anymore (archive table). It has around 4 million records
table 2 - Data_2021 --> This is my current table which is increasing everyday. It has currently 0.8 million records but it will increase till December.
Now I need to 'union all' these two tables and I want only last 13 month data every time I run below query
Select * from Data_2020
union all
select * from Data_2021
I have to run this every month and need only last 13 month data. How can I apply the filter? I have a date column 'date' in both the tables.

declare #StartDate datetime,#CurrentDate datetime
set #CurrentDate = GETDATE();
set #StartDate = DATEADD(MONTH,-13,#CurrentDate)
SELECT *
FROM Data_2020
WHERE DateFieldName > #StartDate -- Or DateFieldName BETWEEN #StartDate AND #CurrentDate
UNION ALL
SELECT *
FROM Data_2021
WHERE DateFieldName > #StartDate -- Or DateFieldName BETWEEN #StartDate AND #CurrentDate

If you want to consider the date column from the tables to get the current max date, you can use this query
DECLARE #CurrentMaxDate DATE
SET #CurrentMaxDate = (SELECT Top 1 [Date] FROM Data_2021 ORDER BY [Date] DESC)
;WITH TempCTE AS (
SELECT * FROM Data_2020
UNION ALL
SELECT * FROM Data_2021
)
SELECT * FROM TempCTE
WHERE [Date] > DATEADD(Month,-13, #CurrentMaxDate)

Related

SQL date difference ignoring years

I have two date columns and I would like to calculate their differences in Months/Days and exclude years.
Assuming the first date is 10/30/2017, comparing it to the current date, its difference should one. If the current date is 10/30/2018, the difference should also be one.
A few examples:
Schedule Date:10/30/2017 Current Date 10/29/2017 Diff 1
Schedule Date:10/30/2017 Current Date 11/30/2017 Diff 30
Schedule Date:10/30/2017 Current Date 10/29/2018 Diff 1
Schedule Date:10/30/2017 Current Date 11/30/2018 Diff 30
Schedule Date:10/30/2017 Current Date 10/29/2019 Diff 1
Schedule Date:10/30/2017 Current Date 11/30/2019 Diff 30
Try This
SELECT ABS(365 * DATEDIFF(year, '10/30/2017', '11/30/2018')
- DATEDIFF(day, '10/30/2017', '11/30/2018')) AS DateDiff;
Note that the difference between 10/30/ and 11/30/ can't be 30 days as you have shown.It is 31 days.
You can use Common Table Expression to get your results like below :
DECLARE #Schedule_Date datetime = '10/30/2017'
DECLARE #Current_Date datetime = '11/30/2019'
;WITH usingCTE AS
(
SELECT CAST(STUFF(CONVERT(varchar, #Schedule_Date, 102), 1, 4, CAST(YEAR(#Current_Date) AS varchar)) AS datetime) AS Schedule_Date
)
SELECT abs(DATEDIFF(day, #Current_Date, Schedule_Date)) FROM usingCTE
Another approach of the query :
DECLARE #Schedule_Date datetime = '10/30/2017'
DECLARE #Current_Date datetime = '11/30/2017'
SELECT ABS(DATEDIFF(day,
REPLACE(#Schedule_Date, DATEPART(year, #Schedule_Date),
DATEPART(year, #Current_Date)), -- replace the year with current year
#Current_Date))
This might work for you, first adjust the date to be in the same year, then calculate the number of days different between the two dates:
DECLARE #ScheduleDate DATE = '2017-10-30';
DECLARE #Dates TABLE (CurrentDate DATE);
INSERT INTO #Dates VALUES ('2017-10-29'),('2017-11-30'),('2018-10-29'),('2018-11-30'),('2019-10-29'),('2019-11-30');
SELECT #ScheduleDate ScheduleDate, *
FROM #Dates a
CROSS APPLY (SELECT AdjustedDate=DATEADD(YEAR, YEAR(#ScheduleDate) - YEAR(a.CurrentDate), a.CurrentDate)) b
CROSS APPLY (SELECT Diff=ABS(DATEDIFF(DAY, #ScheduleDate, b.AdjustedDate))) c

Generate date range in between 2 dates

for the last couple of hours I have been breaking my head over this.
I want to create a result set which contains a series of dates like this:
2011-07-05
2011-07-04
2011-07-03
2011-07-02
2011-07-01
2011-06-30
2011-06-29
2011-06-28
...
Ideally between 2 dates given. But If I can say the last 30 days or the last 100 days from now that would be fine also.
Normally I would this with a CTE like this
;WITH Dates AS
(
SELECT CONVERT(DATE, GETDATE()) [Date]
UNION ALL
SELECT DATEADD(DAY,-1, [Date])
FROM Dates
WHERE [Date] > DATEADD(DAY, -30, CONVERT(DATE, GETDATE()))
)
SELECT [Date]
But I am not allowed to use any statements that can't be executed in a subquery. The program I am using executes queries like this:
Select *
From (
TheQuery
) as t1
This means I can't use declares, no stored procedures, no CTEs..
Is there any way I can obtain the dataset I need with these limitations?
I am using azure SQL
You can use a recursive cte if you put it in a table valued function
CREATE FUNCTION FnDateRange
(
#startDate date,
#endDate date
)
RETURNS #DateRange Table
(myDate date)
AS
begin
with Dates_rte as
(
select #startDate myDate
union all
select cast(dateadd(day,1,myDate) as date)
from Dates_rte
where cast(dateadd(day,1,myDate) as date) <= #endDate
)
insert into #DateRange
select * from Dates_rte option (maxrecursion 0)
return
end
GO
select * from fnDateRange('2017-07-01','2017-07-06')
If you dont't want create a calendar table or a number table, nor use existing table to generate numbers/ date (see for example https://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1)
you could use something like this:
SELECT DATEADD(DAY, -B.N1+1, CONVERT(DATE, GETDATE())) AS D1
FROM
(SELECT 1 AS N1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10) A
CROSS JOIN (SELECT 1 AS N1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10) B
DECLARE #fromdate DATE
DECLARE #todate DATE
DECLARE #tcaldate Table (CalenderDate Date);
set #fromdate='2017-04-17'
set #todate='2017-05-13'
INSERT INTO #tcaldate SELECT TOP (DATEDIFF(DAY, #fromdate, #todate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #fromdate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
Select * from #tcaldate
Hope this helps...
Well, I think the easiest way is to create calendar table and in subquery just select dates between dates.
You can do this by this query:
CREATE TABLE dbo.Calendar ([Date] date)
DECLARE #startDate date, #endDate date
SET #startDate = '2000-01-01'
SET #endDate = '2020-12-31'
WHILE #startDate <= #endDate
BEGIN
INSERT INTO dbo.Calendar
SELECT #startDate
SET #startDate = DATEADD(DD,1,#startDate)
END
Selecting dates:
Select *
From dbo.Calendar WHERE [Date] BETWEEN #date1 AND #date2

Query to aggregate totals between dates

I have data of the following format:
Date Value
08/28 100
09/01 1
09/01 5
09/10 2
I would like my output to be:
Date Value
08/28 100
08/29 100
08/30 100
08/31 100
09/01 106
09/02 106
.
.
.
09/10 108
I'm just getting started with SQL, so any help would be appreciated. What I have right now is below, but that's not really close to what I seek:
SELECT Date, COUNT(DISTINCT(Service)) AS Value
FROM [Directory]
WHERE Date <= #myDate
GROUP BY Date ORDER BY Date
First, you can use a sub query to get the aggregate values
SELECT Date, (SELECT SUM(Value) FROM Directory d WHERE d.Date <= Directory.Date)
FROM [Directory]
WHERE Date <= #myDate
ORDER BY Date
Which would give you something that looks like this:
Date Value
08/28 100
09/01 101
09/01 106
09/10 108
Then you can add a Date table as sgeddes suggested. This article explains if fairly well: http://michaelmorley.name/how-to/create-date-dimension-table-in-sql-server
Then you can modify your query like so
SELECT DateTable.Date, (SELECT SUM(Value) FROM Directory d WHERE d.Date <= Directory.Date)
FROM [Directory] LEFT OUTER JOIN DateTable on Directory.Date = DateTable.Date
WHERE DateTable.Date <= #myDate
ORDER BY DateTable.Date
To get the data format you're looking for.
Based on sgeddes suggestion:
SELECT a.Date, COUNT(DISTINCT(d.Service)) AS Value
FROM [Directory] d
LEFT OUTER JOIN [Date Table] a on d.Date = a.Date
WHERE Date <= #myDate
GROUP BY Date
ORDER BY Date
Use following script in sqlserver :
BEGIN
--If exist then drop temp tables
DROP TABLE #YOURTABLE;
DROP TABLE #TEST1;
DECLARE #MINDATE DATETIME;
DECLARE #MAXDATE DATETIME;
CREATE TABLE #YOURTABLE(
CDATE DATE,
VALUE INT
);
INSERT INTO #YOURTABLE VALUES ('08/28/2014',100),('09/01/2014',1),('09/01/2014',5),('09/10/2014',100);
--select start date and end date from your table
SELECT #MINDATE=MIN(CDATE),#MAXDATE=MAX(CDATE) FROM #YOURTABLE;
CREATE TABLE #TEST1(
CDATE DATE,
VALUE INT
);
;WITH CALENDAR
AS (
SELECT #MINDATE CDATE
UNION ALL
SELECT CDATE + 1
FROM CALENDAR
WHERE CDATE + 1 <= #MAXDATE
)
-- insert all dates with 0 value in temp table
INSERT INTO #TEST1 SELECT CDATE,0 FROM CALENDAR;
--delete dates which are already there in your table
DELETE FROM #TEST1 WHERE CDATE IN (SELECT CDATE FROM #YOURTABLE)
-- insert all dates with values from your table to temporary table which holds dates which are not in your table
INSERT INTO #TEST1 SELECT * FROM #YOURTABLE;
SELECT T1.CDATE,(SELECT SUM(VALUE) FROM #TEST1 T2 WHERE T2.CDATE<=T1.CDATE) FROM #TEST1 T1
END

Grouping by contiguous dates, ignoring weekends in SQL

I'm attempting to group contiguous date ranges to show the minimum and maximum date for each range. So far I've used a solution similar to this one: http://www.sqlservercentral.com/articles/T-SQL/71550/ however I'm on SQL 2000 so I had to make some changes. This is my procedure so far:
create table #tmp
(
date smalldatetime,
rownum int identity
)
insert into #tmp
select distinct date from testDates order by date
select
min(date) as dateRangeStart,
max(date) as dateRangeEnd,
count(*) as dates,
dateadd(dd,-1*rownum, date) as GroupID
from #tmp
group by dateadd(dd,-1*rownum, date)
drop table #tmp
It works exactly how I want except for one issue: weekends. My data sets have no records for weekend dates, which means any group found is at most 5 days. For instance, in the results below, I would like the last 3 groups to show up as a single record, with a dateRangeStart of 10/6 and a dateRangeEnd of 10/20:
Is there some way I can set this up to ignore a break in the date range if that break is just a weekend?
Thanks for the help.
EDITED
I didn't like my previous idea very much. Here's a better one, I think:
Based on the first and the last dates from the set of those to be grouped, prepare the list of all the intermediate weekend dates.
Insert the working dates together with weekend dates, ordered, so they would all be assigned rownum values according to their normal order.
Use your method of finding contiguous ranges with the following modifications:
1) when calculating dateRangeStart, if it's a weekend date, pick the nearest following weekday;
2) accordingly for dateRangeEnd, if it's a weekend date, pick the nearest preceding weekday;
3) when counting dates for the group, pick only weekdays.
Select from the resulting set only those rows where dates > 0, thus eliminating the groups formed only of the weekends.
And here's an implementation of the method, where it is assumed, that a week starts on Sunday (DATEPART returns 1) and weekend days are Sunday and Saturday:
DECLARE #tmp TABLE (date smalldatetime, rownum int IDENTITY);
DECLARE #weekends TABLE (date smalldatetime);
DECLARE #minDate smalldatetime, #maxDate smalldatetime, #date smalldatetime;
/* #1 */
SELECT #minDate = MIN(date), #maxDate = MAX(date)
FROM testDates;
SET #date = #minDate - DATEPART(dw, #minDate) + 7;
WHILE #date < #maxDate BEGIN
INSERT INTO #weekends
SELECT #date UNION ALL
SELECT #date + 1;
SET #date = #date + 7;
END;
/* #2 */
INSERT INTO #tmp
SELECT date FROM testDates
UNION
SELECT date FROM #weekends
ORDER BY date;
/* #3 & #4 */
SELECT *
FROM (
SELECT
MIN(date + CASE DATEPART(dw, date) WHEN 1 THEN 1 WHEN 7 THEN 2 ELSE 0 END)
AS dateRangeStart,
MAX(date - CASE DATEPART(dw, date) WHEN 1 THEN 2 WHEN 7 THEN 1 ELSE 0 END)
AS dateRangeEnd,
COUNT(CASE WHEN DATEPART(dw, date) NOT IN (1, 7) THEN date END) AS dates,
DATEADD(d, -rownum, date) AS GroupID
FROM #tmp
GROUP BY DATEADD(d, -rownum, date)
) s
WHERE dates > 0;

Improve SQL query: Cumulative amounts over time

Suppose I have a SQL table of Awards, with fields for Date and Amount. I need to generate a table with a sequence of consecutive dates, the amount awarded in each day, and the running (cumulative) total.
Date Amount_Total Amount_RunningTotal
---------- ------------ -------------------
1/1/2010 100 100
1/2/2010 300 400
1/3/2010 0 400
1/4/2010 0 400
1/5/2010 400 800
1/6/2010 100 900
1/7/2010 500 1400
1/8/2010 300 1700
This SQL works, but isn't as quick as I'd like:
Declare #StartDate datetime, #EndDate datetime
Select #StartDate=Min(Date), #EndDate=Max(Date) from Awards
; With
/* Returns consecutive from numbers 1 through the
number of days for which we have data */
Nbrs(n) as (
Select 1 Union All
Select 1+n
From Nbrs
Where n<=DateDiff(d,#StartDate,#EndDate)),
/* Returns all dates #StartDate to #EndDate */
AllDays as (
Select Date=DateAdd(d, n, #StartDate)
From Nbrs )
/* Returns totals for each day */
Select
d.Date,
Amount_Total = (
Select Sum(a.Amount)
From Awards a
Where a.Date=d.Date),
Amount_RunningTotal = (
Select Sum(a.Amount)
From Awards a
Where a.Date<=d.Date)
From AllDays d
Order by d.Date
Option(MAXRECURSION 1000)
I tried adding an index to Awards.Date, but it made a very minimal difference.
Before I resort to other strategies like caching, is there a more efficient way to code the running total calculation?
I generally use a temporary table for this:
DECLARE #Temp TABLE
(
[Date] date PRIMARY KEY,
Amount int NOT NULL,
RunningTotal int NULL
)
INSERT #Temp ([Date], Amount)
SELECT [Date], Amount
FROM ...
DECLARE #RunningTotal int
UPDATE #Temp
SET #RunningTotal = RunningTotal = #RunningTotal + Amount
SELECT * FROM #Temp
If you can't make the date column a primary key then you need to include an ORDER BY [Date] in the INSERT statement.
Also, this question's been asked a few times before. See here or search for "sql running total". The solution I posted is, as far as I know, still the one with the best performance, and also easy to write.
I don't have a database setup in front of me so I hope the below works first shot. A pattern like this should result in a much speedier query...you're just joining twice, similar amount of aggregation:
Declare #StartDate datetime, #EndDate datetime
Select #StartDate=Min(Date), #EndDate=Max(Date) from Awards
;
WITH AllDays(Date) AS (SELECT #StartDate UNION ALL SELECT DATEADD(d, 1, Date)
FROM AllDays
WHERE Date < #EndDate)
SELECT d.Date, sum(day.Amount) Amount_Total, sum(running.Amount) Amount_RunningTotal
FROM AllDays d
LEFT JOIN (SELECT date, SUM(Amount) As Amount
FROM Awards
GROUP BY Date) day
ON d.Date = day.Date
LEFT JOIN (SELECT date, SUM(Amount) As Amount
FROM Awards
GROUP BY Date) running
ON (d.Date >= running.Date)
Group by d.Date
Order by d.Date
Note: I changed your table expression up top, it was leaving out the first day before...if this is intentional just slap a where clause on this to exclude it. Let me know in the comments if this doesn't work or doesn't fit and I'll make whatever adjustments.
Here's a working solution based on #Aaronaught's answer. The only gotcha I had to overcome in T-SQL was that #RunningTotal etc. can't be null (need to be converted to zero).
Declare #StartDate datetime, #EndDate datetime
Select #StartDate=Min(StartDate),#EndDate=Max(StartDate) from Awards
/* #AllDays: Contains one row per date from #StartDate to #EndDate */
Declare #AllDays Table (
Date datetime Primary Key)
; With
Nbrs(n) as (
Select 0 Union All
Select 1+n from Nbrs
Where n<=DateDiff(d,#StartDate,#EndDate)
)
Insert into #AllDays
Select Date=DateAdd(d, n, #StartDate)
From Nbrs
Option(MAXRECURSION 10000) /* Will explode if working with more than 10000 days (~27 years) */
/* #AmountsByDate: Contains one row per date for which we have an Award, along with the totals for that date */
Declare #AmountsByDate Table (
Date datetime Primary Key,
Amount money)
Insert into #AmountsByDate
Select
StartDate,
Amount=Sum(Amount)
from Awards a
Group by StartDate
/* #Result: Joins #AllDays and #AmountsByDate etc. to provide totals and running totals for every day of the award */
Declare #Result Table (
Date datetime Primary Key,
Amount money,
RunningTotal money)
Insert into #Result
Select
d.Date,
IsNull(bt.Amount,0),
RunningTotal=0
from #AllDays d
Left Join #AmountsByDate bt on d.Date=bt.Date
Order by d.Date
Declare #RunningTotal money Set #RunningTotal=0
Update #Result Set #RunningTotal = RunningTotal = #RunningTotal + Amount
Select * from #Result