Query to aggregate totals between dates - sql

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

Related

How can I select data from last 13 months?

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)

SQL - Insert all dates within range from table into another

I'm looking to populate a table with dates, based upon values contained within another.
Source : tblA
dtFrom dtTo
2019-01-01 2019-01-03
2019-02-01 2019-02-02
2019-03-01 2019-03-01
Destination : tblB
sDate
2019-01-01
2019-01-02
2019-01-03
2019-02-01
2019-02-02
2019-03-01
SQL Server 2014. As always, thanks in advance :-)
You can use a recursive CTE:
with dates as (
select dtfrom as dt, dtto
from tblA
union all
select dateadd(day, 1, dt), dtto
from dates
where dt < dtto
)
insert tblB (sDate)
select distinct dt
from dates;
The select distinct is only necessary to handle overlapping periods. If you know there are no overlaps, then don't use it.
You can use union to combine values from both columns into one rowset:
insert tblB
(sDate)
select distinct dt
from (
select dtFrom as dt
from tblA
union all
select dtTo
from tblA
) s
Use the always handy Calendar Table, which is a table that holds 1 row for each day, for all days between specific years. You can add additional columns like IsBusinessDay or WorkingStartHour / WorkingEndHour to make your date queries much easier.
-- Create Calendar Table
DECLARE #StartDate DATE = '2000-01-01'
DECLARE #EndDate DATE = '2050-01-01'
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
CREATE TABLE CalendarTable (
Date DATE PRIMARY KEY,
IsWorkingDay BIT
-- Other columns you might need
)
;WITH RecursiveCTE AS
(
SELECT
Date = #StartDate
UNION ALL
SELECT
Date = DATEADD(DAY, 1, R.Date)
FROM
RecursiveCTE AS R
WHERE
DATEADD(DAY, 1, R.Date) <= #EndDate
)
INSERT INTO CalendarTable (
Date,
IsWorkingDay)
SELECT
Date = R.Date,
IsWorkingDay = CASE WHEN DATEPART(WEEKDAY, R.Date) BETWEEN 1 AND 5 THEN 1 ELSE 0 END
FROM
RecursiveCTE AS R
OPTION
(MAXRECURSION 0)
Now with your calendar table, just join with a BETWEEN and INSERT to your destination table. You can use DISTINCT to make sure dates don't repeat:
INSERT INTO tblB (
sDate)
SELECT DISTINCT
sDate = C.Date
FROM
tlbA AS A
INNER JOIN CalendarTable AS C ON C.Date BETWEEN A.dtFrom AND A.dtTo
Let's say for example that you only want to insert records that are working days (monday to friday). You just need to filter the calendar table and done. You can add whichever logic you want on your table and just filter it when using, without repeating complex datetime logics.
INSERT INTO tblB (
sDate)
SELECT DISTINCT
sDate = C.Date
FROM
tlbA AS A
INNER JOIN CalendarTable AS C ON C.Date BETWEEN A.dtFrom AND A.dtTo
WHERE
C.IsWorkingDay = 1
With a Calendar you can inner join on the ranges to produce an Insert statement.
DECLARE #StartDate DATETIME = (SELECT MIN(dtFrom) FROM tblA)
DECLARE #EndDate DATETIME = (SELECT MAX(dtTo) FROM tblB)
;WITH Calendar as
(
SELECT CalendarDate = #StartDate, CalendarYear = DATEPART(YEAR, #StartDate), CalendarMonth = DATEPART(MONTH, #StartDate)
UNION ALL
SELECT CalendarDate = DATEADD(MONTH, 1, CalendarDate), CalendarYear = DATEPART(YEAR, CalendarDate), CalendarMonth = DATEPART(MONTH, CalendarDate)
FROM Calendar WHERE DATEADD (MONTH, 1, CalendarDate) <= #EndDate
)
INSERT INTO tblB
SELECT DISTINCT
C.CalendarDate
FROM
Calendar C
INNER JOIN tblA A ON C.CalendarDate BETWEEN A.dtFrom AND A.dtTo
You can achieve this result by using the below queries.
Steps 1 - Create a Custom Function which will take date range as a parameter and will return date series.
CREATE FUNCTION [dbo].[GenerateDateRange]
(#StartDate AS DATE,
#EndDate AS DATE,
#Interval AS INT
)
RETURNS #Dates TABLE(DateValue DATE)
AS
BEGIN
DECLARE #CUR_DATE DATE
SET #CUR_DATE = #StartDate
WHILE #CUR_DATE <= #EndDate BEGIN
INSERT INTO #Dates VALUES(#CUR_DATE)
SET #CUR_DATE = DATEADD(DAY, #Interval, #CUR_DATE)
END
RETURN;
END;
Step 2 - Join this custom function with your table tblA and insert the record in tblb as needed
insert tblb
select b.* from tblA a cross apply dbo.GenerateDateRange(a.dtFrom, a.dtTo, 1) b

sql server while date not weekend or a specific date

I'm trying to write an sql while loop to increment a date until it doesn't mate a date in two other tables and is not a Saturday or a Sunday.
Something like this
DECLARE #DueDate datetime
SELECT #DueDate = datetime FROM tbl_status WHERE (parent_id = #ComplaintId)
WHILE((SELECT COUNT(date) FROM tbl1 WHERE(date = #DueDate)) > 0 AND (SELECT COUNT(date) FROM tbl2 WHERE(date = #DueDate)) > 0 AND DATEPART(d,#DueDate) = 'Saturday' AND DATEPART(d,#DueDate) = 'Sunday')
BEGIN
#DueDate = DATEADD(d,1,#DueDate)
END
Can anyone help
thanks
As I mentioned in my comment, you are going about this in a very inefficient manner with your while loop.
If you don't have a table of dates to use in a lookup, you can create one with a derived table, otherwise known as a Common Table Expression:
-- Set up the test data:
declare #t1 table (d date);
declare #t2 table (d date);
insert into #t1 values('20161230'),('20170111'),('20170110');
insert into #t2 values('20161225'),('20170105'),('20170106');
-- Declare your DueDate:
declare #DueDate date = '20170105';
-- Use a CTE to build a table of dates. You will want to set the Start and End dates automatically with SELECT statements:
declare #DatesStart date = '20161201';
declare #DatesEnd date = '20170225';
with Tally0 as
(
select x from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) as x(x)
)
,Tally1 as
(
select row_number() over (order by (select null))-1 as rn
from Tally0 t1 -- 10 rows -- Add more CROSS APPLY joins
cross apply Tally0 t2 -- 100 rows -- to get enough rows to cover
cross apply Tally0 t3 -- 1000 rows -- your date range.
)
,Dates as
(
select dateadd(d,t.rn,#DatesStart) as DateValue
from Tally1 t
where t.rn <= datediff(d,#DatesStart,#DatesEnd)
)
select min(d.DateValue) as NextDate -- SELECT the next available Date.
from Dates d
left join #t1 t1
on(d.DateValue = t1.d)
left join #t2 t2
on(d.DateValue = t2.d)
where t1.d is null -- That isn't in either table
and t2.d is null -- and isn't on a Saturday or Sunday.
and datename(weekday,d.DateValue) not in('Saturday','Sunday')
and d.DateValue > #DueDate

T-SQL to select every nth date from column of dates

I have a column of dates. They are all workdays. I would like to generate a list of dates that are 'n' days apart. For example, starting with the most recent date, I want to find the date n days before it, 2n days before it, 3n days before it, etc. I could use a while loop but I wanted to know if I could use SQL set operations instead. Can it be done?
Find the difference between the most_recent_date and dates in your table, then use the modulo function, where n is the interval.
SELECT date
FROM my_table
WHERE mod(most_recent_date - date, n) = 0
This is the perfect case for a CTE:
DECLARE #LastDate datetime;
DECLARE #N int;
DECLARE #NCoefficientMax;
SELECT #N = 1, #NCoefficientMax = 10;
SELECT #LastDate = MyDate
FROM MyTable
ORDER BY MyDate DESC
WITH mycte
AS
(
SELECT DATEADD(dd, #N, #LastDate) AS NextDate, #N AS NCoefficient
UNION ALL
SELECT DATEADD(dd, #N, NextDate), #N + NCoefficient AS NCoefficient
FROM mycte WHERE NCoefficient < #NCoefficientMax
)
SELECT NextDate FROM mycte
Where #NCoefficientMax is the max coefficient for N.
You can use the dateadd funcion
and make select with join to self table.
What that you need to do it -
Insert the result to temporary table,
with additional column then contain the row_number then order like the result
simple example:
declare #t1 table (d datetime, row int)
insert #t1
select d, row_number()over(order by d)
from T1
order by d
select T1A.*, datediff(day,T1A.d,T1B.d) as dif
from #t1 as T1A
left join #t1 as T1B on T1A.row = T1B.row-1
DECLARE #mostRecent datetime2
SELECT #mostRecent = MAX(dateColumn)
FROM table
SELECT columns
FROM table
WHERE (DATEDIFF(day, dateColumn, #mostRecent) % n) = 0

force sql results to return each date even if it is not in database [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
generate days from date range
Here is my query:
SELECT SUM(number) AS number, c_date
FROM my_table
WHERE c_date between '11/6/2012' AND '11/12/2012'
GROUP BY c_date
ORDER BY c_date DESC
It will return something like this
11 '11/12/2012'
9 '11/9/2012'
10 '11/8/2012'
10 '11/7/2012'
10 '11/6/2012'
Now how can I force the return results to include 11/11 and 11/10 with 0 for the number rather than skipping over them entirely. Also I cannot create a date table to store the dates.
Since you can't create a new table, an alternate approach is to build up a "calendar" CTE.
Next, perform a LEFT JOIN from your newly created "calendar" table to my_table:
SELECT ISNULL(SUM(mt.number), 0) AS number, mc.c_date
FROM
my_calendar mc LEFT JOIN
my_table mt ON mt.c_date = mc.c_date
WHERE mc.c_date between '11/6/2012' AND '11/12/2012'
GROUP BY mc.c_date
ORDER BY mc.c_date DESC
You could create a user defined function that returns a date range. It would look like this:
CREATE FUNCTION uspDateRange(#MinDate datetime, #Maxdate datetime)
RETURNS #DateRange TABLE ([Date] datetime)
AS
BEGIN
DECLARE #Date datetime
SET #Date = #MinDate
WHILE (#Date < #MaxDate)
BEGIN
INSERT INTO #DateRange VALUES(#Date)
SET #Date = DATEADD(day, 1, #Date)
END
RETURN
END
GO
Now you can select a range to get the range of all days of October 2012:
SELECT * FROM uspDateRange('2012-10-01', '2013-11-01')
Your query would look like this:
SELECT SUM(number) AS number, c_date
FROM my_table t
INNER JOIN uspDateRange('11/6/2012', '11/12/2012') r ON t.c_date = r.[Date]
GROUP BY c_date
ORDER BY c_date DESC