How to insert data into temp table using Case statement in SQL - sql

I am trying to insert the data into temp table using Case statement below is my code, and with this I am getting this error
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
I'm not able to figure out what I am doing wrong. Is this the correct way of inserting the data into a temp table based on certain condition provided in Case statement?
DECLARE #temp_idCol TABLE
(
uid int IDENTITY(1, 1),
id nvarchar(20)
)
DECLARE #_ThresholdOperator nvarchar(20) = '=',
#_Threshold_Val nvarchar(20) = '0.4'
INSERT INTO #temp_idCol (id)
SELECT
CASE
WHEN #_ThresholdOperator = '='
THEN (SELECT q.id
FROM [dbo].[Summit_Twitter_HashtagDetails] q
WHERE q.Ticket_ID IS NULL
AND q.SentimentAnalyis_Score = #_Threshold_Val
OR q.IsProcessedOrNot = 0)
WHEN #_ThresholdOperator = '>'
THEN (SELECT q.id
FROM [dbo].[Summit_Twitter_HashtagDetails] q
WHERE q.Ticket_ID IS NULL
AND q.SentimentAnalyis_Score > #_Threshold_Val
OR q.IsProcessedOrNot = 0)
WHEN #_ThresholdOperator = '<'
THEN (SELECT q.id
FROM [dbo].[Summit_Twitter_HashtagDetails] q
WHERE q.Ticket_ID IS NULL
AND q.SentimentAnalyis_Score < #_Threshold_Val
OR q.IsProcessedOrNot = 0)
END AS id
FROM
[dbo].[Summit_Twitter_HashtagDetails]

A CASE expression can only return 1 value.
But you can use IF and ELSE
Simplified example:
create table test (id int identity primary key, col decimal(9,1));
insert into test (col) values (0.1),(0.2),(0.3),(0.4),(0.5);
declare #ids table (id int);
declare #_ThresholdOperator varchar(2) = '>',
#_Threshold_Val nvarchar(20) = '0.3';
IF #_ThresholdOperator = '='
insert into #ids (id)
select id from test where col = #_Threshold_Val;
ELSE IF #_ThresholdOperator = '<'
insert into #ids (id)
select id from test where col < #_Threshold_Val;
ELSE IF #_ThresholdOperator = '>'
insert into #ids (id)
select id from test where col > #_Threshold_Val;
ELSE BEGIN
insert into #ids (id) values (0);
update #ids set id = id + 42;
END;
select * from #ids;
id
4
5
db<>fiddle here
Extra
A CASE could still be used in the WHERE clause.
As long it only returns 1 value.
It's just not sargable.
insert into #ids (id)
select id from test
where case #_ThresholdOperator
when '=' then iif(col=#_Threshold_Val,1,0)
when '<' then iif(col<#_Threshold_Val,1,0)
when '>' then iif(col>#_Threshold_Val,1,0)
end = 1;

Related

Insert query with case or if-else logic possible?

I want to insert values present in SELECT Statement below into table #tab3.
But I want to apply some sort of if-else or case logic to check like this one:
if (ABCList,1) = 'DOB' Then insert it into Dob1 else NULL
if (ABCList,2) = '04MARCH 1999' Then insert it into Dobnum else NULL
if (ABCList,3) = 'Passport' Then insert it into Pass1 else NULL
if (ABCList,4) = 'ABCC123' Then insert it into Passnum else NULL
But I cant figure out how to move data directly from a string into table.
MAIN CODE:
DECLARE #string3 varchar(max) = 'DOB;04MARCH 1999;Passport;ABCC123';
DECLARE #sep3 char(1) = ';'
DECLARE #dot3 char(1) = '.'
DECLARE #tab3 TABLE(
id INT IDENTITY(1,1) PRIMARY KEY,
Dob1 varchar(max),
Dobnum varchar(max),
Pass1 varchar(max),
Passnum varchar(max)
);
SELECT REVERSE(REPLACE(REVERSE(#string3), #sep3, #dot3)) as ABClist
INSERT into #tab3 (Dob1,Dobnum,Pass1,Passnum)
values
(
);
select * from #tab3
Could you not use CASE to achieve what you want?
Something such as
INSERT INTO #tab3 (Dob1,Dobnum,Pass1,Passnum)
SELECT
(
CASE
WHEN //Condition == 'DOB1'
THEN //Whatever
ELSE NULL
END
) AS Dob1,
(
CASE
WHEN //Condition == '04MARCH 1999'
THEN //Whatever
ELSE NULL
END
) AS Dobnum,
and so on.

SQL dynamic columns and Update multiple columns

I have a table UserPermission which has a number of columns of TINYINT type. e.g Read, Write, Update, Delete, Access etc.
I get three parameters in the stored procedure: #UserId, #ColNames, #ColValues where #ColNames and #ColValues are comma separated values.
How can I insert or update the table row (if already exists) with the passed column names and corresponding values.
I try to write the dynamic query which runs fine for INSERT but I was unable to write the UPDATE query dynamically with each column and its value to be concatenate.
Any response would be appreciated
Thanks in advance.
This is a somewhat dirty way to do what you require. However, if you create the following Stored Procedure:
CREATE FUNCTION [dbo].[stringSplit]
(
#String NVARCHAR(4000),
#Delimiter NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
WITH Split(stpos,endpos)
AS(
SELECT 0 AS stpos, CHARINDEX(#Delimiter,#String) AS endpos
UNION ALL
SELECT endpos+1, CHARINDEX(#Delimiter,#String,endpos+1)
FROM Split
WHERE endpos > 0
)
SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
'Data' = SUBSTRING(#String,stpos,COALESCE(NULLIF(endpos,0),LEN(#String)+1)-stpos)
FROM Split
)
You can then use that Procedure to join the data together:
DECLARE #TotalCols INT
DECLARE #TotalVals INT
SET #TotalCols = (
SELECT COUNT(ID) AS Total
FROM dbo.stringSplit('department, teamlead', ',')
);
SET #TotalVals = (
SELECT COUNT(ID) AS Total
FROM dbo.stringSplit('IT, Bob', ',')
);
IF #TotalCols = #TotalVals
BEGIN
IF OBJECT_ID('tempdb..#temptable') IS NOT NULL
DROP TABLE #temptable
CREATE TABLE #temptable (
ColName VARCHAR(MAX) NULL
,ColValue VARCHAR(MAX) NULL
)
INSERT INTO #temptable
SELECT a.DATA
,b.DATA
FROM dbo.stringSplit('department, teamlead', ',') AS a
INNER JOIN dbo.stringSplit('IT, Bob', ',') AS b ON a.Id = b.Id
SELECT *
FROM #temptable;
END
It's not very efficient, but it will bring you the desired results.
You can then use the temp table to update, insert and delete as required.
Instead of having a comma delimited list I would create a separate parameter for each Column and make its default value to NULL and in the code update nothing if its null or insert 0. Something like this....
CREATE PROCEDURE usp_UserPermissions
#UserID INT
,#Update INT = NULL --<-- Make default values NULL
,#Delete INT = NULL
,#Read INT = NULL
,#Write INT = NULL
,#Access INT = NULL
AS
BEGIN
SET NOCOUNT ON;
Declare #t TABLE (UserID INT, [Update] INT,[Read] INT
,[Write] INT,[Delete] INT,[Access] INT)
INSERT INTO #t (Userid, [Update],[Read],[Write],[Delete],[Access])
VALUES (#UserID , #Update , #Read, #Write , #Delete, #Access)
IF EXISTS (SELECT 1 FROM UserPermission WHERE UserID = #UserID)
BEGIN
UPDATE up -- Only update if a value was provided else update to itself
SET up.[Read] = ISNULL(t.[Read] , up.[Read])
,up.[Write] = ISNULL(t.[Write] , up.[Write])
,up.[Update] = ISNULL(t.[Update] , up.[Update])
,up.[Delete] = ISNULL(t.[Delete] , up.[Delete])
,up.[Access] = ISNULL(t.[Access] , up.[Access])
FROM UserPermission up
INNER JOIN #t t ON up.UserID = t.UserID
END
ELSE
BEGIN
-- if already no row exists for that User add a row
-- If no value was passed for a column add 0 as default
INSERT INTO UserPermission (Userid, [Update],[Read],[Write],[Delete],[Access])
SELECT Userid
, ISNULL([Update], 0)
, ISNULL([Read], 0)
, ISNULL([Write], 0)
, ISNULL([Delete], 0)
, ISNULL([Access], 0)
FROM #t
END
END

Function return table variable

I'm trying to create a function that return a table variable.So firstly i get data from Table1 and put it in another table variable. Here i want check if this variable isempty the function return the parameter result else return the result of the table variable
The function script is bellow :
USE[DATABase1]
GO
IF OBJECT_ID (N'CodeFunc', N'TF') IS NOT NULL DROP FUNCTION dbo.CodeFunc;
GO
CREATE FUNCTION CodeFunc ( #Code nvarchar(4) , #Table nvarchar(40) = '' )
RETURNS #VirtualDAT TABLE
(
RowID INT IDENTITY ( 1 , 1 ),
Code nvarchar(400)
)
AS
BEGIN
DECLARE #CodeM nvarchar(400)
DECLARE #imax INT SET #imax = ##ROWCOUNT
DECLARE #i INT SET #i = 1
DECLARE #SelectDAT TABLE
(
RowID INT IDENTITY ( 1 , 1 ),
Code nvarchar(400)
)
INSERT #SelectDAT
SELECT Code FROM table1
WHERE table1.id = 41
IF(EXISTS (SELECT 1 FROM #SelectDAT))
BEGIN
WHILE (#i <= #imax)
BEGIN
SELECT #CodeM = Code FROM #SelectDAT WHERE RowID = #i
INSERT INTO #VirtualDAT(Code) VALUES (#CodeM)
SET #i = #i + 1
END
END
ELSE
INSERT INTO #VirtualDAT(Code) VALUES (#Code)
RETURN
END
So this script works without put it inside function.
And i test this function like this :SELECT * FROM dbo.CodeFunc( 'toto',Default ) the result is :
IF(EXISTS (SELECT 1 FROM #SelectDAT)) no record returned
esle the result is ok
As VR46 says. The ##ROWCOUNT will be set to 0 because there is no query before it. Any code executing in a function happens as a seperate set of queries. It was probably returning a value outside the function because you had previously used the query window for another unrelated query
You could re-factor this function quite dramatically. Look below, ##ROWCOUNT will work here as it is just after the insert query and will definitely have a value based on the insert.
I have not been able to test this, but I think something like this should do the same job.
USE[DATABase1]
GO
IF OBJECT_ID (N'CodeFunc', N'TF') IS NOT NULL DROP FUNCTION dbo.CodeFunc;
GO
CREATE FUNCTION CodeFunc ( #Code nvarchar(4) , #Table nvarchar(40) = '' )
RETURNS #VirtualDAT TABLE
(
RowID INT IDENTITY ( 1 , 1 ),
Code nvarchar(400)
)
AS
BEGIN
insert into #VirtualDAT
Select Code from table1 where table1.id = 41
if ##ROWCOUNT = 0
begin
INSERT INTO #VirtualDAT(Code) VALUES (#Code)
end
RETURN
END
Since you are assigning #imax with ##ROWCOUNT right after declaration of variable will be initialized with zero.
From MSDN ##ROWCOUNT
Returns the number of rows affected by the last statement.
If am not wrong you need to assign value to #imax after the insert into..select query.
INSERT #SelectDAT
SELECT Code FROM table1
WHERE table1.id = 41
SET #imax= ##ROWCOUNT
You can do the same in SET BASED APPROACH without using while loop.
CREATE FUNCTION Codefunc (#Code NVARCHAR(4),
#Table NVARCHAR(40) = '')
returns #VirtualDAT TABLE (
rowid INT IDENTITY ( 1, 1 ),
code NVARCHAR(400))
AS
BEGIN
IF EXISTS (SELECT code
FROM table1
WHERE table1.id = 41)
BEGIN
INSERT INTO #VirtualDAT
(code)
SELECT code
FROM table1
WHERE table1.id = 41
END
ELSE
INSERT INTO #VirtualDAT
(code)
VALUES (#Code)
RETURN
END

In operator matching all rows

I want to return matching all values of csv as the traditional "in" operator matches any of the items present in csv:
SELECT * FROM #MyTable
WHERE [UserID] IN (1,2)
The above query will not serve my purpose as I want to match the rows which have both records for a group. In my case group will by typeid.
Query to populate the table:
DECLARE #MyTable TABLE
(
[TypeID] INT ,
[UserID] INT
)
INSERT INTO #MyTable
SELECT 1 ,
1
UNION
SELECT 1 ,
2
UNION
SELECT 2 ,
1
UNION
SELECT 2 ,
2
UNION
SELECT 2 ,
3
UNION
SELECT 3 ,
1
UNION
SELECT 3 ,
2
UNION
SELECT 3 ,
3
UNION
SELECT 3 ,
4
To query the above table I have input string of userid
DECLARE #UserIDString VARCHAR(256)
Here is my requirement:
When the input is '1,2'; I want typeid 1 as the output as that group has all the records present in csv.
If the input is '1,2,3' ; 2 typeid should be returned as that group has all the values present in csv.
If the input is '1,2,3,4' ; 3 typeid should be returned as that group has all the values present in csv.
EDIT:
Here is the split function to split the csv:
CREATE FUNCTION [dbo].[Split_String]
(
#inputString NVARCHAR(2000) ,
#delimiter NVARCHAR(20) = ' '
)
RETURNS #Strings TABLE
(
[position] INT IDENTITY
PRIMARY KEY ,
[value] NVARCHAR(2000)
)
AS
BEGIN
DECLARE #index INT
SET #index = -1
WHILE ( LEN(#inputString) > 0 )
BEGIN-- Find the first delimiter
SET #index = CHARINDEX(#delimiter, #inputString)
-- No delimiter left?
-- Insert the remaining #inputString and break the loop
IF ( #index = 0 )
AND ( LEN(#inputString) > 0 )
BEGIN
INSERT INTO #Strings
VALUES ( RTRIM(LTRIM(CAST(#inputString AS NVARCHAR(2000))) ))
BREAK
END
-- Found a delimiter
-- Insert left of the delimiter and truncate the #inputString
IF ( #index > 1 )
BEGIN
INSERT INTO #Strings
VALUES ( RTRIM(LTRIM(CAST(LEFT(#inputString, #index - 1) AS NVARCHAR(2000)) ) ))
SET #inputString = RIGHT(#inputString,
( LEN(#inputString) - #index ))
END -- Delimiter is 1st position = no #inputString to insert
ELSE
SET #inputString = CAST(RIGHT(#inputString,
( LEN(#inputString) - #index )) AS NVARCHAR(2000))
END
RETURN
END
GO
Edit:
Thanks #Tab, with further modifications I have come to solution:
DECLARE #InputString VARCHAR(256)
DECLARE #Count VARCHAR(256)
--SET #InputString = '1,2'
DECLARE #DummyTable TABLE
(
[position] INT ,
[value] INT
)
INSERT INTO #DummyTable
( [position] ,
[value]
)
SELECT [position] ,
[value]
FROM [dbo].[Split_String](#InputString, ',')
SELECT #Count = COUNT(1)
FROM #DummyTable
SELECT TypeID
FROM #MyTable
WHERE TypeID NOT IN (
SELECT TypeID
FROM #MyTable T
LEFT OUTER JOIN #DummyTable ss ON t.UserId = ss.Value
WHERE ss.Position IS NULL )
GROUP BY TypeID
HAVING COUNT(TypeID) = #Count
Using your split function, you can do an OUTER JOIN and make sure there are no NULL rows:
SELECT TypeID
FROM #MyTable
WHERE TypeID NOT IN (
SELECT TypeID
FROM #MyTable t
LEFT OUTER JOIN [dbo].[Split_String] (#InputString,',') ss
ON t.UserId=ss.Value
WHERE ss.Position IS NULL
) x
Untested, but I think that should do it.
However, this should return ALL the types that meet the requirement of:
that group has all the records present in csv.
In your question, you seem to imply that only one row should be returned, but why would that be the case if more than one row matches all the values in the csv? And what is the rule for determining which row is returned when there is more than one match?

More efficient double coalesce join alternative

I have a procedure with a (slightly more complex) version of the below:
CREATE PROC sp_Find_ID (
#Match1 varchar(10),
#Match2 varchar(10)
) AS
DECLARE #ID int
SELECT #ID = ID
FROM Table1
WHERE Match1 = #Match1
AND Coalesce(Match2,#Match2,'') = Coalesce(#Match2,Match2,'')
SELECT #ID ID
Essentially Match1 is a mandatory match, but Match2 is both optional on the input to the procedure, and on the table being searched. The 2nd match succeeds where the input and/or the table Match2 values are null, or where they're both the same (not null) value.
My question is: Is there a more efficient (or even more readable) way of doing this?
I've used this method a few times, and I feel slightly sullied each time (subjective dirtiness admittedly).
Is there a more efficient (or even more readable) way of doing this?
The example you provided, using COALESCE/etc is non-sargable. You need to separate things so only what needs to be present in the query is run:
DECLARE #ID int
IF #Match2 IS NOT NULL
BEGIN
SELECT #ID = t.id
FROM TABLE1 t
WHERE t.match1 = #Match1
AND (t.match2 = #Match2 OR t.match2 IS NULL)
END
ELSE
BEGIN
SELECT #ID = t.id
FROM TABLE1 t
WHERE t.match1 = #Match1
END
SELECT #ID ID
If you want this to occur in a single SQL statement, dynamic SQL is the only real alternative. I highly recommend reading The curse and blessing of dynamic SQL before reading further:
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = N' SELECT #ID = t.id
FROM TABLE1 t
WHERE t.match1 = #Match1 '
SET #SQL = #SQL + CASE
WHEN #Match2 IS NOT NULL THEN
' AND (t.match2 = #Match2 OR t.match2 IS NULL) '
ELSE
' '
END
BEGIN
EXEC sp_executesql #SQL,
N'#ID INT OUTPUT, #Match1 VARCHAR(10), #Match2 VARCHAR(10)',
#ID, #Match1, #Match2
END
Avoiding OR and ISNULL etc
The EXCEPT bit returns no rows if either side IS NULL
Match2 <> #Match2 means exclude non-NULL non-matching
Something like this
DROP TABLE dbo.Table1
CREATE TABLE dbo.Table1 (ID int NOT NULL, Match1 int NOT NULL, Match2 int NULL)
INSERT dbo.Table1 VALUES (1, 55, 99), (2, 55, NULL)
DECLARE #Match1 int = 55, #Match2 int
SELECT ID
FROM
(
SELECT ID FROM Table1 WHERE Match1 = #Match1
EXCEPT -- #Match2 = NULL, match both rows (99, NULL)
SELECT ID FROM Table1 WHERE Match2 <> #Match2
) foo
SET #Match2 = -1
SELECT ID
FROM
(
SELECT ID FROM Table1 WHERE Match1 = #Match1
EXCEPT -- #Match2 = -1, match ID = 2 only where Match2 IS NULL
SELECT ID FROM Table1 WHERE Match2 <> #Match2
) foo
Don't know if this is any more preferable.
SELECT #ID = ID
FROM Table1
WHERE Match1 = #Match1
AND ((Match2 = #Match2) OR Coalesce(Match2,#Match2) IS NULL)
I would have thought this should do it - providing that the #Match2 value will be NULL if it is optional.
CREATE PROC sp_Find_ID (
#Match1 varchar(10),
#Match2 varchar(10)
) AS
DECLARE #ID int
SELECT #ID = ID
FROM Table1
WHERE Match1 = #Match1
AND Match2 = IsNull(#Match2, Match2)
SELECT #ID ID
Seems simple to me? I must be missing something.. you dont need the Coalesce
SELECT #ID = ID
FROM Table1
WHERE Match1 = #Match1
AND (
(Match2 is null and #Match2 is null)
or
#Match2=Match2
)
SELECT #ID ID