if i want to write a procedure like below, is there some other way that,
to avoid using concatenate SQL statement, i am just afraid, if the input is too long, exceed the limit of max varchar, the code will have big problem.
Thanks
CREATE PROCEDURE UPDATE_ALL_STATUS
#IDs varchar(MAX) = null,
#status int = null
AS
BEGIN
IF #IDs is null
BEGIN
RETURN
END
DECLARE #SQL VARCHAR(MAX)
SET #SQL = 'UPDATE mytable SET status = ' + #status + ' WHERE id in (' + #IDs + ')'
EXECUTE #SQL
END
Instead of dynamic SQL (which is also vulnerable to SQL Injection Attacks) and passing in a VARCHAR(MAX), consider using Table Valued Parameters:
-- Creates the TVP type - only needed once!
CREATE TYPE IntegerTableType AS TABLE
( Identities INT );
GO
CREATE PROCEDURE UPDATE_ALL_STATUS
#IDs IntegerTableType READONLY,
#status int = null
AS
BEGIN
UPDATE mytable
SET status = #status
WHERE id IN
(SELECT Identities FROM #IDs)
END
This MSDN article shows how to call these from your .NET code.
Related
I want to pass 2 parameters to my stored procedure, one of which I wish to use as a part of a variable definition. I am using SQL Server Management Studio 2018. My stored procedure will take a consignment number as a parameter and append it to the variable where the definition will appear similar to this:
passed parameter = #ConsignmentNo
variable #TmpDictionary = 'tmp.' + #ConsignmentNo + '_Dictionary'
So that I may have a generated table name such as
tmp.Cons1234_Dictionary
SQL is throwing an error
Must declare the table variable "#TmpDictionary"
Please see my attempt here:
/****** Object: --------------- Script Date: 29/06/2022 16:17:32 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE InsertProcedureNameHere
#Source varchar (max),
#ConsignmentNo varchar(max)
AS
DECLARE
#sql varchar(MAX),
#loop int,
#max_loop int,
#TmpDictionary nvarchar(max) = 'tmp.' + #ConsignmentNo + '_Dictionary',
#TmpThesaurus nvarchar(max) = 'mail.' + #ConsignmentNo + '_Thesaurus'
--set #TmpLookup = 'tmp.' + #JobNumber + '_Mailing_Lookup'
--set #MailSelection = 'mail. + #JobNumber + '_Mailing_Selection'
SELECT #loop = MIN(ID) FROM #TmpDictionary
WHERE [Source] = #Source
SELECT #max_loop = MAX(ID) FROM #TmpDictionary
WHERE [Source] = #Source
--print #loop print #max_loop
WHILE #loop <= #max_loop
BEGIN
SELECT
---------
FROM #TmpDictionary t
WHERE ID = #loop
BEGIN
SET #sql = '
update t
-------------------
from #Thesaurus t
where 1=1
-------------------
PRINT (#sql)
-- EXEC (#sql)
END
SET #loop = #loop +1
END
Please accept the fact that anything with dashes is just a placeholder for parts of the query that I cannot share or are needed for the purpose of this question. In a nutshell, I need to be able to pass a variable to my stored procedure, and use it within a variable definition to be able to reference tables dynamically using dynamic queries.
I hope this question makes sense!
I'm facing deadlock
was deadlocked on lock resources with another process and has been
chosen as the deadlock victim.
problem In SQL-Server as i'm inserting data in database by picking max id against a specific column then add a increment got the value against which record will be inserted.
i'm calling a procedure as code mentioned below:
CREATE
PROCEDURE [dbo].[Web_GetMaxColumnID]
#Col_Name nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
DECLARE #MaxID BIGINT;
SET NOCOUNT ON;
-- Insert statements for procedure here
BEGIN
BEGIN TRAN
SET #MaxID = (
SELECT Col_Counter
FROM Maintenance_Counter WITH (XLOCK, ROWLOCK)
WHERE COL_NAME = #Col_Name
)
UPDATE Maintenance_Counter
SET Col_Counter = #MaxID + 1
WHERE COL_NAME = #Col_Name
COMMIT
END
SELECT (
CONVERT(
VARCHAR,
(
SELECT office_id
FROM Maintenance
)
) + '' + CONVERT(VARCHAR, (#MaxID))
) AS MaxID
END
any one help me out .....
As Marc already answered, use SEQUENCE. It's available in all supported versions of SQL Server, ie 2012 and later. The only reason to avoid it is targeting an unsupported version like 2008.
In this case, you can set the counter variable in the same statement you update the counter value. This way, you don't need any transactions or locks, eg:
declare #counterValue bigint
UPDATE Maintenance_Counter
SET Col_Counter = Col_Counter + 1 , #counterValue=Col_Counter+1
WHERE COL_NAME = #Col_Name
select #counterValue
Yo can use sequences to generate incremental values avoiding any blocking.
I have adapted my own Counter Generator to be a direct replacement for yours. It creates dynamically the SQL statements to manage sequences, if a Sequence doesn't exist for the value we are looking for, it creates it.
ALTER PROCEDURE [dbo].[Web_GetMaxColumnID]
#Col_Name nvarchar(50)
AS
declare #Value bigint;
declare #SQL nvarchar(64);
BEGIN
if not exists(select * from sys.objects where object_id = object_id(N'dbo.MY_SEQUENCES_' + #Col_Name) and type = 'SO')
begin
set #SQL = N'create sequence dbo.MY_SEQUENCES_' + #Col_Name + ' as bigint start with 1';
exec (#SQL);
end
set #SQL = N'set #Value = next value for dbo.MY_SEQUENCES_' + #Col_Name;
exec sp_executesql #SQL, N'#Value bigint out', #Value = #Value out;
select #Value ;
END
The only inconvenience is that your values can get gaps within (because you could have retrieved a value but finally not used it). This is not a problem on my tables, but you have to consider it.
I have a table:
T (Mon varchar(3));
INSERT INTO T VALUES ('Y');
Case 1:
DECLARE #day varchar(3) = 'Mon'
SELECT Mon
FROM T; --result : Y (no problem)
Case 2:
DECLARE #day VARCHAR(3) = 'Mon'
SELECT #day
FROM T; --result : Mon
If I want to get result Y in second case, how can I do that without re-designing the solution completely.
exec('select '+#day+' from #T;')
You can assign this #day variable using this command.
and dont forget to use "top 1" to limit only 1 row data to get.
SELECT top 1 #day = Mon FROM T;
then You can use #day in other queries.
Thanks for updating your question, I appreciate the simplicity.
EXECUTE/EXEC is a simple way of creating your script, allowing you to piece-meal the strings in variables and can be used with VARCHAR. Keep in mind that both EXEC and sp_executesql are not executed until runtime, so it will not be able to utilize cached query plans effectively or at all.
CREATE TABLE #Example (
Class_Type VARCHAR(2) NOT NULL
, Mary INT NULL
, Harry VARCHAR(100) NULL
, Larry XML NULL
, Sorry NVARCHAR(100) NULL )
INSERT INTO #Example (Class_Type, Mary, Harry, Larry, Sorry)
VALUES ('A', 250, 'Apples', CAST('<List><Class Professor="Harry">1</Class><Books>3</Books></List>' AS XML), N'Angelina')
, ('A', 300, 'Pineapples', CAST('<List><Class Professor="Larry">1</Class><Books>3</Books></List>' AS XML), N'Prince Charles')
, ('B', 15, 'Cucumbers', CAST('<List><Class Professor="Sarry">2</Class><Books>5</Books></List>' AS XML), N'Larry Cable')
GO
Here we use the EXECUTE/EXEC Method:
CREATE PROCEDURE dbo.USP_MyQuery (#Column NVARCHAR(255) )
AS
BEGIN
DECLARE #SQL VARCHAR(MAX)
SET #SQL = ' FROM #Example WHERE Class_Type = ''' + 'A' + ''''
SET #Column = RTRIM(LTRIM(#Column));
IF #Column IN (N'Mary', N'Harry', N'Larry', N'Sorry')
BEGIN
SET #SQL = 'SELECT ' + QUOTENAME(#Column, '[]') + #SQL;
EXEC (#SQL)
END
ELSE PRINT 'Check your spelling. Input does not match any existing columns'
END
GO
EXEC dbo.USP_MyQuery #Column = 'Mary'
sp_executesql is arguably the better method. Because the system procedure can paramertizes the entire statement (free from SQL Injection), SQL Server can reuse cached plans on the second query! For performance, safety, reability reasons and more, it is the preferred method of Dynamic SQL statements (when possible). However, note that all strings passed through must be in UNICODE format (NVARCHAR)
Here we use the sp_executesql system procedure
ALTER PROCEDURE dbo.USP_MyQuery (#Column NVARCHAR(255) )
AS
BEGIN
DECLARE #SQL NVARCHAR(100)
SET #SQL = N'SELECT ' + QUOTENAME(#Column) +
' FROM #Example WHERE Class_Type = ''' + 'A' + ''''
SET #Column = RTRIM(LTRIM(#Column))
IF #Column IN (N'Mary', N'Harry', N'Larry', N'Sorry')
BEGIN
EXEC sp_executesql #statement = #SQL
END
ELSE PRINT 'Check your spelling. Input does not match any existing columns'
END
EXEC dbo.USP_MyQuery #Column = 'Mary'
Not so hard, no? Likely you may want to use more testing and take advantage of functions like REPLACE() for making your query robust from human error.
Lastly, remember that QUOTENAME() removes system names identifiers, and instead replaces the string with a deliminator ('[]' is the default setting)
I have a Database of 10 Columns,
let it be like A,B,C,D,E,F,G,H,I,J
Now if i want to extract data with a mix of 4 options
i.e say B,C,H,J (B is my primary key)
and none of them are mandatory to give data in the option.
say
Case 1 : B,C,H is given as parameters and J is given null
for all such cases I have to make a nested if Else statement ?
Because it will go for 4! (factorial)= 24 cases
Is there any easy option for this???
Yes. One option (but with caveats):
WHERE
(#paramA IS NULL OR Some Condition involving #paramA) AND
(#paramB IS NULL OR Some Condition involving #paramB) AND
....
(#paramH IS NULL OR Some Condition involving #paramH)
The caveat being this might make your stored procedure sensitive to parameter sniffing and cached query plans that are not appropriate for particular sets of parameters.
Another option is to construct dynamic TSQL.
As pointed out by # Damien_The_Unbeliever: Erland Sommarskog's Dynamic Search Conditions in SQL is a great place to start.
Here is an example on how to do this using dynamic SQL but I’d also strongly recommend you read the article Mitch and Demien shared.
CREATE PROCEDURE dbo.SearchProc
(
#param1 datetime,
#param2 int,
#param3 nvarchar
)
AS
BEGIN
DECLARE #sql nvarchar(4000)
DECLARE #parameters nvarchar(300)
SET #sql = 'SELECT * FROM TableName WHERE (1=1) '
if #param1 is not null
SET #sql = #sql + ' AND TableName.Column1 = #param1'
if #param2 is not null
SET #sql = #sql + ' AND TableName.Column2 = #param2'
if #param3 is not null
SET #sql = #sql + ' AND TableName.Column3 = #param3'
SET #parameters = '#param1 datetime, #param2 int, #param3 nvarchar'
EXEC sp_executesql #sql, #parameters, #param1, #param2, #param3
END
Can anyone tell how correct the following code below. Iam tryin to create a stored procedure that returns the rowcount of a table whose name is passed to it.
CREATE PROCEDURE spROWCOUNTER
(
#tablename nvarchar(20)
#rowCountVal int OUTPUT
)
AS
DECLARE #strQuery nvarchar(300)
SET #strQuery = 'SELECT #rowCountVal=COUNT(*) FROM '+#tablename
EXEC(#strQuery)
RETURN #rowCountVal
ERROR MESSAGE :
Incorrect syntax near '#rowCountVal'
Must declare scalar variable '#tablename'
Must declare scalar variable '#rowCountVal'
whereas the code below works fine
ALTER PROCEDURE spROWCOUNTER
(
#rowCountVal int OUTPUT
)
AS
SELECT #rowCountVal=COUNT(*) FROM DEFECT_LOG
RETURN #rowCountVal
CREATE PROCEDURE spROWCOUNTER
#tablename nvarchar(20),
#rowCountVal int OUTPUT
AS
SELECT #rowCountVal = ISNULL(SUM(spart.rows), 0)
FROM sys.partitions spart
WHERE spart.object_id = object_id(#tablename) AND spart.index_id < 2
RETURN #rowCountVal
The syntax problem is easy to solve. There is a missing comma (",") between your parameters. Insert the comma and the stored procedure compiles:
( #tablename nvarchar(20), #rowCountVal int OUTPUT )
Then, there is the major problem: you can't access the #rowCountVal parameter inside the EXEC statement. To solve this problem, you could use the built-in stored procedure sp_executesql.
Read this good article written by the SQL Server MVP Erland Sommarskog.
By the way: you don't have to "return" a variable. Return values are normally used for returning some status values. If you pass an output parameter, it will be automatically returned.
I would use sp_executesql instead of exec. Then you can pass in #rowCountVal as an output variable into the dynamic sql.
create PROCEDURE spROWCOUNTER
(
#tablename nvarchar(20),
#rowCountVal int OUTPUT
)
AS
DECLARE #strQuery nvarchar(300)
SET #strQuery = 'SELECT #rowCountVal = COUNT(*) FROM '+#tablename
exec sp_executesql #strQuery, N'#tablename nvarchar(20), #rowCountVal int OUTPUT', #tablename = #tablename, #rowCountVal = #rowCountVal output
RETURN #rowCountVal
if you want the row count as a function you can also check Speeding up the Performance of Table Counts in SQL Server 2005
Provided in the article function is apparently faster than calling count(*) for very big tables.
In an execute statement, you can use a temporary table to share data:
CREATE PROCEDURE spROWCOUNTER
#tablename nvarchar(20),
#rowCountVal int OUTPUT
AS
CREATE TABLE #Result( Rows INT )
EXEC( 'INSERT INTO #Result( Rows ) SELECT COUNT(*) FROM ' + #tablename )
SELECT #rowCountVal = Rows FROM #Result
RETURN #rowCountVal
yes, i missed the comma. But even after that,the value doesn't get stored in #rowCountVal.
SET #strQuery = 'SELECT #rowCountVal=COUNT(*) *FROM '+ #tablename
EXEC(#strQuery)
the query doesnt return nor displays any value.
By the way, I thought of calling this proc from other stored-procedures to get rowcounts.
Will the following statement work :
set #rCount = exec spROWCOUNTER('DEFECT_LOG')