DATENAME causes 'Distinct' to be ignored - sql-server

Adding DATENAME() function to query causes duplicate rows despite 'distinct'.
TREE - TreeId, CityId, DatePlanted
WATER - WaterId, TreeId(fk), DateWatered
Table1 is one to many with Table 2
Each row in the TREE table indicates the planting of a tree. WATER table is a single instance of watering that tree. A tree is watered many times a year. You get the idea.
I need to return a report the shows the number of trees planted, by month and the number times it was watered.
SELECT t.CityId
, COUNT(distinct t.TreeId) as 'Trees Planted'
, COUNT(w.TreeId) as 'Trees Watered'
FROM TREE t
JOIN WATER w ON t.TreeId = w.TreeId
WHERE w.DateWatered between #Start AND #End
GROUP BY t.CityId
This works fine. However when I try to group by the month, the t.Treeid is no longer distinct, so the number of trees is too high.
SELECT t.CityId
, DATENAME(month, w.DateWatered)
, COUNT(distinct t.TreeId) as 'Trees Planted'
, COUNT(w.TreeId) as 'Trees Watered'
FROM TREE t
JOIN WATER w ON t.TreeId = w.TreeId
WHERE w.DateWatered between #Start AND #End
GROUP BY t.CityId, DATENAME(month, w.DateWatered)
EDIT: I have found why I am getting duplicates but not how to fix it. If a tree is watered in April 2016 then again in May 2016, I get a count of 2 trees planted and 2 trees watered where it should be one tree planted and 2 waterings. If I do the first query with no date returned, I get the correct number. So by adding the date, and even if I group by Year, then Month, with two waterings of the same tree, it is also showing the tree planted twice. I am currently investigating the use of CTEs to maybe keep each part of the query separate.

This demonstrates how to break the problem down into steps within a common table expression (CTE). Note that you can swap out the final select with one of the commented selects to view the intermediate results. It's a convenient way to test, debug, or understand what is going on.
One of the problems you're fighting is trying to summarize the data based only on the dates of waterings. If a tree is planted in a month that has no waterings then it wasn't being counted. The code below summarizes the plantings and waterings separately for the range of dates, then combines them into a single result set.
-- Sample data.
declare #Trees as Table ( TreeId Int Identity, CityId Int, DatePlanted Date );
declare #Waterings as Table ( WateringId Int Identity, TreeId Int, DateWatered Date );
insert into #Trees ( CityId, DatePlanted ) values
( 1, '20160115' ), ( 1, '20160118' ),
( 1, '20160308' ), ( 1, '20160318' ), ( 1, '20160118' ),
( 1, '20170105' ),
( 1, '20170205' ),
( 1, '20170401' ),
( 2, '20160113' ), ( 2, '20160130' ),
( 2, '20170226' ), ( 2, '20170227' ), ( 2, '20170228' );
insert into #Waterings ( TreeId, DateWatered ) values
( 1, '20160122' ), ( 1, '20160129' ), ( 1, '20160210' ), ( 1, '20160601' ),
( 5, '20160120' ), ( 5, '20160127' ), ( 5, '20160215' ), ( 5, '20160301' ), ( 5, '20160515' );
select * from #Trees;
select * from #Waterings;
-- Combine the data.
declare #StartDate as Date = '20100101', #EndDate as Date = '20200101';
with
-- Each tree with the year and month it was planted.
TreesPlanted as (
select CityId, TreeId,
DatePart( year, DatePlanted ) as YearPlanted,
DatePart( month, DatePlanted ) as MonthPlanted
from #Trees
where #StartDate <= DatePlanted and DatePlanted <= #EndDate ),
-- Tree plantings summarized by city, year and month.
TreesPlantedSummary as (
select CityId, YearPlanted, MonthPlanted, Count( TreeId ) as Trees
from TreesPlanted
group by CityId, YearPlanted, MonthPlanted ),
-- Each watering and the year and month it occurred.
TreesWatered as (
select CityId, W.TreeId,
DatePart( year, W.DateWatered ) as YearWatered,
DatePart( month, W.DateWatered ) as MonthWatered
from #Trees as T left outer join
#Waterings as W on W.TreeId = T.TreeId
where #StartDate <= W.DateWatered and W.DateWatered <= #EndDate ),
-- Waterings summarized by city, year and month.
TreesWateredSummary as (
select CityId, YearWatered, MonthWatered,
Count( distinct TreeId ) as Trees, Count( TreeId ) as Waterings
from TreesWatered
group by CityId, YearWatered, MonthWatered )
-- Combine the plantings and waterings for the specified period.
select Coalesce( TPS.CityId, TWS.CityId ) as CityId,
Coalesce( TPS.YearPlanted, TWS.YearWatered ) as Year,
Coalesce( TPS.MonthPlanted, TWS.MonthWatered ) as Month,
Coalesce( TPS.Trees, 0 ) as TreesPlanted,
Coalesce( TWS.Trees, 0 ) as TreesWatered,
Coalesce( TWS.Waterings, 0 ) as Waterings
from TreesPlantedSummary as TPS full outer join
TreesWateredSummary as TWS on TWS.CityId = TPS.CityId and
TWS.YearWatered = TPS.YearPlanted and TWS.MonthWatered = TPS.MonthPlanted
order by CityId, Year, Month;
-- Alternative queries for testing/debugging/understanding:
-- select * from TreesPlantedSummary order by CityId, YearPlanted, MonthPlanted;
-- select * from TreesWateredSummary order by CityId, YearWatered, MonthWatered;
And now you want the missing months (with no activity) included in the results, eh?

SELECT t.CityId
, ISNULL(DATENAME(month, w.DateWatered), DATENAME(month, t.DatePlanted))
, (SELECT COUNT(tDistinct.TreeId) FROM TREE tDistinct
WHERE tDistinct.TreeId = t.TreeId AND DATENAME(month, tDistinct.DatePlanted) = DATENAME(month, t.DateWatered) AND t.DatePlanted between #Start AND #End) as 'Trees Planted'
, COUNT(w.TreeId) as 'Trees Watered'
FROM TREE t
JOIN WATER w ON t.TreeId = w.TreeId
WHERE w.DateWatered between #Start AND #End
GROUP BY t.CityId, DATENAME(month, w.DateWatered), DATENAME(month, t.DatePlanted)
The only drawback here is a scenario in which no tree was watered in a month where a tree was planted your date will be null so i added a check for that...not sure what your data looks like so it may make sense to ignore the ISNULL check in favor of your original grouping
EDITED:
Based upon your requirements I do not believe CTE is necessary; based upon the additional information you have provided I have altered the query slightly to suit your needs:
`SELECT DATENAME(MONTH, myConsolidatedTree.DateAction) as myDate
,(SELECT COUNT(*)
FROM TREE AS t
WHERE
DATENAME(MONTH, myConsolidatedTree.DateAction) = DATENAME(MONTH, t.DatePlanted)
) as myNumberOfPlanted
,(SELECT COUNT(*)
FROM WATER AS w
WHERE
DATENAME(MONTH, myConsolidatedTree.DateAction) = DATENAME(MONTH, w.DateWatered)
) as myNumberOfWatered
FROM(
SELECT t.DatePlanted as DateAction
,t.TreeId as IdAction
,'PLANTED' as TreeAction
FROM TREE t
UNION
SELECT w.DateWatered as DateAction
,w.TreeId as IdAction
,'WATERED' as TreeAction
FROM WATER w) as myConsolidatedTree
WHERE myConsolidatedTree.DateAction between #StartDate and #EndDate
GROUP BY DATENAME(MONTH, myConsolidatedTree.DateAction), DATEPART(MONTH, myConsolidatedTree.DateAction)
ORDER BY DATEPART(MONTH, myConsolidatedTree.DateAction)`
While the consolidated subquery contains more information than is required for this question I left the additional TreeId and derived TreeAction columns there in case you may encounter a need for this in the future.

Related

Multi - Columns OVERLAPPING DATES

;with cte as (
select Domain_Id, Starting_Date, End_Date
from Que_Date
union all
select t.Domain_Id, cte.Starting_Date, t.End_Date
from cte
join Que_Date t on cte.Domain_Id = t.Domain_Id and cte.End_Date = t.Starting_Date),
cte2 as (
select *, rn = row_number() over (partition by Domain_Id, End_Date order by Domain_Id)
from cte
)
select DISTINCT Domain_Id, Starting_Date, max(End_Date) enddate
from cte2
where rn=1
group by Domain_Id, Starting_Date
order by Domain_Id, Starting_Date;
select * from Que_Date
This is the code that I have wrote but i am getting an extra row i.e 2nd row is extra, the expected output should have only 1st, 3rd and 4th row as output so please help me with it.
I have attached an image showing Input, Excepted Output, and the output that I am getting.
You've got so many results in your first cte. Your first cte has consisting domains. So you cannot filter domains based on your cte. So you query has unnecessary rows.
Try this solution. Cte ConsistentDomains has just consistent domains. So based on this cte, we can get not overlapped results.
Create and fill data:
CREATE TABLE FooTable
(
Domain_ID INT,
Starting_Date DATE,
End_Date Date
)
INSERT INTO dbo.FooTable
(
Domain_ID,
Starting_Date,
End_Date
)
VALUES
( 1, -- Domain_ID - int
CONVERT(datetime,'01-01-2011',103), -- Starting_Date - date
CONVERT(datetime,'05-01-2011',103) -- End_Date - date
)
, (1, CONVERT(datetime,'05-01-2011',103), CONVERT(datetime,'07-01-2011',103))
, (1, CONVERT(datetime,'07-01-2011',103), CONVERT(datetime,'15-01-2011',103))
, (2, CONVERT(datetime,'11-05-2011',103), CONVERT(datetime,'12-05-2011',103))
, (2, CONVERT(datetime,'13-05-2011',103), CONVERT(datetime,'14-05-2011',103))
Query to find not overlapping results:
DECLARE #startDate varchar(50) = '2011-01-01';
WITH ConsistentDomains AS
(
SELECT
f.Domain_ID
, f.Starting_Date
, f.End_Date
FROM FooTable f
WHERE f.Starting_Date = #startDate
UNION ALL
SELECT
s.Domain_ID
, s.Starting_Date
, s.End_Date
FROM FooTable s
INNER JOIN ConsistentDomains cd
ON s.Domain_ID = cd.Domain_ID
AND s.Starting_Date = cd.End_Date
), ConsistentDomainsRownumber AS
(
SELECT
cd.Domain_ID
, cd.Starting_Date
, cd.End_Date
, ROW_NUMBER() OVER (PARTITION BY cd.Domain_ID ORDER BY cd.Starting_Date,
cd.End_Date) RN
FROM ConsistentDomains cd
)
SELECT cd.Domain_ID
, convert(varchar, cd.Starting_Date, 105) Starting_Date
, convert(varchar, cd.End_Date, 105) End_Date
FROM ConsistentDomainsRownumber cd WHERE cd.RN = 1
UNION ALL
SELECT
ft.Domain_ID
, convert(varchar, ft.Starting_Date, 105) Starting_Date
, convert(varchar, ft.End_Date, 105) End_Date
FROM dbo.FooTable ft WHERE ft.Domain_ID NOT IN (SELECT cd.Domain_ID FROM
ConsistentDomainsRownumber cd)
Output:
I used the same table creating script as provided by #stepup, but you can also get your outcome in this way.
CREATE TABLE testtbl
(
Domain_ID INT,
Starting_Date DATE,
End_Date Date
)
INSERT INTO testtbl
VALUES
(1, convert(date, '01-01-2011' ,103), convert(date, '05-01-2011',103) )
,(1, convert(date, '05-01-2011' ,103), convert(date, '07-01-2011',103) )
,(1, convert(date, '07-01-2011' ,103), convert(date, '15-01-2011',103) )
,(2, convert(date, '11-05-2011' ,103), convert(date, '12-05-2011',103) )
,(2, convert(date, '13-05-2011' ,103), convert(date, '14-05-2011',103) )
You can make use of self join and Firs_value and last value within the group to make sure that you are comparing within the same ID and overlapping dates.
select distinct t.Domain_ID,
case when lag(t1.starting_date)over (partition by t.Domain_id order by
t.starting_date) is not null
then first_value(t.Starting_Date) over (partition by t.domain_id order by
t.starting_date)
else t.Starting_Date end StartingDate,
case when lead(t.domain_id) over (partition by t.domain_id order by t.starting_date) =
t1.Domain_ID then isnull(last_value(t.End_Date) over (partition by t.domain_id order by t.end_date rows between unbounded preceding and unbounded following),t.End_Date)
else t.End_Date end end_date
from testtbl t
left join testtbl t1 on t.Domain_ID = t1.Domain_ID
and t.End_Date = t1.Starting_Date
and t.Starting_Date < t1.Starting_Date
Output:
Domain_ID StartingDate end_date
1 2011-01-01 2011-01-15
2 2011-05-11 2011-05-12
2 2011-05-13 2011-05-14

Get list of dates without entries in SQL Server

I am looking to find a solution to this problem. I have a table called LogEntry that stores information used by multiple offices, where they have to log any visitors that come in to their office on any given day. If no visitors come in, they are still required to log "No Visitors" for the day. How do I run a query that pulls all dates where an office failed to create even a "No Visitors" log?
I've looked at this question (and the article linked within), but even adapting that query, I'm only able to create a blank row for a date where an office is missing an entry for a date, not specify the actual office that did not create an entry. Is there a way to do what I'm trying to do?
declare #temp table (
CDate datetime,
loc_id varchar(50)
)
insert into #temp SELECT DISTINCT entryDate, locationID FROM LogEntry WHERE entryDate >= '05/01/2017' AND entryDate <= '07-31-2017'
;with d(date) as (
select cast('05/01/2017' as datetime)
union all
select date+1
from d
where date < '07/31/2017'
)
select DISTINCT t.loc_id, CONVERT(date, d.date)
FROM d LEFT OUTER JOIN #temp t ON d.date = t.CDate
GROUP BY t.loc_id, d.date
ORDER BY t.loc_id
As I said, this query returns me a list of dates in the date range, and all locations that submitted entries on that date, but I'd like to find a way to extract essentially the opposite information: if an office (specified by locationID) did not submit an entry on a given day, return only those locationIDs and the dates that they missed.
Sample data
EntryID | locationID | entryDate
=================================
1 1 07-01-2017
2 1 07-02-2017
3 2 07-02-2017
4 1 07-04-2017
Expected Result (for date range of 07-01 to 07-04)
locationID | missedEntryDate
============================
1 07-03-2017
2 07-01-2017
2 07-03-2017
2 07-04-2017
Your first step was good, you create a list of all dates, but you also need a list of all locations. Then you create a cross join to have all combinations and then you perform the left join to find out what is missing.
;with allDates(date) as (
select cast('05/01/2017' as datetime)
union all
select date+1
from d
where date < '07/31/2017'
), allLocations as (
SELECT DISTINCT loc_id
FROM #temp
), allCombinations as (
SELECT date, loc_id
FROM allDates
CROSS JOIN allLocations
)
SELECT AC.loc_id, AC.date
FROM allCombinations AC
LEFT JOIN #temp t
ON AC.date = t.CDate
AND AC.loc_id = t.loc_id
WHERE t.loc_id IS NULL -- didnt find a match on #temp
If your dataset is not too large you can try this:
select t.loc_id, CONVERT(date, d.date)
FROM d
-- Cross join dates to all available locs
CROSS JOIN (SELECT DISTINCT loc_id FROM #temp ) AS Locs
LEFT JOIN
( SELECT loc_id, t.CDate
FROM #temp
GROUP BY loc_id, d.date ) AS t ON d.date = t.CDate AND Locs.loc_id = t.loc_id
ORDER BY Locs.loc_id
This should be a bit faster:
;WITH cte AS (
SELECT a.LocID, RangeStart.CDate, ( CASE WHEN Input.LocID IS NULL THEN 1 ELSE 0 END ) AS IsMissing
FROM ( SELECT DISTINCT LocID FROM #temp ) AS a
CROSS JOIN ( SELECT CONVERT( DATETIME, '2017-05-01' ) AS CDate ) AS RangeStart
LEFT JOIN
( SELECT LocID, MIN( CDate ) AS CDate
FROM #temp
WHERE CDate = '2017-05-01'
GROUP BY LocID ) AS Input ON a.LocID = Input.LocID AND RangeStart.CDate = Input.CDate
UNION ALL
SELECT a.LocID, a.CDate + 1 AS CDate,
ISNULL( ItExists, 0 ) AS IsMissing
FROM cte AS a
OUTER APPLY( SELECT LocID, 1 AS ItExists FROM #temp AS b WHERE a.LocID = b.LocID AND a.CDate + 1 = b.CDate ) AS c
WHERE a.CDate < '2017-07-01'
)
SELECT * FROM cte OPTION( MAXRECURSION 0 )
You can also add an index:
CREATE INDEX IX_tmp_LocID_CDate ON #temp( LocID, CDate )
Sample data set for the second query:
CREATE TABLE #temp( LocID VARCHAR( 50 ), CDate DATETIME )
INSERT INTO #temp
VALUES
( '1', '2017-05-01' ), ( '1', '2017-05-02' ), ( '1', '2017-05-03' ), ( '1', '2017-05-04' ), ( '1', '2017-05-05' ),
( '2', '2017-05-01' ), ( '2', '2017-05-02' ), ( '2', '2017-05-03' ), ( '2', '2017-05-04' ), ( '2', '2017-05-05' )
;WITH d AS (
SELECT CAST( '05/01/2017' AS DATETIME ) AS date
UNION ALL
SELECT date + 2
FROM d
WHERE date < '2018-07-31'
)
INSERT INTO #temp
SELECT LocID, d.date
FROM ( SELECT DISTINCT LocID FROM #temp ) AS a
CROSS JOIN d
OPTION( MAXRECURSION 0 )

How To Count Rows Based on Values of Two Variables in SSIS

I am fairly new to SSIS, and now I have this requirement to exclude weekends in order to do a performance management. Now I have created a calendar and marked the weekends; what I am trying to do, using SSIS, is get the start and end date of every status and count how many weekends are there. I am kind of struggling to know which component to use to achieve this task.
So I have mainly two tables:
1- Table Calendar
2- Table History-Log
Calendar has the following columns:
1- ID
2- date
3- year
4- month
5- day of week
6- isweekend
History-Log has the following:
1- ID
2- Status
3- startdate
4- enddate
Your help is really appreciated.
I'm not an SSIS user, so apologies if this answer does not help, but if I wanted to get the result you describe, based on some test data:
DECLARE #Calendar TABLE (
ID INT,
[Date] DATETIME,
[Year] INT,
[Month] INT,
[DayOfWeek] VARCHAR(10),
IsWeekend BIT
)
DECLARE #HistoryLog TABLE (
ID INT,
[Status] INT,
StartDate DATETIME,
EndDate DATETIME
)
DECLARE #StartDate DATE = '20100101', #NumberOfYears INT = 10
DECLARE #CutoffDate DATE = DATEADD(YEAR, #NumberOfYears, #StartDate);
INSERT INTO #Calendar
SELECT ROW_NUMBER() OVER (ORDER BY d) AS ID,
d AS [Date],
DATEPART(YEAR,d) AS [Year],
DATEPART(MONTH,d) AS [Month],
DATENAME(WEEKDAY,d) AS [DayOfWeek],
CASE WHEN DATENAME(WEEKDAY,d) IN ('Saturday','Sunday') THEN 1 ELSE 0 END AS IsWeekend
FROM
(
SELECT d = DATEADD(DAY, rn - 1, #StartDate)
FROM
(
SELECT TOP (DATEDIFF(DAY, #StartDate, #CutoffDate))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
ORDER BY s1.[object_id]
) AS x
) AS y;
INSERT INTO #HistoryLog
SELECT 1, 3, '2016-01-05', '2016-01-20'
UNION
SELECT 2, 7, '2016-01-08', '2016-01-25'
UNION
SELECT 3, 4, '2016-01-01', '2016-02-03'
UNION
SELECT 4, 3, '2016-02-09', '2016-02-10'
I would use a query like this to return all of the HistoryLog records with a count of the number of weekend days between their StartDate and EndDate:
SELECT h.ID,
h.[Status],
h.StartDate,
h.EndDate,
COUNT(c.ID) AS WeekendDays
FROM #HistoryLog h
LEFT JOIN #Calendar c ON c.[Date] >= h.StartDate AND c.[Date] <= h.EndDate AND c.IsWeekend = 1
GROUP BY h.ID, h.[Status], h.StartDate, h.EndDate
ORDER BY 1
If you wanted to know the number of weekends, rather than the number of weekend days, we'd need to slightly amend this logic (and define how a range containing only one weekend day - or one starting on a Sunday and ending on a Saturday inclusive - should be handled). Assuming you just want to know how many distinct weekends are at least partially within the date range, you could do:
SELECT h.ID,
h.[Status],
h.StartDate,
h.EndDate,
COUNT(weekends.ID) AS Weekends
FROM #HistoryLog h
LEFT JOIN
(
SELECT c.ID,
c.[Date] AS SatDate,
DATEADD(DAY,1,c.[Date]) AS SunDate
FROM #Calendar c
WHERE c.[DayOfWeek] = 'Saturday'
) weekends ON h.StartDate BETWEEN weekends.SatDate AND weekends.SunDate
OR h.EndDate BETWEEN weekends.SatDate AND weekends.SunDate
OR (h.StartDate <= weekends.SatDate AND h.EndDate >= weekends.SunDate)
GROUP BY h.ID, h.[Status], h.StartDate, h.EndDate

Rolling Prior13 months with Current Month Sales

Within a SQL Server 2012 database, I have a table with two columns customerid and date. I am interested in getting by year-month, a count of customers that have purchased in current month but not in prior 13 months. The table is extremely large so something efficient would be highly appreciated. Results table is shown after the input data. In essence, it is a count of customers that purchased in current month but not in prior 13 months (by year and month).
---input table-----
declare #Sales as Table ( customerid Int, date Date );
insert into #Sales ( customerid, date) values
( 1, '01/01/2012' ),
( 1, '04/01/2013' ),
( 1, '01/01/2014' ),
( 1, '01/01/2014' ),
( 1, '04/06/2014' ),
( 2, '04/01/2014' ),
( 3, '01/03/2012' ),
( 3, '01/03/2014' ),
( 4, '01/04/2012' ),
( 4, '04/04/2013' ),
( 5, '02/01/2010' ),
( 5, '02/01/2013' ),
( 5, '04/01/2014' )
select customerid, date
from #Sales;
---desired results ----
yearmth monthpurchasers monthpurchasernot13m
201002 1 1
201201 3 3
201302 1 1
201304 2 2
201401 2 1
201404 3 2
Thanks very much for looking at this!
Dev
You didn't provide the expected result, but I believe this is pretty close (at least logically):
;with g as (
select customerid, year(date)*100 + month(date) as mon
from #Sales
group by customerid, year(date)*100 + month(date)
),
x as (
select *,
count(*) over(partition by customerid order by mon
rows between 13 preceding and 1 preceding) as cnt
from g
),
y as (
select mon, count(*) as cnt from x
where cnt = 0
group by mon
)
select g.mon,
count(distinct(g.customerid)) as monthpurchasers,
isnull(y.cnt, 0) as cnt
from g
left join y on g.mon = y.mon
group by g.mon, y.cnt
order by g.mon
Tell me if this query helps. It extracts all the rows which meet your condition into a Table variable. Then, I use your query and join to this table.
declare #startDate datetime
declare #todayDate datetime
declare #tbl_Custs as Table(customerid int)
set #startDate = '04/01/2014' -- mm/dd/yyyy
set #todayDate = GETDATE()
insert into #tbl_Custs
-- purchased only this month
select customerid
from Sales
where ([date] >= #startDate and [date] <= #todayDate)
and customerid NOT in
(
-- purchased in past 13 months
select distinct customerid
from Sales
where ([date] >= DATEADD(MONTH,-13,[date])
and [date] < #startDate)
)
-- your query goes here
select year(date) as year
,month(date) as month
,count(distinct(c.customerid)) as monthpurchasers
from #tbl_Custs as c right join
Sales as s
on c.customerid = s.customerid
group by year(date) , month(date)
order by year(date) , month(date)
Below query will produce what you are looking for. I am not sure how performance will be on a big table (how big is your table?) but it is pretty straight forward so I think it will be ok. I simply calculate the 13 months earlier on CTE to find my sale window. Than join to the Sales table within that window / customer id and grouping records based on the unmatched records. You don't actually need 2 CTE's here you can do the DATEADD(mm,-13,date) on the join part of the second CTE but I thought it might be more clear this way.
P.S. If you need to change the time frame from 13 months to something else all you have to change is the DATEADD(mm,-13,date) this simply substracts 13 months from the date value.
Hope this helps or at least leads to a better solution
;WITH PurchaseWindow AS (
select customerid, date, DATEADD(mm,-13,date) minsaledate
FROM #Sales
), JoinBySaleWindow AS (
SELECT a.customerid, a.date,a.minsaledate,b.date earliersaledate
FROM PurchaseWindow a
LEFT JOIN #sales b ON a.customerid =b.customerid
--Find the sales for the customer within the last 13 months of original sale
AND b.date BETWEEN a.date AND a.minsaledate
)
SELECT DATEPART(yy,date) AS [year], DATEPART(mm, date) AS [month], COUNT(DISTINCT customerid) monthpurchases
FROM JoinBySaleWindow
--Exclude records where a sale within last 13 months occured
WHERE earliersaledate IS NULL
GROUP BY DATEPART(mm, date), DATEPART(yy,date)
Sorry about the typos they are fixed now.

getting month from a table according to the selected month

I am trying to get a report of patients department and month wise. I am selecting a department and the the month. How can I get only the selected months records department wise. I am trying the following query but not working :
SELECT MONTH(select convert(varchar,creation_Date,105) from Patient_Ref_master)
If you want a single month/year pair the following query will work:
select *
from Patient_Ref_Master
where Cast( '20130801' as Date ) <= Creation_Date and Creation_Date < Cast( '20130901' as Date )
It has the advantage that the query can use an index since it does not need to perform a calculation on each row.
It is often helpful to calculate the limits prior to the query, e.g. the current month:
declare #Start as Date = DateAdd( month, DateDiff( month, 0, GetDate() ), 0 );
declare #End as Date = DateAdd( month, 1, #Start );
select *
from Patient_Ref_Master
where #Start <= Creation_Date and Creation_Date < #End
EDIT: If the use of comparison operators thrown together willy-nilly with boolean operators is overwhelming, I offer the following simplification:
declare #Patient_Ref_Master as Table ( Id Int Identity, Creation_Date Date );
insert into #Patient_Ref_Master ( Creation_Date ) values
( '20130731' ), ( '20130801' ), ( '20130815' ), ( '20130831' ), ( '20130901' );
select * from #Patient_Ref_Master;
declare #Start as Date = DateAdd( month, DateDiff( month, 0, Cast( '20130829' as Date ) ), 0 );
declare #End as Date = DateAdd( month, 1, #Start );
-- Incomprehensible WHERE clause:
select *
from #Patient_Ref_Master
where #Start <= Creation_Date and Creation_Date < #End;
-- Simplified AB version:
with
JustRight as (
select *
from #Patient_Ref_Master
where Creation_Date in ( #Start ) ),
NotTooLate as (
select *
from #Patient_Ref_Master
where Sign( DateDiff( day, #End, Creation_Date ) ) in ( -1 ) ),
NotTooSoon as (
select *
from #Patient_Ref_Master
-- NB: Do NOT include zero in the set of matches. That would be too easy.
where Sign( DateDiff( day, Creation_Date, #Start ) ) in ( -1 ) ),
TheResult as (
select *
from JustRight
union
select *
from NotTooLate
intersect
select *
from NotTooSoon )
select * from TheResult;
No, IN is not listed in the documentation as a comparison operator.
select * from Patient_Ref_master
where MONTH(creation_Date)=1 --target month here, JAN as example
SQLFIDDLE
Also I found similar question where you can get extra workaround as well as other answer in this thread.

Resources