Update column using comma-separated values in SQL Server - sql-server

I have 2 tables in SQL Server:
Table1
ID SkillSetRequired SkillDesc
1 100-1,101-1,102-2,103-3 null
2 100-4,105-2 null
3 100-1,102-2,104-2 null
4 100-5,102-2 null
5 105-1 null
Table2
ID SkillName
100 .Net
101 Java
102 JQuery
103 Sql
104 PHP
105 C
I need to update the SkillDesc column of Table1 with the SkillNames. For example in first row I need to update skilldesc as '.Net,Java,Jquery,SQL'
How can I do this with out using cursors?
I am getting the result in a string from the below query for a single record. But I need to update all the rows.
declare #result varchar(max)
SET #result = ''
SELECT #result = #result + ltrim(rtrim(SkillName)) + ',' from #Table2 where id in(
select SUBSTRING(items, 0 + 1, CHARINDEX('-', items, 1) - 0 - 1) from split('100-1,101-1,102-2,103-3',','))
select #result

Firstly, How did u insert 100-1,101-1,102-2,103-3 into the table ?
Do the below maping while adding the above.
Use a Case statement like
Case
when #Skillset = '100' THEN #desc = .Net
when #Skillset = '101' THEN #desc = Java
....
END
same way for other skill sets.
Then while inserting into the table for every id add the skillset and use the #Desc for the which updates

How did u manage adding 100-1,101-1,102-2,103-3 . This is not the proper way to add...

I think cursor is the best option even though it is taking time to execute.
SELECT ID,SkillSetRequired
FROM #Table1
OPEN #CurSkillUpdate
FETCH NEXT
FROM #CurSkillUpdate INTO #id,#skills
WHILE ##FETCH_STATUS = 0
BEGIN
SET #result = ''
SELECT #result = #result + ltrim(rtrim(SkillName)) + ',' from #Table2 where id in(
select SUBSTRING(items, 0 + 1, CHARINDEX('-', items, 1) - 0 - 1) from split(#skills,','))
update #Table1 set SkillDesc=#result where ID= #id
FETCH NEXT
FROM #CurSkillUpdate INTO #id,#skills
END

Related

Problem when using ##rowcount on function in SQL Server 2019

I am using ##rowcount in my functions like this:
ALTER FUNCTION [dbo].[GetUserNameFamily]
(#UsrID INT)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #Name NVARCHAR(MAX)
DECLARE #Family NVARCHAR (MAX)
DECLARE #cou INT
SELECT #Name = ut.Fname, #Family = ut.Lname
FROM User_tbl ut
WHERE ut.UserID = #UsrID
IF ##ROWCOUNT = 0
RETURN 'row 0'
IF #Name IS NULL
SET #Name = ''
IF #Family IS NULL
SET #Family = ''
RETURN #Name + ' ' + #Family
END
When I use this function in a query like that:
declare #ID int=3118
select *
from Files_tbl
where RefID = #ID -- query rows affected is 0
select
dbo.GetUserNameFamily(TicketResponse_tbl.CreateByUserID) as CreateByFullName
from
TicketResponse_tbl
where
TicketResponse_tbl.TicketID = #ID
My result is:
After removing where in "select Files_tbl" query and changed this query rows affected from 0 to n.
declare #ID int = 3118
select *
from Files_tbl
-- where RefID = #ID -- query rows affected is not 0
select
dbo.GetUserNameFamily(TicketResponse_tbl.CreateByUserID) as CreateByFullName
from
TicketResponse_tbl
where
TicketResponse_tbl.TicketID = #ID
My function result changes to :
This problem occurred after upgrading the database compatibility level to SQL Server 2019
As mentioned by others, there was a bug in the new (2019) feature called Scalar UDF Inlining that involved side-affecting functions such as ##ROWCOUNT. Updating to the latest build of SQL Server (which you should do anyway) would have fixed this.
Be that as it may, to continue using Inlining you can avoid ##ROWCOUNT by simplifying your function like this
CREATE OR ALTER FUNCTION [dbo].[GetUserNameFamily]
(#UsrID INT)
RETURNS NVARCHAR(MAX)
AS
BEGIN
RETURN ISNULL((
SELECT CONCAT(ut.Fname, ' ', ut.Lname)
FROM User_tbl ut
WHERE ut.UserID = #UsrID
), 'row 0');
END
But I would advise you to just transform this into an inline Table Valued Function, which will always be inlined:
CREATE OR ALTER FUNCTION [dbo].[GetUserNameFamily]
(#UsrID INT)
RETURNS TABLE
AS RETURN
SELECT
ISNULL((
SELECT CONCAT(ut.Fname, ' ', ut.Lname)
FROM User_tbl ut
WHERE ut.UserID = #UsrID
), 'row 0') AS UserName;
You use it like this
SELECT n.UserName
FROM YourTable t
CROSS APPLY dbo.GetUserNameFamily(t.Id) n;

Check if one of many temporary table exists and have values in SQL Server 2014

i have a stored procedure where i insert results of many controls in temporary table, at the end i have to check if all table exists and if are empty or not
i start with
IF(NOT EXISTS (SELECT TOP 1 1 FROM #tblControllo1)
AND NOT EXISTS (SELECT TOP 1 1 FROM #tblControllo2)
AND NOT EXISTS (SELECT TOP 1 1 FROM #tblControllo3)
AND NOT EXISTS (SELECT TOP 1 1 FROM #tblControllo4)
...
but i get error when some table does not exists so i have to mix those chek with
OBJECT_ID('tempdb..#tblControllo1') IS NOT NULL
but i don't find an elegant way to do it other than
DECLARE #controllo BIT = 1
IF OBJECT_ID('tempdb..#tblControllo1') IS NOT NULL
IF(EXISTS (SELECT TOP 1 1 FROM #tblControllo1) )
SET #controllo = 0
IF OBJECT_ID('tempdb..#tblControllo2') IS NOT NULL
IF(EXISTS (SELECT TOP 1 1 FROM #tblControllo2) )
SET #controllo = 0
IF OBJECT_ID('tempdb..#tblControllo3') IS NOT NULL
IF(EXISTS (SELECT TOP 1 1 FROM #tblControllo3) )
SET #controllo = 0
....
IF(#controllo = 1)
-- do stuff
is there a better way to do it?
The biggest problem is that you can't execute directly an SQL with SELECT of a table that may not exist because it will throw an error when trying to execute it. The following script uses a Dynamic SQL with a cursor to validate each table you need, breaking at the first failed condition.
DECLARE #AtLeastOneValidationFails BIT = 0
DECLARE TemporaryTableCursor CURSOR FOR
SELECT
G.TemporaryTableName
FROM
(VALUES
('#FirstTable'), -- Your tables to validate here
('#SecondTable'))
AS G (TemporaryTableName)
DECLARE #TemporaryTableName VARCHAR(100)
OPEN TemporaryTableCursor
FETCH NEXT FROM TemporaryTableCursor INTO #TemporaryTableName
WHILE ##FETCH_STATUS = 0
BEGIN
IF OBJECT_ID('tempdb..' + #TemporaryTableName) IS NULL
BEGIN
SET #AtLeastOneValidationFails = 1
BREAK
END
DECLARE #DynamicSQL NVARCHAR(MAX) = N'
IF NOT EXISTS (SELECT 1 FROM ' + #TemporaryTableName + ')
SET #ExistFails = 1'
EXEC sp_executesql
#stmt = #DynamicSQL,
#params = N'#ExistFails BIT OUTPUT',
#ExistFails = #AtLeastOneValidationFails OUTPUT
IF #AtLeastOneValidationFails = 1
BREAK
FETCH NEXT FROM TemporaryTableCursor INTO #TemporaryTableName
END
CLOSE TemporaryTableCursor
DEALLOCATE TemporaryTableCursor
SELECT
AtLeastOneValidationFails = #AtLeastOneValidationFails
This will ensure that the tables exists before issuing the SELECT (thus not failing). Please be careful with table names as this is executing Dynamic SQL.
You can wrap this in a procedure and pass the table names as parameter so you don't repeat it everywhere. You can also edit it to return the failed table name so you can debug it properly.
PD: You can omit the TOP N when doing an EXISTS as the engine is smart enough to just check if the resulting query has at least 1 row.

Can anyone identify why my loop isn't working correctly?

I am trying to do an insert, so that when a clientID value is NULL then insert the client data into the client table.
I have a loop that cycles through data entered into a temp table, when the clientID is null it should do an insert and choose the next sequential client reference for that client then delete the row from the temp table and move onto the next.
The problem is when the loop does the second insert or more, it is using the SAME client reference even though I have specified +1. The below is an extract of the loop, can anyone figure out why after the first insert the client reference stays the same? If I run the insert by itself with the loop and select #result it shows the vaues sequentially so I don't understand why when the script runs it doesn't insert the reference sequentially.
Declare #Id int
WHILE EXISTS(SELECT * FROM #Temp)
begin
select top 1 #id = ID from #temp
IF (select clientID from #m1 where id = #id) is null AND (select renewalinsuredid from #m1 where id=#id) is not null and (select renewalmasterID from #m1 where id=#id) is not null
BEGIN
declare #result varchar(10)
SELECT #Result = (MAX(CAST(SUBSTRING(ClientReference1,3,6) AS INTEGER)) + 1) FROM Client
set #result = 'CR0' + #result
INSERT INTO Client (clientid,InsuredName,InsuredId,MasterInsuredId,ClientReference1)
SELECT newid(),insuredname,RenewalInsuredID,RenewalMasterID,#result from #M1 where id = #id
PRINT 'Client ref '+ cast(#result as varchar(64)) +' inserted for policy ' + #result2
END
DELETE from #temp where ID = #Id
END
you do...
SELECT #Result = (MAX(CAST(SUBSTRING(ClientReference1,3,6) AS INTEGER)) + 1) FROM Client
...to get the maximum client reference CAST to an integer, then add 1 to it - this doesn't guarantee that #Result is 'greater' than all the other ClientReference1, because ClientReference1 appears to be a text field - consider a field '9', cast it to integer, and add 1 - what have you got? A 10 - if I change back to text, then '10' < '9' in terms of alphanumerics
interchanging from numeric to string, and sorting numerics that have changed to strings, can have unwanted effects. Also you add'CR0' to the start of the string, that could confuse things possibly.
So I managed to get it to work after converting the value. It seems to be due to the datatype being int as when converted to varchar the insert works correctly. The main thing that I don't understand is how if i just did select #result to see what the output was - it was correct every time, just didn't seem to insert the value correctly. Thanks for the help people.
declare #result varchar(10)
declare #Length int
declare #Refresult varchar(10)
SELECT #Result = (MAX(CAST(SUBSTRING(ClientReference1,3,6) AS INTEGER)) + 1) FROM Client
SET #Length = CONVERT(INT,#Result)
SET #Result = CONVERT(VARCHAR(10),#Length)
SET #Length = LEN(#Result)
IF(#Length =5)
SET #Result = '0' + #Result
IF #Result IS NULL
BEGIN
SET #Result = '00000' + '1'
END
SET #Refresult = 'CR' + #Result

How to do bitwise OR between two arbitrary long VARBINARY(MAX) values in Sql Server?

Motivation:
I would like to have a trigger that would do something when any of the text fields of any table is updated or inserted into.
The code must not hard code any table knowledge. I.e. it should work on arbitrary tables.
Where did I get so far
DDL trigger on CREATE_TABLE that adds a FOR INSERT,UPDATE DML trigger for the new table
A _ColumnMask table mapping 1-based integers to the respective VARBINARY(MAX) masks compatible with the COLUMNS_UPDATED format.
Having the _ColumnMask table and some aaaCulture table, I am able to write the following query:
;WITH aux AS (
SELECT (MAX(column_id) + 7) / 8 mask_len
FROM sys.columns
WHERE object_id = OBJECT_ID('aaaCulture')
)
SELECT c.name, column_id, CASE WHEN pad.zero IS NULL THEN cm.mask ELSE cm.mask + pad.zero END mask
FROM sys.columns c
JOIN aux ON 1 = 1
JOIN sys.types t ON t.user_type_id = c.user_type_id
JOIN _ColumnMask cm ON cm.n = c.column_id
LEFT JOIN _ColumnMask pad ON pad.n = aux.mask_len - (c.column_id + 7) / 8
WHERE object_id = OBJECT_ID('aaaCulture') AND (t.name LIKE '%char' OR t.name LIKE '%text')
ORDER BY c.column_id
Yielding
name column_id mask
Text1 2 0x020000000000
Text2 4 0x080000000000
Text3 6 0x200000000000
Text4 8 0x800000000000
Text5 10 0x000200000000
Text7 14 0x002000000000
Text8 16 0x008000000000
Text9 18 0x000002000000
Text10 20 0x000008000000
Text11 21 0x000010000000
Text21 24 0x000080000000
Text22 26 0x000000020000
Text23 28 0x000000080000
Text24 30 0x000000200000
Text25 32 0x000000800000
Text26 34 0x000000000200
Text27 36 0x000000000800
Text28 38 0x000000002000
Text29 40 0x000000008000
XRefCode 42 0x000000000002
Text12 44 0x000000000008
For example, suppose I update fields Text9 and Text23:
UPDATE dbo.aaaCulture SET Text9 = 'haha', Text23 = 'xoxo'
The respective COLUMNS_UPDATED value as reported by the DML trigger is:
0x000002080000
Indeed, according to the aforementioned query result:
Text9 has the mask of 0x000002000000
Text23 has the mask of 0x000000080000
Doing bitwise OR between the two masks yields exactly 0x000002080000
Hence I seem to have all the pieces of the puzzle in order to be able to recognize that COLUMNS_UPDATED() includes any text columns. Except, I have no idea how to do it efficiently (I could write some kind of an ugly WHILE loop, I suppose).
Any ideas?
EDIT 1
We update the database by adding a DB upgrade step to a certain sql script (under the version control). My goal is to prevent the devs from checking in any DB steps that update or insert into any text fields in any of the xyzCulture tables, including those created by those new DB steps. Let us not discuss, why we have xyzCulture tables in the first place. There are there, sigh, it is given.
I may resort to preventing update to any field whatsoever in those tables, but first I want to really understand how hard it is to be more specific.
Helas, I could not come up with anything other than the following implementation:
CREATE FUNCTION VarBinaryBitwiseOR(#x VARBINARY(MAX), #y VARBINARY(MAX))
RETURNS VARBINARY(MAX)
AS
BEGIN
DECLARE #pos INT = 0
DECLARE #res VARBINARY(MAX)
DECLARE #tmp VARBINARY(MAX)
WHILE #pos + 8 <= DATALENGTH(#x)
BEGIN
SET #tmp = CAST(CAST(SUBSTRING(#x,#pos + 1,8) AS BIGINT) | CAST(SUBSTRING(#y,#pos + 1,8) AS BIGINT) AS varbinary(MAX))
SET #res = ISNULL(#res + #tmp, #tmp)
SET #pos = #pos + 8
END
IF #pos + 4 <= DATALENGTH(#x)
BEGIN
SET #tmp = CAST(CAST(SUBSTRING(#x,#pos + 1,4) AS INT) | CAST(SUBSTRING(#y,#pos + 1,4) AS INT) AS varbinary(MAX))
SET #res = ISNULL(#res + #tmp, #tmp)
SET #pos = #pos + 4
END
IF #pos + 2 <= DATALENGTH(#x)
BEGIN
SET #tmp = CAST(CAST(SUBSTRING(#x,#pos + 1,2) AS SMALLINT) | CAST(SUBSTRING(#y,#pos + 1,2) AS SMALLINT) AS varbinary(MAX))
SET #res = ISNULL(#res + #tmp, #tmp)
SET #pos = #pos + 2
END
IF #pos + 1 <= DATALENGTH(#x)
BEGIN
SET #tmp = CAST(CAST(SUBSTRING(#x,#pos + 1,1) AS TINYINT) | CAST(SUBSTRING(#y,#pos + 1,1) AS TINYINT) AS varbinary(MAX))
SET #res = ISNULL(#res + #tmp, #tmp)
SET #pos = #pos + 1
END
RETURN #res
END
It only works when the DATALENGTH of both arguments is the same, which is my case exactly.
So, it is both slow and not general purpose (the datalength must be the same).

Can I send array of parameter to store procedure?

I have User table, it has UserId uniqueidentifier, Name varchar and IsActive bit.
I want to create store procedure to set IsActive to false for many user, for example, if I want to deactive 2 users, I want to send Guid of those users to store procedure (prefer as array). I want to know how can I do it?
P.S. I'm working on Microsoft SQL Azure
Along the same lines than Elian, take a look at XML parameters. Generally speaking you should have a cleaner/safer implementation using xml than parsing a list of strings. Click here for a code example
Here is a solution I used a while ago and that was working fine.
Send the list of guid you want to deactive merged into a comma separated string to the sp.
Then in the sp, you first convert this string into a table thanks to a table-valued function.
Here is a sample with bigint, but you can easily modify it so that it works with guid
Step 1 : the table-valued function
CREATE FUNCTION [dbo].[BigIntListToTable] (
#list VARCHAR(max)
)
RETURNS
#tbl TABLE
(
nval BIGINT NOT NULL
) AS
BEGIN
DECLARE #nPos INT
DECLARE #nNextPos INT
DECLARE #nLen INT
SELECT #nPos = 0, #nNextPos = 1
WHILE #nNextPos > 0
BEGIN
SELECT #nNextPos = CHARINDEX(',', #list, #nPos + 1)
SELECT #nLen = CASE WHEN #nNextPos > 0
THEN #nNextPos
ELSE LEN(#list) + 1
END - #nPos - 1
INSERT #tbl (nval)
VALUES (CONVERT(BIGINT, SUBSTRING(#list, #nPos + 1, #nLen)))
SELECT #nPos = #nNextPos
END
RETURN
END
Step 2 : the stored proc
CREATE PROCEDURE [dbo].[spMySP]
#IdList VARCHAR(max)
AS
BEGIN
SET NOCOUNT ON;
SET ROWCOUNT 0
UPDATE dbo.YourTable
SET isActive = 0
FROM dbo.YourTable
INNER JOIN dbo.BigIntListToTable(#IdList) l
ON dbo.YourTable.id = l.nval
END

Resources