I have to modify some SQL code that does not seem to be working as it is supposed to.
The SQL code looks awful to me, but it works for the most part.
Say we had multiple vendors with similar names: Microsoft, Microsoft Corp, and Microsoft, Inc, etc.
All the query returns is Microsoft, even though the existing code includes the line PRI_VENDOR_NAME like '%' #PRI_VENDOR_NAME '%' (or, at least it looks like it does).
I can't seem to check to see if the code is working because it is one big, nasty looking piece of code that is appending data to a long string to execute.
CURRENT PROCEDURE: (Get ready to scream)
ALTER PROCEDURE [dbo].[GetSignalMasterByFilter]
(
#planner varchar(50),
#reorder int,
#release int,
#CMTTED varchar(50),
#partid varchar(50),
#global_short_dt int,
#PRI_VENDOR_NAME varchar(50)
)
AS
BEGIN
DECLARE #Filter nvarchar(4000)
set #Filter = ' '
if #planner <> ''
begin
set #Filter = ' and planner in(' + #planner + ')'
end
if #reorder = 1
begin
set #Filter = rtrim(#Filter) + ' and (REORDER_50 = ' + char(39) + 'Y' + char(39) + ' ) '
end
if #reorder = 2
begin
set #Filter = rtrim(#Filter) + ' and (REORDER_30 = ' + char(39) + 'Y' + char(39) + ' ) '
end
if #reorder = 3
begin
set #Filter = rtrim(#Filter) + ' and (REORDER_POINT = ' + char(39) + 'Y' + char(39) + ' ) '
end
--if #noaction = 1
--begin
--set #Filter = rtrim(#Filter) + ' and reorder in (' + char(39) + 'Excess' + char(39) + ',' + char(39) + 'Watch' + char(39) + ')'
--end
if #release = 1
begin
set #Filter = rtrim(#Filter) + ' and (RELEASE_50 = ' + char(39) + 'Y' + char(39) + ' ) '
end
if #release = 2
begin
set #Filter = rtrim(#Filter) + ' and (RELEASE_30 = ' + char(39) + 'Y' + char(39) + ' ) '
end
if #release = 3
begin
set #Filter = rtrim(#Filter) + ' and (RELEASE_POINT = ' + char(39) + 'Y' + char(39) + ' ) '
end
if #CMTTED <> 'View ALL'
begin
set #Filter = rtrim(#Filter) + ' and CMTTED > ' + char(39) + '0' + char(39) + ' and isnumeric(CMTTED) = 1 '
end
if #global_short_dt = 1
begin
set #Filter = rtrim(#Filter) + ' and (global_short_dt is not null or cast(CMTTED as int) > cast(ON_HAND as int)) '
end
if #global_short_dt = 2
begin
set #Filter = rtrim(#Filter) + ' and (global_short_dt is not null or cast(CMTTED as int) > cast(ON_HAND as int)) AND ((cast(QTY_IN_STATUS as float) + cast(ON_ORDER as float) + cast(ON_HAND as float)) < cast(CMTTED as int)) '
end
if #partid <> ''
begin
set #Filter = rtrim(#Filter) + ' and partid like(' + char(39) + #partid + '%' + char(39) + ')'
end
if #PRI_VENDOR_NAME <> ''
begin
set #Filter = rtrim(#Filter) + ' and PRI_VENDOR_NAME like(' + char(39) + #PRI_VENDOR_NAME + '%' + char(39) + ')'
end
DECLARE #sql nvarchar(4000)
SET #sql = '
SELECT DISTINCT PRIMARY_VENDOR,case when PRI_VENDOR_NAME is null then PRIMARY_VENDOR else PRIMARY_VENDOR +' + char(39) + ' - ' + char(39) + '+ PRI_VENDOR_NAME end as PRI_VENDOR_NAME
FROM SignalReportView WHERE PRIMARY_VENDOR is not null ' + rtrim(#filter) + ' order by PRI_VENDOR_NAME'
--print #sql
EXEC sp_executesql #sql
end
What I want to do is replace that nasty looking string variable with something that I've started below, but SQL is not my strength so it isn't quite returning any data just yet:
MY PROCEDURE VERSION: Does not return the data, but appears to be cleaner and easier to maintain in the future.
ALTER PROCEDURE GetSignalMasterByFilter2(
#planner varchar(50),
#reorder int,
#release int,
#CMTTED varchar(50),
#partid varchar(50),
#global_short_dt int,
#PRI_VENDOR_NAME varchar(50)
) as begin
SELECT DISTINCT
PRIMARY_VENDOR,
case when PRI_VENDOR_NAME is null then PRIMARY_VENDOR else PRIMARY_VENDOR +' - '+ PRI_VENDOR_NAME end as PRI_VENDOR_NAME
FROM
SignalReportView
WHERE
(PRIMARY_VENDOR is not null)
and (
ISNULL(#planner,0)=0 or
planner in (#planner))
and (
(#reorder=1 and REORDER_50='Y') or
(#reorder=2 and REORDER_30='Y') or
(#reorder=3 and REORDER_POINT='Y') or
(1=1)
)
and (
(#release=1 and RELEASE_50='Y') or
(#release=2 and RELEASE_30='Y') or
(#release=3 and RELEASE_POINT='Y') or
(1=1)
)
and (
(#CMTTED='View ALL') or
(0<CMTTED and ISNUMERIC(CMTTED)=1)
)
and (
(
(#global_short_dt=1) and
(
(GLOBAL_SHORT_DT is not null) or
(CAST(ON_HAND as int) < CAST(CMTTED as int))
)
) or
(1=1)
)
and (
(
(#global_short_dt=2) and
(
(GLOBAL_SHORT_DT is not null) or
(
(CAST(ON_HAND as int) < CAST(CMTTED as int)) and
((CAST(QTY_IN_STATUS as float) + CAST(ON_ORDER as float) + CAST(ON_HAND as float)) < CAST(CMTTED as int))
)
)
) or
(1=1)
)
and (
ISNULL(#partid,0)=0 or
(PARTID like '%'+#partid+'%')
)
and (
ISNULL(#PRI_VENDOR_NAME,0)=0 or
(PRI_VENDOR_NAME like '%'+#PRI_VENDOR_NAME+'%')
)
ORDER BY PRI_VENDOR_NAME
end
So, my question is:
Is it a good idea to rewrite the original script with a version that should be easier for other developers to maintain in the future?
If NO, can someone spot why the existing SQL is not returning all vendors?
If YES, can someone guide me with the design of my version? It is NOT currently working - probably because I have some logic wrong. Also, the (1=1) clauses do not set well with me, but I don't know a way around them. Since my procedure does not return any data, I can not use it at this point.
I apologize for not posting the table structures, but they are all rather large, and the Stored Procedure above queries an even nastier looking view (that I can't even follow).
I have been in a similar situation many times. With me.... it's usually my own code that I am trying to clean up.
When doing things like this, please do not ONLY consider code readability. You must also consider the impact on the server. Often times, there are various ways to write a query that produces identical results. In those situations, you should pick the version that executes the fastest. If this means you are using the "uglier" version, so be it.
Clearly, you looked at the original code and thought, "Huh?". This is a good indication that there should be code comments.
I haven't spent much time looking at the code, but it appears as though there are various optional parameters to the procedure (option in that empty string indicates that the code should ignore that parameter). It is possible to write code that accommodates this situation without using dynamic sql, but that code almost always executes slower. Read here for an explanation: Do you use Column=#Param OR #Param IS NULL in your WHERE clause? Don't, it doesn't perform
I think G Mastros gave you the right answer for the question. I only want to clarify as I see it myself.
First of all if your procedure will execute faster, nothing else matters with large data.
Secondly, your version is much harder to follow for me than the first one.
And by adding comments to each param and set it will be totally easier to read.
So my answer is NO and of course it's not returning all vendors because of conditions in #filter. Just print it before execution to see conditions that maked up earlier by given parameters.
Try something like this:
ALTER PROCEDURE GetSignalMasterByFilter2(
#planner varchar(50),
#reorder int,
#release int,
#CMTTED varchar(50),
#partid varchar(50),
#global_short_dt int,
#PRI_VENDOR_NAME varchar(50)
) as
begin
SELECT DISTINCT
PRIMARY_VENDOR,
case when PRI_VENDOR_NAME is null then PRIMARY_VENDOR else PRIMARY_VENDOR +' - '+ PRI_VENDOR_NAME end as PRI_VENDOR_NAME
FROM
SignalReportView
WHERE
PRIMARY_VENDOR is not null
and
(
#Planner IS NULL
OR #planner = ''
OR planner in (#planner))
and
( #reorder NOT IN (1,2,3) OR
(#reorder=1 and REORDER_50='Y') or
(#reorder=2 and REORDER_30='Y') or
(#reorder=3 and REORDER_POINT='Y')
)
and
(
#release NOT IN (1,2,3) OR
(#release=1 and RELEASE_50='Y') or
(#release=2 and RELEASE_30='Y') or
(#release=3 and RELEASE_POINT='Y')
)
and
(
#CMTTED='View ALL' or
0<CMTTED and ISNUMERIC(CMTTED)=1
)
and
(
#global_short_dt NOT IN (1,2) OR
(global_short_dt is not NULL AND #global_short_dt=1 AND CAST(ON_HAND as int) < CAST(CMTTED as int)) OR
(global_short_dt is not NULL AND #global_short_dt=2 AND CAST(ON_HAND as int) < CAST(CMTTED as int)
and (CAST(QTY_IN_STATUS as float) + CAST(ON_ORDER as float) + CAST(ON_HAND as float)) < CAST(CMTTED as int))
)
and
(
#partid IS NULL OR
#partid = '' OR
PARTID like '%'+#partid+'%'
)
and
(
#PRI_VENDOR_NAME IS NULL OR
#PRI_VENDOR_NAME = '' OR
PRI_VENDOR_NAME like '%'+#PRI_VENDOR_NAME+'%'
)
ORDER BY PRI_VENDOR_NAME
end
I think I have fixed all your logic mistakes, but as I don't have any tables it is not tested.
As for the performance, you'd have to check both versions and see. There is no guarantee either way.
Related
I have a table, let's call it "table_X" in that table I have multiple columns (46) and in the future there is a possibility that we will expand it to have more columns, since the source of a table is an old ERP system, we need to transform the dataset in some cases, one of the transformation is that when we replace the '' values with NULLs and here is where I have problem, I wrote a dynamic update, because the previously mentioned reason (in the future we will have more columns), but I got error message and right now I am stuck.
DECLARE #SQL_columnnull NVARCHAR(max)
DECLARE #db2 NVARCHAR(max)
DECLARE #table2 NVARCHAR(max)
SET #db2 = 'db'
SET #table2 = 'table_X'
SELECT #SQL_columnnull_part_1 = STRING_AGG(CAST( N' UPDATE '+#db2+'.[dbo].'+#table2+' WITH (TABLOCK) SET ' AS NVARCHAR(MAX))
+QUOTENAME(COLUMN_NAME,'['']') + N' = NULL WHERE '
+QUOTENAME(COLUMN_NAME,'['']') ,+ N' = '''';')
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table2
AND INFORMATION_SCHEMA.COLUMNS.ORDINAL_POSITION <= 3
for the two first column, the code is able to populate the command parts properly, but when it reaches the last column then the "='';" won't be populated
UPDATE db.[dbo].table_X SET [Column_1] = NULL WHERE [Column_1] = '';
UPDATE db.[dbo].table_X SET [Column_2] = NULL WHERE [Column_2] = '';
UPDATE db.[dbo].table_X SET [Column_3] = NULL WHERE [Column_3]
You are messing a bit about with your STRING_AGG
The syntax is
STRING_AGG ( expression, separator )
Your separator is
+ N' = '''';'
Since the separator is not applied after the last entry, you get the result you see!
I would also be wary of the cast, you are casting the start of the expression as nvarchar(max), however you are the concatenationg non varchar strings.
Finally - why are you doing separate updates for each column? this is very poor performance!
First, a fixed query that does what you want would be:
SELECT
#SQL_columnnull =
STRING_AGG(
CAST(
' UPDATE ' + #db2 + '.[dbo].' + #table2 + ' WITH (TABLOCK) SET '
+ QUOTENAME(COLUMN_NAME, '['']') + N' = NULL WHERE '
+ QUOTENAME(COLUMN_NAME, '['']') +N' = '''''
AS NVARCHAR(MAX))
,';')
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table2
AND INFORMATION_SCHEMA.COLUMNS.ORDINAL_POSITION <= 3
I have included the full string within the cast, and the separator is now simply ";"
For performance I would however do this in stead:
SELECT
#SQL_columnnull =
N' UPDATE ' + #db2 + '.[dbo].' + #table2 + ' WITH (TABLOCK) SET ' +
STRING_AGG(
CAST(
QUOTENAME(COLUMN_NAME, '['']') + N'='+N'IIF('+ QUOTENAME(COLUMN_NAME, '['']') + N'= '''',NULL,'+ QUOTENAME(COLUMN_NAME, '['']')+') '
AS NVARCHAR(MAX))
,',
')
+'
WHERE '+
STRING_AGG(
CAST(
QUOTENAME(COLUMN_NAME, '['']') + N'= '''' '
AS NVARCHAR(MAX))
,' OR ')
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table2
AND INFORMATION_SCHEMA.COLUMNS.ORDINAL_POSITION <= 3
This does just one pass over your table, and updates the columns that have the wrong data.
Finally I would check that all the columns are of a varchar or nvarchar data type, to exclude columns of othe datattypes which might give errors
This gives
UPDATE db.[dbo].table_X
SET [Column_1] = IIF([Column_1] = '', NULL, [Column_1])
,[Column_2] = IIF([Column_2] = '', NULL, [Column_2])
,[Column_3] = IIF([Column_3] = '', NULL, [Column_3])
WHERE [Column_1] = ''
OR [Column_2] = ''
OR [Column_3] = ''
I want to update cells in an SQL table depending on the type of its column. More specifically, I want to replace NULL values by either '',0,0.0 or '1900-01-01' depending on the column type. The table has 60 columns and so a manual approach is awkward.
The approach I have tried uses metadata and I want to generate an update script.
Use case is for an import into Excel where the NULL values must be taken care off in a column type dependant way.
As discussed in the comments, the easiest way seems to be use ''. The only exception (I found) was for decimal/numeric, where implicit and explicit conversion is not allowed.
As shown below, you can write the statement out (but I exclude the decimal column) or do it dynamically:
USE Sandbox;
GO
CREATE TABLE TestTable (ID int IDENTITY(1,1),
SomeInt int,
SomeDecimal decimal(10,2),
SomeFloat float,
SomeMoney money,
SomeDate date,
SomeDatetime datetime,
SomeTime time,
SomeDatetime2 datetime2(1),
SomeVarbinary varbinary(12),
SomeVarchar varchar(10),
SomeBit bit);
INSERT INTO dbo.TestTable (SomeInt,
SomeDecimal,
SomeFloat,
SomeMoney,
SomeDate,
SomeDatetime,
SomeTime,
SomeDatetime2,
SomeVarbinary,
SomeVarchar,
SomeBit)
VALUES(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),
(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
GO
UPDATE dbo.TestTable
SET SomeInt = ISNULL(SomeInt,''),
--SomeDecimal = ISNULL(SomeDecimal,''),
SomeFloat = ISNULL(SomeFloat,''),
SomeMoney = ISNULL(SomeMoney,''),
SomeDate = ISNULL(SomeDate,''),
SomeDatetime = ISNULL(SomeDatetime,''),
SomeTime = ISNULL(SomeTime,''),
SomeDatetime2 = ISNULL(SomeDatetime2,''),
SomeVarbinary = ISNULL(SomeVarbinary,CONVERT(varbinary,'')),
SomeVarchar = ISNULL(SomeVarchar,''),
SomeBit = ISNULL(SomeBit,'')
WHERE ID = 1;
GO
DECLARE #Schema sysname = N'dbo',
#Table sysname = N'TestTable';
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'UPDATE ' + QUOTENAME(#Schema) + N'.' + QUOTENAME(#Table) + NCHAR(13) + NCHAR(10) +
N'SET ' + STUFF((SELECT N',' + NCHAR(13) + NCHAR(10) +
N' ' + QUOTENAME(C.COLUMN_NAME) + N' = ISNULL(' + QUOTENAME(C.COLUMN_NAME) + N',' + CASE C.DATA_TYPE WHEN N'decimal' THEN '0)'
WHEN N'numeric' THEN '0)'
ELSE N'CONVERT(' + QUOTENAME(C.DATA_TYPE) + N',N''''))'
END
FROM INFORMATION_SCHEMA.COLUMNS C
WHERE C.TABLE_SCHEMA = #Schema
AND C.TABLE_NAME = #Table
AND C.COLUMN_NAME != 'ID' --You'll need a better method to ignore your ID column
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,7,N'') + NCHAR(13) + NCHAR(10) +
N'WHERE ID = 2;'; --you won't want this if you're doing every row.
PRINT #SQL;
EXEC sp_executesql #SQL;
GO
SELECT *
FROM dbo.TestTable;
GO
DROP TABLE dbo.TestTable
db<>fiddle
If you're using SQL Server 2017+, you can simplify the above by using STRING_AGG instead of FOR XML PATH and STUFF to create a "delimited" list of the columns in the table.
SELECT COLUMN_NAME AS colname, DATA_TYPE AS coltype
INTO #result_columns
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'mytable';
WITH cte(colname, default_value) AS (
SELECT colname,
CASE
WHEN coltype IN ('varchar','nvarchar') THEN ''''''
WHEN coltype IN ('int','money') THEN '0'
WHEN coltype IN ('date') THEN '''1900-01-01'''
WHEN coltype IN ('float') THEN '0.0'
END AS default_value
FROM #result_columns
)
SELECT 'UPDATE mytable SET ' + colname + '=' + default_value + ' WHERE ' + colname + ' IS NULL;'
FROM cte;
-- result :
UPDATE mytable SET Caracteristique='' WHERE Caracteristique IS NULL;
UPDATE mytable SET Couleur='' WHERE Couleur IS NULL;
UPDATE mytable SET Forme='' WHERE Forme IS NULL;
UPDATE mytable SET Machine='' WHERE Machine IS NULL;
UPDATE mytable SET QteChargee=0.0 WHERE QteChargee IS NULL;
I have stored procedure to find customer, its working fine. If Customer_City_Name is null in the table then I am not able to retrieve the row. SP fails . how to do this
I have to get result even if Customer_City_Name or Customer_Country_Code IS NULL
EXEC findCustomer null,'%',null,null,
SP code:
CREATE PROCEDURE findCustomer
#customerNumber NVARCHAR(100),
#customerNamePattern NVARCHAR(35),
#customerCityNamePattern NVARCHAR(35),
#customerCountryCode NVARCHAR(5)
AS
BEGIN
DECLARE #SQL NVARCHAR(4000)
SET #SQL =
'
SELECT
c.Customer_Number,
c.Customer_Name,
c.Postal_Address_Identifier,
c.Customer_Street_Or_Road_Name,
c.Customer_City_Name,
c.Customer_Territory_Code,
c.Customer_Postal_Code,
c.Customer_Country_Code,
c.Telephone_Number,
c.Mobile_Telephone_Number,
c.Fax_Number,
c.Email_Address
FROM Customer c
WHERE c.Customer_Number LIKE ' +
CASE WHEN #customerNumber IS NOT NULL
THEN '''' + #customerNumber + ''''
ELSE 'c.Customer_Number'
END + '
AND c.Customer_Name LIKE ' +
CASE WHEN #customerNamePattern IS NOT NULL
THEN '''' + #customerNamePattern + ''''
ELSE 'c.Customer_Name'
END + '
AND c.Customer_City_Name LIKE ' +
CASE WHEN #customerCityNamePattern IS NOT NULL
THEN '''' +#customerCityNamePattern + ''''
ELSE 'c.Customer_City_Name'
END + '
AND c.Customer_Country_Code LIKE ' +
CASE WHEN #customerCountryCode IS NOT NULL
THEN '''' +#customerCountryCode + ''''
ELSE 'c.Customer_Country_Code'
END
EXEC sp_executesql #SQL
#user2218371 you should reconsider alternatives instead of dynamic SQL. anyway, this is an alternative code.
CREATE PROCEDURE findCustomer
#customerNumber NVARCHAR(100),
#customerNamePattern NVARCHAR(35),
#customerCityNamePattern NVARCHAR(35),
#customerCountryCode NVARCHAR(5)
AS
BEGIN
DECLARE #SQL NVARCHAR(4000)
SET #SQL =
'
SELECT
c.Customer_Number,
c.Customer_Name,
c.Postal_Address_Identifier,
c.Customer_Street_Or_Road_Name,
c.Customer_City_Name,
c.Customer_Territory_Code,
c.Customer_Postal_Code,
c.Customer_Country_Code,
c.Telephone_Number,
c.Mobile_Telephone_Number,
c.Fax_Number,
c.Email_Address
FROM Customer c
WHERE ' +
CASE WHEN #customerNumber IS NOT NULL
THEN ' c.Customer_Number LIKE ''' + #customerNumber + '''' +
CASE WHEN #customerNamePattern IS NOT NULL THEN ' AND ' ELSE '' END
ELSE ''
END +
CASE WHEN #customerNamePattern IS NOT NULL
THEN ' c.Customer_Name LIKE ''' + #customerNamePattern + '''' +
CASE WHEN #customerCityNamePattern IS NOT NULL THEN ' AND ' ELSE '' END
ELSE ''
END +
CASE WHEN #customerCityNamePattern IS NOT NULL
THEN ' c.Customer_City_Name LIKE ''' + #customerCityNamePattern + '''' +
CASE WHEN +#customerCountryCode IS NOT NULL THEN ' AND ' ELSE '' END
ELSE ''
END +
CASE WHEN #customerCountryCode IS NOT NULL
THEN ' c.Customer_Country_Code LIKE ''' + #customerCountryCode + ''''
ELSE ''
END
EXEC sp_executesql #SQL
When one of the fields is NULL, your WHERE clause looks like this:
WHERE c.Customer_Number LIKE c.Customer_Number
This is clearly intentional, since it is part of the CASE statements.
However, LIKE doesn't return true for a pair of NULL values. (NULL is not LIKE NULL.) This is pretty typical behavior for NULL. = does the same thing.
See this very simple SQL Fiddle for a demonstration: http://sqlfiddle.com/#!3/82a23/3/0.
Assuming I'm reading this correctly, you want to simply ignore the column's value when the procedure argument is NULL. In that case, you will need to adjust your query to leave out the clause entirely or put some always true expression in its place when the argument is NULL.
If you insist on sticking with your dynamic SQL building function (which you should not), try this for your CASE statements:
WHERE ' +
CASE WHEN #customerNumber IS NOT NULL
THEN 'c.Customer_Number LIKE ''' + #customerNumber + ''''
ELSE '0=0'
END + '
Null values cant be compared with any other value, that means you don't get a True or a False.
To work around this issue I use the ISNULL built in function that checks if the value is null, and if it is returns your default value.
You should apply this function to each column where a null value is an option.
http://msdn.microsoft.com/en-us/library/ms184325.aspx
brother, so your query mainly is to perform select function to search for existing customer info
try to modify your where condition as sample below
Select ....
From .....
WHERE (#Customer_Number is null or c.Customer_Number like #Customer_Number + '%')
AND (#customerNamePattern is null or c.Customer_Name like #customerNamePattern + '%' )
AND (#customerCityNamePatternis null or c.Customer_City_Name like #customerCityNamePattern+ '%' )
AND (#customerCountryCode is null or c.c.Customer_Country_Code like #customerCountryCode + '%' )
I have fixed this issue by doing this
AND ISnull(c.Customer_City_Name,'''') LIKE ' +
CASE WHEN #customerCityNamePattern IS NOT NULL
THEN '''' +#customerCityNamePattern + ''''
ELSE 'isnull (c.Customer_City_Name,'''')'
END + '
AND Isnull(c.Customer_Country_Code,'''') LIKE ' +
CASE WHEN #customerCountryCode IS NOT NULL
THEN '''' +#customerCountryCode + ''''
ELSE 'isnull(c.Customer_Country_Code , '''')'
END
1.Thanks to asafrob i have accepted your approach.
2. Thanks to devio In 'LIKE' operator needs to select 'NULL'(DB) values I got idea from
3.Thanks to jpmc26 -- > I like your answer .
Finally thanks to every one answered to my question
I am writing a stored procedure using dynamic SQL.
In my procedure, I have say some 10 tables of similar columns.
For example if I consider Designation & Department tables, Designation table has these columns:
Designation, Code, EntryBy, EntryOn, ModifiedBy, ModifiedOn
and Department table has these columns:
Department, Code, EntryBy, EntryOn, ModifiedBy, ModifiedOn
and similarly I have some eight other tables.
In my stored procedure, I need to update and insert data into all the tables. So, instead of writing update & insert statements for each table, I am using a stored procedure which accepts the table name as a parameter and checks if the row already exists in that table.
If that row is present, then that record will be updated otherwise that record will be inserted into the respective table.
ALTER PROC UpdateMasterItems
(
#MasterTypeTmp varchar(50),
#NameTmp varchar(50),
#CodeTmp varchar(10))
AS
BEGIN
DECLARE #CntTmp numeric(2,0)
EXEC('select count(*)' + #CntTmp + ' from ' + #MasterTypeTmp + ' where ' + #MasterTypeTmp + ' = ' + #NameTmp)
IF(#CntTmp > 1)
BEGIN
EXEC('UPDATE ' + #MasterTypeTmp + ' SET ' + 'Code = ' + #CodeTmp + ', ModifiedBy = CURRENT_USER, MOdifiedOn = CURRENT_TIMESTAMP WHERE' + #MasterTypeTmp + ' = ' + #NameTmp)
RETURN 10
END
ELSE
BEGIN
EXEC('INSERT INTO ' + #MasterTypeTmp + '(' + #MasterTypeTmp + ', Code, EntryBy, EntryOn, ModifiedBy, ModifiedOn ) VALUES (' + #NameTmp + ',' + #CodeTmp + ',' + 'CURRENT_USER, CURRENT_TIMESTAMP, CURRENT_USER, CURRENT_TIMESTAMP )')
RETURN 11
END
END
where #MasterTypeTmp is the table name(can be Department/Designation.....)
I am getting an error while executing the procedure:
Exec Statement:
EXEC UpdateMasterItems 'Designation', 'TestName', 'TestCode'
Error Statements:
Invalid column name 'TestName'.
Invalid column name 'TestCode'.
But TestName & TestCode are not column names. Those are the values for the columns. Please let me know if my dynamic queries are wrong or where the problem is!
Thanks in advance
Mounika
I think the error message is because you have wrapped column names with ' (ie; 'Designation' is wrong). Should be Designation
But there are other issue as well.
I think you cannot define variables out side dynamic sql and assign them inside. Because dynamic sql runs in a different session and therefore variables defined outside the scope are unknown. (ie; #CntTmp)
Even thought you are checking #CntTmp > 1 you are not really assigning count(*) value to it (won't work anyway due to reason 1)
Your #CntTmp will overflow if the record count > 99 (not really an issue if you don't have bad data)
If you need to get this work the way you have described, you have to declare variables, check the existance of records and then update/insert all within the same dynamic query. You could do without a variable using if exists (select ....) update ... else insert...
Your string concatenation are missing some ' and the db interpret the values as column names:
EXEC('INSERT INTO ' + #MasterTypeTmp + '(' + #MasterTypeTmp + ', Code, EntryBy, EntryOn, ModifiedBy, ModifiedOn )
VALUES (''' + #NameTmp + ''',''' + #CodeTmp + ''',' + 'CURRENT_USER, CURRENT_TIMESTAMP, CURRENT_USER, CURRENT_TIMESTAMP )')
First, this part will give you an error: Error converting data type varchar to numeric.
DECLARE #CntTmp numeric(2,0)
EXEC('select count(*)' + #CntTmp + ' from ' + #MasterTypeTmp + ' where ' + #MasterTypeTmp + ' = ' + #NameTmp)
Because the CntTmp is an numeric and can't directly used in that expression.
If you change to this:
EXEC('select count(*)' + cast(#CntTmp as varchar(30)) + ' from ' + #MasterTypeTmp + ' where ' + #MasterTypeTmp + ' = ' + #NameTmp)
It will give you an error because you can't use variable directly in dynamic SQL.
Also, it will not give you the output, because CntTmp is null.
So, you can create another variable to store the result from cast the numeric to varchar and then perform ISNULL function to give the variable a value if it's null.
Second, you're missing ' for your column value.
And here is the working stored procedures:
ALTER PROC UPDATEMASTERITEMS
( #MASTERTYPETMP VARCHAR(50), #NAMETMP VARCHAR(50), #CODETMP VARCHAR(10))
AS
BEGIN
DECLARE #CNTTMP NUMERIC(2,0)
DECLARE #CNTTMPVAL VARCHAR(30) = ISNULL(CAST(#CNTTMP AS VARCHAR(30)) , '')
EXEC ('SELECT COUNT(*) ' + #CNTTMPVAL + ' FROM ' + #MASTERTYPETMP + ' WHERE ' + #MASTERTYPETMP + ' = ''' + #NAMETMP + '''')
IF(#CNTTMP > 1)
BEGIN
EXEC('UPDATE ' + #MASTERTYPETMP + ' SET ' + 'CODE = ''' + #CODETMP + ''', MODIFIEDBY = CURRENT_USER, MODIFIEDON = CURRENT_TIMESTAMP WHERE' + #MASTERTYPETMP + ' = ''' + #NAMETMP + '')
RETURN 10
END
ELSE
BEGIN
EXEC('INSERT INTO ' + #MASTERTYPETMP + '(' + #MASTERTYPETMP + ', CODE, ENTRYBY, ENTRYON, MODIFIEDBY, MODIFIEDON ) VALUES (''' + #NAMETMP + ''',''' + #CODETMP + ''',' + 'CURRENT_USER, CURRENT_TIMESTAMP, CURRENT_USER, CURRENT_TIMESTAMP )')
RETURN 11
END
END
Anyway, you can format your SQL using this
This is probably a bit of a limited, but valuable scenario. I have a SQL Server 2008 database with a table that has millions of records. There appears to be an intermittent problem with several of the records. I'm trying to repro the problem. In an effort to do this, I finally got the ID of an offending record. I would like to generate an INSERT statement associated with this single record in my PROD database. Then I can easily migrate it into my TESTING database in an effort to repro and resolve the problem.
Basically, I need to generate a single INSERT statement for a single record from a single table where I know the primary key value of the record.
Does anyone have any ideas of how I can accomplish this? Essentially, I want to generate insert statements on a conditional basis.
Thank you!
First try to recreate what you want to insert with a SELECT statement.
After that you can insert into the table with a INSERT INTO like this:
INSERT INTO tablename
SELECT ....
If they are on different servers, you can use INSERT like this:
INSERT INTO tablename VALUES (...)
using the values given by the SELECT in the other server fill the values in the insert.
In your specific case I think you can do this:
CREATE PROCEDURE dbo.GenerateSingleInsert
#table NVARCHAR(511), -- expects schema.table notation
#pk_column SYSNAME, -- column that is primary key
#pk_value INT -- 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 + ' + ' + REPLICATE(CHAR(39),3) + ','
+ REPLICATE(CHAR(39),3) + ' + ' + REPLICATE(CHAR(39),2) + '+'
+ 'RTRIM(' + CASE WHEN system_type_id IN (40,41,42,43,58,61) THEN
'CONVERT(CHAR(8), ' + QUOTENAME(name) + ', 112) + '' ''
+ CONVERT(CHAR(14), ' + QUOTENAME(name) + ', 14)'
ELSE 'REPLACE(' + QUOTENAME(name) + ','''''''','''''''''''')' END + ')
+ ' + REPLICATE(CHAR(39),2)
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), 4) + ' + ' + STUFF(#vals, 1, 13, '')
+ 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
GO
So let's try it out:
CREATE TABLE dbo.splunge
(
ID INT, dt DATETIME, rv ROWVERSION, t NVARCHAR(MAX)
);
INSERT dbo.splunge(ID, dt, t)
SELECT 1, GETDATE(), 'foo'
UNION ALL SELECT 2, GETDATE(), 'bar'
UNION ALL SELECT 3, GETDATE(), 'O''Brien';
EXEC dbo.GenerateSingleInsert N'dbo.splunge', N'ID', 1;
SQL
-------------
INSERT dbo.splunge([ID],[dt],[t]) SELECT '1','20120517 10:07:07:330','foo'
EXEC dbo.GenerateSingleInsert N'dbo.splunge', N'ID', 2;
SQL
-------------
INSERT dbo.splunge([ID],[dt],[t]) SELECT '2','20120517 10:07:07:330','bar'
EXEC dbo.GenerateSingleInsert N'dbo.splunge', N'ID', 3;
SQL
-------------
INSERT dbo.splunge([ID],[dt],[t]) SELECT '3','20120517 10:07:07:330','O''Brien'
If there is an IDENTITY column you may need to set SET IDENTITY_INSERT ON for the TEST table, and verify that there is no collision. Probably about 500 caveats I should mention, I haven't tested all data types, etc.
However in the more general case there is a lot more to it than this. Vyas K has a pretty robust stored procedure that should demonstrate how complicated it can get:
http://vyaskn.tripod.com/code/generate_inserts_2005.txt
You are probably far better off using a tool like Red-Gate's SQL Data Compare to pick a specific row and generate an insert for you. As I've blogged about, paying for a tool is not just about the money, it's about the hours of troubleshooting and bug-fixing that someone else has already done for you.
Aaron,
I liked your code, it solved a problem for me. I ran into a few issues using it (like you said I would) with nulls and the text type so I made some changes to address those issues.
ALTER PROCEDURE dbo.GenerateSingleInsert
#table NVARCHAR(511), -- expects schema.table notation
#pk_column SYSNAME, -- column that is primary key
#pk_value INT -- 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) -- datetime types
THEN
'CONVERT(CHAR(8), ' + QUOTENAME(name) + ', 112) + '' ''+ CONVERT(CHAR(14), ' + QUOTENAME(name) + ', 14)'
WHEN system_type_id IN (35) -- text type NOTE: can overflow
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