Related
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;
I am trying to store sensor data recorded to the appropriate target column, depending on what the latest settings are in the settings table (represented as #Unit in the code). The code I have so far gives me a syntax error at line 19 near 'Time', which I am not sure as to why. Any ideas?
CREATE PROCEDURE StoreTemp
#Temperature float,
#Seconds int,
#SessionId int,
#DatasetId int
AS
DECLARE
#Unit varchar(20),
#TempCol varchar(20)
SELECT #Unit = RecordingUnit from ReadLastUnit
SELECT #TempCol = (case #Unit when 'Celsius' then 'Temperature_C' else 'Temperature_F' END)
INSERT INTO DATASET (#TempCol, Time)
VALUES (#Temperature, GETDATE())
GO
You can't use a variable to specify a column name in an insert statement. The column name must be static.
You could use dynamic SQL, however in this case you can just conditionally insert a value into the correct column using a case expression as follows:
CREATE PROCEDURE StoreTemp
(
#Temperature float
, #Seconds int
, #SessionId int
, #DatasetId int
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Unit varchar(20), #TempCol varchar(20);
SELECT #Unit = RecordingUnit from ReadLastUnit;
--SELECT #TempCol = (case #Unit when 'Celsius' then 'Temperature_C' else 'Temperature_F' END);
INSERT INTO DATASET (Temperature_C, Temperature_F, [Time])
SELECT
CASE WHEN #Unit = 'Celsius' THEN #Temperature ELSE NULL END
, CASE WHEN #Unit != 'Celsius' THEN #Temperature ELSE NULL END
, GETDATE();
END
GO
For better performance and a neater query, you could even simplify the insert to do it all e.g.
INSERT INTO DATASET (Temperature_C, Temperature_F, [Time])
SELECT
CASE WHEN RecordingUnit = 'Celsius' THEN #Temperature ELSE NULL END
, CASE WHEN RecordingUnit != 'Celsius' THEN #Temperature ELSE NULL END
, GETDATE()
from ReadLastUnit;
Thats assuming you can always guarantee a single row in ReadLastUnit.
I would like to extract only the last three values from a delimited string and generate a delimited substring with those three values. Could anyone suggest what is the best way to do this. I tried using STRING_SPLIT and was able to successfully split the string into multiple values but I am not sure how to proceed further. Any thoughts would be appreciated.
SELECT value FROM STRING_SPLIT('CatalogTypeCode VARCHAR(10) NOT NULL
,EventID INT NOT NULL
,ModelCode TINYINT NOT NULL
,YearID INT NOT NULL
,PerilSetCode INT NOT NULL
,GrossLoss FLOAT NULL
,GrossSD FLOAT NULL
,GrossMaxLoss FLOAT NULL',',')
Output:
CatalogTypeCode VARCHAR(10) NOT NULL
EventID INT NOT NULL
ModelCode TINYINT NOT NULL
YearID INT NOT NULL
PerilSetCode INT NOT NULL
GrossLoss FLOAT NULL
GrossSD FLOAT NULL
GrossMaxLoss FLOAT NULL
Expected Output :
'GrossLoss FLOAT NULL,GrossSD FLOAT NULL,GrossMaxLoss FLOAT NULL'
Perhaps another option using a little XML in concert with reverse() ... twice
Example
Declare #YourTable table (ID int,SomeCol varchar(150))
Insert Into #YourTable values
(1,'Val1,Val2,Val3,Val4')
,(2,'Val1,Val2,Val3,Val4,Val5,Val6')
,(3,'Val1,Val2')
,(4,'Val1')
,(5,null)
Select A.ID
,LastThree = reverse(concat(Pos1,','+Pos2,','+Pos3))
From #YourTable A
Cross Apply (
Select Pos1 = n.value('/x[1]','varchar(max)')
,Pos2 = n.value('/x[2]','varchar(max)')
,Pos3 = n.value('/x[3]','varchar(max)')
From (Select cast('<x>' + replace(reverse(SomeCol),',','</x><x>')+'</x>' as xml) as n) X
) B
Returns
ID LastThree
1 Val2,Val3,Val4
2 Val4,Val5,Val6
3 Val1,Val2 -- Notice only 2 values
4 Val1 -- Notice only 1 value
5 -- Notice value was null
Unfortunately, split_string() doesn't return the position of the values within the string.
This will work, assuming there are no duplicates among the lines:
SELECT string_agg(line, ',') within group (order by pos) as lines_3
FROM (SELECT TOP (3) s.line, CHARINDEX(line, lines) as pos
FROM (VALUES ('CatalogTypeCode VARCHAR(10) NOT NULL
,EventID INT NOT NULL
,ModelCode TINYINT NOT NULL
,YearID INT NOT NULL
,PerilSetCode INT NOT NULL
,GrossLoss FLOAT NULL
,GrossSD FLOAT NULL
,GrossMaxLoss FLOAT NULL')
) v(lines) OUTER APPLY
STRING_SPLIT(v.lines, ',') s(line)
ORDER BY pos
) s
EDIT:
Oops, the above works on SQL Server 2017, but not 2016. You can use conditional aggregation:
SELECT (MAX(CASE WHEN seqnum = 1 THEN line END) + ','
MAX(CASE WHEN seqnum = 2 THEN line END) + ','
MAX(CASE WHEN seqnum = 3 THEN line END)
) as lines_3
FROM (SELECT TOP (3) s.line,
ROW_NUMBER() OVER (ORDER BY CHARINDEX(line, lines)) as seqnum
FROM (VALUES ('CatalogTypeCode VARCHAR(10) NOT NULL
,EventID INT NOT NULL
,ModelCode TINYINT NOT NULL
,YearID INT NOT NULL
,PerilSetCode INT NOT NULL
,GrossLoss FLOAT NULL
,GrossSD FLOAT NULL
,GrossMaxLoss FLOAT NULL')
) v(lines) OUTER APPLY
STRING_SPLIT(v.lines, ',') s(line)
ORDER BY pos
) s;
I am using this table-valued function for years now. Works fine. After creating function call select top 3 * from lma.dbo.split_test('a,b,c,d,e',',') order by id desc
CREATE FUNCTION [dbo].[Split]
(
#String VARCHAR(MAX),
#Delimiter VARCHAR(10)
)
RETURNS #Temptable TABLE (id int identity, items varchar(MAX))
AS
BEGIN
DECLARE #Idx INT
DECLARE #Slice VARCHAR(MAX)
DECLARE #Delimiterlen INT=LEN(#Delimiter)
--,#String VARCHAR(MAX)='N'
--,#Delimiter VARCHAR(10)=';;'
IF (#String like '%' + #Delimiter + '%')
BEGIN
SELECT #Idx = 1
IF LEN(#String)<#Delimiterlen or #String is null RETURN
WHILE #Idx!= 0
BEGIN
SET #Idx = CHARINDEX(#Delimiter,#String)
IF #Idx!=0
SET #Slice = left(#String,#idx-1)
ELSE
SET #Slice = #String
IF(LEN(#Slice)>0)
INSERT INTO #Temptable(Items) values(#Slice)
--290913 : IF WE WANT TO USE DELIMETER LENGTH GREATER THAN 2
IF LEN(#String) >= (#Idx -1 + #Delimiterlen)
SET #String = RIGHT(#String,LEN(#String) - (#Idx-1+#Delimiterlen))
IF LEN(#String) = 0 BREAK
END
END
ELSE
BEGIN
INSERT INTO #Temptable(Items) values(#String)
END
DELETE #Temptable WHERE items =''
RETURN
END
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
I have two tables named Retail and Activity and the data is as shown below:
Retail Table
Activity Table
My main concern is about Ok and Fault column of the table Retail, as you can see it contains comma separated value of ActivityId.
What i want is, if the Ok column has ActivityId the corresponding column will have Yes, if the Fault column has ActivityId then it should be marked as No
Note I have only four columns that is fixed, it means i have to check that either four of the columns has its value in Ok or Fault, if yes then only i have to print yes or no, otherwise null.
Desired result should be like :
If the value is in Ok then yes other wise No.
I guessing you want to store 'yes' or 'No' in some column. Below is the query to update that column :
UPDATE RetailTable
SET <Result_Column>=
CASE
WHEN Ok IS NOT NULL THEN 'Yes'
WHEN Fault IS NOT NULL THEN 'No'
END
You can use below code as staring point:
DECLARE #Retail TABLE
(
PhoneAuditID INT,
HandsetQuoteID INT,
Ok VARCHAR(50)
)
INSERT INTO #Retail VALUES (1, 1009228, '4,22,5')
INSERT INTO #Retail VALUES (2, 1009229, '1')
DECLARE #Activity TABLE
(
ID INT,
Activity VARCHAR(50)
)
INSERT INTO #Activity VALUES (1, 'BatteryOK?'), (4, 'PhonePowersUp?'), (22,'SomeOtherQuestion?'), (5,'LCD works OK?')
SELECT R.[PhoneAuditID], R.[HandsetQuoteID], A.[Activity], [Ok] = CASE WHEN A.[ID] IS NOT NULL THEN 'Yes' END
FROM #Retail R
CROSS APPLY dbo.Split(R.Ok, ',') S
LEFT JOIN #Activity A ON S.[items] = A.[ID]
I have used Split function provided here:
separate comma separated values and store in table in sql server
Try following query. i have used pivot to show row as columns. I have also used split function to split id values which you can find easily on net:
CREATE TABLE PhoneAudit
(
PhoneAuditRetailID INT,
HandsetQuoteID INT,
Ok VARCHAR(50),
Fault VARCHAR(50)
)
INSERT INTO PhoneAudit VALUES (1,10090,'1,2','3')
CREATE TABLE ActivityT
(
ID INT,
Activity VARCHAR(100)
)
INSERT INTO ActivityT VALUES (1,'Battery')
INSERT INTO ActivityT VALUES (2,'HasCharger')
INSERT INTO ActivityT VALUES (3,'HasMemoryCard')
INSERT INTO ActivityT VALUES (4,'Test')
DECLARE #SQL AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
SELECT #ColumnName= ISNULL(#ColumnName + ',','') + QUOTENAME(Activity) FROM (SELECT DISTINCT Activity FROM ActivityT) AS Activities
SET #SQL = 'SELECT PhoneAuditRetailID, HandsetQuoteID,
' + #ColumnName + '
FROM
(SELECT
t1.PhoneAuditRetailID,
t1.HandsetQuoteID,
TEMPOK.*
FROM
PhoneAudit t1
CROSS APPLY
(
SELECT
Activity,
(CASE WHEN ID IN (SELECT * FROM dbo.SplitIDs(t1.Ok,'',''))
THEN ''YES''
ELSE ''NO''
END) AS VALUE
FROM
ActivityT t2
) AS TEMPOK) AS t3
PIVOT
(
MIN(VALUE)
FOR Activity IN ('+ #ColumnName + ')
) AS PivotTable;'
EXEC sp_executesql #SQL
DROP TABLE PhoneAudit
DROP TABLE ActivityT
There are several ways to do this. If you are looking for a purely declarative approach, you could use a recursive CTE. The following example of this is presented as a generic solution with test data which should be adaptable to your needs:
Declare #Delimiter As Varchar(2)
Set #Delimiter = ','
Declare #Strings As Table
(
String Varchar(50)
)
Insert Into #Strings
Values
('12,345,6,78,9'),
(Null),
(''),
('123')
;With String_Columns As
(
Select
String,
Case
When String Is Null Then ''
When CharIndex(#Delimiter,String,0) = 0 Then ''
When Len(String) = 0 Then ''
Else Left(String,CharIndex(#Delimiter,String,0)-1)
End As String_Column,
Case
When String Is Null Then ''
When CharIndex(#Delimiter,String,0) = 0 Then ''
When Len(String) = 0 Then ''
When Len(Left(String,CharIndex(#Delimiter,String,0)-1)) = 0 Then ''
Else Right(String,Len(String)-Len(Left(String,CharIndex(#Delimiter,String,0)-1))-1)
End As Remainder,
1 As String_Column_Number
From
#Strings
Union All
Select
String,
Case
When CharIndex(#Delimiter,Remainder,0) = 0 Then Remainder
Else Left(Remainder,CharIndex(#Delimiter,Remainder,0)-1)
End As Remainder,
Case
When CharIndex(#Delimiter,Remainder,0) = 0 Then ''
When Len(Left(Remainder,CharIndex(#Delimiter,Remainder,0)-1)) = 0 Then ''
Else Right(Remainder,Len(Remainder)-Len(Left(Remainder,CharIndex(#Delimiter,Remainder,0)-1))-1)
End As Remainder,
String_Column_Number + 1
From
String_Columns
Where
(Remainder Is Not Null And Len(Remainder) > 1)
)
Select
String,
String_Column,
String_Column_Number
From
String_Columns