Execution of ISNULL in SQL Server - sql-server

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?

Related

SSIS SQL Task Unable to assign Parameter Value when using IF EXISTS

I'm using VS 2012 and SQL Server / SSIS.
I originally had a SQL task to check for duplicate values in a table:
SELECT COUNT(*) AS DupNI
FROM dbo.mytable
WHERE XMLFileID = ?
GROUP BY XMLFileID, NINumber
HAVING (COUNT(*) > 1);
The ? is because I am inserting a parameter value, and the result of the query is being assigned to a variable. It works fine if there is a duplicate.
When there are no duplicates, I get this message:
Single Row result set is specified, but no rows were returned
So, to get round this I now use an IF EXISTS, like the below:
IF EXISTS (SELECT COUNT(*) AS DupNI
FROM dbo.mytable
WHERE XMLFileID = ?
GROUP BY XMLFileID, NINumber
HAVING (COUNT(*) > 1))
SELECT COUNT(*) AS DupNI
FROM dbo.mytable
WHERE XMLFileID = ?
GROUP BY XMLFileID, NINumber
HAVING (COUNT(*) > 1)
ELSE
SELECT 0 AS DupNI;
However, now I get the error:
No value given for one or more required parameters.
It appears because I am wrapping the statement in the IF EXISTS, I can no longer inject the parameter values via the ?
Why is this? How do I get around this issue?
Your current query will return multiple rows if there are duplicates, one for each duplicate (XMLFileID, NINumber) pair. If you only want to return a value which indicates whether there are any duplicates in the table, you could use your EXISTS clause as an expression:
SELECT CASE WHEN EXISTS(
SELECT COUNT(*) AS DupNI
FROM dbo.mytable
WHERE XMLFileID = ?
GROUP BY XMLFileID, NINumber
HAVING (COUNT(*) > 1)
) THEN 1 ELSE 0 END AS [Duplicates Exist]
Demo on dbfiddle

ISNULL() returning NULL

Why does the following simple query return null when there are no matching rows (<Condition> is not met by any row)?
SELECT ISNULL(MyField, 0) FROM [MyTable] WHERE <Condition>
I have tried COALESCE() as well, with similar results. How can I return zero when there are no matching rows?
This will work, provided you expect condition to reduce the result set to either 0 or 1 row:
SELECT ISNULL((SELECT MyField FROM [MyTable] WHERE <Condition>),0)
That is, create an outer query with no FROM clause (which will therefore always generate exactly one row) and then use a subquery to obtain your 0 or 1 row of actual data.
Use this:
SELECT ISNULL(COUNT(MyField), 0) FROM [MyTable] WHERE <Condition>
It'll return 0 if row is missing.
You cannot convert 0 rows to 1 row with a null value with any sql built-in fuction because that may cause mis-interpretation of data
But you can customize your result using the below logic (same as you do in .net).
If (select COUNT(MyField) FROM [MyTable] WHERE <Condition>)=0
select 0 as [MyField]
else
SELECT ISNULL(MyField, 0) as [MyField] FROM [MyTable] WHERE <Condition>

SQL Server Column From Another Table With Static Value In View

Is there a way to have a column from another table with value which is always the same inside a View> Example:
SELECT *,
(SELECT value FROM tblStudentPrefixes WHERE PrefixName = 'SeniorPrefix')
AS StudentPrefix
FROM tblStudents
Will the above nested query get executed fro each row? Is there a way to execute it once and use for all rows.
Please note, I'm specifically talking about a View, not a Stored Procedure. I know this can be done in a Stored Procedure.
This actually depends on your table set up. Unless prefixName is constrained to be unique you could come across errors, where the subquery returns more than one row. If it is not constrained to be unique, but happens to be unique for SeniorPrefix then your query will be executed 1000 times. To demonstrate I have used the following DDL:
CREATE TABLE #tblStudents (ID INT IDENTITY(1, 1), Filler CHAR(100));
INSERT #tblStudents (Filler)
SELECT TOP 10000 NULL
FROM sys.all_objects a, sys.all_objects b;
CREATE TABLE #tblStudentPrefixes (Value VARCHAR(10), PrefixName VARCHAR(20));
INSERT #tblStudentPrefixes (Value, PrefixName) VALUES ('A Value', 'SeniorPrefix');
Running your query gives the following IO output:
Table '#tblStudentPrefixes'. Scan count 10000, logical reads 10000
Table '#tblStudents'. Scan count 1, logical reads 142
The key being the 1000 logical reads on tblStudentPrefixes. The other problem with it not being constrained to be unique is that if you have duplicates your query will fail with the 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 you can't constrain PrefixName to be unique, then you can stop it executing for each row and avoid the errors by using TOP:
SELECT *,
(SELECT TOP 1 value FROM #tblStudentPrefixes WHERE PrefixName = 'SeniorPrefix' ORDER BY Value)
AS StudentPrefix
FROM #tblStudents
The IO now becomes:
Table '#tblStudentPrefixes'. Scan count 1, logical reads 1
Table '#tblStudents'. Scan count 1, logical reads 142
However, I would still recommend switching to a CROSS JOIN here:
SELECT s.*, p.Value AS StudentPrefix
FROM #tblStudents AS s
CROSS JOIN
( SELECT TOP 1 value
FROM #tblStudentPrefixes
WHERE PrefixName = 'SeniorPrefix'
ORDER BY Value
) AS p;
Inspection of the execution plans shows that a sub-select using a table spool which is very unnecessary for a single value:
So in summary, it depends on your table set up whether it will execute for each row, but regardless you are giving the optimiser a better chance if you switch to a cross join.
EDIT
In light of the fact that you need to return rows from tblstudent when there is no match for SeniorPrefix in tblStudentPrefixes, and that PrefixName is not currenty constrianed to be unique then the best solution is:
SELECT *,
(SELECT MAX(value) FROM #tblStudentPrefixes WHERE PrefixName = 'SeniorPrefix')
AS StudentPrefix
FROM #tblStudents;
If you do constrain it to be unique, then the following 3 queries produce (essentially) the same plan and the same results, it is simply personal preference:
SELECT *,
(SELECT value FROM #tblStudentPrefixes WHERE PrefixName = 'SeniorPrefix')
AS StudentPrefix
FROM #tblStudents;
SELECT s.*, p.Value AS StudentPrefix
FROM #tblStudents AS s
LEFT JOIN #tblStudentPrefixes AS p
ON p.PrefixName = 'SeniorPrefix';
SELECT s.*, p.Value AS StudentPrefix
FROM #tblStudents AS s
OUTER APPLY
( SELECT Value
FROM #tblStudentPrefixes
WHERE PrefixName = 'SeniorPrefix'
) AS p;
I hope I understand your question right, but try this
SELECT *
FROM tblStudents
Outer Apply
(
SELECT value
FROM tblStudentPrefixes
WHERE PrefixName = 'SeniorPrefix'
) as tble
This is OK. Subquery would be executed for every row on every row (which could provide bad performance).
You could try also:
SELECT tblStudents.*,StudentPrefix.value
FROM tblStudents,
(SELECT value
FROM tblStudentPrefixes
WHERE PrefixName = 'SeniorPrefix')StudentPrefix

T-SQL First_Value() - How can it return more than a single value?

I have the following query:
Select PH.SubId
From dbo.PanelHistory PH
Where
PH.Scribe2Time <> (Select FIRST_VALUE(ReadTimeLocal) OVER (Order By ReadTimeLocal) From dbo.PanelWorkflow Where ProcessNumber = 2690 And dbo.PanelWorkflow.SubId = PH.SubId)
I'm getting an error (512) that says: Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
How can the subquery return more than a single value? There can only be one first value. I must be overlooking something with this query.
By the way, I realize I could easily use Min() instead of First_Value, but I wanted to experiment with some of these Windowing functions.
How many rows do you see?
SELECT FIRST_VALUE(name) OVER (ORDER BY create_date) AS RN
FROM sys.objects
Even though there is only one distinct first value it still returns it for every row in the query.
So if the sub query itself matches multiple rows you will get this error. You could get rid of it with DISTINCT or TOP 1.
Probably not very efficient but you say this is just for experimental purposes.
This isn't an answer. It's just an extended comment generated by the following conclusion:
I could easily use Min() instead of First_Value, but I
wanted to experiment with some of these Windowing functions.
Min can't be used instead of FIRST_VALUE.
Example:
SET NOCOUNT ON;
DECLARE #MyTable TABLE(ID INT, TranDate DATETIME)
INSERT #MyTable VALUES (1, '2012-02-02'), (2, '2011-01-01'), (3, '2013-03-03')
SELECT MIN(ID) AS MIN_ID FROM #MyTable
SELECT ID, MIN(ID) OVER(ORDER BY TranDate) AS MIN_ID_ORDER_BY FROM #MyTable;
SELECT ID, FIRST_VALUE(ID) OVER(ORDER BY TranDate) AS FIRST_VALUE_ID_ORDER_BY FROM #MyTable;
Results:
MIN_ID
-----------
1
ID MIN_ID_ORDER_BY
----------- ---------------
2 2
1 1
3 1
ID FIRST_VALUE_ID_ORDER_BY
----------- -----------------------
2 2
1 2
3 2
FIRST_VALUE() will still return a row for every record that meets tour WHERE clause. TOP 1 should work:
Select PH.SubId
From dbo.PanelHistory PH
Where
PH.Scribe2Time <> (Select TOP 1 ReadTimeLocal
From dbo.PanelWorkflow
Where ProcessNumber = 2690
And dbo.PanelWorkflow.SubId = PH.SubId
Order By ReadTimeLocal DESC)
or MIN:
Select PH.SubId
From dbo.PanelHistory PH
Where
PH.Scribe2Time <> (Select MIN(ReadTimeLocal)
From dbo.PanelWorkflow
Where ProcessNumber = 2690
And dbo.PanelWorkflow.SubId = PH.SubId)
The PARTITION/OVER functions are look-ahead column functions. They aren't row functions - by that, I mean, they don't effect an entire row, number of rows returned, etc. An OVER aggregate can depend on values in other rows, but the tangible result is only to calculate a single column in the current row.
You may have seen something similar to what you are trying to do via an OVER ROW_NUMBER ranking function. Multiple rows are still returned, but only one of them has a ROW_NUMBER of 1. The rest are filtered in an encapsulating WHERE or JOIN predicate.

JOIN ON subselect returns what I want, but surrounding select is missing records when subselect returns NULL

I have a table where I am storing records with a Created_On date and a Last_Updated_On date. Each new record will be written with a Created_On, and each subsequent update writes a new row with the same Created_On, but an updated Last_Updated_On.
I am trying to design a query to return the newest row of each. What I have looks something like this:
SELECT
t1.[id] as id,
t1.[Store_Number] as storeNumber,
t1.[Date_Of_Inventory] as dateOfInventory,
t1.[Created_On] as createdOn,
t1.[Last_Updated_On] as lastUpdatedOn
FROM [UserData].[dbo].[StoreResponses] t1
JOIN (
SELECT
[Store_Number],
[Date_Of_Inventory],
MAX([Created_On]) co,
MAX([Last_Updated_On]) luo
FROM [UserData].[dbo].[StoreResponses]
GROUP BY [Store_Number],[Date_Of_Inventory]) t2
ON
t1.[Store_Number] = t2.[Store_Number]
AND t1.[Created_On] = t2.co
AND t1.[Last_Updated_On] = t2.luo
AND t1.[Date_Of_Inventory] = t2.[Date_Of_Inventory]
WHERE t1.[Store_Number] = 123
ORDER BY t1.[Created_On] ASC
The subselect works fine...I see X number of rows, grouped by Store_Number and Date_Of_Inventory, some of which have luo (Last_Updated_On) values of NULL. However, those rows in the sub-select where luo is null do not appear in the overall results. In other words, where I get 6 results in the sub-select, I only get 2 in the overall results, and its only those rows where the Last_Updated_On is not NULL.
So, as a test, I wrote the following:
SELECT 1 WHERE NULL = NULL
And got no results, but, when I run:
SELECT 1 WHERE 1 = 1
I get back a result of 1. Its as if SQL Server is not relating NULL to NULL.
How can I fix this? Why wouldn't two fields compare when both values are NULL?
You could use Coalesce (example assuming Store_Number is an integer)
ON
Coalesce(t1.[Store_Number],0) = Coalesce(t2.[Store_Number],0)
The ANSI Null comparison is not enabled by default; NULL doesn't equal NULL.
You can enable this (if your business case and your Database design usage of NULL requires this) by the Hint:
SET ansi_nulls off
Another alternative basic turn around using:
ON ((t1.[Store_Number] = t2.[Store_Number]) OR
(t1.[Store_Number] IS NULL AND t2.[Store_Number] IS NULL))
Executing your POC:
SET ansi_nulls off
SELECT 1 WHERE NULL = NULL
Returns:
1
This also works:
AND EXISTS (SELECT t1.Store_Number INTERSECT SELECT t2.Store_Number)

Resources