SQL SELECT Date based on multiple conditions - sql-server

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;

Related

Can the LAG function return a single DATE value that is not NULL

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

How to get max date of previous year in a dataset with a window-function in SQL Server

I have a simple table with just a DATETIME filed.
My question is, how can I get the value related to the end-of-year of previous year, with a window-function query?
I've tried with this query but the result is the end-of-year of the current year:
SELECT datefield, max(datefield) OVER (PARTITION BY YEAR(datefiled)) FROM foo
I am using SQL Server 2012.
Many thanks to all.
If you want to filter records then you need to use Where clause. You need something like this not window function.
SELECT TOP 1 WITH ties *
FROM foo
WHERE datefield <= Datefromparts(Year(Getdate()) - 1, 12, 31)
ORDER BY datefield DESC
or
SELECT *
FROM foo
WHERE datefield = (SELECT Max(datefield) AS last_date_prev_year
FROM foo
WHERE datefield <= Datefromparts(Year(Getdate()) - 1, 12, 31))
I don't think you need to use a windowed function. A simple filter combined with the max function will return the end of the previous year.
-- Where clause removes records from current and future years.
SELECT
MAX(datefield)
FROM
foo
WHERE
YEAR(datefield) < YEAR(GETDATE())
;
Although simple, this approach has a small problem. Using the year function, on datefield in the where clause, makes the query non-sargable. If performance is an issue; you could fix by using DateFromParts as demonstrated in #Prdp's fine answer.
EDIT
This version of the query uses a windowed function, as requested by the OP.
-- Max of previous year, using a windowed function.
SELECT
MAX(datefield) AS LastYearEnd
FROM
(
-- Rank records based on year.
-- Current year is 1, last year is 2, etc.
SELECT
datefield,
DENSE_RANK() OVER (ORDER BY YEAR(datefield) DESC) AS rn
FROM
foo
) AS dr
WHERE
rn = 2
;
The above only returns one record. If you want see the last day of the previous year, next to every record in your table:
-- Returns last day of previous year, relative to dateField.
SELECT
datefield,
DATEADD(YEAR, -1, MAX(datefield) OVER (PARTITION BY YEAR(datefield)))
FROM
foo
;

Alternative to cursor when applying a list of values to a where clause?

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

SSRS 2005 Previous sum of date and time

I have an SSRS report which when I try to calculate the previous value the first line always gives incorrect value since there is no previous number for it to calculate.
=CDec(format(DateDiff("s",previous(Fields!SyncDate.Value),Fields!Date.Value)/60,"0.00"))
The value of the first line comes out 1059831848.62
Is there a way to tell it to skip first line?
Because also i need to sum it all to get total which counts the first line and gives a huge total.
My query consists of the following:
SELECT ToppingCount, DriverName, COUNT(Pizza) AS Count, Date, SyncDate, BranchName, Branch
FROM System
WHERE (Date BETWEEN #datefrom AND #DateTo) AND (Branch IN (#branch)) AND (SystemSource = 'newsys') AND (SystemSource <> 'oldsys')
GROUP BY Pizza, ToppingCount, DriverName, Date, SyncDate, BranchName, Branch
ORDER BY Branch, DriverName, Date
Thanks
You could add a Row_Number column to your query like so..
SELECT ToppingCount,
DriverName,
COUNT(Pizza) AS Count,
Date,
SyncDate,
BranchName,
Branch,
ROW_NUMBER() OVER (ORDER BY Branch,DriverName,Date) Rn
FROM System
WHERE (Date BETWEEN #datefrom AND #DateTo)
AND (Branch IN (#branch))
AND (SystemSource = 'newsys')
AND (SystemSource <> 'oldsys')
GROUP BY Pizza,
ToppingCount,
DriverName,
Date,
SyncDate,
BranchName,
Branch
ORDER BY Branch,
DriverName,
Date
Then just check to see if you are on Rn = 1 before you do your calculation.
=iif(Fields!Rn.Value = 1, "", CDec(format(DateDiff("s",previous(Fields!SyncDate.Value),Fields!Date.Value)/60,"0.00")))
or
=iif(Fields!Rn.Value = 1, 0, CDec(format(DateDiff("s",previous(Fields!SyncDate.Value),Fields!Date.Value)/60,"0.00")))
if you need to reset the Row_Number for groupings, you can add Partition By to reset the number to 1
You can check the value of the previous sync date field and output a 0 or blank if required.
=CDec(iif(IsNothing(previous(Fields!SyncDate.Value)), 0.0, format(DateDiff("s",previous(Fields!SyncDate.Value),Fields!Date.Value)/60,"0.00")))
Without row_number you can use an outer apply to get the last sync date in the query and pre-calculate the 'sync delta':
SELECT
p.ToppingCount,
p.DriverName,
p.Date,
p.SyncDate,
CASE
WHEN i.SynchDate IS NULL THEN 0.0
ELSE DATEDIFF(SS, l.SyncDate, p.Date) / 60.0
END AS SyncTimeDelta,
p.BranchName,
p.Branch,
COUNT(p.Pizza) AS Count
FROM System p
OUTER APPLY
(
-- Join clause depends on how you're grouping
-- in the report.
SELECT TOP 1
i.SyncDate
FROM System i
WHERE
i.SystemSource = 'newsys'
AND i.Branch = p.Branch
AND i.DriverName = p.DriverName
AND i.Date < p.Date
ORDER BY
i.SyncDate DESC
) l
WHERE
(p.Date BETWEEN #datefrom AND #DateTo) AND
(p.Branch IN (#branch)) AND
(p.SystemSource = 'newsys') AND
(p.SystemSource <> 'oldsys') -- This will never be evaluated
GROUP BY
p.Pizza, p.ToppingCount, p.DriverName, p.Date, p.SyncDate, p.BranchName, p.Branch, l.SyncDate
ORDER BY
p.Branch, p.DriverName, p.Date
This way you can just sum the sync delta in the report. I don't know enough about the query but I've made the assumption that:
You want the time between the last sync date before the current [date] column as per your formula for the given branch and driver.
I apologize if there are syntax issues, I'm typing this up without reference.

Return (Cast? Convert?) values as DateTIme from a Distinct year, month query in a store procedure

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.

Resources