Multiple Boolean Expressions in Case When statement - sql-server

Below is the part code that I am trying to make work... Essentially it will give me the date of the previous Monday if today is Monday. If today is not Monday then it will give me the date of the Monday of this week.
WHERE
CASE WHEN
DATEPART(dw,GETDATE()) = '1'
THEN
DateColumn >= DATEADD(wk,DATEDIFF(wk,0,GETDATE()) - 1,0)
ELSE
DateColumn >= DATEADD(wk,DATEDIFF(wk,0,GETDATE()),0)
END
However, when I run the code it says there is an incorrect syntax near '>'. I am not sure if it doesn't like me using multiple Boolean expressions or if there really is something wrong with that syntax.

You got the syntax slightly wrong; it should be:
WHERE
DateColumn >=
CASE WHEN DATEPART(dw,GETDATE()) = 1 -- datepart returns an integer, so no quotes
THEN DATEADD(wk,DATEDIFF(wk,0,GETDATE()) - 1,0)
ELSE DATEADD(wk,DATEDIFF(wk,0,GETDATE()),0)
END

Related

How do I compare 2 dates in SQL Server

I have a string 2021-02-23T06:58:51 that I want to check if it is greater than another date. When I do the below I get an error. Tried CAST still the same error.
select convert(smalldatetime, '2021-02-23T06:58:51') > convert(smalldatetime, GETDATE())
Started executing query at Line 1
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near '>'.
Total execution time: 00:00:00.040
As I said in the comments, columns need to be defined as a scalar expression. The expression you have is not a scalar expression, it is a boolean expression:
convert(smalldatetime, '2021-02-23T06:58:51') > convert(smalldatetime, GETDATE())
This doesn't tell SQL Server what to display, and would be an expression you would normally find in the WHERE. I.e. Display rows where the value of {column} is greater than the current date and time.
What you likely want is a CASE expression or IIF:
SELECT CASE WHEN '2021-02-23T06:58:51' > GETDATE() THEN 1 END 0 END;
SELECT IIF('2021-02-23T06:58:51' > GETDATE(),1,0);
Is this what you want?
select IIF(convert(smalldatetime, '2021-02-23T06:58:51') > convert(smalldatetime, GETDATE()), 'True','False')

Where condition based on month and year

i have the following code:
select COUNT(STL.ITEID)
from STORETRADELINES STL
left join FINTRADE FT ON STL.FTRID=FT.ID
where RIGHT(CONVERT(datetime, FT.FTRDATE, 3), 5) < RIGHT(CONVERT(datetime, GETDATE(), 3), 5)
and FT.FTRDATE > CONVERT(datetime,'01.01.2017',103)
I want to select all the documents that fall between 01.01.2017 and 30.04.2017 (always, the last day of the previous month).
Seem's that this way is not good, because is returning all the docs from 01.01 until today.
Where am i wrong ? Btw, i use sql server 2008.
Thank you
The problem is, you're doing string comparisons. Use date comparisons instead:
select COUNT(STL.ITEID)
from STORETRADELINES STL
left join FINTRADE FT ON STL.FTRID=FT.ID
where FT.FTRDATE < DATEADD(month,DATEDIFF(month,0,GETDATE()),0)
and FT.FTRDATE >= '20170101'
Here, the DATEADD/DATEDIFF pair of calls are rounding the current date down to the start of the current month.
You can use a similar DATEADD/DATEDIFF pair for your start-of-period condition, using year instead of month if you want this query to be generic for any current year
The reason you're "stringly-typed" code doesn't work is that your CONVERT calls are converting datetime values to datetime. The style parameter is ignored here since datetimes don't have a format. You then get an uncontrolled implicit conversion of the datetimes into varchar so that RIGHT can work, and it's that conversion that you should have tried to control.
As it is, this:
select RIGHT(CONVERT(datetime, GETDATE(), 3), 5)
Generates:
:50AM
Which you can hopefully see is nothing like what you wanted.
dateadd(mm,datediff(mm, 0 , current_timestamp),0) will return the first day of current month.
Your query could be
SELECT COUNT(STL.ITEID)
FROM STORETRADELINES STL
left join FINTRADE FT ON STL.FTRID=FT.ID
where
FT.FTRDATE >= '2017-01-01' AND FT.FTRDATE < dateadd(mm,datediff(mm, 0 , current_timestamp),0)

Apply Different WHERE clause depending on value of one field

i'm trying to build a query in which I need to apply 2 different where clauses, depending on the value of Current Month. In this case, I need to show data from the last 2 years, only of the months before the current month:
Example 1:
Current Date is: 01-01-2017
Need to show data from:
01/2015; 02/2015; 03/2015; 04/2015; 05/2015; 06/2015;
07/2015; 08/2015; 09/2015; 10/2015; 11/2015; 12/2015;
01/2016; 02/2016; 03/2016; 04/2016; 05/2016; 06/2016;
07/2016; 08/2016; 09/2016; 10/2016; 11/2016; 12/2016.
Example 2:
Current Date is: 01-03-2017
Need to show data from: 01/2016; 02/2016; 01/2017; 02/2017.
So I built the following query:
SELECT *
FROM TABLE1
WHERE
CASE MONTH(GETDATE())
WHEN 1
THEN YEAR(Data)>=YEAR(GETDATE())-2 and YEAR(data)<YEAR(GETDATE())
ELSE YEAR(Data)>=YEAR(GETDATE())-1 and YEAR(data)<=YEAR(data) and MONTH(data)<MONTH(GETDATE())
END
I'm getting an error.
Can you please help me?
Thank you.
Your syntax is incorrect for sure. THEN is not a logical expression - it is supposed to return value. So you can't write logical expression in THEN/ELSE blocks as you have attempted to. Instead you might try something like:
WHERE
#date >= CASE WHEN a=b THEN '20150101' ELSE '20160202' END
Another thing is: conversions and functions in predicate are very bad for performance. When working with dates you might want to prepare filter predicate before the query when possible, e.g.:
declare
#date_begin date,
#date_end date
set #date_end = DATEADD(..., #arg_date)
set #date_begin = DATEADD(YEAR, -2, #date_end)
select ...
where date between #date_begin and #date_end
in your case it could be something like:
declare
#arg_date DATE = GETDATE(),
#date_begin DATE,
#date_end DATE,
#max_month INT
set #max_month = MONTH(#date)
if #max_month = 1
begin
set #date_end = DATEADD(dd, 1-DATEPART(dy, #arg_date), #arg_date) /* first day of year */
set #date_begin = dateadd(YY, -2, #date_end)
end
else
begin
set #date_end = #arg_date
set #date_begin = dateadd(YY, -1, DATEADD(dd, 1-DATEPART(dy, #date_end), #date_end)) /* first day of year_begin */
end
SELECT *
FROM TABLE1 t
WHERE t.date >= #date_begin and t.date < #date_end
AND (#max_month = 1 OR MONTH(t.date) < #max_month)
another (a better) way is to prepare #periods table variable, put each (date_begin, date_end) pair you need into it and join with TABLE1 - you'll get rid of all function calls from within WHERE clause.
You should realize: you know exactly which periods of each year you need in the result set. There is nothing to compute from stored TABLE1->date column. Just filter it with precomputed date intervals. Don't convert or modify date column - it is already ready to use. Merely apply appropriate filters. MONTH(date) <= 3 is date <= 20170331. Don't torture left part - prepare appropriate right part of such predicates.
The easiest way would be something like:
SELECT *
FROM TABLE1
WHERE
(YEAR(Data)>=YEAR(GETDATE())-2 and YEAR(data)<YEAR(GETDATE()) AND MONTH(GETDATE()) = 1)
OR (YEAR(Data)>=YEAR(GETDATE())-1 and MONTH(data)<MONTH(GETDATE()) and MONTH(GETDATE()) <> 1)
(Note I removed the superfluous and YEAR(data)<=YEAR(data).).
Personally I prefer (and I think it's generally advised) AND/OR logic to a CASE in a WHERE clause.
The error with your CASE statement is caused by the fact that CASE returns an atomic value. It cannot be used in the same way as if in procedural languages.
You can't swap in additional statements to your where clause using case statements. Instead, you need to resolve the case to an equality:
select *
from Table1
where case month(getdate()) -- You want to avoid using functions on fields in your WHERE claises, as this can reduce performance.
when 1 then case when Data >= dateadd(year,datediff(year,0,getdate())-2,0)
and Data < dateadd(year,datediff(year,0,getdate()),0)
then 1 -- Data rows the meet the criteria will return 1.
else 0 -- Data rows that do not will return 0.
end
else case when (Data >= dateadd(year,datediff(year,0,getdate())-1,0)
and Data < dateadd(m,datediff(m,0,getdate())-12,0)
)
or (Data >= dateadd(year,datediff(year,0,getdate()),0)
and Data < dateadd(m,datediff(m,0,getdate()),0)
)
then 1
else 0
end
end = 1 -- Then limit the results to only those rows that returned a 1.
In your specific instance however, this can be simplified to a standard or:
select *
from Table1
where (month(getdate()) = 1
and Data >= dateadd(year,datediff(year,0,getdate())-2,0)
and Data < dateadd(year,datediff(year,0,getdate()),0)
)
or (month(getdate()) <> 1
and (Data >= dateadd(year,datediff(year,0,getdate())-1,0)
and Data < dateadd(m,datediff(m,0,getdate())-12,0)
)
or (Data >= dateadd(year,datediff(year,0,getdate()),0)
and Data < dateadd(m,datediff(m,0,getdate()),0)
)
)
Note the use of brackets above to separate out the logical tests. Where a Data row meets either one of those criteria it will be returned in your query.

sum with case statement and datetime format

I have a simple query and am noticing when I write a sum statement, the resultant sum numbers do not match what I would expect. I think it is when evaluating a column with 'datetime' format. Unsure how to get around it. Having trouble searching the correct phrase to find the solution, but I expect it to be a common one. Maybe a cast or convert to DATE format, but those did result in answers which made sense.
SELECT X.X_ITEMID,
SUM(CASE WHEN X.ActivityDate < '11/29/2016' THEN X.Qty ELSE 0 END) AS SUM_X
FROM ERP_X X
WHERE X_ITEMID = 'abcdef'
GROUP BY X.X_ITEMID
When you compare date[time]s to character literals, the date is converted to a character (using the default date format), and then they are compared lexicographically, which is almost always a bad idea. Instead, you should explicitly convert your string literal to a datetime, and allow the database to compare them properly:
SELECT X.X_ITEMID,
SUM(CASE WHEN X.ActivityDate < CONVERT(datetime, '11/29/2016', 101)
THEN X.Qty
ELSE 0
END) AS SUM_X
FROM ERP_X X
WHERE X_ITEMID = 'abcdef'
GROUP BY X.X_ITEMID
Try bellow code, I hope it gives you correct result what you need.
SELECT X.X_ITEMID,
SUM(CASE WHEN convert(char(8),X.ActivityDate,112) < '20161129'
THEN X.Qty
ELSE 0
END) AS SUM_X FROM ERP_X X
WHERE X_ITEMID = 'abcdef'
GROUP BY X.X_ITEMID

SQL Server Function WIthin Case Statement

select top 10 *, case
when datediff(day,DateOfSale, getDate()) > 5 then '5'
when datediff(day,DateOfSale, getDate()) > 10 then '10'
... (more datediff clauses)
...
...
else '20'
end as jack
from Foo
Is SQL Server smart enough to evaluate the datediff function call once within the case statement and then use that value for every when clause? Or is the function is getting called 'n' times, where 'n' is the amount of when clauses?
It's hard to see how SQL Server could evaluate the call once. The call has a column as parameter and so has to be evaluated for every row.
Thus, your condition is better written like:
when DateOfSale < dateadd(day, -5, getdate()) then '5'
In this case the difference is small. Date calculations are cheap.
The classic example where the function call does matter is a where condition on a table with an index on the date column. For example, YourTable with an index on (dt). This query would allow an index to be used:
select * from YourTable where dt < dateadd(day, -5, getdate())
While this query would not:
select * from YourTable where datediff(day, DateOfSale, getDate()) > 5
It's puzzling that so many answers are mentioning indexes. Indeed, DATEDIFF is not SARGable, but that's completely irrelevant here as CASE WHEN doesn't cause the query optimizer in SQL Server to consider index usage (other than trying to find a covering scannable path). The candidacy of DATEDIFF-involved expressions for index pathing is completely irrelevant to this question, as far as I can tell.
It's pretty easy to demonstrate that SQL Server does, indeed, stop evaluating predicates inside CASE statements once the first true predicate is found.
To demonstrate that fact, let's cook up some sample data:
CREATE TABLE Diffy (SomeKey INTEGER NOT NULL IDENTITY(1,1), DateOfSale DATE);
DECLARE #ThisOne AS DATE;
SET #ThisONe = '2012-01-01';
WHILE #thisONe < '2013-01-01'
BEGIN
INSERT INTO Diffy (DateOfSale) VALUES(#ThisOne);
SET #ThisOne = DateAdd(d, 1, #ThisOne);
END;
Then, let's SELECT it in the pattern of the original question. Note that the original question specifies a TOP 10 clause without an ORDER BY clause, so the values we actually get back are random. But if we add a clause to the CASE that would poison evaluation, we can see what's happening:
SELECT TOP 10 *, CASE
WHEN datediff(day,DateOfSale, getDate()) > 5 then '5'
WHEN datediff(day,DateOfSale, getDate()) > 10 then '10'
WHEN 1/0 > 1then 'boom'
ELSE '20' END
AS Jack
FROM Diffy;
Note that if we ever evaluated 1/0 > 1, then we'd expect something like a 'Divide by zero error encountered.'. However, running this query against my server yields ten rows, all with '5' in the Jack column.
If we take away the TOP 10, sure enough we get some rows and then get the Divide by zero error. Thus, we can safely conclude that SQL Server is doing early exit evaluation on the CASE statement.
On top of it, the documentation also tells us so:
The CASE statement evaluates its conditions sequentially and stops with the first condition whose condition is satisfied.
Perhaps the question is meant to ask if the common DATEDIFF() subexpression is hoisted from all the CASE statements, computed once, and then evaluated within each predicate's context. By observing the output of SET SHOWPLAN_TEXT ON, I think we can conclude that's not the case:
|--Compute Scalar(DEFINE:([Expr1004]=CASE WHEN datediff(day,CONVERT_IMPLICIT(datetimeoffset(7),[Scratch3].[dbo].[Diffy].[DateOfSale],0),CONVERT_IMPLICIT(datetimeoffset(3),getdate(),0))>(5) THEN '5' ELSE CASE WHEN datediff(day,CONVERT_IMPLICIT(datetimeoffset(7),[Scratch3].[dbo].[Diffy].[DateOfSale],0),CONVERT_IMPLICIT(datetimeoffset(3),getdate(),0))>(10) THEN '10' ELSE CASE WHEN (1)/(0)>(1) THEN 'boom' ELSE '20' END END END))
|--Index Scan(OBJECT:([Scratch3].[dbo].[Diffy].[DiffyHelper]))
From that, we can conclude that the structure of this query means that DATEDIFF() is evaluated for each row and for each predicate, so O(rows * predicates) calls, at worst. That causes some CPU load for the query, but DATEDIFF() isn't quite that expensive and shouldn't be much of a concern. If, in practice, it turns out to be causing a performance problem, there are ways to manually hoist the computation from the query. For example, DATEDIFF() on the date-relative side of the comparison.
Sure, but not in your case (the expression is based on a table column value that changes for each row), but in any event, don't execute the datediff on the table column value, run a dateadd on the predicate (comparison) value so your query can still use any existing index on DateOfSale...
select top 10 *,
case When DateOfSale < dateadd(day, -20, getDate()) then '20'
When DateOfSale < dateadd(day, -15, getDate()) then '15'
When DateOfSale < dateadd(day, -10, getDate()) then '10'
When DateOfSale < dateadd(day, -5, getDate()) then '5'
else '20' end jack
from Foo

Resources