Dynamic SQL update Image - sql-server

I have been battling with this statement:
ALTER PROCEDURE [dbo].[transact_image_update]
-- Add the parameters for the stored procedure here
#transact_recordID_int int,
#image1_bin image,
#image2_bin image,
#transact_referenceNo_str nvarchar(25),
#userID_last uniqueidentifier,
#tableName nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #sqlUpdt01 nvarchar(4000)
SET #sqlUpdt01 = '
Update [dbo].[' + #tableName + '] SET [image1_bin] = '+ CONVERT(VARCHAR(MAX), CONVERT(VARBINARY(MAX), #image1_bin), 2)
+ ', [image2_bin] = '+ CONVERT(VARCHAR(MAX), CONVERT(VARBINARY(MAX), #image2_bin), 2)
+', [userID_last] = '''+ convert(nvarchar(4000),#userID_last)
+ ''' WHERE (transact_recordID_int = '+convert(varchar,#transact_recordID_int) +')
AND ([transact_referenceNo_str] = ''' +convert(varchar, #transact_referenceNo_str)
+''' )
AND (locked_bol = 0)
'
exec sp_executesql #sqlUpdt01
Basically, I have many DB tables with similar schema but different names (for types of transactions) and would like this ONE procedure to make the update given the table name as argument. This script compiles successfully but execution cannot update the image field. Is there a conversion I'm missing?
Please help.

in correct type cast in below line
in correct line
+ ''' WHERE (transact_recordID_int = '+ convert(varchar,#transact_recordID_int) +
correct line
+ ''' WHERE (transact_recordID_int = '+ #transact_recordID_int +

Related

SQL Server stored procedure error: "Error Message: Must declare the scalar variable" referencing a variable with sp_executesql but not without

When I want to add datetime column to entire table I can write a stored procedure which takes a date as input and then stores it into the table - like so:
ALTER PROCEDURE [dbo].[set_datettime]
(#importDate VARCHAR(100))
AS
BEGIN
UPDATE [dbo].[table1]
SET uploadDate = #importDate
END
But when I want to make the table dynamic I need to use sp_executesql. So my thought is I can do this:
ALTER PROCEDURE [dbo].[set_datettime]
(#tableName VARCHAR(100), #importDate VARCHAR(100))
AS
BEGIN
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = 'UPDATE dbo.' + quotename(#tableName) + ' SET uploadDate = #importDate';
EXECUTE sp_executesql #Sql
END
but now I get an error:
Error Message: Must declare the scalar variable #importDate
Despite clearly declaring the variable. Even if I try to explicitly declare the variable again I get the error that I cant declare duplicate variables.
Other thing which I tried was to do:
SET #Sql = 'UPDATE dbo.' + quotename(#tableName) + ' SET uploadDate = ' + #importDate;
But this throws an error
Invalid column name 10-10-2019
Lastly I was able to accomplish the task (somewhat) by changing to
SET #Sql = 'UPDATE dbo.' + quotename(#tableName) + ' SET uploadDate = GETDATE()';
But in this solution I define the date in the stored procedure and doesn't take it as input, which is not ideal.
How can I have dynamic table definition while still keeping the date input variable dynamic also?
You need to parametrise your dynamic statement. I'm typing on my phone right now, so I apologise for any typographical errors:
EXEC sp_executesql #SQL, N'#importdate date', #importdate;
Never inject parameters in your dynamic statements. It creates huge security flaws in your SQL, called SQL Injection.
Edit: not on my phone now, so can write out the complete SP:
ALTER PROCEDURE [dbo].[set_datettime]
(#tableName sysname, #importDate date)
AS
BEGIN
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = 'UPDATE dbo.' + quotename(#tableName) + ' SET uploadDate = #importDate;';
EXECUTE sp_executesql #Sql, N'#importDate date', #importDate;
END;
Test it first:
declare #tableName varchar(100) = 'sometable'
, #importDate varchar(100) = '10-10-2019'
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = 'UPDATE dbo.' + quotename(#tableName) + ' SET uploadDate = ' + #importDate
select #Sql
This give you an incorrect statement:
UPDATE dbo.[sometable] SET uploadDate = 10-10-2019
Try this:
SET #Sql = 'UPDATE dbo.' + quotename(#tableName) + ' SET uploadDate = ' + '''' + #importDate + ''''
Which gives you
UPDATE dbo.[sometable] SET uploadDate = '10-10-2019'

SQL Server procedure with parameters for table name, field name, and ID of the record

I am trying this in SQL Server and it throws an error:
ALTER PROCEDURE [dbo].[GET_TEXT_DETAIL]
#id UNIQUEIDENTIFIER,
#table VARCHAR(255),
#field VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql VARCHAR(200)
SET #sql = 'select ' + QUOTENAME(#field) + ' from ' + QUOTENAME(#table) + ' where ID = ' + QUOTENAME(#id)
EXEC (#sql)
END
I get this error:
Msg 207, Level 16, State 1, Line 1
Invalid column name 'CFC2776A-6EE1-E511-A172-005056A218B0'.
Is there any way to do this so I don't have to make a bunch or procedures to pull text from a bunch of different tables?
QUOTENAME has optional second parameter quote char, so you were close and this could be solved by:
... QUOTENAME(#id, '''')
but the most proper way for this case is passing the parameter:
set #cmd = '
SELECT t.' + QUOTENAME(#field) + '
FROM ' + QUOTENAME(#table) + ' t
WHERE t.ID = #ID'
exec sp_executesql #cmd, N'#ID uniqueidentifier', #ID
And server will be able to reuse plan as #srutzsky mentioned. Because #ID is no longer part of a query text and #cmd text remains the same for different #ID (and same #table+#field).
ALTER PROCEDURE [dbo].[GET_TEXT_DETAIL]
(
#id UNIQUEIDENTIFIER,
#table SYSNAME,
#field SYSNAME
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = '
SELECT ' + QUOTENAME(#field) + '
FROM ' + QUOTENAME(#table) + '
WHERE ID = ''' + CAST(#id AS VARCHAR(36)) + ''''
--PRINT #SQL
EXEC sys.sp_executesql #SQL
END

SQL Server insert parameterized name table to IF EXISTS

I want to insert #inputData to ColumnData if it doesn't exists to prevent duplicate data to a table with name as parameter #TableName. Error at the 'dbo.#TableName'.
SET #insertSQL = 'INSERT INTO '+ #TableName + ' (ColumnData) VALUES ('''+#inputData+''');'
IF NOT EXISTS (SELECT 1 FROM [dbo].[#TableName] WHERE ColumnData = #inputData)
EXECUTE(#insertData) -- EXECUTE #insertData if ColumnData is not found
I also tried this (also throws error):
SET #insertSQL = 'INSERT INTO '+ #TableName + ' (ColumnData) VALUES ('''+#inputData+''');'
IF NOT EXISTS (EXECUTE('SELECT 1 FROM [dbo]. '+#TableName ' WHERE ColumnData = ' + #inputData))
EXECUTE(#insertData) -- EXECUTE #insertData if ColumnData is not found
It look like you are splitting the dynamic SQL statement. Try something like this:
DECLARE #SQL NVARCHAR(MAX) = N'
IF NOT EXISTS (SELECT 1 FROM dbo.' + QUOTENAME(#TableName) + ' WHERE ColumnData = #inputData
INSERT INTO ' + QUOTENAME(#TableName) + ' (ColumnData) VALUES (#inputData)'
EXEC sp_executesql #SQL, N'#inputData VARCHAR(128)', #inputData = #inputData
Table name cannot be parameterized, so it must be appended. However, QUOTENAME is strongly recommended to avoid SQL injection.
On the other hand, #inputData can be sent as a parameter, that's why sp_executesql can be used.
Generally speaking, unless you are not dealing with parameters, try to always use sp_executesql instead of simple EXEC (), as EXEC forces into ugly string concatenation and possible injectable SQL statements.

SQL Server: executing dynamic/inline sql table name within EXEC and passing geometry as geometry parameter

I'm trying to execute an inline SQL statement within a stored procedure. I'm working with SQL Server 2008.
The problem is that I can't execute the first inline statement (with WHERE clause). It crashes because the string within EXEC(...) is dynamically created and all concatenated variables must be of type varchar.
Error that appears when calling procedure:
An expression of non-boolean type specified in a context where a
condition is expected, near 'ORDER'.
The procedure looks like:
CREATE PROCEDURE loadMyRows
#table_name nvarchar(50),
#bounding_box varchar(8000)
AS
BEGIN
-- *********************************** COMMENT *********************************
-- ** This two code lines are correct and will return true (1) or false (0), **
-- ** but they doesn't work within inline EXEC(...) **
--DECLARE #bb geometry = geometry::STGeomFromText(#bounding_box, 4326);
--select TOP(5) wkt.STWithin(#bb) AS 'bool'
-- *********************************** COMMENT *********************************
IF #bounding_box <> ''
BEGIN
DECLARE #bb geometry = geometry::STGeomFromText(#bounding_box, 4326);
EXEC(
'SELECT TOP (' + #row_limit + ') * ' +
'FROM ' + #real_table_name + ' ' +
'WHERE wkt.STWithin('+#bb+') ' + -- <-- doesn't work :-(
-- 'WHERE wkt.STWithin(geometry::STGeomFromText('''+#bounding_box+''', 4326)) ' +
-- ^^ doesn't work, too :-(
'ORDER BY id ASC '
);
END
ELSE
BEGIN
EXEC(
'SELECT TOP (' + #row_limit + ') * ' +
'FROM ' + #real_table_name + ' ' +
'ORDER BY id ASC'
);
END
END
I've found a working solution for this problem. The way the MSDN showed me was http://msdn.microsoft.com/en-US/library/ms175170.aspx. There's written:
[...] the string is executed as its own self-contained batch.
That let me know, if I want to execute a dynamic statement with a table variable as string, it's the same as I would execute the query without the EXECUTE command, like:
SELECT TOP(#row_limit) *
FROM #real_table_name
WHERE ...
ORDER BY id ASC;
And this would probably not work for the table name.
So, if I write instead:
DECLARE #sql_statement nvarchar(MAX) = 'SELECT TOP(#limit) *
FROM ' + #real_table_name + '
ORDER BY id ASC';
-- declaration of parameters for above sql
DECLARE #sql_param_def nvarchar(MAX) = '#limit int';
EXECUTE sp_executesql #sql_statement, #sql_param_def, #limit = #row_limit;
Then, this would work. This is because I define the #sql_statement simply as a concatenated string which will just resolve the dynamic table name at runtime to a string with the name of the real existing table. The #limit parameter is untouched and is still a parameter.
If we then execute the batch we only must pass a value for the #limit parameter and it works!
For the geometry parameter it works in the same way:
DECLARE #bb geometry = geometry::STGeomFromText(#bounding_box, 4326);
SET #sql_statement = 'SELECT TOP(#limit) *
FROM ' + #real_table_name + '
WHERE wkt.STWithin(#geobb) = 1
ORDER BY id ASC';
-- NOTE: This ' = 1' must be set to avoid my above described error (STWithin doesn't return a BOOLEAN!!)
-- declaration of parameters for above sql
SET #sql_param_def = '#limit int, #geobb geometry';
EXECUTE sp_executesql #sql_statement, #sql_param_def, #limit = #row_limit, #geobb = #bb;
Hope this was clear ;-)
create proc usp_insert_Proc_Into_temp
#tempTable nvarchar(10) output
as
begin
set #tempTable = '##temp'
declare #query nvarchar(200)
--Select statement
set #query = 'select 1 as A,2 as B, 3 as C into'+ ' '+#tempTable+''
exec(#query)
end
go
declare #tempTable nvarchar(10)
exec usp_insert_Proc_Into_temp #tempTable output
exec('select * from' + ' '+ #tempTable+'')
exec ('drop table'+ ' '+#tempTable+'')

How to create a UDF or View in another database that references the correct sys.objects table in the caller?

Using SQL Server 2008, I'd like to create a UDF that gives me the create date of an object. This is the code:
create function dbo.GetObjCreateDate(#objName sysname) returns datetime as
begin
declare #result datetime
select #result = create_date from sys.objects where name = #objname
return #result
end
go
I'd like to put this UDF in the master database or some other shared database so that it is accessible from anywhere, except that if I do that then the sys.objects reference pulls from the master database instead of the database that I'm initiating my query from. I know you can do this as the information_schema views sit in master and just wrap calls to local instances of sys.objects, so I'm hoping there's a simple way to do that with my UDF as well.
Try this:
CREATE FUNCTION dbo.GetObjCreateDate(#objName sysname, #dbName sysname)
RETURNS datetime AS
BEGIN
DECLARE #createDate datetime;
DECLARE #params nvarchar(50);
DECLARE #sql nvarchar(500);
SET #params = '#createDate datetime OUTPUT';
SELECT #sql = 'SELECT #createDate = create_date FROM ' + #dbName + '.sys.objects WHERE name = ''' + #objname + '''';
EXEC sp_executesql #sql, #params, #createDate = #createDate OUTPUT;
RETURN #createDate
END
;
Why not do this instead?
Create a stored procedure that creates a view in the master database containing all of the information in sys.objects from each database on the server.
Create a DDL Trigger that gets fired whenever a CREATE, ALTER or DROP statement is executed for a database. The trigger would then execute the stored procedure in step #1. This allows the view to be automatically updated.
(Optional) Create a user defined function that queries the view for the creation date of a given object.
Stored Procedure DDL:
USE [master];
GO
CREATE PROCEDURE dbo.BuildAllServerObjectsView
AS
SET NOCOUNT ON;
IF OBJECT_ID('master.dbo.AllServerObjects') IS NOT NULL
EXEC master..sp_SQLExec 'DROP VIEW dbo.AllServerObjects;';
IF OBJECT_ID('tempdb..Databases') IS NOT NULL
DROP TABLE #Databases;
DECLARE #CreateView varchar(8000);
SET #CreateView = 'CREATE VIEW dbo.AllServerObjects AS' + CHAR(13)+CHAR(10) + CHAR(13)+CHAR(10);
SELECT name COLLATE SQL_Latin1_General_CP1_CI_AS AS 'name'
INTO #Databases
FROM sys.databases
ORDER BY name;
DECLARE #DatabaseName nvarchar(100);
WHILE (SELECT COUNT(*) FROM #Databases) > 0
BEGIN
SET #DatabaseName = (SELECT TOP 1 name FROM #Databases ORDER BY name);
SET #CreateView +='SELECT N'+QUOTENAME(#DatabaseName, '''')+' AS ''database_name''' + CHAR(13)+CHAR(10)
+ ' ,name COLLATE SQL_Latin1_General_CP1_CI_AS AS ''object_name''' + CHAR(13)+CHAR(10)
+ ' ,object_id' + CHAR(13)+CHAR(10)
+ ' ,principal_id' + CHAR(13)+CHAR(10)
+ ' ,schema_id' + CHAR(13)+CHAR(10)
+ ' ,parent_object_id' + CHAR(13)+CHAR(10)
+ ' ,type' + CHAR(13)+CHAR(10)
+ ' ,type_desc' + CHAR(13)+CHAR(10)
+ ' ,create_date' + CHAR(13)+CHAR(10)
+ ' ,modify_date' + CHAR(13)+CHAR(10)
+ ' ,is_ms_shipped' + CHAR(13)+CHAR(10)
+ ' ,is_published' + CHAR(13)+CHAR(10)
+ ' ,is_schema_published' + CHAR(13)+CHAR(10)
+ ' FROM ' + QUOTENAME(#DatabaseName) + '.sys.objects';
IF (SELECT COUNT(*) FROM #Databases) > 1
SET #CreateView += CHAR(13)+CHAR(10) + CHAR(13)+CHAR(10) + ' UNION' + CHAR(13)+CHAR(10);
ELSE
SET #CreateView += ';';
DELETE #Databases
WHERE name = #DatabaseName;
END;
--PRINT #CreateView --<== Uncomment this to see the DDL for the view.
EXEC master..sp_SQLExec #CreateView;
IF OBJECT_ID('tempdb..Databases') IS NOT NULL
DROP TABLE #Databases;
GO
Function DDL:
USE [master];
GO
CREATE FUNCTION dbo.GetObjCreateDate(#DatabaseName sysname, #objName sysname) RETURNS DATETIME AS
BEGIN
DECLARE #result datetime;
SELECT #result = create_date
FROM master.dbo.AllServerObjects
WHERE [database_name] = #DatabaseName
AND [object_name] = #objname;
RETURN #result;
END
GO
Sample Usage:
SELECT master.dbo.GetObjCreateDate('MyDatabase', 'SomeObject') AS 'Created';
SELECT master.dbo.GetObjCreateDate(DB_NAME(), 'spt_monitor') AS 'Created';
Does it have to be a function? If you just want it accessible everywhere, a trick is to put your code in a varchar and sp_executesql it:
create procedure dbo.GetObjCreateDate(#objName sysname)
as
declare #sql nvarchar(max)
select #sql = 'select create_date from sys.objects where name = ''' + #objname + ''''
EXEC sp_executesql #sql
go
There seems to be an undocumented stored procedure that allows you to create your own system objects: sp_ms_marksystemobject
You can read more on http://www.mssqltips.com/tip.asp?tip=1612
Have a look at How to Write Your Own System Functions. I believe that it may help you

Resources