T-SQL openquery doesn't update on NULL value - sql-server

I have a Microsoft SQL Server trigger that updates a remote database with new values when the local database is updated. Everything works fine, and I tested the script and it updates fine, unless there is a null value.
The code is below:
DECLARE #TSQL nvarchar(4000);
SELECT #TSQL =
'UPDATE
OPENQUERY(TEST,''SELECT * FROM test_db WHERE id = ' + convert(VARCHAR(MAX), #id) +''')
SET
parent_id = ' + convert(VARCHAR(MAX), #parent_id) + ', user_id = ' + convert(VARCHAR(MAX), #user_id) + ', item_id = ' + convert(VARCHAR(MAX), #item_id) + ''
EXEC (#TSQL)
Everything works well if all the fields have values, but if one column is null, then the query doesn't update the row at all, no errors thrown. I tried to use COALESCE() to change the null variables to empty strings, and it will then update the row, but all the null columns become 0's and I want them to stay as NULL values. All the columns in both database allow null values and default to null so I'm not sure why I cannot update the database.
Any help would be nice, thanks!

Try this. Use ISNULL and if the value is null, use 'NULL' in single quotes. When the string is concatenated together, it won't keep the quotes, so it would set it to a NULL value and not a string of 'NULL'.
DECLARE #TSQL nvarchar(4000);
SELECT #TSQL =
'UPDATE
OPENQUERY(TEST,''SELECT * FROM test_db WHERE id = ' + convert(VARCHAR(MAX), #id) +''')
SET
parent_id = ' + ISNULL(convert(VARCHAR(MAX), #parent_id), 'NULL') + ',
user_id = ' + ISNULL(convert(VARCHAR(MAX), #user_id), 'NULL') + ',
item_id = ' + ISNULL(convert(VARCHAR(MAX), #item_id), 'NULL') + ''
EXEC (#TSQL)

Related

Openquery with TSQL: how to insert NULL in nvarchar column?

Im trying to use a stored procedure to insert some string values into a remote DB with Openquery. The argument #val_a can sometimes be some random string, but sometimes it can be NULL.
Following syntax works if #val_a is NULL, but not if it's a string 'asdf'.
DECLARE #TSQL nvarchar(4000);
SELECT #TSQL =
'UPDATE
OPENQUERY(TEST,''SELECT * FROM test_db WHERE id = ' + convert(VARCHAR(MAX), #id) +''')
SET
val_a = ' + ISNULL(convert(VARCHAR(MAX), #val_a), 'NULL') + ';'
EXEC (#TSQL)
But, in order to work with the string 'asdf', the syntax has look like this:
DECLARE #TSQL nvarchar(4000);
SELECT #TSQL =
'UPDATE
OPENQUERY(TEST,''SELECT * FROM test_db WHERE id = ' + convert(VARCHAR(MAX), #id) +''')
SET
val_a = ''' + ISNULL(convert(VARCHAR(MAX), #val_a), 'NULL') + ''';'
EXEC (#TSQL)
But here, NULLs are inserted as the string 'NULL', not as a NULL value.
Is there a way to write the TSQL query in such form that both NULL and 'asdf' are inserted correctly in the table?
You may use like that:
val_a = ' + ISNULL(convert(VARCHAR(MAX), '''' + #val_a + ''''), 'NULL') + ';'

How to use while loop to iterate through each table name in SQL Server?

I'm working with Dynamic SQL (still in the learning phase) and I'm stuck at a part where I need to use a WHILE loop:
SET #tableName = (SELECT DISTINCT TableName FROM #dataStructure)
Here basically I want to make sure that the operations inside the while loop should occur for all the tables in the #tableName (defined above). I don't know how I can give this condition as an input for the while loop.
WHILE() #HOW CAN I PUT THE CONDITION HERE????
BEGIN
SET #str = ''
SET #sqlstr = ''
SELECT #table = TableName FROM #dataStructure
SET #str = 'UPDATE a0' + char(13) + char(10)
+ ' SET a0.Mjolnir_Source_ID = CONCAT( '
SELECT #str = #str + IIF(ReferenceTable IS NULL, 'a0.' + columnName , alias + '.Mjolnir_Source_ID') + ','
FROM #dataStructure
WHERE TableName = #tableName AND ReferenceTable IS NOT NULL
ORDER BY columnName
SELECT #str = #str + ') FROM ' + #table + ' a0'
SELECT #sqlstr = #sqlstr + +
+ ' INNER JOIN ' + QUOTENAME(#U4SM_db_name) + '.dbo.' + QUOTENAME(ReferenceTable) + ' ' + alias + char(13) + char(10)
+ ' ON a0.' + columnName + ' = ' + alias + '.' + ReferenceColumn + char(13) + char(10)
FROM #dataStructure
WHERE TableName = #tableName AND ReferenceTable IS NOT NULL
ORDER BY columnPosition
select #str + #sqlstr
select #sqlstr
SET #tableName = #tableName + 1
END
Can anyone please help me out here?
Here's an example of a WHILE loop. Basically, you get the first TableName, then if it's NOT NULL, you do your functions. Then get the next table name, and repeat as necessary.
DECLARE #CurrentTableName nvarchar(100)
DECLARE #CustomSQL nvarchar(4000)
SET #CurrentTableName = (SELECT TOP 1 TableName FROM #dataStructure ORDER BY TableName)
WHILE #CurrentTableName IS NOT NULL
BEGIN
SET #CustomSQL = 'SELECT TOP 10 * FROM ' + #CurrentTableName
EXEC (#CustomSQL)
SET #CurrentTableName = (SELECT TOP 1 TableName FROM #dataStructure WHERE TableName > #CurrentTableName ORDER BY TableName)
END
Note that SQL commands often cannot contain variable names in key spots (e.g., SELECT * FROM #tableName). Instead, you save it as an SQL string (what I've called #CustomSQL above) and then EXEC it (put brackets around the variable name though).
Edit: Do this on a test site first before production, and know where the 'cancel query' button is. It's not often, but it's also not unknown, that the 'getting the next row' part isn't properly written and it just runs in a perpetual loop.
FETCH CURSOR with WHILE. Example:
DECLARE myCursor CURSOR FOR
SELECT DISTINCT TableName FROM #dataStructure;
OPEN myCursor;
FETCH NEXT FROM myCursor INTO #table:Name;
WHILE ##FETCH_STATUS = 0
BEGIN
Print ' ' + #TableName
FETCH NEXT FROM myCursor INTO #TableName;
END;
CLOSE myCursor;
DEALLOCATE myCursor;
GO
Don't recreate the wheel unless you need a better wheel:
sp_MSforeachtable
https://www.sqlshack.com/an-introduction-to-sp_msforeachtable-run-commands-iteratively-through-all-tables-in-a-database/
If you are worried about using an undocumented procedure in production that might change in the future, simply script it out and create your own custom named version.

I am getting null in the description for the update trigger written below

I have a log table called tr_log in which description column is where theupdated details are reflected, but i am getting null record in the table. I have done delete and insert operations and for that proper statement was inserted
alter trigger tr_trtest_update
on trtest
for update
as
begin
declare #id int,
#oldname varchar(20),#newname varchar(20),
#oldsalary int,#newsalary int
declare #auditstring varchar(250)
select * into #tempinserted from inserted
while(exists( select id from #tempinserted))
begin
set #auditstring='Emp with ' + cast(#id as nvarchar(5))
select top 1 #id=id,#newname=name,#newsalary=sal from #tempinserted
select #oldname=name,#oldsalary=sal from deleted where id=#id
if (#oldname<>#newname)
set #auditstring=#auditstring+ 'with name ' + #oldname + ' has been updated to ' + #newname
if(#oldsalary<>#newsalary)
set #auditstring=#auditstring+ 'with salary ' + cast(#oldsalary as nvarchar(20))+
' has been updated to ' + cast(#newsalary as nvarchar(20))
insert into tr_log(description)values(#auditstring)
delete from #tempinserted where id=#id
end
end
Anything concatenated with NULL results in NULL. Be sure to wrap all variables/fields being concatenated with a COALESCE() (could also use ISNULL() if you are on SQL Server).
By the way, loops and cursors in triggers are terrible for performance. Try this:
insert into tr_log(description)
SELECT
'Emp with ' + cast(dat2.id as nvarchar(20)) + dat2.[NameChanged] + dat2.[SalaryChanged]
FROM (
SELECT
dat1.*,
CASE
WHEN oldname<>[newname] THEN ' with name ' + ISNULL([oldname],'NULL') + ' has been updated to ' + ISNULL([newname],'NULL')
ELSE ''
END AS [NameChanged],
CASE
WHEN oldsalary<>newsalary THEN ' with salary ' + cast(ISNULL(oldsalary,0) as nvarchar(20))+ ' has been updated to ' + cast(ISNULL(newsalary,0) as nvarchar(20))
ELSE ''
END AS [SalaryChanged]
FROM (
SELECT
i.id AS [ID],i.name AS [newname],i.sal AS [newsalary],d.name AS [oldname],d.sal AS [oldsalary]
FROM inserted i
INNER JOIN deleted d On i.ID=d.ID
) dat1
) dat2

Handling blank columns in dynamic sql

We have a stored procedure built using dynamic sql where our application passes in some column names. Occasionally blank or null values are passed through for the column name. In these cases the sp needs to return a null value for the column. I've tried various ways of handling this but whatever I do I seem to get the error below:
An object or column name is missing or empty. For SELECT INTO statements, verify each column has a name. For other statements, look for empty alias names. Aliases defined as "" or [] are not allowed. Change the alias to a valid name.
Here is an example query with case when trying to handle null column names in the #Col2 param:
DECLARE
#Col1 varchar(32) = 'name',
#Col2 varchar(32) = '',
#sqlCommand nvarchar(MAX) = ''
SET #sqlCommand = #sqlCommand + N'
SELECT ' + quotename(#Col1) + ' AS ' + quotename(#Col1) + ',
CASE WHEN ' + quotename(#Col2) + ' IS NULL OR ' + quotename(#Col2) + ' = ''''
THEN NULL
ELSE ' + quotename(#Col2) + '
END AS Col2
FROM sys.columns c '
EXECUTE sp_executesql #sqlCommand
Solution
DECLARE
#Col1 varchar(32) = 'name',
#Col2 varchar(32) = '',
#sqlCommand nvarchar(MAX) = ''
SET #sqlCommand = #sqlCommand + N'
SELECT ' + quotename(#Col1) + ' AS ' + quotename(#Col1) + ',' +
CASE WHEN #Col2 IS NULL OR #Col2 = ''
THEN 'NULL '
ELSE quotename(#Col2)
END + ' AS Col2
FROM sys.columns c '
--print #sqlCommand
EXECUTE sp_executesql #sqlCommand
Notes
When you have an issue with Dynamic SQL, try using the Print statement to output the generated command to SSMS (or if you're not using SSMS, find some other way to pull back the generated SQL so you can see what's going on.
Doing this with your original code shows that the statement generated was:
SELECT [name] AS [name],
CASE WHEN [] IS NULL OR [] = ''
THEN NULL
ELSE []
END AS Col2
FROM sys.columns c
... which you can probably see the issue with.
The problem was that you got confused between the SQL you were using to generate the Dynamic SQL, and the SQL required to be part of the Dynamic SQL statement being returned.

SQL Server global temp table - Invalid object error

SELECT #SqlCmdEnd =',Member_Cd as x_Member_Cd,
LOD.Val_Seq as x_GroupSort,
vs.Member_RCID as x_Org_Key,
LOD.Val_Cd as x_Level_Text,
Type_RCID as x_Type_RCID
into ##VS_Wide'+#l_Random+'
We have a dynamic query inside a stored procedure which uses a global temp table as shown above. It is working fine for normal scenarios but during load, concurrency test we are getting an error:
Invalid object ##VS_Wide12345
12345 is a random number generated using the below script.
So what could be the possible reason for this error? Any help on this will be appreciated.
select #l_Random = replace(right(rand(), 5), '.', '')
Try this one -
DECLARE #l_Random CHAR(32) = REPLACE(NEWID(), '-', '')
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = '
IF OBJECT_ID(''##VS_Wide' + #l_Random + ''') IS NOT NULL
DROP TABLE ##VS_Wide' + #l_Random + '
SELECT 1 AS a
INTO ##VS_Wide' + #l_Random + '
SELECT * FROM ##VS_Wide' + #l_Random + '
'
PRINT #SQL
EXEC sys.sp_executesql #SQL
Output -
IF OBJECT_ID('##VS_WideC016ADF003FD4254B6BBFB5053E9C956') IS NOT NULL
DROP TABLE ##VS_WideC016ADF003FD4254B6BBFB5053E9C956
SELECT 1 AS a
INTO ##VS_WideC016ADF003FD4254B6BBFB5053E9C956
SELECT * FROM ##VS_WideC016ADF003FD4254B6BBFB5053E9C956

Resources