T-SQL Query null issue - sql

I want to write a stored procedure which will return true or false based on the following condition:
if 2 columns in all the rows in a result set are null, then return true
if at least one is not null, then return false
Like for example in the following query
select *
from Products
where productID = 123
and ProductType is null
and ProductDate is null
The above query can return 1 row or return 100 rows, so if all are null, then it will return true; if at least 1 is not null, then it will return false.

Given this sample data and desired results:
CREATE TABLE dbo.Products
(
ProductID int,
ProductType int,
ProductDate date
);
INSERT dbo.Products(ProductID, ProductType, ProductDate)
VALUES
(123, NULL, NULL),(123, NULL, NULL), -- this should return 1
(456, 5, NULL), (456, NULL, NULL), -- this should return 0
(789, NULL, GETDATE()); -- this should return 0
We can do:
CREATE PROCEDURE dbo.whatever1
#ProductID int
AS
BEGIN
DECLARE #return int = CASE WHEN EXISTS (SELECT 1
FROM dbo.Products
WHERE productID = #ProductID
AND (ProductType IS NOT NULL OR ProductDate IS NOT NULL)
) THEN 0 ELSE 1 END;
RETURN #return;
END
Then to execute:
DECLARE #hr int;
EXEC #hr = dbo.whatever1 #ProductID = 123;
SELECT #hr;
EXEC #hr = dbo.whatever1 #ProductID = 456;
SELECT #hr;
EXEC #hr = dbo.whatever1 #ProductID = 789;
SELECT #hr;
Results:
1
0
0
The tricky bit is when you say "return" we're not quite sure if you mean the explicit RETURN keyword, or an OUTPUT parameter, or a resultset. I feel like an OUTPUT parameter would be more appropriate in this case, since RETURN is generally reserved for error/status (and can only be an int). So we can use a more appropriate type depending on what we need:
CREATE PROCEDURE dbo.whatever2
#ProductID int,
#return bit OUTPUT
AS
BEGIN
SET #return = CASE WHEN EXISTS (SELECT 1
FROM dbo.Products
WHERE productID = #ProductID
AND (ProductType IS NOT NULL OR ProductDate IS NOT NULL)
) THEN 0 ELSE 1 END;
END
Then to execute:
DECLARE #return bit;
EXEC dbo.whatever2 #ProductID = 123, #return = #return OUTPUT;
SELECT #return;
EXEC dbo.whatever2 #ProductID = 456, #return = #return OUTPUT;
SELECT #return;
EXEC dbo.whatever2 #ProductID = 789, #return = #return OUTPUT;
SELECT #return;
Results:
1
0
0
Working example here (though be aware that fiddle displays bit as true/false instead of 1/0): db<>fiddle
If either of these procedures don't work like you expect, please adjust the fiddle with your sample data that you think is producing wrong results, and please be specific about what you mean by "return" and "always give me a value of" - we have no idea where you are seeing 1 and why you would expect to see 0.

Try:
IF EXISTS (SELECT 1 FROM Products WHERE ProductID = 123 AND (ProductType IS NOT NULL OR ProductDate IS NOT NULL))
SELECT CAST (0 AS bit)
ELSE
SELECT CAST (1 AS bit)
Using exists is more efficient than a count since it will only scan the table until the condition is met. Count will always scan the whole table.

select IIF(COUNT(*) = 0, 'true','false')
from Products
where productID=#ProductID and ProductType is null and ProductDate is null

Related

I am having the error with a Subquery returning more than one value. How do I reduce to one?

At the very end of the Stored procedure a SELECT statement is made to display the contents of the Table including function that will simultaneously populate fields in the table.
Here is the Select Statement:
IF #type = 'SH'
SELECT DISTINCT *
FROM #History
ORDER BY 1, 2, 3, 4, 5
ELSE
SELECT DISTINCT AmhazName
,Activity
,ServiceName
,Sarid
,PerformedDate
,UserRole
,Details
,dbo.ufn_SarHistoryActionText(sarid, status, performeddate) AS [ActionText]
,FullName
,CategoryDescription
,StatusDescription
,ActionPerformed
,Case
when Details like '%ProjManagerId%'
Then dbo.ufn_GetUserForHistoryReport (PerformedDate, SarId, '%ProjManagerId%')
Else
--when Details like '%UserId%'
dbo.ufn_GetUserForHistoryReport (PerformedDate, SarId, '%UserId%')
--(select 'no user') as [AssignedUser]
End as [AssignedUser]
--,dbo.ufn_GetPMForHistoryReport(PerformedDate, SarId) as [AssignedUser]
FROM #history
ORDER BY 1, 2, 3, 4, 5
DROP TABLE #Historyw
Here is the function I believe is causing problems:
ALTER FUNCTION [dbo].[ufn_SarHistoryActionText]
(
-- Add the parameters for the function here
#sarID int
, #status varchar(6)
, #statusDate datetime
)
RETURNS varchar(100)
AS
BEGIN
-- Declare the return variable here
DECLARE #Result varchar(100)
set #Result = (
SELECT C.ActionText
from LuStatusChange as C
WHERE C.FromStatus = dbo.ufn_SarHistoryPriorStatus(#sarID,#status,#statusDate)
AND C.ToStatus = #status
)
-- Return the result of the function
RETURN #Result
END
GO
As I debug and walk through loads of values, I haven't come across anything that resulted in multiple values. maybe I'm missing something.
Add TOP 1 in the select inside the function:
SELECT TOP 1 C.ActionText
Can you replace
set #Result = (
SELECT C.ActionText
from LuStatusChange as C
WHERE C.FromStatus = dbo.ufn_SarHistoryPriorStatus(#sarID,#status,#statusDate)
AND C.ToStatus = #status
)
as below:
#Result ***IN*** (
SELECT C.ActionText
from LuStatusChange as C
WHERE C.FromStatus = dbo.ufn_SarHistoryPriorStatus(#sarID,#status,#statusDate)
AND C.ToStatus = #status
)
If functionally your query should not written more than 1 row, something is wrong with your query.

Temp table has only one row inserted

Hi I have an SP in which i create a temporary table to store some values.
ALTER PROCEDURE [dbo].[test]
#id int,
#funds_limit money
AS
BEGIN
SET NOCOUNT ON;
DECLARE #threshold money;
CREATE TABLE #ConfigurationTemp
(id int,
name varchar(100) not null,
type varchar(100),
value varchar(100))
INSERT #ConfigurationTemp EXEC get_config #id, 'testType', null
select #threshold = value
from #ConfigurationTemp
where id=#id and name='testLimit'
print #threshold
IF (#funds_limit IS NOT NULL) AND (#threshold < #funds_limit)
BEGIN
DROP TABLE #ConfigurationTemp;
RETURN 1000;
END
select #threshold = value
from #ConfigurationTemp
where id=#id and name='testLimit1'
print #threshold
IF (#funds_limit IS NOT NULL) AND (#threshold < #funds_limit)
BEGIN
DROP TABLE #ConfigurationTemp;
RETURN 1001;
END
END
RETURN 0;
END
The temporary table have multiple rows.
eg:
1, fund_limit, testType, 10
2, fund_min_limit, testType, 20
I need to first validate the value for fund_limit (10) with the user input value (which will be an input parameter to the SP). If the validation fails, i return with an error code. If not, I go for the next check. i.e., fund_min_limit. I do the same with it and return a different error code. If no validation fails, i will return 0 which is considered to be a success.
In this case, I am getting same value for threshold always. i.e., the value of first row... 10.
How can I get the different threshold value from the temp table with respect to the name?
When you assign scalar variable with select - it may be not assigned (unchanged - may keep value from previous assignment) if this select returned zero rows. To ensure your variable changed it's value rewrite it as set expression.
So if you misspelled second threshold name you may be "getting" same #threshold value because second statement does not assign anything to your variable i.e. variable contains value from prior assignment (select). You may test it with additional variable for second threshold - it will be always NULL if i guessed the issue reason.
Also you are applying same #id filter which is a scalar variable. But your rows have different ids. So there is no chances right now to get any other threshold's value than for #id given.
set #threshold = (select t.value
from #ConfigurationTemp t
where t.name='testLimit')
print #threshold
IF #threshold < #funds_limit
RETURN 1000;
set #threshold = (select t.value
from #ConfigurationTemp t
where t.name='testLimit 2')
print #threshold
IF #threshold < #funds_limit
RETURN 1001;
If will succeed only when both arguments are NOT NULL.
One more approach:
declare
#threshold_a int,
#threshold_b int,
#threshold_c int
;with test as
(
select 'a' as name, 25 as value
union all
select 'b', 3
union all
select 'c', 100
union all
select 'd', -1
)
select
#threshold_a = case when t.name = 'a' then t.value else #threshold_a end,
#threshold_b = case when t.name = 'b' then t.value else #threshold_b end,
#threshold_c = case when t.name = 'c' then t.value else #threshold_c end
from test t
select
#threshold_a as [a],
#threshold_b as [b],
#threshold_c as [c]
GO
single select, several variables.
You have RETURN in your IF statment.
... RETURN 1000
and
... RETURN 1001
After insert a row the procedure end.
Maybe you want to assign a result to a variable
#return_Value = ''
#return_Value = #return_Value + '1000, '
....
#return_Value = #return_Value + '1001, '
RETURN #return_Value

Complex SQL selection query

I have a stored procedure which needs a different if condition to work properly.
The procedure has 2 parameter namely, #CategoryID and #ClassID, which basically come from a UI tree view functionality. #CategoryID corresponds to the parent nodes, while #ClassID corresponds to the child nodes.
Based upon the above parameters I need to make a selection(Column Code) from a table which has CategoryID and ClassID as columns.
Now there are 2 scenarios:
Scenario 1
#CategoryID:A
#ClassID:B (which is a child node of CategoryID A)
Result needed: Codes corresponding to only ClassID B, which is basically the intersection
Scenario 2
#CategoryID:A
#ClassID: C (which is not a child node for CategoryID A)
Result needed: Codes corresponding to the CategoryID A, as well as ClassID B, basically a union
The procedure which I wrote gives me correct answer for the second scenario, but the first scenario it fails. Below is my procedure:
ALTER PROCEDURE [dbo].[uspGetCodes]
#CategoryID varchar(50),
#ClassID varchar(50)
AS
BEGIN
BEGIN TRY
DECLARE #SQLQuery NVARCHAR(MAX)
SET #SQLQuery=N'SELECT Code FROM dbo.ClassToCategoryMapping WHERE '
IF (#CategoryID IS NULL OR #CategoryID='')
BEGIN
SET #SQLQuery=#SQLQuery + 'ClassId IN ('+#ClassID+')'
PRINT(#SQLQuery)
EXEC(#SQLQuery)
END
ELSE IF (#ClassID IS NULL OR #ClassID='')
BEGIN
SET #SQLQuery=#SQLQuery+'CategoryID IN ('+#CategoryID+')'
PRINT(#SQLQuery)
EXEC(#SQLQuery)
END
ELSE
BEGIN
SET #SQLQuery=#SQLQuery+'(CategoryID IN ('+#CategoryID+') OR ClassId IN ('+#ClassID+') )'
PRINT(#SQLQuery)
EXEC(#SQLQuery)
END
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() AS 'ErrorNumber', ERROR_MESSAGE() AS 'ErrorMessage', ERROR_SEVERITY() AS 'ErrorSeverity', ERROR_STATE() AS 'ErrorState', ERROR_LINE() AS 'ErrorLine'
RETURN ERROR_NUMBER()
END CATCH
END
The Last Else part actually does an 'OR', which gives me the union of the Codes for CategoryID's and ClassID's irrespective whether the given ClassID is a child of the given CategoryID or not.
My question over here would be, how to write the condition to achieve both the scenarios.
Latest Sample Data:
Scenario 1
#CategoryId=2,5, #ClassID=10 (Here 10 is the child while 2 is the parent, CategoryID 2 corresponds to ClassID's 10, 11, 12)
Expected Result: 10, 26, 27 (26 and 27 correspond to the CategoryID 5)
Scenario 2
#CategoryID=2, #ClassID=13,15 (13 and 15 is the child of a different parent, CategoryID 2 corresponds to ClassID's 10, 11 ,12)
Expected Result: 10, 11, 12, 13, 15
Data in Table dbo.ClasstoCategoryMapping will be somewhat as below:
CategoryID ClassID Code
2 10 200
2 11 201
2 12 202
5 26 501
5 27 502
6 15 601
6 16 602
6 17 603
7 20 701
7 21 702
7 22 703
I guess I have made my question quite clear, if no then, folks can ask me to edit it. I would be happy to do so. I urge the experts to assist me in this problem. Any pointers too will be quite appreciated.
Regards
Anurag
If I understand the question correctly, what you require in your result set is:
(all supplied classid) + (all classid for supplied categoryid with no matching supplied classid)
That would translate to the following:
CREATE PROCEDURE [dbo].[uspGetCodes]
(
#CategoryID varchar(50),
#ClassID varchar(50)
)
AS
BEGIN
SELECT COALESCE(CM.CategoryID, CM2.CategoryID) AS CategoryID,
COALESCE(CM.ClassID, CM2.ClassID) AS ClassID,
COALESCE(CM.Code, CM2.Code) AS Code
--Matched classIDs:
FROM dbo.udfSplitCommaSeparatedIntList(#ClassID) CLAS
JOIN dbo.ClassToCategoryMapping CM
ON CM.ClassId = CLAS.Value
--Unmatched CategoryIDs:
FULL
OUTER
JOIN dbo.udfSplitCommaSeparatedIntList(#CategoryID) CAT
ON CM.CategoryID = CAT.Value
LEFT
JOIN dbo.ClassToCategoryMapping CM2
ON CM.CategoryID IS NULL
AND CM2.CategoryID = CAT.Value
END
I have included Category, Class and Code in the result since its easier to see what's going on, however I guess you only really need code
This makes use of the following function to split the supplied comma separated strings:
CREATE FUNCTION [dbo].[udfSplitCommaSeparatedIntList]
(
#Values varchar(50)
)
RETURNS #Result TABLE
(
Value int
)
AS
BEGIN
DECLARE #LengthValues int
SELECT #LengthValues = COALESCE(LEN(#Values), 0)
IF (#LengthValues = 0)
RETURN
DECLARE #StartIndex int
SELECT #StartIndex = 1
DECLARE #CommaIndex int
SELECT #CommaIndex = CHARINDEX(',', #Values, #StartIndex)
DECLARE #Value varchar(50);
WHILE (#CommaIndex > 0)
BEGIN
SELECT #Value = SUBSTRING(#Values, #StartIndex, #CommaIndex - #StartIndex)
INSERT #Result VALUES (#Value)
SELECT #StartIndex = #CommaIndex + 1
SELECT #CommaIndex = CHARINDEX(',', #Values, #StartIndex)
END
SELECT #Value = SUBSTRING(#Values, #StartIndex, LEN(#Values) - #StartIndex + 1)
INSERT #Result VALUES (#Value)
RETURN
END
this is the sample query that can achieve your goal, is this what you want?
DECLARE #SAMPLE TABLE
(
ID INT IDENTITY(1,1),
CategoryId INT,
ClassID INT
)
INSERT INTO #sample
VALUES(2,10)
INSERT INTO #sample
VALUES(2,11)
INSERT INTO #sample
VALUES(2,12)
INSERT INTO #sample
VALUES(3,13)
DECLARE #CategoryID INT
DECLARE #ClassID Int
--Play around your parameter(s) here
SET #CategoryID = 2
SET #ClassID = 13
--Snenario 1
--#CategoryId=2, #ClassID=10 (Here 10 is the child while 2 is the parent, CategoryID 2 corresponds to ClassID's 10, 11, 12)
--Expected Result: 10
IF EXISTS(SELECT * FROM #SAMPLE WHERE CategoryId = #CategoryID AND ClassID = #ClassID)
SELECT ClassID FROM #SAMPLE WHERE CategoryId = #CategoryID AND ClassID = #ClassID
--Scenario 2
--#CategoryID=2, #ClassID=13 (13 is the child of a different parent, CategoryID 2 corresponds to ClassID's 10, 11 ,12)
--Expected Result: 10, 11, 12, 13
ELSE
SELECT ClassID FROM #SAMPLE WHERE ClassID = #ClassID OR CategoryId = #CategoryID
Try this
select * from yourtable
where CategoryId = #CategoryID and ClassID = #ClassID
union
select * from
(
select * from yourtable where ClassID = #ClassID
union
select * from yourtable where CategoryId = #CategoryID
) v
where not exists (select * from yourtable where CategoryId = #CategoryID and ClassID = #ClassID)
UPDATE FOR DELIMITED STRING
If you have a comma delimited string then it is best to use a CLR function to create the table, but you could use a SQL function. Examples of how to do this are easy to find with a Google search... but for reference here is one good article on the subject -> http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings I expect at some point there will be native support on most platforms.
Given that you have a function that returns a table of one column (named ID) of type int, or an empty table on a null input. Note: You may have to have the null return a table with one row containing an invalid value (a value that will never join), say -1.
The code is as simple as this:
SELECT Code
FROM ClassToCategoryMapping
LEFT JOIN MakeTableFromCVS(#CategoryID) AS CatTable
ON CatTable.ID = CategoryID
LEFT JOIN MakeTableFromCVS(#ClassID) AS ClassTable
ON ClassTable.ID = ClassID
WHERE
CASE
WHEN #CatgoryID IS NULL THEN -1 ELSE CatTable.ID
END = ISNULL(CatTable.ID,-1)
AND
CASE
WHEN #ClassID IS NULL THEN -1 ELSE ClassTable.ID
END = ISNULL(ClassTable.ID,-1)
AND
COALESCE(CatTable.ID,ClassTable.ID,-1) != -1
The logic is the same as below. Because the join will vary the values if it is not null we have to use a different trick. Here we use a marker value (in this case -1) to signal the null value. Any value that won't appear in the comma separated list will work as this marker value, remember it must be of the same type.
You don't need dynamic SQL here and you will find SQL server is better at optimizing if you don't use dynamic SQL. In fact, you don't even need an if statement If you can be sure the input is always null you can do this:
SELECT Code
FROM ClassToCategoryMapping
WHERE
How this works
This query checks for both CategoryID and ClassID columns match the incoming parameters but "ignores" the input when they are null by checking the column against itself. This is an handy SQL trick.
Note if you do need to check for empty strings then this will be almost as fast
DECLARE #myCatID varchar(max)
DECLARE #myClassID varchar(max)
SET #myCatID = #CategoryID
SET #myClassID = #ClassID
IF LTRIM(RTRIM(#CategoryID) = '' THEN SET #myCatID = NULL
IF LTRIM(RTRIM(#ClassID) = '' THEN SET #myClassID = NULL
SELECT Code
FROM ClassToCategoryMapping
WHERE CatgoryID = ISNULL(#myCatID,CategoryID)
AND ClassID = ISNULL(#myClassID,ClassID)
You can replace ISNULL() with COALESCE() if you want... they do the same thing in this case.

conditional stored procedure with/without passing parameter

I created a stored procedure which when passed nothing as parameter should return the entire table. But if the studentId is passed, then return her details.
Something like this
create procedure usp_GetStudents #studentId int = null
as
if (#studentId = null)
select * from Student
else
select * from Student where studentId = #studentId
Output
exec usp_GetStudents -- No records returned though there are records in the table
exec usp_GetStudents #studentId = null -- No records returned
exec usp_GetStudents #studentId = 256 -- 1 entry returned
Just curious to know if anything is wrong in the syntax/logic for returning all the entries of the table?
Thank you
You're trying to test for null using =, a comparison operator. If you're using ANSI nulls, any comparison against null is false.
Where #studentId is any value (or null) the following expressions are all false:
#studentId = null -- false
#studentId > null -- false
#studentId >= null -- false
#studentId < null -- false
#studentId <= null -- false
#studentId <> null -- false
So, in order to test for null you must use a special predicate, is null, i.e.:
#studentId is null
Shorter way to do that:
create procedure usp_GetStudents #studentId int = null
as
select * from Student
where studentId = isnull(#studentId,studentId)
You can't chack if value is null using =.
For your example you have to replace condition #studentId = null to is null syntax.
Try to change your code as below:
create procedure usp_GetStudents #studentId int = null
as
if (#studentId is null)
select * from Student
else
select * from Student where studentId = #studentId
Change the = to an is
create procedure usp_GetStudents #studentId int = null
as
if (#studentId is null)
select * from Student
else
select * from Student where studentId = #studentId

stored procedure refuses to insert data

I have the following table:
ThisCategoryID int IDENTITY, AUTO_INCREMENT
Title text
Type text
CategoryID int ALLOW NULLS
IsActive bit
OrderIndex int ALLOW NULLS
with this data:
ThisCategoryID Title Type CategoryID IsActive OrderIndex
0 Lunch Menu Section NULL True 3
2 Dessert Menu Section NULL True 1
3 Banh Mi Food Item 0 True 4
and the following stored procedure:
ALTER PROCEDURE [dbo].[sp_new_category]
#Title text,
#Type text,
#CategoryID int = null,
#IsActive bit,
#OrderIndex int = null
AS
DECLARE #Identity int
IF (SELECT count(*) FROM Category WHERE difference(title, #title) = 4) > 0
BEGIN
INSERT INTO Category (Title, Type, CategoryID, IsActive, OrderIndex) VALUES (#Title, #Type, #CategoryID, #IsActive, #OrderIndex)
SET #Identity = scope_identity()
END
ELSE
BEGIN
SET #Identity = -1
END
SELECT #Identity AS ID
RETURN #Identity
There is no item in the table with the title "Snack", yet the sp_new_category yields -1, every time I run it with the following parameters:
#Title: Snack
#Type: Menu Section
#CategoryID: NULL
#IsActive: True
#OrderIndex: NULL
Can someone explain to me why that is?
I believe your intent for the following conditional is to check if someone is trying enter an already existing item by Title (matched with SOUNDEX):
IF (SELECT count(*) FROM Category WHERE difference(title, #title) = 4) > 0
However, the way the conditional is written, you will only add items if they are similar. Try this instead:
DECLARE #Identity int
IF (SELECT count(*) FROM Category WHERE difference(title, #title) = 4) > 0
BEGIN
SET #Identity = -1
END
ELSE
BEGIN
INSERT INTO Category (Title, Type, CategoryID, IsActive, OrderIndex) VALUES (#Title, #Type, #CategoryID, #IsActive, #OrderIndex)
SET #Identity = scope_identity()
END
SELECT #Identity AS ID
RETURN #Identity
SELECT difference('Snack', 'Lunch')
UNION SELECT difference('Snack', 'Dessert')
UNION SELECT difference('Snack', 'Banh Mi')
3
1
2
None of your differences ever equal 4, so your insert is never run, so #identity will always be -1
difference is the difference in SOUNDEX encodings for text, which is an archaic hash for English names. It is not suitable for foreign words. If you tell us what you think you are accomplishing by using it, we may be able to help you.
Am I missing something?
The IF statement is false so he goes to the ELSE and puts SET #Identity = -1. So it'll return -1.