I have an audit table that stores a master table's "status" values in multiple rows, each row has a date/time stamp. Let's call the master table "Project" and it moves through different status changes: Open, Pending, Closed. However sometimes the Project can move back to Open after it's been closed. Data would be something like this:
ProjectId Date Status
1234 07-01-2015 Open
1234 07-03-2015 Pending
1234 07-05-2015 Closed
1234 07-06-2015 Open
I need to take a date parameter value, and determine what status the Project "1234" was in on that given date.
ex: Date=07-02-2015 would give me "Open"
Date=07-07-2015 would also give me "Open"
Date=07-03-2015 would give me "Pending"
The problem I'm having is that the SQL needs to look at the surrounding rows (if any)
1) Limit the rows to only rows with date inside the window.
2) Sort the result by date descending (putting the most recent at the top)
3) Select the first row
DECLARE #CheckDate DATETIME = '07-07-2015',
#ProjectId INT = 1234
-- To ignore time, set the check date to the first millisecond of the
-- next day and use a less than operator
SET #CheckDate = DATEADD(dd, DATEDIFF(dd, 0, #CheckDate) + 1, 0)
SELECT TOP 1
[Status]
FROM [Project]
WHERE [ProjectId] = #ProjectId
AND [Date] < #CheckDate
ORDER BY [Date] DESC
If you have 2012 or later, you can do this with Lead as follows:
declare #date datetime = '7-2-15' --or whatever
select ProjectID, Status from (
select *
, lead(date) over (partition by projectID order by date) as NextEvent
from MyTable) a
where #date between [date] and isnull(NextEvent, #date + 1)
Otherwise, you can approximate lead using row_number and a self-join.
Note, depending on how precise your dates are, you may want to use something like where #date >= date and #date < isnull(NextEvent, #date + 1) instead of between.
Related
I have a set of date in a Table which contains weekly date.
I want to select the following:
If the date is less than 2 months old then i want to select all the date (weekly).
If the date is more than 2 months old then i only want to select the last date of each month (monthly).
I tried the following code:
SELECT DISTINCT(Date) FROM [Table] WHERE Date IN
(CASE
WHEN Date> DATEADD(month, -2, GETDATE())
THEN Date
ELSE MAX(Date) GROUP BY Month(Date),Year(Date)
);
But without success:
Incorrect syntax near the keyword 'GROUP'.
If for instance the current Date is 13/09/2022,
13/09/2022 - 2 months = 13/07/2022
If i have the following Date in my Table:
06/05/2022
13/05/2022
20/05/2022
31/05/2022
07/06/2022
10/06/2022
17/06/2022
24/06/2022
30/06/2022
08/07/2022 (<13/07/2022)
15/07/2022 (>13/07/2022)
22/07/2022
29/07/2022
05/08/2022
12/08/2022
19/08/2022
26/08/2022
Then the final output should be:
31/05/2022
30/06/2022 (<13/07/2022)
15/07/2022 (>13/07/2022)
22/07/2022
29/07/2022
05/08/2022
12/08/2022
19/08/2022
26/08/2022
Your syntax is completely invalid, I'm not going to bother fixing it.
DISTINCT is not a function, it works over the whole set of columns.
You can't use aggregates inside a WHERE, even if they would be window functions (which they're not).
The GROUP BY is inside a CASE which makes no sense.
Instead I'm just going off your requirements
If the date is less than 2 months old then I want to select all the date (weekly).
If the date is more than 2 months old then I only want to select the last date of each month (monthly).
You can use a ROW_NUMBER strategy for this.
SELECT
t.Date
FROM (
SELECT *,
rn = ROW_NUMBER() OVER (PARTITION BY EOMONTH(t.Date) ORDER BY t.Date DESC)
FROM [Table] t
) t
WHERE (
t.Date > DATEADD(month, -2, GETDATE())
OR rn = 1
)
ORDER BY
Date;
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
EDIT -----------------
I'm going to try to clarify.
I have this table:
A user will input a start date and an end date.
Let's say the user inputs 2011-10-21 for the start and 2011-12-18 for the end.
This will bring back all the records:
But what if the user inputs 2011-10-22 and 2011-12-18?
The 2011-10-22 is BETWEEN the date range of the FIRST ROW 2011-10-21 (start) & 2011-10-23 (end).
SINCE IT IS BETWEEN THE DATE RANGE OF THE FIRST RECORD, I WANT to return that row as well. I DO NOT WANT TO EXCLUDE IT.
So if if I run a query like this:
select * from table where PayPeriodStart between '2011-10-22' and '2011-12-18'
I WANT to see this:
and NOT this:
PLEASE NOTE... the user can enter in any start or end date and it'll be at least 1 week.
So they can pick 2011-11-09 (start) and 2011-12-04 (end) for example.
You'll want the search criteria to return the rows where the period start date is before the end date criteria and the period end date is after the start date criteria. In other words:
declare #Table table (PayPeriodStart smalldatetime, PayPeriodEnd smalldatetime)
insert into #Table (PayPeriodStart, PayPeriodEnd)
select '2010-01-01', '2010-12-31' -- test before the criteria; is not returned
union select '2011-10-21', '2011-10-23' -- overlaps start date; is returned
union select '2011-10-24', '2011-11-06' -- fully in date range; is returned
union select '2011-11-07', '2011-11-20' -- fully in date range; is returned
union select '2011-11-21', '2011-12-04' -- fully in date range; is returned
union select '2011-12-05', '2011-12-18' -- overlaps end date; is returned
union select '2012-01-01', '2012-12-31' -- test after the criteria; is not returned
-- This yields all but the first row
select * from #Table where PayPeriodStart between '2011-10-22' AND '2011-12-10'
-- This will yield all the rows that overlap the search criteria
select * from #Table
where PayPeriodStart <= '2011-12-10' -- the end search date
and PayPeriodEnd >= '2011-10-22' -- the start search date
If you are looking for all records that are valid between '2011-10-22' AND '2011-12-10' , this is the query you should use
select * from table where ('2011-10-22' >= PayPeriodStart AND '2011-10-22' <= PayPeriodEnd) Or ('2011-10-22' < PayPeriodStart AND '2011-12-10' > PayPeriodEnd)
I have never been a fan of the between syntax for the edge case reason, so I tend to use this syntax instead:
select * from table where ('2011-10-22' <= PayPeriodStart AND PayPeriodStart <= '2011-12-10')
Same end result, but I can ensure I include the boundaries as needed.
Are you asking for
select * from table where '2011-10-22' between PayPeriodStart AND PayPeriodEnd
AND '2011-12-10' between PayPeriodStart AND PayPeriodEnd
If you want to get data based on first of the month and last of the month or for fixed time interval like 30 day window, you may have to use DateAdd, DateDiff functions to calculate dates.
Following example is based on FirstOfTheMonth, LastOfTheMonth calculation
your reference data
declare #refDate date = getdate()
select #min = dateadd(mm, datediff(mm, 0, #refDate), 0) -- First of the month
, #max = dateadd(dd, -1, dateadd(mm, datediff(mm, 0, #refDate) + 1, 0)) --Last of the 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)
I have database with a Publications table that is many-to-may joined to iself through a SubPublications table
My stored procedure returns all of the distinct Year-Month combos from a ReleaseDate field of Publications of a specified type that are not related to a specific (by id) publication (hence the 2 params, see below).
QUESTION:
The proc works fine, but I want the return column type as DateTime2 with a dummy date of 1. As it is now, it returns 2 columns of integers. How do I do this?
I know I could do the conversion in my app code, but I'd rather have it delivered as a datetime from the DB.
My SQL ain't great. I don't even know if I should use a cast or a convert.
I can't find an example online of converting back to datetime within a query like that. Can anyone help? Here's the proc I wrote, as it stands:
ALTER PROCEDURE sp_DistinctPubMonthYears
#PubType char(1),
#PubId int = 0
AS
BEGIN
SELECT
DISTINCT TOP (100) PERCENT
DATEPART(month, ReleaseDate) AS month,
DATEPART(year, ReleaseDate) AS year
FROM(
SELECT
Publications.ReleaseDate AS ReleaseDate,
Publications.PublicationId As PubId,
Publications.PubType AS PubType,
SubPublications.PublicationId AS ParentId
FROM
Publications LEFT JOIN SubPublications
ON
Publications.PublicationId = SubPublications.PublicationId
WHERE
Publications.PubType = #PubType AND
Publications.PublicationId <> #PubId AND
(
SubPublications.PublicationId <> #PubId OR
/*either it's parent is NOT the one we're searching on or */
SubPublications.PublicationId IS NULL
/*or it's not joined to anything at all */
)
) AS sub
ORDER BY year ASC, month ASC
END
GO
You don't need TOP and you may as well ORDER BY the expression.
This DATEADD/DATEDIFF expression will give you start of current month
SELECT DISTINCT
CAST(
DATEADD(month, DATEDIFF(month, 0, ReleaseDate), 0) AS datetime2
) AS myCol
FROM(
...
ORDER BY
DATEADD(month, DATEDIFF(month, 0, ReleaseDate), 0)
Edit: As Faust mentioned, we can order on the alias if you prefer.
...
ORDER BY
myCol
In this case the result is the same.
If the CAST was to varchar then you would have different results. This is why I tend to use the expression not the alias but it's quite trivial. Surely I'd test my changes..., no?
DATEADD(MONTH, DATEDIFF(MONTH, '1600-01-01T00:00:00', ReleaseDate), '1600-01-01T00:00:00') should get you your yyyy-MM-dd 00:00:00 date. 1600-01-01T00:00:00 is just an arbitrary date that is best picked to be prior to any dates you may be storing in your ReleaseDate column.