I am new to SQL query world and got stuck into one requirement.
In my Query i have toDate and fromdate input parameter, based on business logic it will return result like below.
Result:-
Month
Dec-16
Dec-16
Dec-16
Feb-17
Feb-17
Mar-17
Mar-17
now query should need to return the data for each month , if we dont have data for perticular month(in image which is Jan) then it should insert data and return data for that month too, in image we can see for Jan we dont have any data.
You can use a calendar or dates table for this sort of thing.
Without a calendar table, you can generate an adhoc set of months using a common table expression with just this:
declare #fromdate date = '20161201';
declare #todate date = '20170301';
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, Months as (
select top (datediff(month, #fromdate, #todate)+1)
[Month]=convert(date,dateadd(month,row_number() over(order by (select 1))-1,#fromdate))
from n as deka cross join n as hecto cross join n as kilo
order by [Month]
)
/* your query here: */
select
d.[Month]
, sum_col = sum(t.col)
from Months
left join tbl t
on d.[Month] = t.[Month]
group by d.[Month]
Number and Calendar table reference:
Generate a set or sequence without loops - 2 - 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
Solved Query:-
Declare #customDate DATETIME
declare #datafound integer
set #customDate = #fromDate
WHILE #customDate < #toDate
BEGIN
select #datafound = count(1) from #temp where datepart(month, MonthDate) = datepart(month, #customDate)
if #datafound = 0
select Format(#customDate,'MMM-yy') as Month
SET #customDate = DATEADD(month, 1,#customDate)
END;
Related
This is a fairly common issue that I've seen a lot of people duck tape together solutions for, but it's never quite right. Hoping this forum can get it ironed out. I have a table:
create table temp
( PatientID varchar(12),
AdmitDate datetime,
DischargeDate datetime
)
insert into temp values ('Patient1','1/30/2020 13:23:44', '2/2/2020 15:12:52')
What I'd like to count is the number of midnights the patient was admitted in the correct month. So in the example above the patient would be admitted at midnight on 1/31, 2/1, and 2/2 dates. So my output in sql should look something like:
01-2020 02-2020
------- --------
1 2
I know it has to be dynamic SQL, because the columns need to be created with respect to the date range queried. Although, I'm pretty stumped as to next steps.
create table #temp
( PatientID varchar(12),
AdmitDate datetime,
DischargeDate datetime
)
insert into #temp values ('Patient1','1/30/2020 13:23:44', '2/2/2020 15:12:52')
--Virtually creates a dates table
;with dates(thedate) as (
select dateadd(yy,years.number,0)+days.number
from master..spt_values years
join master..spt_values days
on days.type='P' and days.number < datepart(dy,dateadd(yy,years.number+1,0)-1)
where years.type='P' and years.number between 100 and 150
-- note: 100-150 creates dates in the year range 2000-2050
-- adjust as required
)
select dateadd(m,datediff(m, 0, d.thedate),0) [Month], count(1) PatientDays
from dates d
join #temp t on d.thedate between t.[AdmitDate] and t.[DischargeDate]
group by datediff(m, 0, d.thedate)
order by [Month];
I have a table containing 3 columns [MONTHNAME], [MONTHSTART] and [MONTHEND]. For reporting, I need to group all prior months together but leave the current month grouped by weeks. To do this I need to get the prior month's ending date. Below is the query I am using and it works properly, but is there a better way of determining the prior month's ending date without creating a table or using CTE with the LAG function? There was no way I found to get the LAG function to return a single value so I had to use the following. Our month ending dates do not fall on the calendar month ending date so I am pulling the data from a custom calendar.
DECLARE #tblMonthEndingDates TABLE
([MONTHSTART] DATE
,[MONTHEND] DATE
)
INSERT INTO #tblMonthEndingDates
VALUES('01-01-2018', '01-26-2018'),
('01-27-2018', '03-02-2018'),
('03-03-2018', '03-30-2018'),
('03-31-2018', '04-27-2018'),
('04-28-2018', '06-01-2018'),
('06-02-2018', '06-30-2018'),
('07-01-2018', '07-27-2018'),
('07-28-2018', '08-31-2018'),
('09-01-2018', '09-28-2018'),
('09-29-2018', '10-26-2018'),
('10-27-2018', '11-30-2018'),
('12-01-2018', '12-31-2018')
DECLARE #dtTbl TABLE(RN INT
,MS DATE
,ME DATE
);
INSERT INTO #dtTbl
SELECT ROW_NUMBER() OVER(ORDER BY [MONTHSTART]) AS ROWNUM
,[MONTHSTART]
,[MONTHEND]
FROM #tblMonthEndingDates;
WITH cteDt
AS
(
SELECT d2.[MS]
,LAG(d1.[ME]) OVER(ORDER BY d1.[MS]) AS PRIORDT
,d2.[ME]
FROM #dtTbl d1
LEFT JOIN #dtTbl d2 ON d1.[RN] = d2.[RN]
)
SELECT [PRIORDT]
FROM cteDt
WHERE [MS] <= GETDATE() AND [ME] >= GETDATE()
So for this month I would want 09-28-2018 as the return value which the query does, I just want to know if there is a better/shorter way of returning that value.
You can do something like this:
Select
Max(MonthEnd)
From #tblMonthEndingDates
Where MonthEnd <= GetDate()
That will give you the most recent MonthEnd date before or on today's date. Obviously, if you need strictly before, you'd use < rather than <=
I used this query to get the start and end dates for the last #n months. You can adapt to meet your needs.
declare #t table (SD date,ED date)
declare #i int = 0
,#SD date
,#ED date
,#datetoEval date
,#EOM date
,#n int = 60
while(#i<=#n)
BEGIN
set #datetoEval = DATEADD(month,-1*#i,getdate())
set #SD = cast(cast(month(#datetoEval) as varchar(2)) + '/1/' + cast(year(#datetoEval) as varchar(4)) as DATE)
set #ED = dateadd(day,-1,DATEADD(MONTH,1,#SD))
insert into #t
values(#SD,#ED)
set #i=#i+1
END
select * from #t
I was overthinking it. Last month ended the day before this one started.
SELECT DATEADD(DAY, -1, MONTHSTART) AS MONTHEND
FROM #tblMonthEndingDates
WHERE
GETDATE() BETWEEN MONTHSTART AND MONTHEND
I create a view like this. Can I add an clustered index on the column "Start" and will it have any effect?
create view v_Weeks with SchemaBinding as
with Periodes AS
(
SELECT start = CONVERT(Date,'2013-08-05')
union ALL
SELECT DateAdd(day,7,start) from Periodes where start < dateadd(year,2,GETDATE())
)
select Start,DATEADD(DAY,7,Start) as [End], datepart(ISO_WEEK,start) as week, DATEPART(YEAR,Start) as Year from Periodes
You need a calendar table. This is exactly what you are trying to do without technically doing it.
If you just need one day per week and only for two years than you could use something like this:
declare #fromdate date = '20130805';
declare #thrudate date = '20150805';
create table v_Weeks (
[Start] date not null primary key
, iso_week tinyint not null
, [year] smallint not null
);
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, dates as (
select top (datediff(week,#fromdate,#thrudate)+1)
[Date]=convert(date
,dateadd(week, row_number() over (order by (select 1)) -1, #fromdate)
)
from n as deka cross join n as hecto cross join n as kilo cross join n as tenK
order by [Date]
)
insert into v_weeks
select
Start = [Date]
, iso_week = datepart(iso_week,[date])
, [year] = datepart(year,[date])
from dates ;
Calendar and Numbers table references:
Calendar Tables - Why You Need One - David Stein
Generate a set or sequence without loops - 1 - Aaron Bertrand
Generate a set or sequence without loops - 3 - Aaron Bertrand
Creating a Date Table/Dimension in SQL Server 2008 - David Stein
Creating a date dimension or calendar table in SQL Server - Aaron Bertrand
TSQL Function to Determine Holidays in SQL Server - Tim Cullen
The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
What's an alternative to getting a distinct number of dates, say all the dates for September:
9/1/2016
9/2/2016
9/3/2016
and apply each value to a query. Say something like:
Select GuitarId,GuitarBrand
From GuitarSales
Where GuitarDate = #date
I don't want to use a cursor, is there an alternative to doing this?
I tried a CTE but even then I'd have to apply the cursor for each date.
If you want all the dates for a month you can use
Select GuitarId,GuitarBrand
From GuitarSales
Where month(GuitarDate) = 9
and year(GuitarDate) = 2016;
If I understand you correctly, you need a list of all dates in September. This is a quick solution to get a gapless list of all days in September: In your query you can use this as source and LEFT JOIN your actual data.
WITH RunningNumbers AS
(
SELECT TOP(30) ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1 AS Nr
FROM sys.objects
)
SELECT {d'2016-09-01'}+Nr AS RunningDate
FROM RunningNumbers
There are many examples, how you can create a tally table on the fly. Small numbers (like 30 in this example) can be taken easily from any table with sufficient rows.
If you need this more often you might think about a Numbers-Table
a related question: https://stackoverflow.com/a/39387790/5089204
create a persitant numbers table with a lot of usefull side data: https://stackoverflow.com/a/32474751/5089204
Assuming you have an index on GuitarDate here is a way you can create a SARGable where predicate so you can still leverage the speed of using an index seek.
declare #date datetime = '2016-09-10' --just to demonstrate starting with September 10, 2016
select gs.GuitarId
, gs.GuitarBrand
From GuitarSales gs
where gs.GuitarDate >= dateadd(month, datediff(month, 0, #date), 0) --beginning of the month for #date
and gs.GuitarDate < dateadd(month, datediff(month, 0, #date) + 1, 0) --beginning of next month
I am struggling to create data where there is no data, I have a query that returns similar data to this:
This table shows that for client 123 we had payments in June, July and December only.
The only notable item in the query is a DATEDIFF between the month opened and MonthPayment (this gives the mnth column).
Now where I’m falling over is I need to create the above columns but for all the months that have passed regardless like below
Ignore the month payment field for the none paying months, this shouldn't return anything!
What you need to do is connect this table to a list of all values you might want, as in the question pointed to by Zohar Peled. Your case is slightly complicated, since presumably you need to be able to return multiple clients at a time and only want to see data that pertains to that client's start and end range. I've adapted code from a similar answer I posted some time ago, which should show you how this is done.
-- set up some sample data
DECLARE #MyTable TABLE
(
ClientNo INT,
Collected NUMERIC(5,2),
MonthPayment DATETIME,
MonthOpened DATETIME,
CurrentMonth DATETIME
)
INSERT INTO #MyTable
(
ClientNo,
Collected,
MonthPayment,
MonthOpened,
CurrentMonth
) -- note: I'm in the US, so I'm using the US equivalent of the months you asked for
SELECT 123, 147.25, '7/1/2014', '12/1/2013', '4/1/2015'
UNION
SELECT 123, 40, '12/1/2014', '12/1/2013', '4/1/2015'
UNION
SELECT 123, 50, '6/1/2014', '12/1/2013', '4/1/2015'
-- create a recursive CTE that contains a list of all months that you could wish to see
--define start and end limits
Declare #todate datetime, #fromdate datetime
Select #fromdate=(SELECT MIN(MonthOpened) FROM #MyTable), #todate=DATEADD(MONTH, 1, GETDATE())
-- define CTE
;With DateSequence( DateValue ) as
(
Select #fromdate as DateValue
union all
Select dateadd(MONTH, 1, DateValue)
from DateSequence
where DateValue < #todate
)
--select result
SELECT
ClientStartEnd.ClientNo,
ISNULL(MyTable.Collected, 0.00) AS Collected,
DateSequence.DateValue AS MonthPayment,
ClientStartEnd.MonthOpened,
DATEDIFF(MONTH, ClientStartEnd.MonthOpened, DateSequence.DateValue) + 1 AS Mnth,
ClientStartEnd.CurrentMonth
FROM
DateSequence
INNER JOIN
(
SELECT DISTINCT
ClientNo,
MonthOpened,
CurrentMonth
FROM #MyTable
) ClientStartEnd ON
DateSequence.DateValue BETWEEN
ClientStartEnd.MonthOpened AND
ClientStartEnd.CurrentMonth
LEFT JOIN
#MyTable MyTable ON
ClientStartEnd.ClientNo = MyTable.ClientNo AND
DateSequence.DateValue = MyTable.MonthPayment
OPTION (MaxRecursion 0)