Select missing months between 2 dates (in same year) - sql-server

I'm trying to get all months in which no transaction is placed for the same year (If different years is not possible)
This is my query to get transactions between 2 dates, but don't know how can I select only months for which transaction in database is missing
SELECT *
FROM Installment
WHERE OrderId = 1
AND InstallmentDate
BETWEEN cast('8/02/2014' as date)
AND cast('12/25/2014' as date)
InstallmentId OrderId CustomerKey InstallmentAmount InstallmentDate
18 1 INS-1 3000 2014-09-03
92 1 INS-1 3000 2014-10-13
137 1 INS-1 3000 2014-11-05
in this case record for the 12th month and 8th month is missing, how can I get this with SQL Server Query ?
Update
select yymm.yy, yymm.mm
from (select distinct year(InstallmentDate) as yy, month(InstallmentDate) as mm
from Installment
where InstallmentDate BETWEEN '2014-09-02' and '2015-01-15'
) yymm left join
Installment i
on i.OrderId = 1 and
year(i.InstallmentDate) = yymm.yy and
month(i.InstallmentDate) = yymm.mm
where i.OrderId is not null;
Gordon's query is returning all the years and months from table between 2 dates, just by changing i.OrderId is null to i.OrderId is not null here is the out of his query
yy mm
2014 9
2014 10
2014 11
Expected Output (if possible)
yy mm
2014 12
2015 1

Using the following recursive CTE:
DECLARE #start DATE = '2014-09-02'
DECLARE #end DATE = '2015-01-15'
;WITH IntervalDates (date)
AS
(
SELECT #start
UNION ALL
SELECT DATEADD(MONTH, 1, date)
FROM IntervalDates
WHERE DATEADD(MONTH, 1, date)<=#end
)
SELECT YEAR(date) AS Year, MONTH(date) AS Month
FROM IntervalDates
you can get a list of all Years/Months between the two dates of interest:
Year Month
==============
2014 9
2014 10
2014 11
2014 12
2015 1
Using EXCEPT on the above CTE:
;WITH IntervalDates (date)
AS
(
SELECT #start
UNION ALL
SELECT DATEADD(MONTH, 1, date)
FROM IntervalDates
WHERE DATEADD(MONTH, 1, date)<=#end
)
SELECT YEAR(date) AS Year, MONTH(date) AS Month
FROM IntervalDates
EXCEPT
SELECT DISTINCT YEAR(InstallmentDate) AS yy, MONTH(InstallmentDate) AS mm
FROM Installment
WHERE OrderId = 1 AND InstallmentDate BETWEEN #start AND #end
yields the required result set:
Year Month
=============
2014 12
2015 1

To do this in SQL, you need to start with a list of months. Assuming you have at least one record for each month in the table, you can then get the missing dates easily using a subquery. The rest of the query is just a left join and checking for non-matches:
select yymm.yy, yymm.mm
from (select distinct year(InstallmentDate) as yy, month(InstallmentDate) as mm
from Installment
where InstallmentDate BETWEEN '2014-09-02' and '2015-01-15'
) yymm left join
Installment i
on i.OrderId = 1 and
year(i.InstallmentDate) = yymm.yy and
month(i.InstallmentDate) = yymm.mm
where i.OrderId is null;

Simplest way I can think of is to have a date dimension table that contains (at least) date, and 1st of month then. For creating one take a look at something like https://dba.stackexchange.com/questions/74957/best-approach-for-populating-date-dimension-table , although that one doesn't have firstOfMonthDate in it as my example show but the idea is the same.
then your query becomes
SELECT DISTINCT
firstOfMonthDate
FROM
dateRef dr
LEFT OUTER JOIN
InstallmentDate i ON dr.date = i.InstallmentDate AND i.OrderId = 1
WHERE
i.InstallmentDate IS NULL
AND
dr.date BETWEEN #startDate and #endDate
change firstOfMonthDate for fiscal month etc. as required. This would work across any range of dates you have in your table so different years wouldn't be an issue.

Try the below script. I retrieve all dates between the specified dates and use a LEFT JOIN to get those which are not present in your table:
DECLARE #startDate AS DATETIME, #endDate AS DATETIME
DECLARE #dates AS TABLE (CurrentDate DATETIME)
SET #startDate = '2014-01-01'
SET #endDate = '2014-01-31';
with GetDates AS
(
SELECT #startDate AS TheDate
UNION ALL
SELECT DATEADD("DD", 1, TheDate) FROM GetDates
WHERE TheDate < #endDate
) INSERT INTO #dates SELECT TheDate FROM GetDates
OPTION (MAXRECURSION 0)
SELECT DISTINCT YEAR(d.CurrentDate), MONTH(d.CurrentDate) FROM #dates d
LEFT JOIN InstallmentDate i ON i.InstallmentDate BETWEEN #startDate AND #endDate AND OrderId = 1
WHERE i.InstallmentDate IS NULL
Hope this helps...

Related

How to show all days recorded

I have a table in Sql Server named Lab_Analysis(ID int,Date Date,HeatNo decimal(18,3),Mn decimal(18,3),CaO decimal(18,3))
Now if i want to View Sum of HeatNo, Mn, CaO then i will have to enter Month and Year. Suppose i give month = 2(means February) then it should show sum of HeatNo, Mn, CaO of each days of that Month and if on any days there is no Record found then it should 0. For this I did following:
Declare #D table(Dt int,Val decimal(18,3))
insert into #D values(1,0),(2,0),(3,0),(4,0),(5,0),(6,0),(7,0),(8,0),(9,0),
(10,0),(11,0),(12,0),(13,0),(14,0),(15,0),(16,0),(17,0),(18,0),(19,0),
(20,0),(21,0),(22,0),(23,0),(24,0),(25,0),(26,0),(27,0),(28,0),(29,0),(30,0),(31,0)
SELECT D.Dt,ISNULL(SUM(L.HeatNo),0) HeatNo FROM #D D LEFT JOIN Lab_Analysis
L ON DATEPART(dd,L.Date)=D.Dt where DATEPART(MM,L.Date)=2 GROUP BY D.Dt
Here in the February month Records are inserted only on 11 and 13 February. The above query is showing only rows but it shows all days records. if no record found on any days then it should show 0 which is not showing by the above query. How to solve this?
You are using the L.Date in your where clause DATEPART(MM,L.Date)=2, that converts your LEFT JOIN into an INNER JOIN, you need to add this condition in the ON clause
Declare #D table(Dt int,Val decimal(18,3))
insert into #D values(1,0),(2,0),(3,0),(4,0),(5,0),(6,0),(7,0),(8,0),(9,0),
(10,0),(11,0),(12,0),(13,0),(14,0),(15,0),(16,0),(17,0),(18,0),(19,0),
(20,0),(21,0),(22,0),(23,0),(24,0),(25,0),(26,0),(27,0),(28,0),(29,0),(30,0),(31,0)
SELECT D.Dt,ISNULL(SUM(L.HeatNo),0) HeatNo
FROM #D D
LEFT JOIN Lab_Analysis L ON
DATEPART(dd,L.Date)=D.Dt
AND DATEPART(MM,L.Date)=2
GROUP BY D.Dt
Instead of create that set of values for each month, I recomend you to use a CTE to create your dates
DECLARE
#Year INT,
#Month INT,
#start DATE,
#end DATE
SET #Year = 2019
SET #Month = 2
SET #start = DATEFROMPARTS(#Year, #Month, 1)
SET #end = DATEADD(day, -1,DATEADD(month, 1, #start))
;WITH dates AS (
SELECT #start as theDate
UNION ALL
SELECT DATEADD(day, 1, theDate)
FROM dates
WHERE DATEADD(day, 1, theDate) <= #end
)
SELECT D.theDate,ISNULL(SUM(L.HeatNo),0) HeatNo
FROM dates D
LEFT JOIN Lab_Analysis L ON L.Date = theDate
GROUP BY theDate

Select all days of the current week

Good Day! I am working on a chart where I need to display all the days of the current week to show the sales per Week. So far, I am able to display all the days of the current week, I'm just having a trouble in displaying the sales for each day of the week.Since there are no records in the database for the days of the week, it the TOTAL_SALES column should all return a Null value. Instead, it returns the total sales recorded in the database. Here is my Stored Procedure query so far.
WITH DAYSOFTHEWEEK AS
(
SELECT 0 DAY
UNION ALL
SELECT DAY + 1 FROM DAYSOFTHEWEEK WHERE DAY < 6
)
SELECT DATEADD(DAY, DAY, DATEADD(DAY, 2-DATEPART(WEEKDAY, CONVERT (date, GETDATE())), CONVERT (date, GETDATE()))) AS DAY_OF_THE_WEEK,
SUM([ORDER].NET_AMOUNT) AS TOTAL_SALES
FROM DAYSOFTHEWEEK, [ORDER]
GROUP BY DAYSOFTHEWEEK.DAY
I tried adding this condition statement,
WHERE DAYSOFTHEWEEK.DAY IN ([ORDER].ORDER_DATE)
But it returns this error
Operand type clash: date is incompatible with int
Can someone help me out on this?Is there a work around with the code that I already have? Thanks in advance!
What I think you're after is a SUM of each day's sales for the current week with NULL if there are no sales. The secret is to left join your date list onto your data:
-- Setup some fake sales data
WITH TestData(N, Order_Date, Net_Amount) AS (
SELECT 1 N, CAST(GETDATE() AS DATE) Order_Date, RAND() * 100 Net_Amount
UNION ALL
SELECT N+1 N, CAST(GETDATE()-N/5 AS DATE) Order_Date, RAND(CHECKSUM(NEWID())) * 100 Net_Amount FROM TestData
WHERE N < 20
)
SELECT TestData.Order_Date, TestData.Net_Amount INTO #Order FROM TestData
--Set the first day of the week (if required)
SET DATEFIRST 7 --Sunday
;WITH Days(N,DayOfTheWeek) AS (
SELECT 1 N, DATEADD(DAY, 1-DATEPART(WEEKDAY, GETDATE()), CONVERT(DATE,GETDATE())) DayOfTheWeek
UNION ALL
SELECT N+1 N,DATEADD(DAY, 1, DayOfTheWeek) DayOfTheWeek FROM Days
WHERE N < 7
)
SELECT d.DayOfTheWeek, SUM(Net_Amount) TotalAmount
FROM Days d
LEFT JOIN #Order ON d.DayOfTheWeek = Order_Date
GROUP BY d.DayOfTheWeek
DayOfTheWeek TotalAmount
------------ ----------------------
2016-08-07 219.036784917497
2016-08-08 273.319570812461
2016-08-09 271.148114731087
2016-08-10 194.780039228967
2016-08-11 NULL
2016-08-12 NULL
2016-08-13 NULL
Here is every day this week, starting at your datefirst date, which can be temporarily varied for the query with SET DATEFIRST if you need to have some other week start date
I think you have some sales table there that you haven't shown us, you need to join to that on date, then group by
WITH DAYSOFTHEWEEK AS
(
SELECT cast(dateadd(
day,
-datepart(weekday,getdate()) + 1 ,
GETDATE()
)
as date) [DAY], 0 as cnt
UNION ALL
SELECT dateadd(day,1,[DAY]), cnt + 1 FROM DAYSOFTHEWEEK WHERE cnt < 6
)
select DAYSOFTHEWEEK.[day], SUM([ORDER].NET_AMOUNT) AS TOTAL_SALES from daysoftheweek
JOIN
SalesTable on
CAST(SalesTable.SalesDate date) = DAYSOFTHEWEEK.[day]
GROUP BY DAYSOFTHEWEEK.[day]
A little over complicated for me:
To get name of the week use, for example
SELECT DATENAME(dw,getdate())
But you really need something like this:
SELECT ProductName,Sum(Sales) From NameOfTable GROUP BY
DATENAME(ww,salesDate)

Select count with 0 count

Lets say I have following query:
SELECT top (5) CAST(Created AS DATE) as DateField,
Count(id) as Counted
FROM Table
GROUP BY CAST(Created AS DATE)
order by DateField desc
Lets say it will return following data set
DateField Counted
2016-01-18 34
2016-01-17 99
2016-01-14 1
2015-12-28 1
2015-12-27 6
But when I have Counted = 0 for certain Date I would like to get that in result set. So for example it should look like following
DateField Counted
2016-01-18 34
2016-01-17 99
2016-01-16 0
2016-01-15 0
2016-01-14 1
Thank you!
Expanding upon KM's answer, you need a date table which is like a numbers table.
There are many examples on the web but here's a simple one.
CREATE TABLE DateList (
DateValue DATE,
CONSTRAINT PK_DateList PRIMARY KEY CLUSTERED (DateValue)
)
GO
-- Insert dates from 01/01/2015 and 12/31/2015
DECLARE #StartDate DATE = '01/01/2015'
DECLARE #EndDatePlus1 DATE = '01/01/2016'
DECLARE #CurrentDate DATE = #StartDate
WHILE #EndDatePlus1 > #CurrentDate
BEGIN
INSERT INTO DateList VALUES (#CurrentDate)
SET #CurrentDate = DATEADD(dd,1,#CurrentDate)
END
Now you have a table
then you can rewrite your query as follows:
SELECT top (5) DateValue, isnull(Count(id),0) as Counted
FROM DateList
LEFT OUTER JOIN Table
on DateValue = CAST(Created AS DATE)
GROUP BY DateValue
order by DateValue desc
Two notes:
You'll need a where clause to specify your range.
A join on a cast isn't ideal. The type in your date table should match the type in your regular table.
One more solution as a single query:
;WITH dates AS
(
SELECT CAST(DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY [object_id]) - 1, '2016-01-14') as date) 'date'
FROM sys.all_objects
)
SELECT TOP 5
[date] AS 'DateField',
SUM(CASE WHEN Created IS NULL THEN 0 ELSE 1 END) AS 'Counted'
FROM dates
LEFT JOIN Table ON [date]=CAST(Created as date)
GROUP BY [date]
ORDER BY [date]
For a more edgy solution, you could use a recursive common table expression to create the date list. PLEASE NOTE: do not use recursive common table expressions in your day job! They are dangerous because it is easy to create one that never terminates.
DECLARE #StartDate date = '1/1/2016';
DECLARE #EndDate date = '1/15/2016';
WITH DateList(DateValue)
AS
(
SELECT DATEADD(DAY, 1, #StartDate)
UNION ALL
SELECT DATEADD(DAY, 1, DateValue)
FROM DateList
WHERE DateList.DateValue < #EndDate
)
SELECT DateValue, isnull(Count(id),0) as Counted
FROM DateList
LEFT OUTER JOIN [Table]
ON DateValue = CAST(Created AS DATE)
GROUP BY DateValue
ORDER BY DateValue DESC

TSQL loop months in sequence

I have an query that I'm feeling out-of-my depth with.
I need to loop through months between two dates and return a subset of data for each month with a blank row for months with no data.
For example:
TransactionID | Date | Value
1 | 01/01/2015 | £10
2 | 16/01/2015 | £15
3 | 21/01/2015 | £5
4 | 15/03/2015 | £20
5 | 12/03/2015 | £15
6 | 23/04/2015 | £10
Needs to return:
Month | Amount
January | £30
February | £0
March | £35
April | £10
My query will rely on specifying a date range so I can set the first and last date of the query.
I feel like I maybe over thinking this, but have gotten to that stage where you start to feel like you tying yourself in knots.
The key is having access to a list of integers to represent the months in the range. If you don't have a Numbers Table, then spt_values will do in a pinch.
SqlFiddle Demo
SELECT
[Year] = YEAR(DATEADD(month,[i],#range_start))
,[Month] = DATENAME(month,DATEADD(month,[i],#range_start))
,[Amount] = ISNULL(SUM([Value]),0)
FROM (
SELECT TOP (DATEDIFF(month,#range_start,#range_end)+1)
ROW_NUMBER() OVER(ORDER BY (SELECT 1))-1 [i]
FROM master.dbo.spt_values
) t1
LEFT JOIN #MyTable t2
ON (t1.[i] = DATEDIFF(month,#range_start,t2.[Date]) )
GROUP BY [i]
ORDER BY [i]
SQL is a tricky language at first. You actually do not want a loop. In fact, you pretty much never want to loop in SQL except in very few cases. Try this out:
DECLARE #StartDate DATE,
#EndDate DATE;
SET #StartDate = '01 January 2015';
SET #EndDate = '30 April 2015';
WITH CTE_Months
AS
(
SELECT #StartDate dates
UNION ALL
SELECT DATEADD(MONTH,1,dates)
FROM CTE_Months
WHERE DATEADD(MONTH,1,dates) < #EndDate
)
SELECT YEAR(B.[date]) AS yr,
DATENAME(MONTH,B.[Date]) AS month_name,
SUM(ISNULL(B.Value,0)) AS Amount
FROM CTE_Months A
LEFT JOIN yourTable B
ON YEAR(A.[date]) = YEAR(B.[date])
AND MONTH(A.[date]) = MONTH(B.[date])
GROUP BY YEAR(B.[date]),DATENAME(MONTH,B.[Date])
One way: create a table called months with a monthnum int field and 12 rows of [1..12]
declare #start date = '01 jan 2015',
#end date = '30 apr 2015'
select
datename(month, dateadd(month, monthnum, 0) - 1),
isnull(Amount, 0)
from months
left join (
select
month(date) Month,
sum(Value) Amount
from tbl
where date between #start and #end
group by month(date)
) T on (T.Month = months.monthnum)
where months.monthnum between month(#start) and month(#end)
order by monthnum
The following code will generate one output row for each month between the first and last transaction dates. Spanning a year boundary, or multiple years, is handled correctly.
-- Some sample data.
declare #Transactions as Table
( TransactionId Int Identity, TransactionDate Date, Value Int );
insert into #Transactions ( TransactionDate, Value ) values
( '20141125', 10 ), ( '20150311', 20 ), ( '20150315', 5 ), ( '20150509', 42 );
select * from #Transactions;
with
-- Determine the first and last dates involved.
Range as (
select Min( TransactionDate ) as FirstDate, Max( TransactionDate ) as LastDate
from #Transactions ),
-- Generate a set of all of the months in the range.
Months as (
select DateAdd( month, DateDiff( month, 0, FirstDate ), 0 ) as Month,
DateAdd( month, DateDiff( month, 0, LastDate ), 0 ) as LastMonth
from Range
union all
select DateAdd( month, 1, Month ), LastMonth
from Months
where Month < LastMonth )
-- Summarize the transactions.
select M.Month, Coalesce( Sum( T.Value ), 0 ) as Total
from Months as M left outer join
#Transactions as T on DateAdd( month, DateDiff( month, 0, T.TransactionDate ), 0 ) = M.Month
group by M.Month
order by M.Month
option ( MaxRecursion 1000 );

Grouping date periods (by number of days) but exclude the weekends?

I have a table with start and end dates in. My goal is to have a table that has grouped these dates into how many days the period spans. I thought I had the solution with a simple SQL statement (MS SQL Server 2005) but I want to exclude weekends.
SELECT DATEDIFF(D, StartDate, EndDate)+1 AS Days,
COUNT(ID) as Count
FROM myDateTable
GROUP BY DATEDIFF(D, StartDate, EndDate)
This gives a record set of:
Days Count
1 4
2 2
4 1
7 2
Is this possible to exclude the weekends in the SQL statement and if not can it be done using ASP and a array perhaps?
Well then, using Sql Server 2005, you can try something like
DECLARE #Table TABLE(
ID INT,
StartDate DATETIME,
EndDate DATETIME
)
INSERT INTO #Table (ID,StartDate,EndDate) SELECT 1, '25 Jan 2009', '31 Jan 2009'
INSERT INTO #Table (ID,StartDate,EndDate) SELECT 2, '01 Jan 2009', '07 Jan 2009'
INSERT INTO #Table (ID,StartDate,EndDate) SELECT 3, '01 Jan 2009', '14 Jan 2009'
DECLARE #MinDate DATETIME,
#MaxDate DATETIME
SELECT #MinDate = MIN(StartDate) ,
#MaxDate = MAX(EndDate)
FROM #Table
--Create a temp result set between the Min and Max dates, with all dates, and their weekday names
;WITH DayValues AS(
SELECT #MinDate DateVal,
DATENAME(dw, #MinDate) DateValName
UNION ALL
SELECT DateVal + 1,
DATENAME(dw, DateVal + 1) DateValName
FROM DayValues
WHERE DateVal + 1 <= #MaxDate
),
--select the count of days for each StartDate and EndDate pair, excluding Saturdays and Sundays
DateCounts AS(
SELECT ID,
(
SELECT COUNT(1)
FROM DayValues
WHERE DateVal BETWEEN StartDate AND EndDate
AND DateValName NOT IN ('Saturday', 'Sunday')
) DateCount
FROM #Table
)
--Now group and count
SELECT DateCount,
COUNT(ID) TotalCount
FROM DateCounts
GROUP BY DateCount
OPTION (MAXRECURSION 0)
Output
DateCount TotalCount
----------- -----------
5 2
10 1
EDIT: Brief Explenation
You need to determine the number of days between (and including) 2 dates, that are not weekends.
So using a CTE, I create a temporary result set of dates ebwteen the Min and Max dates, and their Weekday Name (eg Monday, Tuesday... Sunday).
Then, for each of your date pairs, I count the number of entries that does not correspond to Saturday and Sunday.
Here's an ASP function that counts days weekdays between two dates.
<%
Dim StartDate, EndDate
StartDate = CDate("1/1/2010")
EndDate = CDate("2/1/2010")
Response.Write(WeekDayCount(StartDate, EndDate))
Function WeekDayCount(StartDate, EndDate)
dim tempDate, dayCount
tempDate = StartDate
dayCount = 0
'Step forward one day, counting non-week days
While tempDate <> EndDate
'The 1 and 7 might need to be tweaked depending on the locale of your
'server. 1 = Sunday, 7 = Saturday
If DatePart("w", tempDate) <> 1 And DatePart("w", tempDate) <> 7 Then
dayCount = dayCount + 1
End If
tempDate = DateAdd("d", 1, tempDate)
Wend
WeekDayCount = dayCount
End Function
%>
Have a look at ##DATEFIRST,
and look a this;
SELECT DATEPART(DW,GETDATE()).
You should be able to run a query WHERE the 'DW' is not equal to the weekend numbers.

Resources