SQL Server stored procedure: delete record dynamically - tsql

I need to delete (read/update) some values in some tables, and would like to use a stored procedure to reduce security issues.
Since tables are many and records even more and it is not reasonable to write a stored procedure for each combination, and since everybody has this kind of need, I thought could have been easy to find a stored procedure to do this.. but googling a lot I did not find a simple and short answer, so I tried to build my own stored procedure. But I'm afraid it could have some security issues: principally when I declare #Table as nvarchar(30).. I tried to declare as TABLE but it returns error..
Can suggest what is not acceptable and suggest a solution?
Thanks
Here the stored procedure for deleting.. but for other action could be similar:
CREATE PROCEDURE dbo.spDeleteRecord
(
#UID nvarchar(20) = NOT NULL,
#PWD nvarchar(30) = NOT NULL,
#Table sysname,
#WhereField sysname,
#WhereValue nvarchar(150) = NOT NULL
)
AS
SET NOCOUNT ON;
DECLARE #S nvarchar(max) = '',
#P nvarchar(max) = ''
SET #S = 'DELETE t
from dbo.'+quotename(#Table)+' t
join dbo.subUsers su on t.UID = su.UID
where ' + quotename(#WhereField) + ' = #_WhereValue
and su.SUID = #_UID
and su.PWD = #_PWD'
SET #P = '#_UID nvarchar(50),
#_PWD nvarchar(50),
#_Table sysname,
#_WhereField sysname,
#_WhereValue nvarchar(150)'
--PRINT #S
EXEC sp_executesql #S, #P, #UID, #PWD, #Table, #WhereField, #WhereValue
SET NOCOUNT OFF
Thanks for reading
Joe

You would need to do it like this:
SET #S = 'DELETE t
from dbo.'+quotename(#Table)+' t
join dbo.subUsers su on t.UID = su.UID
where ' + quotename(#WhereField) + ' = #_WhereValue
and su.SUID = #_UID
and su.PWD = #_PWD'
SET #P = '#_UID nvarchar(50),
#_PWD nvarchar(50),
#_WhereValue nvarchar(150)'
--PRINT #S
EXEC sp_executesql #S, #P, #_WhereValue = #WhereValue, #_UID = #UID, #_PWD = #PWD
Basically, the parameter list can only refer to parameters that are actually embedded in the SQL string.
Also, note that #Table and #WhereField would be more correct as datatype sysname. I would also probably have restricted #UID, #PWD, and #WhereValue to NOT NULL because I hate unhandled nulls.
However, you really need to consider if you want to do this. To me this feels like leaving a loaded gun lying around. What happens when you call this with a table that happens to have a UID field that happens to coincide to the values in the dbo.subUsers table even though no relation exists there? I don't see any significant advantage of this method over just running the parameterized query from your application, and the fact that the query changes between executions means that you may run into problems with parameter sniffing so you may end up with suboptimal execution plans.

Related

Value not Store in Dynamic SQL

I've different different tables to categorically store data and a log table where all the transactions log are recorded
e.g. 1) VoucherNO, Add, ...
2) VoucherNO, Delete, ..
After I backup the database and restore in another server for my Reporting Purpose. That time I want to ensure all the log data and transaction are available in TestDB if not then I remove log from 'AUD_USER_ACTIVITY'.
To find the transaction exist or not, I create a dynamic sql select statement and check whether record is exist or not.
Basis on #RecExist Value I do the action like if records is not available in TestDB the log will be remove, if record exist immediately break this loop and going for next procedure
But #RecExist variable is not updating in Dynamic SQL Execution. Please guide me
declare #MvDocNo varchar(50)
DECLARE #SCtr as DECIMAL(10,0)
declare #LocationCode varchar(4)
declare #UName Nvarchar(40)
declare #toe varchar(30)
declare #QryTxt as nvarchar(MAX);
Declare #RecExist as INT =0;
SET #RecExist=0
WHILE #RecExist=0
BEGIN
select top 1 #MvDocNo=DOCNO, #SCtr=SrlNo,#LocationCode =DMLTYPE,#UName=TABLENAME
FROM R_AUDDB..AUD_USER_ACTIVITY
WHERE DBNAME='TestDB' and DMLTYPE not in ('AD','D','PD') ORDER BY SRLNO DESC;
select top 1 #toe=docno from TestDB..M_TYPEOFENTRY where TBLNAME=#UName;
set #QryTxt='Select #RecExist=1 From R_TestDB..'+#UName+ ' Where '+#toe+'='''+#MvDocNo+''''
exec (#QryTxt)
IF #RecExist=0
BEGIN
DELETE R_AUDDB..AUD_USER_ACTIVITY WHERE SRLNO=#SCtr
END
END
The following code sample demonstrates how to check for a row in a table with a specific column and value using dynamic SQL. You ought to be able to change the values of the first three variables to reference a table and column in your database for testing.
Note that SQL injection is still possible: there is no validation of the table or column names.
-- Define the table to check and the target column name and value.
declare #TableName as SysName = 'Things';
declare #ColumnName as SysName = 'ThingName';
declare #TestValue as NVarChar(32) = 'Beth';
-- Create a SQL statement to check for a row in the target table with the specified column name and value.
declare #SQL as NVarChar(1024);
declare #Result as Bit;
-- Note that only object names are substituted into the statement at this point and QuoteName() is used to reduce problems.
set #SQL = N'select #iResult = case when exists ( select 42 from dbo.' + QuoteName( #TableName ) +
N' where ' + QuoteName( #ColumnName ) + N' = #iTestValue ) then 1 else 0 end;'
select #SQL as SQL;
-- Execute the SQL statement.
-- Note that parameters are used for all values, i.e. the target value and return value.
execute sp_executesql #stmt = #SQL,
#params = N'#iTestValue NVarChar(32), #iResult Bit output',
#iTestValue = #TestValue, #iResult = #Result output
-- Display the result.
select #Result as Result;

check which well known text can be transformed using geometry::STPolyFromText(

I have some data which I bulk import into this table structure:
CREATE TABLE #Temp
(
WellKnownText NVARCHAR(MAX)
)
Some of the entries are not valid. So something like this:
SELECT geometry::STPolyFromText(WellKnownText,4326) FROM #Temp
does not work for all rows and thus falls over.
What is the best way to detect which WellKnownText are not valid? I have used MakeValid in the past - so ideally I would like to fix entries as much as possible.
PS:
This does not work:
SELECT * FROM #Temp
WHERE geometry::STPolyFromText(WellKnownText,4326).STIsValid() = 0
PPS:
I chose a loop based approach in the end along those lines:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL DROP TABLE #Temp;
IF OBJECT_ID('tempdb..#Temp1') IS NOT NULL DROP TABLE #Temp1;
DECLARE #LoopCounter INT = 1;
DECLARE #MaxCounter INT;
DECLARE #Valid BIT;
DECLARE #ValidCounter INT;
DECLARE #WellKnownText NVARCHAR(MAX);
CREATE TABLE #Temp
(
Guid UNIQUEIDENTIFIER,
PostcodeFraction NVARCHAR(50),
WellKnownText NVARCHAR(MAX),
GeoJson NVARCHAR(MAX)
);
CREATE TABLE #Temp1
(
Guid UNIQUEIDENTIFIER,
PostcodeFraction NVARCHAR(50),
WellKnownText NVARCHAR(MAX),
GeoJson NVARCHAR(MAX)
);
BULK INSERT #Temp FROM 'D:\PolygonData.txt' WITH (FIELDTERMINATOR = '\t', FIRSTROW = 2, ROWTERMINATOR = '\n');
ALTER TABLE #Temp ADD Id INT IDENTITY(1,1);
SELECT #MaxCounter = MAX(Id) FROM #Temp
SET #ValidCounter = 0;
WHILE(#LoopCounter <= #MaxCounter)
BEGIN
BEGIN TRY
SELECT #WellKnownText = WellKnownText FROM #Temp WHERE Id = #LoopCounter;
SET #Valid = GEOMETRY::STGeomFromText(#WellKnownText,4326).STIsValid();
SET #ValidCounter = #ValidCounter + 1;
END TRY
BEGIN CATCH
SET #Valid = 0;
END CATCH
IF(#Valid = 1)
BEGIN
INSERT INTO #TEMP1
SELECT Guid, PostcodeFraction, WellKnownText, GeoJson FROM #Temp WHERE Id = #LoopCounter;
END
SET #LoopCounter = #LoopCounter + 1;
END
PRINT #ValidCounter;
SELECT * FROM #TEMP1;
As requested in the comments, some possible solutions
I guess you're really looking for a function that can be CROSS APPLYed, something like
SELECT * FROM #Temp T
CROSS APPLY IsWKTValidFunc(T.WellKnownText, 4326) F
WHERE F.IsValid = <somecondition>
(Or even added to as computed column to give you a flag that's set on inserting your WKT)
Stored Proc
https://gis.stackexchange.com/questions/66642/detecting-invalid-wkt-in-text-column-in-sql-server has a simple SP that wraps GEOMETREY::STGeomFromText in a try catch block.
However, stored procs cannot be CROSS APPLYed (or called from a UDF that can be) so this would result in a cursor based solution.
UDF
A UDF can be cross applied, but can't have a TRY-CATCH block. You also can't call the above SP from a UDF. So not much use there.
CLR UDF
Wrap the GEOMETREY::STGeomFromText call in a CLR UDF that can be CROSS APPLIED, can have try catch and other error checking, rules etc, and return a flag indicating valid text. I haven't tried this one out but this sounds like the best option if CLR is enabled in your environment.
Hope this gives you some ideas. Feedback in the comments to these suggestions appreciated.

TSQL: Determine number of columns returned by Stored Procedure

This probably has been asked before, but I was unable to find a satisfying answer.
I need to insert results of a stored procedure into a temporary table, something like:
INSERT INTO #TEMP EXEC MY_SP
I don't know in advance how many columns the SP will return, so I need to prepare my #TEMP table (via dynamic ALTER .. ADD commands) to add columns to match SP resultset.
Assumption is - SP accepts no parameters and number of columns is always the same. But how do I determine that number in pure TSQL outside of SP so I can store it for example into a variable?
Tough one, especially if someone else is denying you the necessary permissions to run e.g. OPENROWSET.
Have you considered unpacking/script the SP and add its contents directly to your T-SQL? In this way you can modify and adapt it however you may want.
Otherwise, if you could explain more about the SP:
What does the SP do?
What kind of information does it output? One-N columns, - how many rows?
Is it slow/fast? (Could we perhaps use a brute-force [try-catch] approach)?
How does it determine the columns to output and how often does that change?
Can you pre-determine the columns in any way? (So that you may use an INSERT #temp EXEC sp_getData syntax).
Best of luck!
It's a bit awkward, but you can do something like:
SELECT * INTO #temp
FROM OPENROWSET('SQLOLEDB','Data Source=MyServer;Trusted_Connection=yes;Integrated Security=SSPI', 'EXECUTE MyDB.MySchema.MyProcedure #MyParm=123')
I've requested an EXECUTE INTO syntax, like SELECT INTO to avoid having to know the shape of the stored proc output in advance, but it was rejected
Let me say from the start that if I had to do this I would try find a way to do it outside the SQL environment or something else, because the solution I am providing is not a good way to do this, but it works. So I am not saying that this is a good idea.
I have a sp called test:
CREATE PROCEDURE Test
AS
SELECT 1 as One, 2 as Two
To execute this dynamically I do the following:
DECLARE #i int
SET #i = 1;
DECLARE #SUCESS bit
SET #SUCESS = 0
WHILE(#SUCESS = 0)
BEGIN
DECLARE #proc VARCHAR(MAX)
DECLARE #count int
SET #count = 1
SET #proc = 'DECLARE #t TABLE ( c1 varchar(max) '
WHILE #count < #i
BEGIN
SET #proc = #proc + ', c' + CONVERT(varchar, #count + 1) + ' varchar(max) '
print #proc
SET #count = #count + 1
END
SET #proc = #proc + '); INSERT INTO #t EXEC Test'
BEGIN TRY
EXEC(#proc);
SET #SUCESS = 1
END TRY
BEGIN CATCH
SET #i = #i+1
END CATCH
END
SET #proc = #proc + '; SELECT * into ##t FROM #t '
EXEC( #proc )
SELECT * from ##t
This is a poor solution to your problem because you have lost the data type of your columns, their names etc.
I don't understand the syntax, and this probably isn't the best way, but someone seems to have done this with converting to xml and parsing it: Dynamic query results into a temp table or table variable

Incorrect Stored Procedure Syntax

I am using the below stored procedure to upload files into a db where each file has a correspondence table but there are times when the table not exist in that case i want to add the file name into a table called NewTables.I can not get the stored proc syntax working can someone help me.I believe the mistake is in the first part where i check if the table exist
ALTER proc [dbo].[UploadCSVFiles]
#FilePath varchar(100) ,
#FileName varchar(100),
#TableName varchar(250)
AS
BEGIN
DECLARE #SqlStmt nvarchar(max)
DECLARE #ErrorCode int
SET #SqlStmt='Truncate table dbo.[' + #TableName +']'
EXEC(#SqlStmt);
set #SqlStmt =N'
IF not EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N''[dbo].['+#TableName +N']'') AND type in (N''U''))
BEGIN
INSERT INTO dbo.NewTables ('+#TableName+N','+#FileName+N') Values('+#TableName+N','+#FileName+N')
END
ELSE
BEGIN
INSERT INTO '+#TableName+N'
select *
from openrowset(''MSDASQL''
,''Driver={Microsoft Access Text Driver (*.txt, *.csv)};
DefaultDir='+#FilePath+N'''
,''select * from "'+#FileName+N'"'')
END
'
EXEC(#SqlStmt);
Thanks
Rao
Thanks fpop and Christine, I have made the suggestions you made but still I get an error
Msg 4701, Level 16, State 1, Line 1
Cannot find the object "Customer" because it does not exist or you do not have permissions.
it seems the If statement does not insert the new table
here is the final version
USE [MyDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER proc [dbo].[UploadFiles_2]
#FilePath varchar(100) ,
#FileName varchar(100),
#TableName varchar(250)
AS
BEGIN
DECLARE #SqlStmt nvarchar(max)
DECLARE #ErrorCode int
SET #SqlStmt='Truncate table dbo.[' + #TableName +']'
EXEC sp_executesql #SqlStmt;
set #SqlStmt =N'
IF ( NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N''[dbo].['+#TableName +N']'') AND type in (N''U''))
BEGIN
INSERT INTO dbo.NewTables (TableName,FileName) Values('''+#TableName+N''','''+#FileName+N''')
END
ELSE
BEGIN
INSERT INTO '+#TableName+N'
select *
from openrowset(''MSDASQL''
,''Driver={Microsoft Access Text Driver (*.txt, *.csv)};
DefaultDir='+#FilePath+N'''
,''select * from "'+#FileName+N'"'')
END'
EXEC sp_executesql #SqlStmt;
END
Hint: You can always add PRINT #SQLSTMT to see the code your procedure has generated.
There are two error in the script, both in line:
INSERT INTO dbo.NewTables ('+#TableName+N','+#FileName+N') Values('+#TableName+N','+#FileName+N')
First error: NewTables table must have 2 columns to keep table name and file name. Let's call them COL_TBL, COL_FILE
Second error: You have to add quotes in Values part of statement
Here is how it should look like:
INSERT INTO dbo.NewTables (COL_TBL, COL_FILE) Values('''+#TableName+N''','''+#FileName+N''')
btw, You didn't post code entirely, there is an END at the end missing. Please, next time copy the whole code so others can reproduce the error.
EDIT: Please consider following: using sp_executesql instead of EXEC, to avoid sql injection bugs do not concatenate parameters into dynamic sql and finally check if table exists before truncating it
Try this
IF (NOT EXISTS (SELECT * FROM sys.objects WHERE [type] LIKE 'U' AND name LIKE 'mytable'))
SELECT 'not found';
ELSE
SELECT 'found';

Array-like access to variables in T-SQL

In my stored procedure I have multiple similar variables #V1, #V2 ... #V20 (let's say 20 of them) FETCHED from a record. How would I use dynamic SQL to make 20 calls to another stored procedure using those variables as parameters?
Of course #V[i] syntax is incorrect, but it expresses the intent
fetch next from maincursor into #status, #V1, #V2, ...
while #i<21
begin
-- ??? execute sp_executesql 'SecondSP', '#myParam int', #myParam=#V[i]
-- or
-- ??? execute SecondSP #V[i]
set #i = #i+1
end
As others have said, set up a temporary table, insert the values that you need into it. Then "iterate" through it executing the necessary SQL from those values. This will allow you to have 0 to MANY values to be executed, so you don't have to set up a variable for each.
The following is a complete sample of how you may go about doing that without cursors.
SET NOCOUNT ON
DECLARE #dict TABLE (
id INT IDENTITY(1,1), -- a unique identity column for reference later
value VARCHAR(50), -- your parameter value to be passed into the procedure
executed BIT -- BIT to mark a record as being executed later
)
-- INSERT YOUR VALUES INTO #dict HERE
-- Set executed to 0 (so that the execution process will pick it up later)
-- This may be a SELECT statement into another table in your database to load the values into #dict
INSERT #dict
SELECT 'V1Value', 0 UNION ALL
SELECT 'V2Value', 0
DECLARE #currentid INT
DECLARE #currentvalue VARCHAR(50)
WHILE EXISTS(SELECT * FROM #dict WHERE executed = 0)
BEGIN
-- Get the next record to execute
SELECT
TOP 1 #currentid = id
FROM #dict
WHERE executed = 0
-- Get the parameter value
SELECT #currentvalue = value
FROM #dict
WHERE id = #currentid
-- EXECUTE THE SQL HERE
--sp_executesql 'SecondSP', '#myParam int', #myParam =
PRINT 'SecondSP ' + '#myParam int ' + '#myParam = ' + #currentvalue
-- Mark record as having been executed
UPDATE d
SET executed = 1
FROM #dict d
WHERE id = #currentid
END
Use a #TempTable
if you are at SQL Server 2005 you can create a #TempTable in the parent stored procedure, and it is available in the child stored procedure that it calls.
CREATE TABLE #TempTable
(col1 datatype
,col2 datatype
,col3 datatype
)
INSERT INTO #TempTable
(col1, col2, col3)
SELECT
col1, col2, col3
FROM ...
EXEC #ReturnCode=YourOtherProcedure
within the other procedure, you have access to #TempTable to select, delete, etc...
make that child procedure work on a set of data not on one element at a time
remember, in SQL, loops suck performance away!
Why not just use the table variable instead, and then just loop through the table getting each value.
Basically treat each row in a table as your array cell, with a table that has one column.
Just a thought. :)
This seems like an odd request - will you always have a fixed set of variables? What if the number changes from 20 to 21, and so on, are you constantly going to have to be declaring new variables?
Is it possible, instead of retrieving the values into separate variables, to return them each as individual rows and just loop through them in a cursor?
If not, and you have to use the individual variables as explained, here's one solution:
declare #V1 nvarchar(100)
set #V1 = 'hi'
declare #V2 nvarchar(100)
set #V2 = 'bye'
declare #V3 nvarchar(100)
set #V3 = 'test3'
declare #V4 nvarchar(100)
set #V4 = 'test4'
declare #V5 nvarchar(100)
set #V5 = 'end'
declare aCursor cursor for
select #V1
union select #V2 union select #V3
union select #V4 union select #V5
open aCursor
declare #V nvarchar(100)
fetch next from aCursor into #V
while ##FETCH_STATUS = 0
begin
exec TestParam #V
fetch next from aCursor into #V
end
close aCursor
deallocate aCursor
I don't really like this solution, it seems messy and unscalable. Also, as a side note - the way you phrased your question seems to be asking if there are arrays in T-SQL. By default there aren't, although a quick search on google can point you in the direction of workarounds for this if you absolutely need them.