Comparison operator use in Case statement - sql

I am having some trouble with the syntax when I am trying to use the below query in SQL Server. I wanted to show WHERE clause based on condition.
This is my code:
DECLARE #isActual = 0
SELECT
SolverRunId, PointId, TimeStampUtc, Value,
UnitOfMeasure, Reliability
FROM
(SELECT
bt.SolverRunId, bt.PointId, bt.TimeStampUtc,
bt.Value, bt.UnitOfMeasure, bt.Reliability
FROM
cte bt
WHERE
bt.TimeStampUtc = bt.TargetTimeUtc
UNION ALL
SELECT
a.SolverRunId, a.PointId, a.TimeStampUtc, a.Value,
a.UnitOfMeasure, a.Reliability
FROM
cte a
WHERE
-- I tried using this case but it is syntactically incorrect
CASE
WHEN #isActual = 0 THEN a.TimeStamUtc > #endDateUtc
ELSE a.TimestampUtc <= #endDateUtc
END
-- instead of this. I wanted to have conditional where based on #isActual value from 0 to 1
a.TimeStampUtc > #endDateUtc
AND a.SolverRunId = #maxRun) x
ORDER BY
SolverRunId, PointId, TimeStampUtc;
I wanted to have the where condition to be evaluated based on #isActual set to true or false

As mentioned in the comments, don't use a CASE in the WHERE just use proper boolean logic with AND and OR clauses. In your question your variable #isActual is also missing a data type, so I have assumed it is a bit:
DECLARE #isActual bit = 0;
SELECT SolverRunId,
PointId,
TimeStampUtc,
Value,
UnitOfMeasure,
Reliability
FROM (SELECT bt.SolverRunId,
bt.PointId,
bt.TimeStampUtc,
bt.Value,
bt.UnitOfMeasure,
bt.Reliability
FROM cte bt
WHERE bt.TimeStampUtc = bt.TargetTimeUtc
UNION ALL
SELECT a.SolverRunId,
a.PointId,
a.TimeStampUtc,
a.Value,
a.UnitOfMeasure,
a.Reliability
FROM cte a
WHERE a.TimeStampUtc > #endDateUtc
AND a.SolverRunId = #maxRun
AND ((#isActual = 0 AND a.TimeStamUtc > #endDateUtc)
OR (#isActual = 1 AND a.TimestampUtc <= #endDateUtc))) x
ORDER BY SolverRunId,
PointId,
TimeStampUtc;
You may also want experiment with adding RECOMPILE to the OPTION clause of the above, as the query plan requirements for when #isActual has a value of 1 or 0 could be quite different.

Related

How to use an SQL Comparator in the base 'Case' selector in the 'When' logic without having to re-write conditions

I have an SQL query joined on multiple tables (all INNER JOINS).
The below is an example of the query I am trying to run (the ? is to illustrate the position in which I presume the answer to my question will be rectified).
Case
(
SELECT Count(ID)
FROM CPD_Candidates cpdCan
WHERE
cpdCan.CandidateID = can.CandidateID
AND
(
cpdCan.DateEnded >= GETDATE()
OR
coalesce(cpdCan.DateEnded, '') = N'1-Jan-1900'
)
AND
cpdCan.Deleted <> 1
)
When ? > 0 then 'Bigger' else 'Equal or Smaller' End
)
The idea with the above is that instead of the ? the actual value I want to compare against would be Count(ID), if it's greater than 0 I want it to SELECT 'Bigger', otherwise it should SELECT 'Equal or Smaller'. So a more-accurate depiction of what I wish to run would be the below.
Case
(
SELECT Count(ID)
FROM CPD_Candidates cpdCan
WHERE
cpdCan.CandidateID = can.CandidateID
AND
(
cpdCan.DateEnded >= GETDATE()
OR
coalesce(cpdCan.DateEnded, '') = N'1-Jan-1900'
)
AND
cpdCan.Deleted <> 1
)
When
Count(cpdCan.ID) > 0 then 'Bigger' else 'Equal or Smaller' End
)
Of course there is a syntax error above but I am enquiring as to whether it is possible to compare like in the above SQL query structure but replacing Count(cpdCan.ID) > 0 with some other means to achieve that value & logic?
If this is un-achievable in SQL Server 2016 what other means would be a better solution to this XY?
I think that you mean:
case when
(
SELECT Count(ID)
FROM CPD_Candidates cpdCan
WHERE
cpdCan.CandidateID = can.CandidateID
AND (cpdCan.DateEnded >= GETDATE() OR coalesce(cpdCan.DateEnded, '') = N'1-Jan-1900')
AND cpdCan.Deleted <> 1
) > 0
then 'Bigger'
else 'Equal or Smaller'
End

Assign null if subquery retrieves multiple records. How can it be done?

I have the following query. I simplified it for demo purpose. I am using SQL Server - t-sql
Select tm.LocID = (select LocID from tblLoc tl
where tl.LocID = tm.LodID )
from tblMain tm
if the subquery returns multiple records, I like to assign tm.LocID to null else if there is only 1 record returned then assign it to tm.LocID. I am looking for a simple way to do this. Any help would be appreciated.
One way I can see is to have a CASE statement and check if (Count * > 1 ) then assign null else return the value but that would require a select statement within a select statement.
You have the right idea about using a case expression for count(*), but it will not require another subquery:
SELECT tm.LocID = (SELECT CASE COUNT(*) WHEN 1 THEN MAX(LocID) END
FROM tblLoc tl
WHERE tl.LocID = tm.LodID )
FROM tblMain tm
or just use a HAVING clause, like
Select tm.LocID = (select LocID from tblLoc tl
where tl.LocID = tm.LodID
group by locID
having count(*) = 1)
)
from tblMain tm
Your query above (and many of the other answers here) is a correlated subquery which will be very slow since it performs a separate aggregation query on each record. This following will address both your problem and potentially perform a bit better since the count happens in a single pass.
SELECT
CASE
WHEN x.locid IS NOT NULL THEN x.locid
ELSE NULL
END
FROM tblMain m
LEFT JOIN (
SELECT
locid
FROM tblLoc
GROUP BY locid
HAVING COUNT(1) = 1
) x
ON x.locid = m.locid
;
The above is in Postgres syntax (what I'm familiar with) so you would have to make it TSQL compatible.

What is best way to merge 2 select statement?

I have a sql server query statement like this:
WITH A
AS (
SELECT (
SUM(CASE
WHEN (t1.price) > 0
THEN (t1.price)
ELSE 0
END)
) AS pr1
,(
ABS(SUM(CASE
WHEN (t1.price) < 0
THEN (t1.price)
ELSE 0
END))
) AS pr2
FROM dbo.price_table AS t1
)
,B
AS (
SELECT (WHEN(pr1 - pr2) < 0 THEN ABS(pr1 - pr2) ELSE 0 END) AS res
FROM A
)
SELECT res
FROM B
in my query, i use 2 select statement to achieve "res" column, but i want achieve to "res" column in 1 select statement.
what is best way for merge 2 select statement to 1 select statement query?
Your calculation seems way to complicated. You are taking the sum of the positive values. Then the sum of the negative values, using ABS() to make that value positive, and subtracting this result. Guess what? That is the same as taking the SUM() of all the values in the first place.
So, I think this statement is equivalent:
SELECT (CASE WHEN SUM(t1.price) < 0
THEN ABS(SUM(t1.price))
ELSE 0
END)
FROM dbo.price_table t1;
Common Table Expressions (CTEs) are a way of writing reusable sub-queries. There's not one method that works with all CTEs (CTEs can be self-referencing which is not possible with subqueries_. In your example, the query
WITH A AS
(
{A query}
)
,B AS
(
SELECT ... res FROM A
)
SELECT res FROM B
could be rearranged to
SELECT res FROM
(
SELECT {expression} res FROM
(
{A query}
)
)
which is just
SELECT {expression} res FROM
(
{A query}
)
your solution is fine.
Even thou you use multipple "Select" statements, they will compile in one query.
So your query is equivalent to the query proposed by Gordon Linoff.
If you have more complicated logic you can create SINGLE query: take your last "Select ..." and substituse "pr1" and "pr2" with subqueries (or expressions). You may receive very complex statement with repeatable parts.
When you use "WITH" you split logical parts with good readability, while performance is not decreased. I always prefere "WITH" in complicate queries.

SQL : select a comparison ? eg, a boolean result?

I was wondering if something like this was possible in SQL :
select (
(select count(*) from T) = (select count(*) from T t where t.something = thing)
)
This is probably very far from the actual SQL if it is possible, I don't write database requests so often.
How could I get the result of my comparison with a single request ? Basically, if I had no time, I would just make two requests and compare the results in Java (boooooo !! I know).
Although your query should work, the following is probably faster because only a single query is needed
select total_count = thing_count
from (
select count(*) as total_count,
sum(case when something = 42 then 1 end) as thing_count
from t
) t
The above is ANSI SQL and should work in any DBMS supporting a real boolean type. In Oracle you would need to use an expression in the outer select:
select case when total_count = thing_count then 1 else 0 end
from (
select count(*) as total_count,
sum(case when something = 42 then 1 end) as thing_count
from t
) t
I would write your query like this:
SELECT (CASE WHEN (select count(*) from T) = (select count(*) from T t where t.something = thing) THEN 1 ELSE 0 END)
However, if the first T is the same as the second T then what you actually want to check is if there are any records where t.something <> thing .. right ?
In that case you could simply do :
SELECT (CASE WHEN EXISTS (select * from T t where t.something != thing) THEN 1 ELSE 0 END)

How to Short-Circuit SQL Where Clause

I am trying to perform the following query in SQL server:
declare #queryWord as nvarchar(20) = 'asdas'
SELECT * FROM TABLE_1
WHERE (ISDATE(#queryWord) = 1)
AND TABLE_1.INIT_DATE = CONVERT(Date, #queryWord)
This obviously causes an error because 'asdas' cannot be converted to Date. Although, I was expecting a different behavior. That is, because ISDATE(#queryWord) = 1 is false, I was expecting SQL to not check the second condition, but apparently, it does.
I know there are some other ways to perform this query but this is not my question. I wonder if there is some way to do not check the second condition is the first one does not satisfy. I am curious because I thought that SQL already did this.
SQL Server does not do short-circuiting (nor should it).
If you need it to not try something under some circumstances, you need to force that in the way that you write your query.
For this query the easiest fix would be to use a CASE expression in your WHERE clause.
declare #queryWord as nvarchar(20) = 'asdas'
SELECT * FROM TABLE_1
WHERE TABLE_1.INIT_DATE = (CASE WHEN ISDATE(#queryWord) = 1
THEN CONVERT(Date, #queryWord)
ELSE NULL END)
Off-hand, CASE and query-nesting are the only two supported ways that I can think of to force an order of evaluation for dependent conditions in SQL.
I Guess you could do it in 2 passes:
declare #queryWord as nvarchar(20) = 'asdas'
select
*
from
(
SELECT * FROM TABLE_1
WHERE (ISDATE(#queryWord) = 1) ) t1
where t1.INIT_DATE = CONVERT(Date, #queryWord)
So your inner query runs the first test and the outer query the second. In a single query, I don't believe there is any way to force any order of evaluating conditions.
Why not do a CASE in the WHERE condition?
DECLARE #tester TABLE (
theDate DATE,
theValue INT
)
INSERT INTO #tester VALUES ('2013-10-17', 35)
INSERT INTO #tester VALUES ('2013-10-16', 50)
INSERT INTO #tester VALUES ('2013-10-15', 2)
declare #queryWord as nvarchar(20) = 'asdas'
SELECT *
FROM #tester
WHERE theDate =
CASE
WHEN ISDATE(#queryWord) = 1 THEN CONVERT(Date, #queryWord)
ELSE theDate
END
SET #queryWord = '2013-10-17'
SELECT *
FROM #tester
WHERE theDate =
CASE
WHEN ISDATE(#queryWord) = 1 THEN CONVERT(Date, #queryWord)
ELSE theDate
END
It can be "simulated" with a CASE statement. But you have to make the first condition giving a TRUE value to avoid checking of the 2nd condition :
declare #queryWord as nvarchar(20) = 'asdas'
SELECT *
FROM TABLE_1
WHERE (CASE
WHEN ISDATE(#queryWord) = 0 THEN 0
WHEN TABLE_1.INIT_DATE = CONVERT(Date, #queryWord) THEN 1
ELSE 0 END) = 1
There is no defined evaluation order in a SQL statement -- except in the case of case expressions, and even there the order isn't so much defined as the result guaranteed. The conditions in your where clause could theoretically be done in parallel or alternating order.
Case expressions differ not by having a defined order, but by having a guaranteed result. IOW, case when 1=1 then 0 When longrunningfunction() = 1 then 2 end is guaranteed to return zero, but there is no promise not to run the longrunningfunction.