Handling blank columns in dynamic sql - sql-server

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.

Related

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.

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)

Altering a column (null -> not null) without knowing the data type

Is there a possibility to alter a column from "allows null" to "does not allow null" without knowledge of the actual data type of the column?
I think no, so I have made as the basic skeleton code for my stored procedure:
SELECT t.name,c.max_length FROM sys.types t
LEFT JOIN sys.columns c ON(t.system_type_id = c.system_type_id)
WHERE object_id=OBJECT_ID(#TableName) AND c.name=#FieldName;
and
EXEC('UPDATE ' + #TableName + ' SET ' + #FieldName + ' = ' + #DefaultValue + ' WHERE ' + #FieldName + ' IS NULL');
EXEC('ALTER TABLE ' + #TableName + ' ALTER COLUMN ' + #FieldName + ' NOT NULL');
I guess now I only have to get the return values from the first query back into the second. I can't get my head around how to get the values into a variable and then access them again. Ideas?
Since the INFORMATION_SCHEMA has all required information and is part of a SQL standard, it might be better to use that in this case (however, SQL Server's ALTER TABLE ALTER COLUMN is non-standard anyway so it might not matter as much).
Either way, you should also be checking for whether there's character length and/or numeric precision being specified, and make sure you're altering the table in the correct schema (and not getting dbo.TableName instead of customschema.TableName). You could try something like this (I used INFORMATION_SCHEMA here but you could easily refactor this to use the sys.columns view):
DECLARE #retVal VARCHAR(500);
SELECT #retVal =
CASE WHEN CHARACTER_MAXIMUM_LENGTH > 0
THEN CONCAT(DATA_TYPE, '(', CHARACTER_MAXIMUM_LENGTH ,')')
WHEN CHARACTER_MAXIMUM_LENGTH = -1 AND DATA_TYPE <> 'xml'
THEN CONCAT(DATA_TYPE, '(MAX)')
WHEN DATA_TYPE IN ('numeric', 'decimal')
THEN CONCAT(DATA_TYPE, '(', NUMERIC_PRECISION,',', NUMERIC_SCALE,')')
ELSE DATA_TYPE
END
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = #schemaName
AND TABLE_NAME = #tableName
AND COLUMN_NAME = #columnName
#retVal will now capture datatypes like int, varchar(100), varbinary(MAX), or decimal(10,2) correctly.
And then build up a dynamic SQL Query like this:
DECLARE #sql VARCHAR(MAX);
SET #sql = 'ALTER TABLE ' + #schemaName + '.' + #tableName + ' ALTER COLUMN ' + #columnName + ' ' + #retVal + ' NOT NULL;'
EXEC(#sql);
You select values into variables like this:
SELECT #Var1=t.name,#Var2=c.max_length FROM sys.types t
LEFT JOIN sys.columns c ON(t.system_type_id = c.system_type_id)
WHERE object_id=OBJECT_ID(#TableName) AND c.name=#FieldName;
This of course assumes that you have already declared Var1 & Var2, and that your query will only return one row.

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

use table name as parameter in sql function

I want create function, which use table name as parameter. As I search I need use dynamic sql. I try such code:
CREATE FUNCTION get_column_id
(
#TableName VARCHAR(30),
#ColumnName VARCHAR(30),
)
RETURNS int
AS
BEGIN
IF EXISTS
(
DECLARE #SQL VARCHAR(50)
SET #sql = 'SELECT' + #ColumnName + 'FROM' + #TableName + 'WHERE #ColumnName = #ColumnNameValue';
EXEC(#sql)
)
BEGIN
But get errors. Is where any way to procceed this?
I try use dynamic sql in such way
DECLARE #SQL VARCHAR(50)
SET #SQL = 'SELECT' + #ColumnName + 'FROM' + #Table + 'WHERE #ColumnName = #ColumnNameValue'
EXEC(#SQL)
DECLARE #TableName table (Name VARCHAR(30))
INSERT INTO #TableName VALUES (#SQL)
IF EXISTS
(SELECT Name FROM #TableName WHERE Name = #ColumnNameValue)
But get Invalid use of a side-effecting operator 'EXECUTE STRING' within a function.
Does anyone knows how bypass this constraint?
The error is the concatenation of string which lacks space in between,
SET #sql = 'SELECT ' + #ColumnName + ' FROM ' + #TableName + ' WHERE ' + #ColumnName + ' = ' + #ColumnNameValue;
-- ^ SPACE HERE ^ ^ ^ and here
if for instance the data type of the column is string, you need to wrap the value with single quotes,
SET #sql = 'SELECT ' + #ColumnName + ' FROM ' + #TableName + ' WHERE ' + #ColumnName + ' = ''' + #ColumnNameValue + '''';
UPDATE 1
You also need to declare the parameter #ColumnNameValue, eg
CREATE FUNCTION get_column_id
(
#TableName VARCHAR(30),
#ColumnName VARCHAR(30),
#ColumnNameValue VARCHAR(30)
)
A UDF (user defined function) in Sql Server must be deterministic. Beside your syntax errors you won't be able to accomplish your task.
if you check this article on MSDN:
http://msdn.microsoft.com/en-us/library/ms178091.aspx
You can see the citation below:
Deterministic functions always return the same result any time they are called
with a specific set of input values and given the same state of the database.
Nondeterministic functions may return different results each time they are
called with a specific set of input values even if the database state that
they access remains the same.

Resources