Pass List of Integers to Stored Procedure - sql-server

Here is my stored procedure:
ALTER PROCEDURE [dbo].[Dan]
#numbers varchar(10)
AS
BEGIN
SET NOCOUNT ON;
select #numbers numbers
END
In SSMS, I can execute it successfully like this:
exec dbo.Dan '1.2'
In ColdFusion, I can execute it successfully with either of these two sets of commands:
<cfstoredproc procedure="dbo.dan" datasource="ClinicalDataDev">
<cfprocparam cfsqltype="cf_sql_varchar" value="1,2">
<cfprocresult name="abc">
</cfstoredproc>
<cfquery name="abcd" datasource="ClinicalDataDev">
exec dbo.Dan <cfqueryparam cfsqltype="cf_sql_varchar" value='1,2' list="no">
</cfquery>
However, I'm looking to improve on this by specifying the value as a list of integers. Since cfprocparam does not have a list attribute, I think I am restricted to the cfquery approach. My efforts and results so far are:
<cfqueryparam cfsqltype="cf_sql_integer" value='1' list="no">
executes successfully. The purpose is to see if the procedure accepts an
integer - it does.
<cfqueryparam cfsqltype="cf_sql_integer" value='1,2' list="no">
also executes sucessfully, but returns a value of 42006. Probably not
worth persuing.
<cfqueryparam cfsqltype="cf_sql_integer" value='1,2' list="yes">
throws an error for two many paramters.
The same thing happens with cf_sql_varchar.
As stated earlier, I can pass the list as a string, but that seems like a bit of a hack. Is there a way to pass the list of integers as a list of integers?

As other commenters mentioned before, passing table-valued parameters is the way to go. This will require you to change the input in the SP to take a table input and in your query you can do
<cfquery>
CREATE TABLE #temp (usedID int)
INSERT INTO #temp
(usedID)
VALUES
(1)
,(2)
exec dbo.Dan #temp
</cfquery>
You may have to change the way you pass your parameters to the SP, but this is the general idea.

rodmunera's answer has the correct general idea. Here is how I finally got it to work.
In sql server, I started with this:
CREATE TYPE pt.IntegerTableType AS TABLE
( integerIN int);
grant execute on type::pt.IntegerTableType to theAppropriateRole
Then I changed by stored proc to this:
ALTER PROCEDURE [dbo].[Dan]
#numbers pt.IntegerTableType readonly
AS
BEGIN
SET NOCOUNT ON;
select 1 record
where 1 in (select integerIN from #numbers)
END
The Coldfusion code is this:
<cfset numbers = "1,2">
<cfquery name="abcd" datasource="ClinicalDataDev">
declare #dan as pt.IntegerTableType
insert into #dan
select null
where 1 = 2
<cfloop list="#numbers#" index="number">
union
select <cfqueryparam cfsqltype="cf_sql_integer" value="#number#">
</cfloop>
exec dbo.Dan #dan
</cfquery>
<cfdump var="#abcd#">

Related

hov create ms sql Trigger on many rows (like oracle FOR EACH ROW)

In oracle i create triggers without problems, but in tsql i heave one big problem :)
I have a job, who insert rows into one tabel:
SELECT wa.IdAnkiety
,wa.IdUsterkiService
,wa.LiczbaGlosowTak
,wa.LiczbaGlosowNie FROM [SERV-SQL01].[DomTransfer].[dbo].[D5Cts_WynikiAnkiet] wa where not
exists (select wy.IdUsterkiService from dba.DOM_WyAnkiet wy
where wy.IdUsterkiService=wa.IdUsterkiService and
wy.IdAnkiety=wa.IdAnkiety)```
And on this: dba.DOM_WyAnkiet tble I heave a trigger
ALTER trigger [dba].[wyankiet]
on [dba].[DOM_WyAnkiet] AFTER insert
AS
begin
declare #DefectActId uniqueidentifier,
#tak int,
#nie int;
select #DefectActId=IdUsterkiService, #tak=tak, #nie=nie from inserted;
if #tak>#nie
update dba.DefectAct set status='49A86504-2E7D-46AB-A022-FC4C3C8CA853', InspectionDate=GETDATE() where DefectActId=#DefectActId;
if #nie>#tak
update dba.DefectAct set status='2EDA9FB2-8ED2-43AC-8C30-348D3F060CE3',InspectionDate=GETDATE() where DefectActId=#DefectActId;
if #nie=#tak
update dba.DefectAct set status='588B5BAA-F7CE-455D-9489-B2B956949449',InspectionDate=GETDATE() where DefectActId=#DefectActId;
end
But seems thats nor works
in oracle i add the FOR EACH ROW command and something like here above are works.
To handle a multi-row insert. join to the inserted virtual table instead of assigning scalar variables. Below is an example (untested) of this set-based technique using a CASE expression:
ALTER trigger [dba].[wyankiet]
ON[dba].[DOM_WyAnkiet] AFTER INSERT
AS
UPDATE da
set status=
CASE
WHEN i.tak>i.nie THEN '49A86504-2E7D-46AB-A022-FC4C3C8CA853'
WHEN i.nie>i.tak THEN '2EDA9FB2-8ED2-43AC-8C30-348D3F060CE3'
WHEN i.nie=i.tak THEN '588B5BAA-F7CE-455D-9489-B2B956949449'
END
, InspectionDate=GETDATE()
FROM dba.DefectAct AS da
JOIN inserted AS i ON i.DefectActId=da.DefectActId;

SQL Server 2012: get the SQL code that fired a trigger, without DBCC INPUTBUFFER or sys.dm_exec_input_buffer

Is there a way of getting the SQL code that fired a trigger from inside the fired trigger, without using DBCC INPUTBUFFER or sys.dm_exec_input_buffer?
I need this for a trigger that logs the new value, the old value and the statement that made the change in that table.
Even though DBCC INPUTBUFFER resolves the challenge, I cannot use it because I need to use "INSERT INTO ... EXEC" in order to get the query that fired the trigger and the trigger is fired by many statements that already use "INSERT INTO ... EXEC", so I will get the error
An INSERT EXEC statement cannot be nested
From my research, sys.dm_exec_input_buffer might do the trick, but I cannot use it since it is available only for SQL Server 2014 SP4 and newer (as mentioned here: Get last command in SQL Server without DBCC INPUTBUFFER), and I am using an older version.
I have tried several ways of solving the problem but without success. I cannot get the SQL statement that fired the trigger but only the last executing statement which is the trigger.
To see the problem, take a look at the following code:
--Create the table that will have the trigger
CREATE TABLE [dbo].[___testTrigger]
(
[text] [NVARCHAR!(50) NOT NULL
) ON [PRIMARY]
GO
CREATE TRIGGER dbo.TestTriggerAuditLog
ON dbo.___testTrigger
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
--Version 1: without "INSERT INTO ... EXEC" but does not get the text of the statement that fired the trigger. Instead, it gets the current running query, which is the trigger
SELECT sqltext.TEXT,
req.session_id,
req.status,
req.command,
req.cpu_time,
req.total_elapsed_time
FROM sys.dm_exec_requests req
CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS sqltext
WHERE req.session_id = ##SPID
--Version 2: gets the statement that fired the trigger, but we need to use "INSERT INTO ... EXEC"
DECLARE #inputbuffer TABLE (EventType NVARCHAR(30),Parameters INT,EventInfo NVARCHAR(4000))
INSERT INTO #inputbuffer EXEC('dbcc inputbuffer('+##Spid+') WITH NO_INFOMSGS')
SELECT * FROM #inputbuffer AS I
END
I know that in a trigger is not ok to have SELECT statements! I did it just to make the example simpler.
Now, we can insert some data to see what we get:
--test
INSERT INTO dbo.___testTrigger (text)
VALUES (N'This is a test test')
We will get the 2 selects returning different results, as can be seen in the bellow image.
Any ideas of what could I use to get the same result as DBCC INPUTBUFFER but without using "INSERT INTO ... EXEC" and without using sys.dm_exec_input_buffer as it is not available in my SQL Server version?
create table dbo.abcd(id int);
go
create trigger dbo.triggerabc on dbo.abcd for insert, update, delete
as
begin
declare #t table(query nvarchar(4000));
insert into #t (query)
select EventInfo
from OPENROWSET('SQLNCLI', 'Server=localhost;Trusted_Connection=yes;',
'
declare #spid nvarchar(10), #sql nvarchar(1000);
select #spid = cast(session_id as nvarchar(10))
from sys.dm_exec_requests
where session_id > 50
and wait_type = ''OLEDB''
and wait_resource like ''SQLNCLI%(SPID='' + cast(##spid as varchar(10)) + '')'';
select #sql = ''dbcc inputbuffer('' + #spid + '') WITH NO_INFOMSGS'';
exec(#sql) with result sets( (EventType NVARCHAR(30),Parameters SMALLINT,EventInfo NVARCHAR(4000)) );
'
) ;
select * from #t;
end
go
insert into abcd(id) values(123)
go
insert into abcd(id)
exec('select 456')
go
drop table abcd
go
Here's a very simple solution.
But first, since triggers don't fire on select it probably isn't very accurate to refer to "queries" firing the trigger. It would probably be more accurate to call them "statements."
Anyway, add a column to your table such as StatementName varchar(10) and then in each insert statement that will fire the trigger, add a value such as 'Statement1', 'Statement2', etc.
Then the trigger can just check the inserted row and know what statement fired the trigger.

Merge Insert/Update prevent PK violations and deadlocks?

I have stored procedure that handles Insert and Update transactions in one of my tables for now. I'm still testing to see if there is any potential problem with this solution and how I can improve the process. This SP takes few arguments then checks for matching ID and preforms Insert or Update. I have read this Article about primary key violation error, showing that MERGE is vulnerable to concurrency problems like a multi-statement conditional INSERT/UPDATE . Seems that they have solved some issues using WITH (HOLDLOCK). I'm new in stored procedure and merge world. I would like to here your opinion if this is reliable code for application with high transactions? I might have multiple users Inserting in the same table or running Update statement at the same time. Also is there any potential issue with parameter sniffing in this case? If it is should I consider using OPTION (RECOMPILE) or that only applies to SELECT search queries? Here is example of my SQL code:
USE [TestDB]
GO
/****** Object: StoredProcedure [dbo].[SaveMaster] Script Date: 08/21/2018 10:05:21 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: M, D
-- Create date: 08/21/2018
-- Description: Insert/Update Master table
-- =============================================
ALTER PROCEDURE [dbo].[SaveMaster]
#RecordID INT = NULL,
#Status BIT = NULL,
#Name VARCHAR(50) = NULL,
#Code CHAR(2) = NULL,
#ActionDt DATETIME = NULL,
#ActionID UNIQUEIDENTIFIER = NULL
AS
MERGE dbo.Master WITH (HOLDLOCK) AS Target
USING (SELECT #RecordID,#Status,#Name,#Code,#ActionDt,#ActionID)
AS Source (RecordID,Status,Name,Code,ActionDt,ActionID)
ON Target.RecID = Source.RecordID
WHEN MATCHED THEN
UPDATE
SET Target.Status = Source.Status,
Target.Name = Source.Name,
Target.Code = Source.Code,
Target.ActionDt = Source.ActionDt,
Target.ActionID = Source.ActionID
WHEN NOT MATCHED THEN
INSERT(
Status,Name,Code,ActionDt,ActionID
)VALUES(
Source.Status,
Source.Name,
Source.Code,
Source.ActionDt,
Source.ActionID
);
RETURN ##ERROR;
Here is example on how I call Stored Procedure with server side language (ColdFusion 2016):
<cftransaction action="begin">
<cftry>
<cfstoredproc procedure="SaveMaster" datasource="#dsn#">
<cfprocparam dbvarname="#RecordID" value="#trim(arguments.frm_recid)#" cfsqltype="cf_sql_integer" null="#!len(trim(arguments.frm_recid))#" />
<cfprocparam dbvarname="#Status" value="#trim(arguments.frm_status)#" cfsqltype="cf_sql_bit" null="#!len(trim(arguments.frm_status))#" />
<cfprocparam dbvarname="#Name" value="#trim(arguments.frm_name)#" cfsqltype="cf_sql_varchar" maxlength="50" null="#!len(trim(arguments.frm_name))#" />
<cfprocparam dbvarname="#Code" value="#trim(frm_code)#" cfsqltype="cf_sql_char" maxlength="2" null="#!len(trim(frm_code))#" />
<cfprocparam dbvarname="#ActionDt" value="#trim(NOW())#" cfsqltype="cf_sql_datetime" />
<cfprocparam dbvarname="#ActionID" value="#trim(SESSION.UserID)#" cfsqltype="cf_sql_idstamp" null="#!len(trim(SESSION.UserID))#" />
<cfprocresult name="MasterResult"/>
</cfstoredproc>
<cfset local.fnResults = {status : "200", message : "Record successully saved!", RecID : MasterResult.RecID}>
<cfcatch type="any">
<cftransaction action="rollback" />
<cfset local.fnResults = {status : "400", message : "Error! Please contact your administrator."}>
</cfcatch>
</cftry>
</cftransaction>
As you can see I expect that Stored Procedure returns RecID that should be returned (Same ID that I pass in my stored procedure for existing records, or if does not exist then will be generated and returned like this for Insert SELECT SCOPE_IDENTITY() AS RecID; or Update like this SELECT #RecordID AS RecID). If anyone have any suggestions and know the best way to return RecID from SP that runs Insert/Update with Merge please let me know.
If anyone have any suggestions and know the best way to return RecID from SP that runs Insert/Update with Merge please let me know.
You can add an OUTPUT clause to your MERGE statement. That will allow you to return a result set containing the new ID and, if you like, what action it selected:
MERGE dbo.Master WITH (HOLDLOCK) AS Target
USING (SELECT #RecordID,#Status,#Name,#Code,#ActionDt,#ActionID)
AS Source (RecordID,Status,Name,Code,ActionDt,ActionID)
ON Target.RecID = Source.RecordID
WHEN MATCHED THEN
UPDATE
SET Target.Status = Source.Status,
Target.Name = Source.Name,
Target.Code = Source.Code,
Target.ActionDt = Source.ActionDt,
Target.ActionID = Source.ActionID
WHEN NOT MATCHED THEN
INSERT(
Status,Name,Code,ActionDt,ActionID
)VALUES(
Source.Status,
Source.Name,
Source.Code,
Source.ActionDt,
Source.ActionID
)
OUTPUT inserted.RedIC,$action as Action;
I'm assuming coldfusion will be able to consume this result set. If not, switch to the variant of OUTPUT which populates a table variable instead (OUTPUT ... INTO) and use that to set OUTPUT parameters which you add to the procedure.

functional reason why stored procedures don't support INSERT/EXECUTE INTO?

In SQL Server, there's no way to create a temp table on the fly from the results of a stored procedure, ala:
CREATE TABLE #temptable AS
EXEC spMyStoredProc
or
EXEC spMyStoredProc INTO #temptable
or something like that. Instead, you have to know the SP layout beforehand, and have to do something like this:
CREATE TABLE #temptable (col1 INT, col2 VARCHAR(255))
INSERT INTO #temptable
EXEC spMyStoredProc
Is there a functional reason why this is the case? Maybe a limitation of SQL Server? Or is it just something that hasn't been added to the SQL spec yet, and I can hold out hope that one day they'll support it?
A stored procedure can return many result sets, or none, and it can vary entirely depending upon the execution of the stored procedure.
When it is compiled it's meta-information does not describe it as having any specific expectable result set output.
I expect given those constraints, they elected not to implement this because of the lack of strong typing of what a stored procedure may return.
Not from a sproc, but you can use tabled values functions to do something similar.
Select * From fnMyFunction
You'd be able to insert into a # table if you desired.
Try this out
DECLARE #temptable TABLE (ID INT, NAME VARCHAR(255))
declare #query varchar(max)
set #query='Select whatever from whereever'
INSERT INTO #temptable
EXEC (#Query)
select *from #temptable
If I needed such functionality I would use an inline UDF, like this:
CREATE PROCEDURE MySample
AS
SELECT a,b,c FROM dbo.MyInlineUDF(1,2,3)
GO
SELECT * INTO #t
FROM dbo.MyInlineUDF(1,2,3) WHERE 1=0
INSERT INTO #t EXEC MySample

Creating a stored procedure to return the rowcount of another stored procedure

Is it possible to do this? I have some filters set in my source Stored Procedure and I really don't want to have to duplicate it in another just to get the rowcount.
The only way I know how to do this is to insert into a temp table from the stored procedure and then select the count. Unfortunately, there's no pretty way to perform a "select" on a stored procedure.
CREATE TABLE #stuff (id int, status char(6))
INSERT #stuff (id, status)
EXEC dbo.sp_get_stuff
SELECT count(*) FROM #stuff
DROP TABLE #stuff
Edit
The above method will allow you to select from a stored procedure, but as Greg pointed out, a rowcount can be simplified to:
EXEC dbo.sp_get_stuff
SELECT ##Rowcount
This also works:
create proc pTest1
as
select * from comp
go
create proc pTest2
as
exec pTest1
select ##rowcount
GO
If you are really trying to fine tune as much as possible, then you will have to change the source stored procedure. If you are looking at performance, then returning the rowset just to get the count is not something to even consider.

Resources