Joining multiple tables returns NULL value - sql-server

I have 3 different outcomes of CTE that I need to LEFT JOIN each other:
Main Table #Policies contains all VehiclePolicyLimitsID values:
#LiabilityPremium:
#HiredPremium:
As an example I mimic the results of CTE's into 3 table variables:
declare #Policies table (VehiclePolicyLimitsID int)
insert into #Policies values (2101891),
(2101892),
(2101893),
(2101894),
(2119235),
(2119236),
(2119237),
(2119238),
(2190860),
(2190861),
(2190862),
(2190863)
--select * from #Policies
declare #LiabilityPremium table (Quoteid int, ClassCode int, VehiclePolicyLimitsID int, LiabilityPremium money)
insert into #LiabilityPremium values (728436,3199,2101892,1723),
(728436, 23199,2101893,1855),
(728436,68199,2101894,133),
(741626,3199,2119236,0),
(741626,23199,2119237,0),
(741626,68199,2119238,0),
(774168,3199,2190861,0),
(774168,23199,2190862,0),
(774168,68199,2190863,0)
--select * from #LiabilityPremium
declare #HiredPremium table (Quoteid int, ClassCode int, VehiclePolicyLimitsID int, LiabilityPremium money)
insert into #HiredPremium values ( 728436, NULL, 2101891, 25),
(741626, NULL, 2119235, 0),
(774168, NULL, 2190860, 0)
--select * from #HiredPremium
select
COALESCE(l.Quoteid,h.QuoteID,'') as QuoteID,
COALESCE(l.ClassCode,h.ClassCode,'') as ClassCode,
COALESCE(l.VehiclePolicyLimitsID,h.VehiclePolicyLimitsID,'') as VehiclePolicyLimitsID,
l.LiabilityPremium + h.LiabilityPremium as LiabilityPremium
from #Policies p
left join #LiabilityPremium l ON l.VehiclePolicyLimitsID = p.VehiclePolicyLimitsID
left join #HiredPremium h ON h.VehiclePolicyLimitsID = p.VehiclePolicyLimitsID
But for some reason the outcome of LiabilityPremium is all NULL's:
I would expect the result looks like this with total LiabilityPremium = $3,736
Is any way to join somehow to receive desirable result?

That is because null on either side of the addition operator will yield a result of null. You can use ISNULL(LiabilityPremium, 0) Example:
ISNULL(l.LiabilityPremium,0) + ISNULL(h.LiabilityPremium,0) as LiabilityPremium
or you can use COALESCE instead of ISNULL.
COALESCE(l.LiabilityPremium,0) + COALESCE(h.LiabilityPremium,0) as LiabilityPremium
Edit
I am not sure if this is coincidence with this small data set or expected but if it is always expected that either #LiabilityPremium.LiabilityPremium or #HiredPremium.LiabilityPremium will always be null then there is no need to perform addition. Instead use COALESCE directly on those 2 columns.
COALESCE(l.LiabilityPremium, h.LiabilityPremium) as LiabilityPremium

COALESCE(l.LiabilityPremium,0) + COALESCE(h.LiabilityPremium,0) as LiabilityPremium

That's because of
l.LiabilityPremium + h.LiabilityPremium
If any of the two is NULL, the expression is NULL.
This expression should fix it
COALESCE(l.LiabilityPremium, 0.00) + COALESCE(h.LiabilityPremium, 0.00)

Related

Ordering slash separated list

I have column 'GUI_KVLevelName'.
It has data as :
500.00/69.00/34.50
500.00/400.00/138.00
500.00/69.00
500.00/400.00
500.00/345.00/34.50
57.00/8.30
I want to use order by with this. It is a varchar column but i want to order it as numeric. So how could i use order by this column?
Here is a little cheat that may work
By removing the decimal points, we are converting the individual values into a larger INT. This is then converted to and then sorted via a hierarchyid type
Example
Declare #YourTable Table ([GUI_KVLevelName] varchar(50))
Insert Into #YourTable Values
('500.00/69.00/34.50')
,('500.00/400.00/138.00')
,('500.00/69.00')
,('500.00/400.00')
,('500.00/345.00/34.50')
,('0.45/5.30') -- Added for leading zero
,('0.10/9.30') -- Added for leading zero
Select *
From #YourTable
Order By try_convert(hierarchyid,replace('/'+replace([GUI_KVLevelName],'.','1')+'/','/0','/'))
Returns
GUI_KVLevelName
0.10/9.30
0.45/5.30
500.00/69.00
500.00/69.00/34.50
500.00/345.00/34.50
500.00/400.00
500.00/400.00/138.00
Second answer in case you can't GTD two decimal places.
Example
Declare #YourTable Table ([GUI_KVLevelName] varchar(50))
Insert Into #YourTable Values
('500.00/69.00/34.50')
,('500.00/400.00/138.00')
,('500.00/69.00')
,('500.00/400.00')
,('500.00/345.00/34.50')
,('0.45/5.30')
,('0.1/9.30')
,('0.01/9.30')
,('0.05/9.30')
,('1.3/4.30')
Select A.*
From #YourTable A
Cross Apply (
Select Pos1 = xDim.value('/x[1]','money')
,Pos2 = xDim.value('/x[2]','money')
,Pos3 = xDim.value('/x[3]','money')
,Pos4 = xDim.value('/x[4]','money')
,Pos5 = xDim.value('/x[5]','money')
,Pos6 = xDim.value('/x[6]','money')
,Pos7 = xDim.value('/x[7]','money')
From (Select Cast('<x>' + replace([GUI_KVLevelName],'/','</x><x>')+'</x>' as xml) as xDim) as A
) B
Order By Pos1,Pos2,Pos3,Pos4,Pos5,Pos6,Pos7
Returns
GUI_KVLevelName
0.01/9.30
0.05/9.30
0.1/9.30
0.45/5.30
1.3/4.30
500.00/69.00
500.00/69.00/34.50
500.00/345.00/34.50
500.00/400.00
500.00/400.00/138.00

T-SQL using 2 parameters and isNull

Could someone help me to understand the logic of this query (T-SQL in SQL Server 2014) in simple way?
Select
c.ContractID
From
dba.contract as c
Inner Join
dba.Person as r on (c.ContractID = r.ContractID
and IsNull(isPrimary, 0) = 1)
The part that I dont understand is the isNull(isPrimary, 0) = 1.
What does that mean? Btw isPrimary is one of the columns in dba.person
Thank you so much!
isNull(isPrimary, 0) = 1
isNull is a function of SQL which is used to verify null variable
and above snippet describe as if isPrimary variable is null then replace this null value with 0. the purpose of this method to handle null pointer exception.
If you want to watch how it works, you can create table in your database:
use [your_database_name];
create table dbo.test_table
(
t int null
);
insert into dbo.test_table
values (0), (1), (2), (NULL);
select t, isnull(t, 0) as function_result
from dbo.test_table
Sample script for you to understand IsNull(isPrimary, 0) = 1 condition gives
the result and helps in handling Null pointer exception.
DECLARE #table AS TABLE (Id int, isPrimary varchar(20))
INSERT INTO #table
SELECT 1,1001 UNION ALL
SELECT 2,1002 UNION ALL
SELECT 3,NULL UNION ALL
SELECT 4,1004
SELECT Id,ISNULL(isPrimary,0) UIdnum FROM #table
SELECT * FROM #table WHERE ISNULL(isPrimary,0)=1
SELECT * FROM #table WHERE ISNULL(isPrimary,0)=0
The IS_NULL function is only replacing the value of isPrimary to 0, in case the isPrimary is equal to NULL.
Your check is only true when isPrimary is not null (because if it is, it will be replaced by 0) AND isPrimary = 1.
SELECT c.contractid
FROM dba.contract AS c
INNER JOIN dba.person AS r ON (c.contractid = r.contractid AND isprimary = 1)
WHERE isprimary IS NOT NULL

Sql Server UDF behaves differently when a variable with value null is passed than when constant null is passed

I'm writing a stored procedure with quite a lot of expensive work to do that may or may not take a filter parameter. Doing the filtering is itself quite expensive, and the table being filtered is large. I just tried to change the inner filtering function so throw an error if called with invalid parameters, as a warning to developers not to use it that way.
BUT - If I call my outer test function with NULL, it works as I'd expect, not calling the inner function and not throwing the error. If I call my outer test function with a variable with the VALUE of NULL, then it calls the filter function with a null parameter, and throws the error, even thought the code only says to call the function when the value is not null.
What's going on here?
Much simplified example:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MyTable]') AND type in (N'U')) DROP TABLE MyTable
GO
CREATE TABLE MyTable (Pk int, Field int)
GO
INSERT INTO MyTable VALUES (1, 1)
INSERT INTO MyTable VALUES (2, 4)
INSERT INTO MyTable VALUES (3, 9)
INSERT INTO MyTable VALUES (4, 16)
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[FilterRows]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) DROP FUNCTION FilterRows
GO
CREATE FUNCTION FilterRows(#searchParameter int)
RETURNS #Pks TABLE
(
Pk int
)
AS
BEGIN
IF (#searchParameter IS null)
BEGIN
-- This is bad news. We don't want to be here with a null search, as the only thing we can do is return every row in the whole table
-- RAISERROR ('Avoid calling FilterRows with no search parameter', 16, 1)
-- we can't raise errors in functions!
-- Make it divide by zero instead then
INSERT INTO #Pks SELECT Pk FROM MyTable WHERE 1/0 = 1
END
ELSE
BEGIN
INSERT INTO #Pks SELECT Pk FROM MyTable WHERE Field > #searchParameter
END
RETURN
END
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[OuterFunction]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) DROP FUNCTION OuterFunction
GO
CREATE FUNCTION OuterFunction(#searchParameter int)
RETURNS TABLE AS
RETURN
SELECT *
FROM
MyTable
WHERE
(#SearchParameter IS NULL) OR (#searchParameter IS NOT NULL AND Pk IN (SELECT Pk FROM dbo.FilterRows(#searchParameter)))
GO
SELECT * FROM dbo.OuterFunction(2) -- Returns filtered values
SELECT * FROM dbo.OuterFunction(null) -- returns everything, doesn't call FilterRows
DECLARE #x int = null
SELECT * FROM dbo.OuterFunction(#x) -- WTF! Throws error!
The difference when a value null is passed than when constant null is passed is the same difference between using (is Null) and (= null)
#var = null -- considered as false
#var is null -- considered as unknown
for more details : SQL is null and = null
so if you want to make behavior of both (calling constant null & pass Null value) is the same, use the following tricky although I don't prefer this one.
Alter FilterRows function to be
IF (#searchParameter = null)
--IF (#searchParameter is null)
Note: sorry for typing this answer here, it is supposed to be comment instead of answer, the rule is "You must have 50 reputation to comment" and I have only 22 :(
I think what's going on is that in
SELECT * FROM MyTable WHERE (#SearchParameter IS NULL) OR
(#searchParameter IS NOT NULL AND Pk IN (SELECT Pk FROM dbo.FilterRows(#searchParameter)))
The query analyzer can see that the subquery
(SELECT Pk FROM dbo.FilterRows(#searchParameter))
does not depend on any values from MyTable. As it's constant for all rows, it runs that subquery first, in order to join MyTable to the results. So it executes it before evaluating the WHERE clause where it tests whether #searchParameter IS NULL or not.
When #searchParameter is just "NULL" and not a variable with value NULL, then the analyzer can short-circuit the whole where clause in the execution plan and so knows not to pre-calculate the subquery.
Or, something like that.

CAST/CONVERT empty string to INT in SQL Server

I came across a bug where I was using CAST(Col1 AS INT) + CAST(Col2 AS INT) where both Col1 and Col2 are VARCHAR and I was getting valid results out when Col1 or Col2 was blank and I didn't expect this. I checked and CAST (and CONVERT) both have this default behavior of replacing blank with 0:
SELECT CAST('' AS INT)
SELECT CONVERT(INT, '')
I checked the info page and I can't see any reference to explain why this is the behavior (or change it through a server setting). I can of course work around this but I wanted to ask why this is the behavior as I do not think it is intuitive.
I'd actually rather this CAST failed or gave NULL, is there a server setting somewhere which effects this?
Consider an INT in SQL Server. It can be one of three values:
NULL
0
Not 0
So if you're casting/converting an empty string, which you are assuming is a number, then 0 is the most logical value. It allows for a distinction between NULL and 0.
SELECT CAST(NULL AS INT) -- NULL
SELECT CAST('' AS INT) -- 0
SELECT CAST('42' AS INT) -- 42
I'd say that's logical.
If you did:
SELECT CAST('abc' AS INT)
You'd get:
Conversion failed when converting the varchar value 'abc' to data type int.
If you do wish to handle empty strings as NULL use NULLIF as Bogdan suggests in his answer:
DECLARE #val VARCHAR(2) = ''
SELECT CAST(NULLIF(#val,'') AS INT) -- produces NULL
NULLIF returns the first expression if the two expressions are not equal. If the expressions are equal, NULLIF returns a null value of the type of the first expression.
Finally, if your columns are storing INT values, then consider changing its data type to INT if you can.
As you probably know NULL is a marker that indicates that a data value does not exist. And '' is a value, empty but value.
So MS SQL cast (or converts) empty value into 0 by default. To overcome this and show it as NULL you can use NULLIF
Simple example:
SELECT int_as_varchars as actual,
cast(NULLIF(int_as_varchars,'') as int) as with_nullif,
cast(int_as_varchars as int) as just_cast
FROM (VALUES
('1'),
(''),
(NULL),
('0')
) as t(int_as_varchars)
Output:
actual with_nullif just_cast
1 1 1
NULL 0
NULL NULL NULL
0 0 0
As you see NULLIF in that case will help you to get NULL instead of 0.
What about this ?
declare #t table(bucket bigint);
INSERT INTO #t VALUES (1);
INSERT INTO #t VALUES (2);
INSERT INTO #t VALUES (-1);
INSERT INTO #t VALUES (5);
INSERT INTO #t VALUES (0);
declare #Bucket bigint = 0 --filter by 0
select * from #t
where 1=1
AND ((#Bucket is Null or cast(#Bucket as nvarchar) = '') or bucket=#Bucket)

SQL Server CTE Implicit Column Datatype issue

I'm running into a really odd error using a SQL Server Common Table Expression (CTE).
I think it is easiest to explain my issue by giving a code example.
CREATE TABLE #Test1 (TestCol varchar(3));
INSERT INTO #Test1 VALUES ('012')
INSERT INTO #Test1 VALUES ('ABC')
--This simple shows the Cast works as expected, and only on rows where the TestCol value is numeric
SELECT TestCol, CAST(TestCol as int) as IntCast
FROM #Test1
WHERE ISNUMERIC(TestCol) = 1;
--Here I create a cte using the same SQL as show above.
with cte as (
SELECT
TestCol, CAST(TestCol as int) as IntCast
FROM #Test1
WHERE ISNUMERIC(TestCol) = 1
)
/*
I have two examples below. The first can be executed to check for the existence of our 'ABC' value. It doesn't show up, which is expected.
The Second example, simple checks to see if our IntCast column is greater than 10. This throws an exception
*/
--SELECT * FROM cte
SELECT * FROM cte WHERE IntCast > 10
DROP TABLE #Test1
The exception here is
Conversion failed when converting the varchar value 'ABC' to data type int
I'm curious as to where this is occurring?
with cte as (
SELECT TestCol, Case when ISNUMERIC(TestCol) = 1 then CAST(TestCol as int) else NULL end as IntCast FROM #Test1
)

Resources