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

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') + ';'

Related

Using a temporary table on SRSS report

I have a stored procedure which works without a problem at the sql server side. However, when I feed a SRSS report with this stored procedure, I am having an error such as; Invalid object name '##tempTable'.
Here is my stored procedure;
ALTER PROCEDURE [dbo].[Link_SP_Inventory]
-- Add the parameters for the stored procedure here
#StoreId int,
#StartDate date,
#EndDate date
AS
BEGIN
DECLARE #QUERY nvarchar(MAX);
SET #QUERY = N'SELECT * INTO ##tempTable ' +
N'FROM OPENQUERY("172.11.111.11", N''EXEC [DB].dbo.SP_inventory ' + CONVERT(varchar(10),#StoreId) + ',' + '''' + QUOTENAME(CONVERT(varchar,#StartDate,112),'''') + '''' + ',' + '''' + QUOTENAME(CONVERT(varchar,#EndDate,112) + '''','''') + ')';
EXEC sp_executesql #QUERY;
SET NOCOUNT ON;
SELECT * FROM ##tempTable
drop table ##tempTable
END
GO
How can I solve this issue? Thanks.
A Table referenced in a Dynamic statement can only be referenced inside that dynamic statement. Take this simple query:
EXEC sp_executesql N'SELECT 1 AS I INTO #temp;';
SELECT *
FROM #temp;
Notice the statement fails with:
Msg 208, Level 16, State 0, Line 3
Invalid object name '#temp'.
It seems, however, you don't need to temporary table, and this will work fine:
ALTER PROCEDURE [dbo].[Link_SP_Inventory]
-- Add the parameters for the stored procedure here
#StoreId int,
#StartDate date,
#EndDate date
AS
BEGIN
DECLARE #QUERY nvarchar(MAX);
SET #QUERY = N'SELECT * ' +
N'FROM OPENQUERY("172.11.111.11", N''EXEC [DB].dbo.SP_inventory ' + CONVERT(varchar(10),#StoreId) + ',' + '''' + QUOTENAME(CONVERT(varchar(8),#StartDate,112),'''') + '''' + ',' + '''' + QUOTENAME(CONVERT(varchar(8),#EndDate,112) + '''','''') + ')';
EXEC sp_executesql #QUERY;
END;
Try doing this
ALTER PROCEDURE [dbo].[Link_SP_Inventory]
-- Add the parameters for the stored procedure here
#StoreId int,
#StartDate date,
#EndDate date
AS
BEGIN
DECLARE #QUERY nvarchar(MAX);
SET #QUERY = N'SELECT * INTO ##temp_global ' +
N'FROM OPENQUERY("172.11.111.11", N''EXEC [DB].dbo.SP_inventory ' + CONVERT(varchar(10),#StoreId) + ',' + '''' + QUOTENAME(CONVERT(varchar,#StartDate,112),'''') '''' + ',' + '''' + QUOTENAME(CONVERT(varchar,#EndDate,112) + '''','''') + ')';
EXECUTE (#QUERY)
SELECT * FROM ##temp_global
DROP TABLE ##temp_global
END
I think I had this once and I had to create the temp table initially with the column names specified and then insert into it, so that your dataset would be able to pick up the column names. So something like this may work:
ALTER PROCEDURE [dbo].[Link_SP_Inventory]
-- Add the parameters for the stored procedure here
#StoreId int,
#StartDate date,
#EndDate date
AS
BEGIN
DECLARE #QUERY nvarchar(MAX);
CREATE TABLE ##tempTable ([ColumnOne] VARCHAR(10), [ColumnTwo] DATETIME) --Add required columns here
SET #QUERY = N'SELECT * FROM OPENQUERY("172.11.111.11", N''EXEC [DB].dbo.SP_inventory ' + CONVERT(varchar(10),#StoreId) + ',' + '''' + QUOTENAME(CONVERT(varchar,#StartDate,112),'''') + '''' + ',' + '''' + QUOTENAME(CONVERT(varchar,#EndDate,112) + '''','''') + ')';
INSERT INTO ##tempTable ([ColumnOne],[ColumnTwo])
EXEC (#QUERY);
SET NOCOUNT ON;
SELECT * FROM ##tempTable
DROP TABLE ##tempTable
END
GO

How to SELECT and UNION from a group of tables in schema in SQL Server 2008 R2 using a variable to define the database

This is a progression from the question asked here: How to SELECT and UNION from a group of Tables in the schema in SQL Server 2008 R2
I would like to do very much the same thing and the answer given by MarkD works perfectly for the database I am currently working with. Although admittedly I'd like to understand exactly how. How does the query below build the union query from the list of tables returned by the information_schema?
DECLARE #Select_Clause varchar(600) = N'SELECT [Patient_Number] AS [ID number]
,[Attendance Date] AS [Date Seen]
,[Attendance_Type] AS [New/Follow up]
,[Episode Type] AS [Patient Type]
,[Local Authority District]
,Postcode, N''Shaw'' AS Clinic '
,#Where_Clause varchar(100) = N' WHERE [EPISODE TYPE] LIKE N''HIV'''
,#Union_Clause varchar(100) = N' UNION ALL '
,#Query nvarchar(max) = N''
,#RawDataBase varchar(50) = N'BHT_1819_RawData'
,#Schema varchar(50) = N'HIVGUM'
,#Table_Count tinyint;
DECLARE #Table_Count_def nvarchar(100) = N'#TableSchema varchar(50)
,#Table_CountOUT tinyint OUTPUT'
,#Start_Position int = LEN(REPLACE(#Select_Clause, N' ', N'-'))
,#Length int;
SET #Query = N'SELECT #Table_CountOUT = COUNT(*) FROM ' + #RawDataBase +
N'.INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA LIKE #TableSchema';
EXEC sp_executesql #query, #Table_Count_def, #TableSchema=#Schema,
#Table_CountOUT=#Table_Count OUTPUT;
SET #Query = N'';
IF #Table_Count > 0
Begin
IF OBJECT_ID(N'dbo.HIV_Cumulative', N'U') is not null
DROP TABLE dbo.HIV_Cumulative;
SELECT #Query = #Query + #Select_Clause + N' FROM ' + #RawDataBase +
N'.HIVGUM.' + TABLE_NAME + #Where_Clause + #Union_Clause
FROM BHT_1819_RawData.INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA LIKE #Schema;
SET #Length = LEN(REPLACE(#query, N' ', N'-')) - #Start_Position -
LEN(REPLACE(#Where_Clause + #Union_Clause, N' ', N'-'));
SELECT #Query = SUBSTRING(#QUERY , #Start_Position+1, #Length)
SET #Query = #Select_Clause + N' INTO BHT_SLR..HIV_Cumulative ' + #QUERY
+ #Where_Clause;
EXEC sp_executesql #Query
End
ELSE
PRINT N'No tables present in database ' + #RawDataBase + N' for Schema ' +
#Schema + N'. You must import source data first.';
The added complication is that I am querying the tables on a separate DB - currently BHT_1819_RawData - so have hard coded the database where it queries the information_schema. What I would really like to do is to specify the separate database using a variable. So that it can be reconfigured to extract from BHT_1920_RawData. I am fairly familiar with exec and sp_executesql, but have only occasionally used output parameters so am not sure what is required here. The attempts that I have made haven't worked. Once I have got this right, I will need to create several other similar scripts that work on the same principle.
Once I realised what needed to happen, I went through some trial and error and came up with a solution:
SET #ParmDef = N'#QueryOut nvarchar(2500) OUTPUT';
SET #sql_string = N'SELECT #QueryOut = #QueryOut + N'''
+ #Select_Clause + ' FROM '
+ #RawDataBase
+ N'.[' + #Schema + N'].'' + TABLE_NAME + N'' '
+ #Where_Clause
+ #Union_Clause
+ N''' FROM '
+ #RawDataBase
+ N'.INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA LIKE '''
+ #Schema
+ N''' AND TABLE_NAME NOT LIKE N''%_YTD%''';
EXEC sp_executesql #sql_string, #ParmDef, #QueryOut=#Query OUTPUT;
SET #Length = LEN(REPLACE(#query, N' ', N'-')) - #Start_Position -
LEN(REPLACE(#Where_Clause + #Union_Clause, N' ', N'-'));
SELECT #Query = SUBSTRING(#QUERY , #Start_Position, #Length+1);
SET #Query = REPLACE(#Select_Clause, N'''''', '''') + N' INTO ' + #New_Table + N' ' +
#QUERY + REPLACE(#Where_Clause, N'''''', '''');
EXEC sp_executesql #Query;

T-SQL openquery doesn't update on NULL value

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)

Get the column_Name if I know the column value in sql server

My requirement is to compare data between two environments and i there is diff in both tables of both environments, insert that data to a temp table and display it.The above solution is not suiting for my scenario. I will explain my scenario in a better way.
In a Curor Cur1, I have all data of DEV from a Table(Report) where Rep_ID=1, Getting corresponding data from the TEST of REPORT Table where Rep_ID=1 In a while loop I am comparing the data of DEV and TEST
if (#DevData1 <> #TestData1)
BEGIN Get ColumnName from Report table where #DevData1 =1 Insert Into #TempTable (ColumnName, DevData1, TestData1)
ENDS Cur1 Ends
When I try to get the column name for a varchar column, I am getting the column name properly with the below query
Declare #ColStrRep nvarchar(1000)= 'select #retValOut= Col.value(''local-name(.)'', ''varchar(max)'') from (select * from Rep_attr where Rep_Name = '''+#reptName +''' for xml path(''''), type) as T(XMLCol) cross apply T.XMLCol.nodes(''*'') as n(Col) where Col.value(''.'', ''varchar(100)'') = '+#reptName +''
print #ColStrRep
EXEC Sp_executesql #ColStrRep,N'#retValOut nvarchar(100) out',#Column_Name OUT
But when I try to get the columnName for an integer column, and that too when we have the same value as 1 in the table( like RepID=1, Flag=1 , IsEmpty=1 etc), the query is getting confused and instead of Rep_ID, it retrieves the column IsEmpty. SO I need another query which just give me the columnname for a columnValue.
Thanks and Regards,
Sajitha
This solution will search using LIKE operator for varchar columns (i.e. column like '%5%') and a strict value for int columns (i.e. column=5)
DECLARE #table_name SYSNAME = 'your_table',
#search_string VARCHAR(100) = '5', --what to search
#column_name SYSNAME,
#type_name SYSNAME,
#sql_string VARCHAR(2000)
BEGIN TRY
DECLARE columns_cur CURSOR
FOR
SELECT columns.name, types.name type_name FROM sys.columns
JOIN sys.types ON columns.system_type_id = types.system_type_id
JOIN sys.objects ON columns.object_id=objects.object_id
WHERE objects.type = 'U' AND objects.name=#table_name
AND types.name IN ('varchar', 'nvarchar', 'int', 'bigint', 'smallint') --types of columns which you want to use for search
OPEN columns_cur
FETCH NEXT FROM columns_cur INTO #column_name, #type_name
WHILE (##FETCH_STATUS = 0)
BEGIN
IF #type_name IN ( 'varchar', 'nvarchar')
SET #sql_string = 'IF EXISTS (SELECT * FROM ' + #table_name + ' WHERE [' + #column_name + '] LIKE ''%' + #search_string + '%'') RAISERROR(''' + #table_name + ', ' + #column_name + ''',0,1) WITH NOWAIT'
ELSE
SET #sql_string = 'IF EXISTS (SELECT * FROM ' + #table_name + ' WHERE [' + #column_name + '] = TRY_CAST(''' + #search_string + ''' AS '+ #type_name +')) RAISERROR(''' + #table_name + ', ' + #column_name + ''',0,1) WITH NOWAIT'
EXECUTE(#sql_string)
FETCH NEXT FROM columns_cur INTO #column_name, #type_name
END
CLOSE columns_cur
DEALLOCATE columns_cur
END TRY
BEGIN CATCH
CLOSE columns_cur
DEALLOCATE columns_cur
RAISERROR(' - No access to table %s',0,1,#table_name) WITH NOWAIT
END CATCH
Thanks for the suggestion.
But I could manage the situation with the below query.
In case, the column value is a varchar, then the below query gives me the column Name.
Declare #ColStrDesc nvarchar(1000)= 'select
#retValOut= Col.value(''local-name(.)'', ''varchar(max)'')
from (select *
from Rep_attr
where Rep_Name = '''+#reptName +'''
for xml path(''''), type) as T(XMLCol)
cross apply
T.XMLCol.nodes(''*'') as n(Col)
where Col.value(''.'', ''varchar(100)'') = '''+#rep_Desc +''''
print #ColStrDesc
EXEC Sp_executesql #ColStrDesc,N'#retValOut nvarchar(100) out',#Column_Name OUT
In case, the column Value is an integer , thenn below query gives me column name.
Declare #ColErr nvarchar(1000)= 'SELECT #retValOut= STUFF(''''
+ CASE WHEN EXISTS (SELECT 1 FROM [dbo].[Rep_attr] WHERE Cast([Rep_Errs] as VARCHAR(64)) = '+#rep_Errs+') THEN '' Rep_Errs'' ELSE '''' END , 1, 1, '''')'
EXEC Sp_executesql #ColErr,N'#retValOut nvarchar(100) out',#Column_Name OUT

Insert script for a particular set of rows in SQL

I am using SQL Server 2008. I use to take the script of my data from SQL table using Tasks --> Generate Scripts option.
Here is my problem:
Let's say I have 21,000 records in Employee table. When I take the script of this table, it takes the insert script for all 21000 records. What is the solution if I want to take only the script of 18000 records from the table?
Is there any solution using SQL query or from the tasks wizard?
Thanks in advance...
Create a new View where you select your desired rows from your Employee table e.g. SELECT TOP 21000...
Then simply script that View instead of the Table.
In case the views are not an option for you I wrote the following code based on the Aaron Bertrand's answer here that will give the insert statement for a single record in the db.
CREATE PROCEDURE dbo.GenerateSingleInsert
#table NVARCHAR(511), -- expects schema.table notation
#pk_column SYSNAME, -- column that is primary key
#pk_value NVARCHAR(10) -- change data type accordingly
AS
BEGIN
SET NOCOUNT ON;
DECLARE #cols NVARCHAR(MAX), #vals NVARCHAR(MAX),
#valOut NVARCHAR(MAX), #valSQL NVARCHAR(MAX);
SELECT #cols = N'', #vals = N'';
SELECT #cols = #cols + ',' + QUOTENAME(name),
#vals = #vals + ' + '','' + ' + 'ISNULL('+REPLICATE(CHAR(39),4)+'+RTRIM(' +
CASE WHEN system_type_id IN (40,41,42,43,58,61) -- dateteime and time stamp type
THEN
'CONVERT(CHAR(8), ' + QUOTENAME(name) + ', 112) + '' ''+ CONVERT(CHAR(14), ' + QUOTENAME(name) + ', 14)'
WHEN system_type_id IN (35) -- text type
THEN
'REPLACE(CAST(' + QUOTENAME(name) + 'as nvarchar(MAX)),'+REPLICATE(CHAR(39),4)+','+REPLICATE(CHAR(39),6)+')'
ELSE
'REPLACE(' + QUOTENAME(name) + ','+REPLICATE(CHAR(39),4)+','+REPLICATE(CHAR(39),6)+')'
END
+ ')+' + REPLICATE(CHAR(39),4) + ',''null'') + '
FROM sys.columns WHERE [object_id] = OBJECT_ID(#table)
AND system_type_id <> 189 -- can't insert rowversion
AND is_computed = 0; -- can't insert computed columns
SELECT #cols = STUFF(#cols, 1, 1, ''),
#vals = REPLICATE(CHAR(39),2) + STUFF(#vals, 1, 6, '') + REPLICATE(CHAR(39),2) ;
SELECT #valSQL = N'SELECT #valOut = ' + #vals + ' FROM ' + #table + ' WHERE '
+ QUOTENAME(#pk_column) + ' = ''' + RTRIM(#pk_value) + ''';';
EXEC sp_executesql #valSQL, N'#valOut NVARCHAR(MAX) OUTPUT', #valOut OUTPUT;
SELECT SQL = 'INSERT ' + #table + '(' + #cols + ') SELECT ' + #valOut;
END
I took the above code and wrapped it the following proc that will use the where clause you give it to select which insert statements to create
CREATE PROCEDURE dbo.GenerateInserts
#table NVARCHAR(511), -- expects schema.table notation
#pk_column SYSNAME, -- column that is primary key
#whereClause NVARCHAR(500) -- the where clause used to parse down the data
AS
BEGIN
declare #temp TABLE ( keyValue nvarchar(10), Pos int );
declare #result TABLE ( insertString nvarchar(MAX) );
declare #query NVARCHAR(MAX)
set #query =
'with qry as
(
SELECT ' + #pk_column + ' as KeyValue, ROW_NUMBER() over(ORDER BY ' + #pk_column + ') Pos
from ' + #table + '
' + #whereClause + '
)
select * from qry'
insert into #temp
exec sp_sqlexec #query
Declare #i int, #key nvarchar(10)
select #i = count(*) from #temp
WHILE #i > 0 BEGIN
select #key = KeyValue from #temp where Pos = #i
insert into #result
exec [dbo].[GenerateSingleInsert] #table, #pk_column, #key
set #i = #i - 1
END
select insertString from #result
END
Calling it could look like the following. You pass in the table name, the table primary key and the where clause and you should end up with your insert statements.
set #whereClause = 'where PrettyColorsId > 1000 and PrettyColorsID < 5000'
exec [dbo].GenerateInserts 'dbo.PrettyColors', 'PrettyColorsID', #whereClause
set #whereClause = 'where Color in (' + #SomeValues + ')'
exec [dbo].GenerateInserts 'dbo.PrettyColors', 'PrettyColorsID', #whereClause

Resources