SQL Case Statement: Inside Where Clause - sql

I have read some other Q&A about case statements inside the 'WHERE' clause, but I cannot truly understand how to use it. I will post below a snippet of the code. I believe I am ignorant of a fundamental principle concerning how to use a case statement, and this is why the code will not compile/run. I appreciate any help.
where i.status IN ('CR','L','O')
and i.FGCs > 0
and i.LastShpd > CAST(CONVERT(CHAR(11),DATEADD(DAY,-180,GETDATE()),113) AS datetime)
and (Case
When n.OnOrder IN ('0', '')
Then i.OnOrder = 0 or i.LastShpd < CAST(CONVERT(CHAR(11),DATEADD(DAY,-21,GETDATE()),113) AS datetime)))
End)
Order by i.LastShpd desc
To explain what I have above, I already got the appropriate 'SELECT' and 'FROM' statement. Now I am filtering the results based on those the shown variables (ecx LastShpd). What I wanted the case statement to do was: When n.OnOrder = 0, I want to keep only the rows where i.OnOrder = 0 or if i.LastShpd has been greater than 21 days.

I don't think you need a Case for this:
where i.status IN ('CR','L','O')
and i.FGCs > 0
and i.LastShpd > CAST(CONVERT(CHAR(11),DATEADD(DAY,-180,GETDATE()),113) AS datetime)
and (
(n.OnOrder IN ('0', '') and i.OnOrder = 0)
or i.LastShpd < CAST(CONVERT(CHAR(11),DATEADD(DAY,-21,GETDATE()),113) AS datetime)
)
Re-reading your question maybe is this other way:
where i.status IN ('CR','L','O')
and i.FGCs > 0
and i.LastShpd > CAST(CONVERT(CHAR(11),DATEADD(DAY,-180,GETDATE()),113) AS datetime)
and (
n.OnOrder Not IN ('0', '')
or i.OnOrder = 0
or i.LastShpd < CAST(CONVERT(CHAR(11),DATEADD(DAY,-21,GETDATE()),113) AS datetime)
)

When using a CASE within a WHERE clause, you still need to have both sides of the operation defined (i.e. [CASE CONDITION] = [SOMETHING]). This can get tricky depending on what you want to do, but the easiest thing is to have your case statement execute as a true/false type of condition so that you end up with [CASE] = 1 or [CASE] = 0.
In your case (accidental pun!):
where i.status IN ('CR','L','O')
and i.FGCs > 0
and i.LastShpd > CAST(CONVERT(CHAR(11),DATEADD(DAY,-180,GETDATE()),113) AS datetime)
and (Case
When n.OnOrder IN ('0', '') AND (i.OnOrder = 0 or i.LastShpd < CAST(CONVERT(CHAR(11),DATEADD(DAY,-21,GETDATE()),113) AS datetime))
THEN 1
ELSE 0
End) = 1
Of course as another answer has pointed out, a case is not really necessary in this particular instance. However, if you had more complex conditions this could be helpful.

Related

SQL Server CASE Statement Evaluate Expression Once

I may be missing something obvious! Thanks in advance for any help.
I am trying to use a CASE statement in an inline SQL Statement. I only want to evaluate the expression once, so I am looking to put the expression in the CASE section, and then evaluate the result in each WHEN. Here is the example:
SELECT
MyTable.ColumnA,
CASE DateDiff(d, MyTable.MyDate, getDate())
WHEN <= 0 THEN 'bad'
WHEN BETWEEN 1 AND 15 THEN 'reasonable'
ELSE 'good'
END as MyCalculatedColumn,
MyTable.SomeOtherColumn
I know I can do this:
CASE
WHEN DateDiff(d, MyTable.MyDate, getDate()) <= 0 THEN 'bad'
WHEN DateDiff(d, MyTable.MyDate, getDate()) BETWEEN 1 AND 15 THEN 'reasonable'
ELSE 'good'
END
But in my first example, SQL does not seem to like this statement:
WHEN <= 0 THEN 'bad'
Note that the statement is inline with other SQL, so I can't do something like:
DECLARE #DaysDiff bigint
SET #DaysDiff = DateDiff(d, MyTable.MyDate, getDate())
CASE #DaysDiff
WHEN <= 0 THEN 'bad'
WHEN BETWEEN 1 AND 15 THEN 'reasonable'
ELSE 'good'
END
My actual DateDiff expression is much more complex and I only want to maintain its logic, and have it evaluated, only once.
Thanks again...
You can use apply for this purpose:
SELECT MyTable.ColumnA,
(CASE WHEN day_diff <= 0 THEN 'bad'
WHEN BETWEEN 1 AND 15 THEN 'reasonable'
ELSE 'good'
END) as MyCalculatedColumn,
MyTable.SomeOtherColumn
FROM MyTable CROSS APPLY
(VALUES (DateDiff(day, MyTable.MyDate, getDate()))) v(day_diff)
APPLY is a very handy way to add calculated values into a statement. Because they are defined in the FROM clause, they can be used in SELECT, WHERE, and GROUP BY clauses where column aliases would not be recognized.
I see your problem. CASE expression WHEN value can only do an equality check
You could try using a CTE (Common Table Expression), do everything except the case statement in the CTE and then put the CASE in the final SELECT at the end. I'm not sure whether it will prevent the expression being evaluated twice - thats kindof the optimisers problem, not yours (thats how I like to think about it)
WITH cteMyComplexThing AS(
SELECT MyTable.ColumnA,
DateDiff(d, MyTable.MyDate, getDate()) as ComplexThing,
MyTable.SomeOtherColumn
FROM MyTable
)
SELECT
ColumnA,
CASE
WHEN ComplexThing <= 0 THEN 'bad'
WHEN ComplexThing BETWEEN 1 AND 15 THEN 'reasonable'
ELSE 'good'
END as MyCalculatedColumn,
SomeOtherColumn
FROM cteMyComplexThing
The WHEN clause in a CASE statement needs both sides of the condition. <=0 cannot stand by itself.
CASE #DaysDiff
WHEN ? <= 0 THEN 'bad'
WHEN BETWEEN 1 AND 15 THEN 'reasonable'
ELSE 'good'
END

Filter in where clause via a parameter with case statement in select

I have a case statement in my SELECT that looks like this:
CASE
when ((DATEDIFF(dd,ecf.uploadDate,GETDATE())/365.23076923074 > 2) AND (DATEDIFF(yy,u.birthday,GETDATE()) < 18)) then 'Yes'
when ((DATEDIFF(dd,ecf.uploadDate,GETDATE())/365.23076923074 > 5) AND (DATEDIFF(yy,u.birthday,GETDATE()) >= 18) AND u.source <> 4) then 'Yes'
when (DATEDIFF(dd,ecf.uploadDate,GETDATE())/365.23076923074 < 2) then 'No'
else 'No'
end as [Retake Photo]
How do I pass in a report parameter (Yes, No, or All) that will filter what gets shown? I need something in my WHERE clause that can reference what was determined in the part of the select statement.
I am using SQL Server 2012 Reporting Services and developing my SSRS report in Visual Studio 2013.
Thanks for any advice on how to get this accomplished. I'm still relatively new to creating these reports.
Either rewrite the case into a where clause or put the complete query in a subquery
select * from
(
select
CASE
when ((DATEDIFF(dd,ecf.uploadDate,GETDATE())/365.23076923074 > 2) AND (DATEDIFF(yy,u.birthday,GETDATE()) < 18)) then 'Yes'
when ((DATEDIFF(dd,ecf.uploadDate,GETDATE())/365.23076923074 > 5) AND (DATEDIFF(yy,u.birthday,GETDATE()) >= 18) AND u.source <> 4) then 'Yes'
when (DATEDIFF(dd,ecf.uploadDate,GETDATE())/365.23076923074 < 2) then 'No'
else 'No'
end as [Retake Photo]
from your_table
) tmp
where [Retake Photo] = 'Yes'
Try adding this to your WHERE clause
Assuming the parameter that you pass in for Yes, No, All, is called #Value
WHERE (#Value = 'All' OR
#Value =
CASE
WHEN ((DATEDIFF(dd,ecf.uploadDate,GETDATE())/365.23076923074 > 2) AND (DATEDIFF(yy,u.birthday,GETDATE()) < 18)) then 'Yes'
WHEN ((DATEDIFF(dd,ecf.uploadDate,GETDATE())/365.23076923074 > 5) AND (DATEDIFF(yy,u.birthday,GETDATE()) >= 18) AND u.source <> 4) then 'Yes'
WHEN (DATEDIFF(dd,ecf.uploadDate,GETDATE())/365.23076923074 < 2) then 'No'
ELSE 'No'
END )
You can also add a filter to the results in SSRS itself, but this would be a less efficient way of doing things, since all the records will be extracted and returned to the client and only then will the filter be applied.
To do this, right click on the controls (Tablix Properties or what ever control you're using), from the left, select Filters, and add a filter.
Keep the original query just as you wrote it (almost):
CASE
when DateDiff(yy, ecf.uploadDate, GETDATE()) > 2
AND DateDiff(yy, u.birthday, GetDate()) < 18 then 'Yes'
when DateDiff(dd, ecf.uploadDate, GETDATE()) > 5
AND DateDiff(yy, u.birthday, GetDate()) >= 18
AND u.source <> 4 then 'Yes'
else 'No'
end as [Retake Photo]
Just make it a CTE and filter that:
with
Q1 as(
-- your original query
)
select *
from Q1
where #Value = 'All'
or #Value = [Retake Photo];

Result showing two rows to one row

I wrote a query to count positive and negative results per day. My problem is that I am getting two rows per postivie to negative day.
I want one row with a positive and negative column for each day( no multiple days).
SELECT case([MsrSplatPositive]) when '0' then count([MsrSplatPositive]) end as 'Negative'
,case([MsrSplatPositive]) when '1' then count([MsrSplatPositive]) end as 'Positive'
,CONVERT(VARCHAR(10), f.NewsDate, 106) as 'Date'
FROM [News].[dbo].[Score] s
inner join [News].[dbo].[Feed] f
on f.ScoreId = s.Id
where f.NewsDate between dateadd(day,-30,GETDATE()) and GETDATE()
group by [MsrSplatPositive],CONVERT(VARCHAR(10), f.NewsDate, 106)
Remove MsrSplatPositive from the GROUP BY.
That is forcing the positive and negative results into different days.
And then use a SUM(CASE...) instead of CASE(COUNT...)
You have this in the SELECT:
case([MsrSplatPositive]) when '0' then count([MsrSplatPositive]) end as 'Negative'
and instead want this:
sum(CASE WHEN MsrSplatPositive = 0 THEN 1 ELSE 0 END) as 'Negative'
EDIT:
All fixed up should be something like:
SELECT SUM(CASE WHEN MsrSplatPositive = 0 THEN 1 ELSE 0 END) 'Negative'
,SUM(MsrSplatPositive 'Positive'
,CONVERT(VARCHAR(10), f.NewsDate, 106) as 'Date'
FROM News.dbo.Score s
INNER JOIN
News.dbo.Feed f on f.ScoreId = s.Id
WHERE f.NewsDate BETWEEN DATEADD(DAY,-30,GETDATE()) and GETDATE()
GROUP BY f.NewsDate
Try this...I haven't run the query but think this should achieve the result. I have modified your original query to return 0 instead of null in the positive and negative column. Then used the modified SQL as a temp table and grouped by the date values.
Select Sum(Temp.Negative), Sum(Temp.Positive), Temp.Date From
(
SELECT case([MsrSplatPositive]) when '0' then count([MsrSplatPositive])
else 0 end as 'Negative'
,case([MsrSplatPositive]) when '1' then count([MsrSplatPositive]
else 0) end as 'Positive'
,CONVERT(VARCHAR(10), f.NewsDate, 106) as 'Date'
FROM [News].[dbo].[Score] s
inner join [News].[dbo].[Feed] f
on f.ScoreId = s.Id
where f.NewsDate between dateadd(day,-30,GETDATE()) and GETDATE()
group by [MsrSplatPositive],CONVERT(VARCHAR(10), f.NewsDate, 106)
) Temp
Group By Temp.Date
Hope this helps.

SQL conditional WHERE simplification

I currently have a statement in my WHERE clause like this:
AND ((#includeExpired = 0 AND lic.[DateExpiresUtc] > GETDATE())
OR
(#includeExpired = 1 AND lic.[DateExpiresUtc] <> GETDATE())
)
Which just looks ugly, I've tried just including a simpler version of the statement like:
(#includeExpired = 0 AND lic.[DateExpiresUtc] > GETDATE())
But when #includeExpired is 1 it all fails to select anything.
Is there a better way of doing this?
According to your variabel name #includeExpired, which implies that this variable decides about additionally including expired records in the result set, the check in the second part, lic.[DateExpiresUtc] <> GETDATE(), is not necessary and the other check in the first part is not necessary.
Try this:
AND (lic.[DateExpiresUtc] > GETDATE()) OR (#includeExpired = 1)
Just one idea.
(CASE
WHEN #includeExpired = 1 THEN 1
WHEN #includeExpired = 0 AND lic.[DateExpiresUtc] > GETUTCDATE() THEN 1
ELSE 0 END) = 1

Mysql - Improving consultation query in 'group by'

I have a query with 'group by':
SELECT date_audience,
Sum( If( quality_apuration = '1', 1, 0 ) ) AS very_good,
Sum( If( quality_apuration = '2', 1, 0 ) ) AS good,
Sum( If( quality_apuration = '3', 1, 0 ) ) AS bad,
Sum( If( quality_apuration = '4', 1, 0 ) ) AS no_apuration,
Count(quality_apuration) AS total
FROM pp_base
WHERE date_audience >= '2011-01-01' AND date_audience <= '2011-02-28'
GROUP BY date_audience ORDER BY date_audience ASC
Where to return the following result (or see http://jsbin.com/imuru5/):
As the table X has foreign key to another table Y, eventually someone will ask to include one more item in the table Y, for example: 'exccelent', 'regular', etc. And I will also have to adjust the schedule as php $query[0]['very_good'], $query[0]['good'], $query[0]['bad'], etc, adding among other items, spending more time .
Does anyone have any idea how I can improve this query, in order to automate the results?
Thanks, Vinicius.
I can see two options.
1) Dealing with it in the application. The following query will perform neccesary aggregation in the database and return 4 rows for each date_audience (one for each value of quality_apuration).
select date_audience
,quality_apuration
,count(*)
from pp_base
where date_audience >= date '2011-01-01'
and date_audience <= date '2011-02-28'
group
by date_audience
,quality_apuration
order
by date_audience
,quality_apuration;
This is preferred when you expect changes to the quality_apuration values.
2) Dealing with it in the database. You could define a view like the following:
create or replace view pp_view as
select date_audience
,sum(case when quality_apuration = '1' then 1 else 0 end) as very_good
,sum(case when quality_apuration = '2' then 1 else 0 end) as good
,sum(case when quality_apuration = '3' then 1 else 0 end) as bad
,sum(case when quality_apuration = '4' then 1 else 0 end) as no_apuration
,count(quality_apuration) as total
from pp_base
group
by date_audience;
...from the application you would then select as follows:
select ...
from pp_view
where date_audience >= date '2011-01-01'
and date_audience <= date '2011-02-28'
order
by date_audience;
Of course, whenever you add another value for quality_apuration, you would have to modify the view definition. Still, it's better than modifying all queries.