Split a date into two dates - sql-server

I want to splite two dates into four dates:
Date1:04/01/2012
Date2:12/05/2015
The result that I want is
If datepart(year,date2)=datepart(year,getdate())
Case1
Date1:04/01/2012
Date2:31/12/2014
Date3:01/01/2015
Date4:12/05/2015
Else
Case2
Date1:04/01/2012
Date2:12/05/2015
My question how to obtain date2 and date3 in case1?

You can kind of create them like this:
select '01/01/'+(select cast(datepart(yy,getdate()) as varchar))
select '31/12/'+(select cast(datepart(yy,getdate())-1 as varchar))

If I understand you correctly you want to add "fake" records to your select statement if the first date is in a year that is before the current year and the second one is in the current year.
I've taken the liberty to assume you don't want to add 31/12/ if the first date is actually the 31 of December in the last year.
Here is my suggestion:
;With cte as (
SELECT DateValue, ROW_NUMBER() OVER (ORDER BY DateValue) As rn
FROM Tbl
)
-- Get the first date
SELECT DateValue
FROM cte
WHERE rn = 1
UNION ALL
/*
Add the last date of the previous year.
The where clause will enable you to add this to the select result only on your terms
(if the second date is on the current year and the first date is before Dec 31th of the last year)
*/
SELECT CAST(CAST(YEAR(GETDATE())-1 as varchar) + '-12-31' as date)
FROM cte
WHERE rn = 2
AND YEAR(DateValue) = YEAR(GETDATE())
AND CAST(CAST(YEAR(GETDATE())-1 as varchar) + '-12-31' as date) > (SELECT DateValue FROM cte WHERE rn = 1)
UNION ALL
/*
Add the first date of the current year.
Note that the where clause here is only testing that the second date is on the current year,
while the first date is before the current year.
So it will add the Jan 1st of the current year even if the first date is Dec 31th of the last year.
*/
SELECT CAST(CAST(YEAR(GETDATE()) as varchar) + '-01-01' as date)
FROM cte
WHERE rn = 2
AND YEAR(DateValue) = YEAR(GETDATE())
AND YEAR(GETDATE()) > (SELECT YEAR(DateValue) FROM cte WHERE rn = 1)
UNION ALL
-- add the second date
SELECT DateValue
FROM cte WHERE rn = 2
You can see it working on this fiddle.

Related

How to get third Thursday of last month

My report generator should get all entries from third Thursday of last month.
How can I achieve This in ms sql server ?
You can use something like this:
WHERE DATEPART(dw,[YourDateColumn]) = 5 -- Thursday
AND DATEPART(d,[YourDateColumn]) BETWEEN 15 AND 21 -- Third thursday in month
AND DATEDIFF(m,GETDATE(),[YourDateColumn])=-1 -- Last month
But you need to be aware that the query could be slow because of the functions.
See also: Avoid Using Function in WHERE Clause. Why?
For something like this is so much easier with a calender table. See
Create a calender date table
The code below finds the third Thursday of last month
(last = previous or last = current? if last = current, substitute dateadd (month, -1, getdate()) with getdate()).
You can wrap it into a function and use in WHERE clause of your query
or calculate it just before filtering using a variable,
so your query will look like
SELECT...WHERE dt >= fn()
or
SELECT...WHERE dt >= #dt
option(recompile)
So indexes(if any) can be used
with nums as -- numbers 1..31
(
select number as n
from master..spt_values
where type = 'p'
and number between 0 and 30
)
, thur as
(
select dt,
dd,
ROW_NUMBER() over (order by dt) as rn
from nums cross apply
(
select cast(convert(char(6), dateadd (month, -1, getdate()), 112) + '01' as date) as dt0 -- the first of last month
)a
cross apply( select dateadd(day, n, dt0) as dt) a1
cross apply( select datename(dw, dt) as dd, month(dt) as mm) a2
where dd = 'Thursday' and mm = month(dt0)
)
select dt
from thur
where rn = 3;

TSQL Query 12 months of Data - Include Months without records

I am trying to create a 12 month grid view of all questions that were submitting for each month in that 12 month period.
SELECT
YEAR(h.metaInsert) [Year],
MONTH(h.metaInsert) [Month],
DATENAME(MONTH,h.metaInsert) [Month Name],
COUNT(1) [Total Documents]
FROM
Document_Count_History AS h
WHERE
YEAR(h.metaInsert) = 2017
GROUP BY
YEAR(h.metaInsert), MONTH(h.metaInsert), DATENAME(MONTH, h.metaInsert)
ORDER BY
1, 2
This returns the data perfectly for the months that have it, but I get no data returned for those with 0 records for that specific month.
My goal is to see all 12 months along with the count of documents. If there are no documents, it will simply be a 0 for that month but it will be included in the result set.
How can I take what I have and apply the missing months?
You could use something like this to generate the sequence of months for your query:
declare #StartDate date = '20170101'
,#NumberOfYears int = 1;
;with Months as (
select top (12*#NumberOfYears)
[Month] = dateadd(Month, row_number() over (order by number) -1, #StartDate)
, NextMonth = dateadd(Month, row_number() over (order by number), #StartDate)
from master.dbo.spt_values
)
select
year(m.Month) [Year],
Month(m.Month) [Month],
datename(Month,m.Month) [Month Name],
count(h.*) [Total Documents]
from Months as m
left join Document_Count_History AS h
on h.metaInsert >= m.Month
and h.metaInsert < m.NextMonth
--where h.metaInsert >= '20170101'
group by m.Month
order by m.Month
Although you may want to consider adding a Calendar table, or Date Dimension.
Calendar and Numbers table references:
Generate a set or sequence without loops - 1 - Aaron Bertrand
The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
Creating a Date Table/Dimension in SQL Server 2008 - David Stein
Calendar Tables - Why You Need One - David Stein
Creating a date dimension or calendar table in SQL Server - Aaron Bertrand
An example months table:
create table dbo.Months(
MonthStart date not null primary key
, NextMonthStart date not null
, [Year] smallint not null
, [Month] tinyint not null
, [MonthName] varchar(16) not null
);
declare #StartDate date = '20100101'
,#NumberOfYears int = 30;
insert dbo.Months(MonthStart,NextMonthStart,[Year],[Month])
select top (12*#NumberOfYears)
[MonthStart] = dateadd(month, row_number() over (order by number) -1, #StartDate)
, NextMonthStart = dateadd(month, row_number() over (order by number), #StartDate)
, [year] = year(dateadd(month, row_number() over (order by number) -1, #StartDate))
, [Month] = Month(dateadd(month, row_number() over (order by number) -1, #StartDate))
, MonthName = datename(Month,dateadd(month, row_number() over (order by number) -1, #StartDate))
from master.dbo.spt_values;
and your query would simplify to:
select
m.[Year],
m.[Month],
m.[MonthName],
count(h.*) [Total Documents]
from Months as m
left join Document_Count_History AS h
on h.metaInsert >= m.MonthStart
and h.metaInsert < m.NextMonthStart
where m.Year = 2017
group by m.Month, m.Year, m.MonthName
order by m.MonthStart
You need a date dimension. Specifically, you need a table that has all the values for months. Then, you can do a left-join on the table that gets the totals, and pull out a sum value.

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)

Group by Week in Transact-SQL

I'm attempting to get a count of items within a 7-day period, while still being able to report the first date of said seven day period. The closest I've come so far is
WITH w (w, n)
AS
(
SELECT MIN(CAST(CreatedDate AS DATE))
OVER(
PARTITION BY DATEPART(WEEK, CAST(CreatedDate AS DATE))
ORDER BY DATEPART(WEEK, CAST(CreatedDate AS DATE))
)
,COUNT(*)
FROM dbo.Tbl
WHERE
CreatedDate >= CAST(DATEADD(MONTH,-6,GETDATE()) AS DATE)
GROUP BY CAST(CreatedDate AS DATE)
)
SELECT w.w AS [Week of], SUM(w.n) AS [Items]
FROM w
GROUP BY w.w
ORDER BY 1 DESC
But this unfortunately does not work for the first or last week of the year, and will not work if the date range includes more than one year.
Is there a way to group by a seven day period while still being able to get the first date in said period?
You can use the following to get your weeks based on your date value. You will need to play around with the days added to get the right days of the week for your situation, but you will always be guaranteed the same start and end day. In this case the week starts on Sunday and ends on Saturday:
declare #Date datetime
set #Date = '20160802'
select #Date - DATEPART(dw, #Date) + 1 as FirstDateOfWeek
,#Date + (7 - DATEPART(dw, #Date)) as LastDateOfWeek
For a fully bulletproof solution you could add in logic to check and use ##DATEFIRST and assign the adjustment values accordingly.
Once you have your week start value, you can use that in your group by:
select CreatedDate - DATEPART(dw, #Date) + 1 as FirstDateOfWeek
,count(*)
from tbl
group by CreatedDate - DATEPART(dw, #Date) + 1
order by FirstDayOfWeek

SQL SMS 2008 -Count column ids and count duplicate ids if createddate is greater than 3 months between ids

*Edit (Hopefully to be more clear)
Table below, I would like to count ids and count duplicate ids where the createddate has a gap of 3 months or more for that ID.
Query I have so far...
if object_id('tempdb..#temp') is not null
begin drop table #temp end
select
top 100
a.id, a.CreatedDate
into #temp
from tbl a
where 1=1
--and year(CreatedDate) = '2015'
if object_id('tempdb..#temp2') is not null
begin drop table #temp2 end
select t.id, count(t.id) as Total_Cnt
into #temp2
from #temp t
group by id
select distinct #temp2.Total_Cnt, #temp2.id, #temp.CreatedDate, DENSE_RANK() over (partition by #temp.id order by createddate) RK
from #temp2
inner join #temp on #temp2.id = #temp.id
where 1=1
order by Total_Cnt desc
Results:
Total_cnt id createddate rk
3 1 01-01-2015 1
3 1 03-02-2015 2
3 1 01-02-2015 3
2 2 05-01-2015 1
2 2 05-02-2015 2
1 3 06-01-2015 1
1 4 07-01-2015 1
Count ids and only count duplicate ids when the createddate from the id is greater than 3 months.
Something like this...
Total_cnt id Countwith3monthgap
3 1 2
2 2 1
1 3 1
1 4 1
You can use a cte and ROW_NUMBER to get your order and self join the cte based on the order..
WITH cte AS
( SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY CreatedDate) Rn
FROM
Test
)
SELECT
c1.ID,
COUNT(CASE WHEN c2.CreatedDate IS NULL THEN 1
WHEN c1.CreatedDate >= DATEADD(month,3,c2.CreatedDate) THEN 1
END)
FROM
cte c1
LEFT JOIN cte c2 ON c1.ID = c2.ID
AND c1.RN = c2.RN + 1
GROUP BY
c1.ID
You also need to use a conditional count where the Previous CreatedDate is null or if the Current CreatedDate is >= the Previous CreatedDate + 3 months
If you happen to be using SQL 2012+ you can also use LAG here to get the same result
SELECT
ID,
COUNT(*)
FROM
(SELECT
ID,
CreatedDate CurrentDate,
LAG(CreatedDate) OVER (PARTITION BY ID ORDER BY CreatedDate) PreviousDate
FROM
Test
) T
WHERE
PreviousDate IS NULL
OR CurrentDate >= DATEADD(month, 3, PreviousDate)
GROUP BY
ID
You can use a lag to get the previous date, Null for the first in the list
SELECT
id,
lag(CreatedDate,1) OVER (PARTITION BY Id ORDER BY CreatedDate) AS PreviousCreateDate,
CreatedDate
FROM #t
You can use that as a subquery and get the difference in months using DATEDIFF
SELECT sub.id,DATEDiff(month, sub.PreviousCreateDate ,sub.CreatedDate)
FROM (SELECT
id,
lag(CreatedDate,1) OVER (PARTITION BY Id ORDER BY CreatedDate) AS PreviousCreateDate,
CreatedDate
FROM #t) sub
WHERE DATEDiff(month, sub.PreviousCreateDate ,sub.CreatedDate) >=3
OR sub.PreviousCreateDate IS NULL
You can then take your totals
SELECT sub.id,COUNT(sub.id) as cnt
FROM (SELECT
id,
lag(CreatedDate,1) OVER (PARTITION BY Id ORDER BY CreatedDate) AS PreviousCreateDate,
CreatedDate
FROM #t) sub
WHERE DATEDIFF(month, sub.PreviousCreateDate ,sub.CreatedDate) >=3
OR sub.PreviousCreateDate IS NULL
GROUP BY sub.id
Note that using datediff the last day of january is three months before the first day of march. That appears to be the logic you were after.
You might want to define your three month gap criteria as
WHERE sub.PreviousCreateDate <= DATEADD(month, -3, sub.CreatedDate)
OR sub.PreviousCreateDate IS NULL
or
WHERE sub.CreatedDate >= DATEADD(month, +3, sub.PreviousCreateDate )
OR sub.PreviousCreateDate IS NULL
I'm guessing that your desired definition of three-month gap doesn't coincide with datediff()'s. Most of the logic here is to look back at the previous date and decide if the gap is big enough to qualify.
When datediff() counts three months difference we still need to make sure the day of month is later than the first one (per example and ID 5). If difference is more than three months then we're good automatically.
But I'm also assuming that you would want to treat the distance from November 30th to February 28th (or 29th in a leap year) as a full three months because the end date falls on the final day of the month. By adjusting the end date by an extra day this is an easy scenario to snag as it will bump the date into the following month and increase the month difference by one as well. If that's not what you want then just remove the dateadd(day, 1, ...) portion and use only the raw CreatedDate value.
You sample data is limited so I'm also making the assumption that the gaps are measure between consecutive dates. If you're wanting to find blocks of runs that don't span more than three months across the set, then that's a different problem and you should clarify with more information.
Since you've indicated that you're probably on SQL Server 2008 you'll have to do without the lag() function. Although the first query could be adjusted for that it's likely easier to go with the second approach at the end.
with diffs as (
select
ID,
row_number() over (partition by ID order by CreatedDate) as RN,
case when
datediff(
month,
lag(CreatedDate, 1) over (partition by ID order by CreatedDate),
CreatedDate
) = 3
and
datepart(
day,
lag(CreatedDate, 1) over (partition by ID order by CreatedDate)
) <= datepart(day, CreatedDate)
or
datediff(
month,
lag(CreatedDate, 1) over (partition by ID order by CreatedDate),
/* adding one day to handle gaps like Nov30 - Feb28/29 and Jan31 - Apr30 */
dateadd(day, 1, CreatedDate)
) >= 4
then 1
else 0
end as GapFlag
from <T> /* <--- your table name here */
), gaps as (
select
ID, RN,
sum(1 + GapFlag) over (partition by ID order by RN) as Counter
from diffs
)
select ID, count(distinct Counter - RN) as "Count"
from gaps
group by ID
The rest of the logic is a typical gaps and islands scenario looking for holes in the sum(1 + GapCount) sequence with the offset of 1 acting pretty much like row_number().
http://sqlfiddle.com/#!6/61b12/3
JamieD77's approach is also valid. I was originally thinking your problem involved more than looking at the rows in sequence. Here's how I would tweak it for the gap definition I've been running with:
with data as (
select ID, CreatedDate, row_number() over (partition by ID order by CreatedDate) as RN
from T
)
select ID, count(*) as "Count"
from data d1 left outer join data d0
on d0.ID = d1.ID and d0.RN = d1.RN - 1 /* connect to the one before */
where
datediff(month, d0.CreatedDate, d1.CreatedDate) = 3
and datepart(day, d0.CreatedDate) <= datepart(day, d0.CreatedDate)
or datediff(month, d0.CreatedDate, dateadd(day, 1, d0.CreatedDate)) >= 4
or d0.ID is null
group by ID
Edit: You have changed the question since yesterday.
Change this line in the first query to include the total count:
...
select count(*) as TotalCnt, ID, count(distinct Counter - RN) as GapCount
...
Second would look like:
with data as (
select ID, CreatedDate, row_number() over (partition by ID order by CreatedDate) as RN
from T
)
select
count(*) as TotalCnt, ID,
count(case when
datediff(month, d0.CreatedDate, d1.CreatedDate) = 3
and datepart(day, d0.CreatedDate) <= datepart(day, d0.CreatedDate)
or datediff(month, d0.CreatedDate, dateadd(day, 1, d0.CreatedDate)) >= 4
or d0.ID is null then 1 end
) as GapCount
from data d1 left outer join data d0
on d0.ID = d1.ID and d0.RN = d1.RN - 1 /* connect to the one before */
where
group by ID

Resources