Use IF in SELECT part of query - sql-server

I need to create something like this
SELECT x.id
, x.name
, x.type
,(
IF x.type = 1
(SELECT SUM(Col1) FROM TableA WHERE ... etc)
ELSE IF x.type = 2
(SELECT SUM(Col2) FROM TableB WHERE ... etc)
) AS Total
FROM TableX as x
So I am trying to select a different sub query according to the value of x.type
Wing

Try to use LEFT JOIN and COALESCE. Use your conditions of x.type to join the tables.
COALESCE (Transact-SQL): Evaluates the arguments in order and returns the current value of the first expression that initially does not evaluate to NULL.
https://msdn.microsoft.com/en-us/library/ms190349.aspx
SELECT x.id
, x.name
, x.type
, COALESCE(SUM(TableA.Column), SUM(TableB.Column)) as column_xyz
FROM TableX as x
LEFT JOIN TableA ON x.type = 1 AND ...
LEFT JOIN TableB ON x.type = 2 AND ...
You can also use CASE WHEN ... THEN ... instead of COALESCE to define which column to use.

Use CASE statement
SELECT x.id,
x.name,
x.type,
CASE
WHEN x.type = 1 THEN (SELECT Sum(Col1)
FROM TableA Where...)
WHEN x.type = 2 THEN (SELECT Sum(Col2)
FROM TableB Where .. )
END AS Total
FROM TableX AS x

You can use CASE WHEN as the below:
SELECT
x.id,
x.name,
x.type,
CASE
WHEN x.type = 1 THEN (SELECT SUM(A.Col1) FROM TableA A WHERE 1 = 1)
WHEN x.type = 2 THEN (SELECT SUM(B.Col2) FROM TableB B WHERE 1 = 1)
ELSE NULL END AS Total
FROM
TableX as x

You can use case expression:
select t.* ,
Case when t.type = 1 then (select sum(col1) ... TableA)
when t.type = 2 then (select sum(col2) ... TableB)
End as Total
From tableX t

By using variable -
DECLARE #SumA INT = SELECT SUM(Col1) FROM TableA WHERE ... etc
DECLARE #SumB INT = SELECT SUM(Col2) FROM TableB WHERE ... etc
SELECT x.id
, x.name
, x.type
,( CASE x.type
WHEN 1 THEN #SumA
WHEN 2 THEN #SumB
END
) AS Total
FROM TableX as x
Choose the datatype for Sum variable accordingly (if Decimal).

Related

Not allowing Multiple CASE Subquery Results when using IN

I'm working with the following query:
SELECT *
FROM TableA a
WHERE a.FieldA IN (
CASE
--select subquery returns a single value
WHEN a.FieldB = 'Value1'
THEN (select b.ID from TableB b where b.FK_Field = '123')
--select subquery returns multiple values
WHEN a.FieldB = 'Value2'
THEN (select c.ID from TableC c where c.FK_Field = '123')
END
)
The first case select statement returns only a single b.ID. If I just have that statement, my code works.
The second case statement, however, returns multiple c.IDs. When I add that check, I get the following error:
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression.
If I would have WHERE a.FieldA =, then I understand that the subquery can only return 1 value. I however have WHERE a.FieldA IN, so why is it complaining if there are multiple values returned?
How can I implement this kind of check?
As #marc_s explained in a comment:
CASE in T-SQL is an expression (like a+b) which returns a single,
atomic value - you cannot use it to selectively run SQL snippets that
return entire result sets
In order to resolve this error, I removed the CASE statement and instead used a bunch of AND and OR statements to accomplish the same kind of check.
SELECT *
FROM TableA a
WHERE
(a.FieldB = 'Value1'
AND a.FieldA IN (select b.ID from TableB b where b.FK_Field = '123'))
OR (a.FieldB = 'Value2'
AND a.FieldA IN (select c.ID from TableC c where c.FK_Field = '123'))
This code is a bit messier than a CASE statement, but it works.
Lots of ways of doing this, here is one way using union all and a correlated EXISTS statement
;WITH cte AS (
SELECT 'Value1' as FieldB, b.Id
FROM
TableB
WHERE
b.FK_FieldId = '123'
UNION ALL
SELECT 'Value1' as FieldB, c.Id
FROM
TableB
WHERE
c.FK_FieldId = '123'
)
SELECT *
FROM
TableA a
WHERE
EXISTS (SELECT 1
FROM
cte c
WHERE
a.FieldB = c.FieldB
AND a.FieldA = c.Id)
The problem with the way you have written it is that you are getting a non-scalar value (meaning more than 1 row) where sql is expecting a scalar value. In the case expression only scalar values can be used in the THEN part as well as some rules in WHEN as well. To solve you need to break apart your case expression to multiple where statements and/or use some other technique such as the one above.
Or you could write your case expression like this:
SELECT *
FROM
TableA a
WHERE
(CASE
WHEN a.FieldB = 'Value1' AND a.FieldA IN (select b.ID from TableB b where b.FK_Field = '123') THEN 1
WHEN a.FieldB = 'Value2' AND a.FieldA IN (select c.ID from TableC c where c.FK_Field = '123') THEN 1
ELSE 0
END) = 1
Don't use case in predicates if it is not necessary - using case make your argument non-SARG (you qry will not use index).
SELECT *
FROM TableA a
WHERE EXISTS(
SELECT NULL
FROM TableB b
WHERE a.FieldB = 'Value1'
AND b.FK_Field = '123'
AND a.FieldA = b.ID))
OR EXISTS(
SELECT NULL
FROM TableC c
WHERE a.FieldB = 'Value2'
AND c.FK_Field = '123'
AND a.FieldB = c.ID))
Use indexes. And try to make your qry readable.
Or if you would like use semi-join:
SELECT *
FROM TableA a
WHERE a.FieldA IN (
SELECT b.ID
FROM TableB b
WHERE a.FieldB = 'Value1'
AND b.FK_Field = '123'))
OR a.FieldB IN (
SELECT c.ID
FROM TableC c
WHERE a.FieldB = 'Value2'
AND c.FK_Field = '123'))
Both of this solutions are with SARG.

Find combination which does not have 'AC' status

Consider the data below which is stored in temp table. How to get all those ID / State / Group combination which does not have Status record having 'AC' value ?
Data Image
The result should yield
ID State Group
2 FL LI
3 FL VA
5 FL LI
There are several query patterns that will achieve that result.
An example of one of those patterns, using an GROUP BY operation, with aggregation of a condition (to exclude the groupings where there's a row in the grouping that has a status value of 'AC')
SELECT t.id
, t.state
, t.group
FROM mytable t
GROUP
BY t.id
, t.state
, t.group
HAVING MAX(CASE WHEN t.status='AC' THEN 1 ELSE 0 END) = 0
An example of another pattern, using an anti-join:
SELECT t.id
, t.state
, t.group
FROM mytable t
LEFT
JOIN mytable s
ON s.id = t.id
AND s.state = t.state
AND s.group = t.group
AND s.status = 'AC'
WHERE s.id IS NULL
GROUP
BY t.id
, t.state
, t.group
Select *
FROM TableName t
WHERE NOT EXISTS (SELECT 1
FROM TableName
WHERE [State] = t.[State]
AND [Group] = t.[Group]
AND [Status] = 'AC')

MSSQL subquery result to show and calculate

I need show a subquery result and use this same result to calculate other value, is possible set this value in a variable in MS SQL 2008 or something like this?
exemple:
SELECT
#test = (SELECT COUNT(*) FROM [tableTest] WHERE [tableTest].[columnA] = [tableA].[columnA]) as 'Counter'
, (#test * 50) as 'Calc'
, [tableA].[columnA]
FROM tableA
you may use a cte and join on it.
with cte as (select count(*) cnt, columnA from [tableTest] group by columnA)
select
c.cnt as 'Counter',
c.cnt * 50 as 'Calc',
a.columnA
from tableA a
join cte c on c.columnA = a.columnA
It could also be done with a subquery, of course
select
a.columnA,
c.cnt as 'Counter',
c.cnt * 50 as 'Calc'
from tableA a
join (select columnA, count(*) as cnt
from tableTest
group by columnA) c
on c.columnA = a.columnA
Why can't you do this. Move the subquery outside of select statement and store the result in a variable
Then use that variable for calculations.
declare #test int = (SELECT COUNT(*) FROM [tableTest])
SELECT
#test as 'Counter'
, (#test * 50) as 'Calc'
, [tableA].[columnA]
FROM tableA
Update :
SELECT [Counter],
( [Counter] * 50 ) AS 'Calc',
columnA
FROM (SELECT (SELECT Count(*)
FROM [tableTest]
WHERE [tableTest].[columnA] = [tableA].[columnA]) AS 'Counter',
[tableA].[columnA]
FROM tableA) A
You can also use correlated sub-queries:
SELECT
Counter = (SELECT COUNT(*) FROM tableTest t WHERE t.columnA = a.columnA),
Calc = (SELECT COUNT(*) FROM tableTest t WHERE t.columnA = a.columnA) * 50,
a.columnA
FROM tableA a
It'll be optimized to be only evaluated once.
Try:
SELECT t2.[Count]
,t2.[Count] * 50 As [Calc]
,[tableA].[columnA]
FROM TableA
CROSS APPLY
(
SELECT COUNT(*) AS [Count]
FROM TableTest
WHERE [tableTest].[columnA] = [tableA].[columnA]
) t2

OUTER APPLY table to itself multiple times - can CASE be used

Is it necessary to OUTER APPLY multiple times in the following query, or could I get the same result by somehow using 1 APPLY with some included CASE statements?
(p.s. I realise FIRST_VALUE is an alternative but I'm interested in the use of APPLY)
SELECT
Name,
AgeCat,
Country,
X.DateKey,
WagerAmt = SUM(Revenue),
FirstNAC = z.DateKey,
FirstNA = j.DateKey,
FirstN = q.DateKey
FROM #x X
OUTER APPLY (
SELECT TOP(1) DateKey
FROM #x Y
WHERE X.Name = Y.Name AND
X.AgeCat = Y.AgeCat AND
x.Country = y.Country
ORDER BY DateKey
) z
OUTER APPLY (
SELECT TOP(1) DateKey
FROM #x Y
WHERE X.Name = Y.Name AND
X.AgeCat = Y.AgeCat
ORDER BY DateKey
) j
OUTER APPLY (
SELECT TOP(1) DateKey
FROM #x Y
WHERE X.Name = Y.Name
ORDER BY DateKey
) q
GROUP BY
Name,
AgeCat,
Country,
X.DateKey
z.DateKey,
j.DateKey,
q.DateKey;
I don't see how this can be done with a case. A sub-select of some type will be necessary.
EDIT:
As JOINs the query would look like this:
SELECT
Name,
AgeCat,
Country,
X.DateKey,
WagerAmt = SUM(Revenue),
FirstNAC = z.DateKey,
FirstNA = j.DateKey,
FirstN = q.DateKey
FROM #x X
inner join
(
SELECT TOP(1) DateKey
FROM #x Y
WHERE X.Name = Y.Name AND
X.AgeCat = Y.AgeCat AND
x.Country = y.Country
ORDER BY DateKey
) z
inner join
(
SELECT TOP(1) DateKey
FROM #x Y
WHERE X.Name = Y.Name AND
X.AgeCat = Y.AgeCat
ORDER BY DateKey
) j
inner join (
SELECT TOP(1) DateKey
FROM #x Y
WHERE X.Name = Y.Name
ORDER BY DateKey
) q
GROUP BY
Name,
AgeCat,
Country,
X.DateKey,
z.DateKey,
j.DateKey,
q.DateKey;
Note that the GROUP BY has changed as well: since APPLY returns 1 row only, and theoretically these sub-selects could return multiples, their fields need to be in the GROUP too.

Case statement in sql using other selected columns in the same statement

I would like to know if the following is possible in SQL server 2005. Column A and B are calculated using other case statements in my actual stored proc. I don't want to repeat the same for another field unnecessarily. If this is not syntactically possible, any other ideas?
SELECT A, B, CASE WHEN column1='1' THEN A ELSE B END Col1.
Modified version of actual query provided as requested. CTE kind of seems to be tough in this model. WANNABE is the column I want to accomplish in the sub select statement.
SELECT 1 AS Region, 'Test',
CAST(Work AS NUMERIC(18,2)) Work,
Work + 2 AS Work2,
WANNABE
FROM
(
SELECT
ROW_NUMBER() OVER(PARTITION BY G.Value, C.C, FR.Mod1 ORDER BY FR.Date DESC, FG.Date DESC, FC.Date DESC) ROW,
CASE WHEN COALESCE(FR.Mod1, '') = '' THEN '' ELSE FR.Mod1 END Mod1,
CASE WHEN #var1=1 AND #var2 = 1 THEN FR.Col1 * G.Value
WHEN #var1=1 AND #var2 = 0 THEN FP.Col1 * G.Value END Work,
CASE WHEN 1=1 THEN Work ELSE 1 END WANNABE,
(
SELECT Col3
FROM Table2
WHERE c = FR.Value
) AS Custom
FROM MainTable FR
JOIN #C C ON FR.Col2 = C.Col2
LEFT JOIN Function1(#VersionDate) cv ON cv.Code = C.Code
LEFT JOIN Function2(#VersionDate) hv ON hv.Code = C.Code
LEFT JOIN #G G ON 1 = 1
LEFT JOIN SubTable1 FG ON FG.Number = G.Value, 2 AND FG.Date = #VersionDate
LEFT JOIN SubTable2 FO ON FO.Number = G.Value
AND FO.Date = #VersionDate AND FO.Code = FR.Code AND FR.Mod1 = FO.Mod1
LEFT JOIN SubTable3 FP ON FP.Code = FR.Code AND FP.VersionDate = #Versiondate
AND CASE WHEN DATALENGTH(FR.Mod1) = 0 THEN '00' ELSE FR.Mod1 END = CASE WHEN DATALENGTH(FP.Mod1) = 0 THEN '00' ELSE FP.Mod1 END
LEFT JOIN SubTable4 FC ON FC.Date = #VersionDate
WHERE FR.Date = #VersionDate
) x
WHERE x.Row = 1
AND RTRIM(LTRIM(x.Col1)) IN ('', '2')
You can define the A,B column aliases in a CTE then reference them in an outer select.
;WITH CTE AS
(
SELECT CASE ... END AS A,
CASE ... END AS B,
column1
FROM your_table
)
SELECT A,
B,
CASE WHEN column1='1' THEN A ELSE B END Col1
FROM CTE
Similarly you can also define them in a CROSS APPLY that is sometimes a bit less verbose.
A silly example just to show the syntax is
SELECT A,
B,
CASE WHEN type='P' THEN A ELSE B END Col1
FROM master..spt_values
CROSS APPLY (SELECT CASE WHEN number %2 = 1 THEN 1 END,
CASE WHEN number %2 = 0 THEN 0 END) T(A,B)
Following your update you can replace the derived table with a CTE and nest CTEs as follows
;WITH x as
(
SELECT
ROW_NUMBER() OVER(PARTITION BY G.Value, C.Code, FR.Mod1 ORDER BY FR.Date DESC, FG.Date DESC, FC.Date DESC) ROW,
...<snip>
WHERE FR.Date = #VersionDate
),
x2 As
(
SELECT *,
CASE WHEN 1=1 THEN Work ELSE 1 END WANNABE
FROM x
)
SELECT 1 AS Region, 'Test',
CAST(Work AS NUMERIC(18,2)) Work,
Work + 2 AS Work2,
WANNABE
FROM x2
WHERE x2.Row = 1
AND RTRIM(LTRIM(x2.Col1)) IN ('', '2')
Yeah it is posible, but how is all your sql statement? You can use the case statement in the select statement as you are using it.
Something like this
SELECT SUM((CASE WHEN column1='1' THEN 10 ELSE 0 END)) AS A, SUM((CASE WHEN column1='2' THEN 10 ELSE 0 END)) AS B
FROM YourTable

Resources