I want to write a query that selects rows by condition, but if there is no response for that condition, the code should select the same columns, but without the condition.
What the right way to do this? Thanks!
This is my example - select this:
select top 1 *
from tbl
where isActive = 1
but if there is no response, select this instead:
select top 1 *
from tbl
Note that the query is big and complex, so I prefer not to select one and then select the second one, if the first one is null. Also because I have a union after this and it throws an error with this syntax.
Assuming your query is a select top x ... query I would simply use the order by as suggested by Peter's answer. Assuming it's not involving top x, since you wrote your actual query is big and complicated, you can use a common table expression.
The idea is that you encapsulate the big and complex query inside the cte, but instead of writing the where clause to filter out records, you use a case expression to return 1 or 0 if the condition is true or false for each record.
Then you select from that cte where either the case expression results with 1 or there are no records in the cte where the case expression results with 1.
Create and populate sample table (Please save us this step in your future questions)
DECLARE #T AS TABLE
(
Id int identity(1,1),
IsActive bit
)
INSERT INTO #T VALUES
(1),(1),(NULL),(1),(1),(NULL),
(1),(1),(NULL),(1),(1),(NULL),
(1),(1),(NULL),(1),(1),(NULL),
(1),(1),(NULL),(1),(1),(NULL)
The common table expression:
;WITH CTE AS
(
SELECT Id, IsActive,
CASE WHEN IsActive = 1 THEN 1
ELSE 0
END As FoundRecords
FROM #T
)
The query:
SELECT Id, IsActive
FROM CTE
WHERE FoundRecords = 1
OR NOT EXISTS
(
SELECT 1
FROM CTE
WHERE FoundRecords = 1
)
Results:
Id IsActive
1 True
2 True
4 True
5 True
7 True
8 True
10 True
11 True
13 True
14 True
16 True
17 True
19 True
20 True
22 True
23 True
You can see a live demo on rextester
The simplest way is to not use where isActive = 1 but instead order by isActive in descending order:
select top 1 *
from tbl
order by isActive desc
You might even want to consider adding additional ordering fields (e.g. id, name, date), because without that the resulting item could be unpredictable.
You can use the IF-Else condition, right?
DECLARE #result INT
SET #result = CONVERT(INT, (SELECT COUNT(*) FROM tbl WHERE isActive = 1))
IF (#result > 0)
BEGIN
select top 1 * from tbl where isActive = 1;
END
ELSE
BEGIN
select top 1 * from tbl;
END
Related
I have a query which uses a IN Filter and works fine. I am wondering if there
is something like a wildcard char which will not filter anything
Select *
FROM [tbl_Leads]
where p_contact_first_name in ('Tom')
the above works as desired but what happens if i don't want to filter by anything and return all. I know i can create a second query and removing the IN clause but from the logic if possible it would be nicer if i can check for existence of filter value and if none present replace it with wildcard char
The IN operator doesn't allow wildcards or partial values to match. In fact it's just a syntactic sugar of a chaining of OR logical operators.
This query:
SELECT 1 FROM SomeTable AS T
WHERE T.Column IN (1, 2, 3, 4)
Is exactly the same as:
SELECT 1 FROM SomeTable AS T
WHERE
T.Column = 1 OR
T.Column = 2 OR
T.Column = 3 OR
T.COlumn = 4
And this is why having a NULL value with a NOT IN list will make all the logic result be UNKNOWN (hence interpreted as false and never return any record):
SELECT 1 FROM SomeTable AS T
WHERE T.Column NOT IN (1, 2, NULL, 4)
Will be:
SELECT 1 FROM SomeTable AS T
WHERE
NOT(
T.Column = 1 OR
T.Column = 2 OR
T.Column = NULL OR -- Always resolve to UNKNOWN (handled as false for the whole condition)
T.COlumn = 4
)
You have a few options to conditionally apply a filter like IN:
Use OR against another condition:
DECLARE #ApplyInFilter BIT = 0
SELECT 1 FROM SomeTable AS T
WHERE
(#ApplyInFilter = 1 AND T.Column IN (1, 2, 3, 4)) OR
#ApplyInFilter = 0
Avoid the query altogether (have to repeat whole statement):
DECLARE #ApplyInFilter BIT = 0
IF #ApplyInFilter = 1
BEGIN
SELECT 1 FROM SomeTable AS T
WHERE
T.Column IN (1, 2, 3, 4)
END
ELSE
BEGIN
SELECT 1 FROM SomeTable AS T
END
Use Dynamic SQL to conditionally omit the filter:
DECLARE #ApplyInFilter BIT = 0
DECLARE #DynamicSQL VARCHAR(MAX) = '
SELECT 1 FROM SomeTable AS T '
IF #ApplyInFilter = 1
SET #DynamicSQL += ' WHERE T.Column IN (1, 2, 3, 4) '
EXEC (#DynamicSQL)
Unfortunately, the best approach if you plan to have multiple conditional filters is the Dynamic SQL one. It will be the hardest to code but best for performance (with some caveats). Please read George's Menoutis link to fully understand pros and cons of each approach.
You can make use of not exists to get the desired results. From my understanding if you have a name like Tom you want only that row and if it does not you want all other rows to be displayed.
select 1 as ID, 'Tom' as Name into #temp
union all
select 2 as ID, 'Ben' as Name union all
select 3 as ID, 'Kim' as Name
union all
select 4 as ID, 'Jim' as Name
This query will check if Tom exists then display only that row if not display all.
select * from #temp
where name = 'TOm' or not exists (select 1 from #temp where name = 'Tom')
Result from above query:
ID Name
1 Tom
Lets test it, by deleting the row where Tom record is.
Delete from #temp
where name = 'Tom'
If you run the same query you get the following result.
select * from #temp
where name = 'TOm' or not exists (select 1 from #temp where name = 'Tom')
ID Name
2 Ben
3 Kim
4 Jim
As said by Dale Burrell, the fast way to implement dynamic search conditions (exactly what your problem is) is to put code like:
....and field=values or #searchThisField=0
The other solution would be dynamic sql.
I consider Erland Sommarskog's article to be the epitome of analyzing this specific subject.
Make two requests. The performance of these two queries will be better than that of a single universal query. You can compare the execution plan for these queries.
Environment: MS SQL Server 2016.
I have a table which contains (Jasper Reports) layout representations like this (only relevant fields shown for brevity):
ID Name Key Version
1 CoverLetter <guid1> 1.00.00
2 Contract <guid2> 1.00.00
3 CoverLetter <guid1> 1.00.01
Goal:
I need an additional calculated field which is set to true or false according to
whether the record is the highest version of any given Layout or not (Same layout but different versions have same key, different layouts have different key).
Like this:
ID: Name: Key: Version: isHighestVersion: (calculated field)
1 CoverLetter <guid1> 1.00.00 false
2 Contract <guid2> 1.00.00 true
3 CoverLetter <guid1> 1.00.01 true
The SQL query which shows only the highest versions of each Layout is like this:
( SELECT TACMasterlayouts.*
FROM
(SELECT
TACMasterLayoutKey, MAX(TACMasterLayoutVersion) as TACMasterLayoutVersion
FROM
TACMasterlayouts
GROUP BY
TACMasterLayoutKey) AS latest_TACMasterLayouts
INNER JOIN
TACMasterlayouts
ON
TACMasterlayouts.TACMasterLayoutKey = latest_TACMasterLayouts.TACMasterLayoutKey AND
TACMasterlayouts.TACMasterLayoutVersion = latest_TACMasterLayouts.TACMasterLayoutVersion
)
But I need all records - the ones with highest version number per same key flagged with true and the rest flagged with false.
What I already did:
Searched google and SO but didn't find anything similar which I could transform into what I need.
Just change your INNER JOIN To a LEFT OUTER JOIN
and use a case in your
Select
EG
CASE WHEN latest_TACMasterLayouts.TACMasterLayoutKey IS NOT NULL THEN 1 ELSE 0 END as isHighestVersion
Thanks John,
this has pointed me into the right direction.
It has to be a RIGHT OUTER JOIN - otherwise only the records with highest version are shown.
As reference here the fully working code:
SELECT TACMasterlayouts.*, CASE WHEN latest_TACMasterLayouts.TACMasterLayoutKey IS NOT NULL THEN 1 ELSE 0 END as isHighestVersion
FROM
(SELECT TACMasterLayoutKey, MAX(TACMasterLayoutVersion) as TACMasterLayoutVersion
FROM
TACMasterlayouts
GROUP BY
TACMasterLayoutKey) AS latest_TACMasterLayouts
RIGHT OUTER JOIN
TACMasterlayouts
ON
TACMasterlayouts.TACMasterLayoutKey = latest_TACMasterLayouts.TACMasterLayoutKey AND
TACMasterlayouts.TACMasterLayoutVersion = latest_TACMasterLayouts.TACMasterLayoutVersion
)
You need to do some parsing in order to get desired result.
First, you split your version numbers into separate ints, then assign row_number based on them, and then based on row number, you put 1-true or 0-false in a extra column, which I called IsLatest.
In SQL Server there is no true or false, you can use BIT datatype, which has two values (just like boolean): 1 and 0.
Try this query:
declare #tbl table(ID int,Name varchar(20),[Key] varchar(10),Version varchar(10));
insert into #tbl values
(1,'CoverLetter','<guid1>','1.00.00'),
(2,'Contract','<guid2>','1.00.00'),
(3,'CoverLetter','<guid1>','1.00.01');
select ID, [Key], [version],
case when rn = 1 then 1 else 0 end IsLatest
from (
select *,
row_number() over (order by
cast(substring([version], 1, FirstDot - 1) as int) desc,
cast(substring([version], FirstDot + 1, SecondDot - FirstDot - 1) as int) desc,
cast(substring([version], SecondDot + 1, 100) as int) desc) rn
from (
select ID, [Key], [version],
charindex('.', [version]) FirstDot,
charindex('.', [version], charindex('.', [version]) + 1) SecondDot
from #tbl
) a
) a
Here is how i am using ISNULL condition to check for student address.
It works fine but how ISNULL function treat the null codition i.e the second parameter which is display if first condition is null.
Will it calculate Value for second parameter when first condition is not null?
select
...
...
(CASE
WHEN st.ADDRESS='Y' THEN st.LOCATION
ELSE
ISNULL(
(SELECT TOP 1 STDLOC.LOCATION FROM STDLOC
INNER JOIN COMLOC ON STKLOC.LOCATION=COMLOC.CODE AND COMLOC.ADDRESS='Y'
WHERE STDLOC.ZIBCODE=st.ZIBCODE)
,(SELECT TOP 1 COMLOC.LOCATION FROM COMLOC COMLOC.ZIBCODE=st.ZIBCODE))
END
) AS STDUDENTLOCATION
FROM STUDENT st
Both queries inside the ISNULL will be executed, even if the first query will return a value.
Here is a simple test I've made:
Create and populate sample table:
DECLARE #T AS TABLE
(
Col int
)
INSERT INTO #T Values(1),(2)
SELECT ISNULL(
(SELECT TOP 1 Col FROM #T ORDER BY Col DESC),
(SELECT TOP 1 Col FROM #T ORDER BY Col )
)
Execution plan image:
As you can clearly see, the execution plan includes both queries.
I also was looking for an answer. After some reading I came out with my own way to check it.
Deviding by zero will give an error, so we can try:
SELECT ISNULL( (SELECT TOP 1 object_id FROM sys.columns), 5 / 0)
This will give correct result. BUT
SELECT ISNULL( (SELECT TOP 0 object_id FROM sys.columns), 5 / 0)
It will throw an error, because result of first query gives NULL so it tries the second query which fails
ISNULL is a T-SQL specific function that will use the specified second parameter as the return value if the first parameter is NULL(https://msdn.microsoft.com/en-us/library/ms184325.aspx).
Use COALESCE function if you want to return the first non-null value from multiple arguments, and this is a standard function that is supported by all types of relational databases.
This POST provide a good answer for the question:
Is Sql Server's ISNULL() function lazy/short-circuited?
While using case when in where clause in sql query it's not working.
Problem :
I have two tables named TblEmployee and TblAssociate.Both tables contains common columns PeriodId, EmpId and AssociateId. My requirement is to fetch values from
TblEmployee with combination of EmpId and AssociateId from TblAssociate should be excluded.And the exclusion should be based on PeriodId condition.`
If(#PeriodID<50)
BEGIN
SELECT *
FROM TblEmployee
WHERE (EmpId+AssociateId) NOT IN (SELECT EmpId+AssociateId FROM TblAssociate)
END
ELSE
BEGIN
SELECT *
FROM TblEmployee
WHERE (EmpId) NOT IN (SELECT EmpId FROM TblAssociate)
END
The above code is working, but I need to avoid that IF-ELSE condition and I wish to use 'case when' in where clause.Please help
Try this:
SELECT *
FROM TblEmployee
WHERE (EmpId + CASE WHEN #PeriodID<50 THEN AssociateId ELSE 0 END) NOT IN
(SELECT EmpId + CASE WHEN #PeriodID<50 THEN AssociateId ELSE 0 END FROM TblAssociate)
You say your code is working but this is rather odd, since it doesn't make much sense to add together id values. In any case, the above statement produces a result that is equivalent to the code originally posted.
You could use AND-OR combination in the WHERE clause. Additionally, you should not be using + as it may lead to incorrect result. You can rewrite your query as:
SELECT e.*
FROM TblEmployee e
WHERE
(
#PeriodID < 50
AND NOT EXISTS(
SELECT 1
FROM TblAssociate a
WHERE
a.EmpId = e.EmpId
AND a.AssociateId = e.AssociateId
)
)
OR
(
#PeriodID >= 50
AND NOT EXISTS(
SELECT 1
FROM TblAssociate a
WHERE a.EmpId = e.EmpId
)
)
The addition of IDs do not guarantee uniqueness. For instance, if EmpId is 5 and AssociateId is 6, then EmpId + AssociateId = 11, while EmpId + AssociateId = 11 even if EmpId is 6 and AssociateId is 5. In the query below, I made sure that the subquery will stop searching when the first record is found and will return a single record, having the value of 1. We select the employee if and only if 1 is among the results. In the subquery we check the operand we are sure of first and then check if we are not in a period where AssociateId must be checked, or it matches.
select *
from TblEmployee
where 1 in (select top 1 1
from TblAssociate
where TblEmployee.EmpId = TblAssociate.EmpId and
(#PeriodID >= 50 or TblEmployee.AssociateId = TblAssociate.AssociateId))
What could be wrong with this query:
SELECT
SUM(CASE
WHEN (SELECT TOP 1 ISNULL(StartDate,'01-01-1900')
FROM TestingTable
ORDER BY StartDate Asc) <> '01-01-1900' THEN 1 ELSE 0 END) AS Testingvalue.
The get the error:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
As koppinjo stated what your current (broken) query is doing is checking if you have a NULL-value (or StartDate = '01-01-1900') in your table, return either a 1 or a 0 depending on which, and then attempting to SUM that single value.
There are 2 different logical things you want.
Either getting the amount of rows that has a StartDate or checking if any row is missing StartDate.
SELECT --Checking if there is a NULL-value in table
(
CASE WHEN
(SELECT TOP 1 ISNULL(StartDate,'01-01-1900')
FROM TestingTable
ORDER BY StartDate Asc) <> '01-01-1900' THEN 1
ELSE 0
END
) AS TestingValue
SELECT SUM(TestingValue) TestingValue --Give the count of how many non-NULLs there is
FROM
(
SELECT
CASE WHEN
ISNULL(StartDate,'01-01-1900') <> '01-01-1900' THEN 1
ELSE 0
END AS TestingValue
FROM TestingTable
) T
Here is a SQL Fiddle showing both outputs side by side.
Hard to say, but you probably want something like this:
SELECT
SUM(TestingValue)
FROM
(SELECT
CASE
WHEN ISNULL(StartDate,'01-01-1900') <> '01-01-1900'
THEN 1
ELSE 0
END AS TestingValue
FROM TestingTable) t
As your original query is written now, your subquery will return 1 value overall, so your sum would be 1 or 0 always, not to mention it is illegal. To get around that, this SQL will apply the case statement to every row in the TestingTable and insert the result into a derived table (t), then the 'outer' select will sum the results. Hope this helps!