I am building an application using Microsoft Access as a front-end and SQL Server as the back end.
I have a Stored Procedure that inserts customer information. One of the pieces of information that will be inserted for each customer are their Credit Card numbers. The number of credit cards for each customer could be anywhere from 1-50.
I am planning on using a Dynamic array in VBA to add the credit card numbers. The trouble I am having is I am not quite sure how to pass the values to the stored procedure.
My first thought was to create a ton of parameters (CC Number 1, CC Number 2, CC Number 3,etc) but obviously this isn't the correct way to do it.
After the credit card numbers have been added to the Dynamic Array, I can ReDim it to get the count of how many credit card numbers I will need to insert.
What would be the best way to pass lets say, 14 credit card values to one parameter and insert each value as a new row?
Unfortunately, AFAIK, VBA doesn't support table valued parameters (ADO.NET does, but not the vanilla VB6 implementation that we have to use in Access).
You could send a stream of function calls to the server by looping through a recordset or something, but that's going to be painfully slow. If you don't mind repeating yourself, though, you could send one big command that contains a bunch of EXEC statements chained one after the other (if you're using DAO, be sure to specify SET NOCOUNT ON at the start of the query).
If you're looking for a pure-SQL solution, here's the strategy I take for these sorts of problems:
Concatenate the array of values into a string with some sort of separator (e.g. "," or "|").
Pass the string to a function or query that converts it to a table of values.
INSERT or MERGE the table of values into the final target table.
Here's an example of how you can do it:
SET NOCOUNT ON
DECLARE #x XML;
DECLARE #CreditCards AS TABLE (CreditCardNumber VARCHAR(16));
DECLARE #FinalTable AS TABLE (CreditCardNumber VARCHAR(16));
DECLARE #CreditCardList AS VARCHAR(8000);
DECLARE #Divider AS Varchar(10);
SET #Divider=',';
SET #CreditCardList='1234567890123456,1111111111111111,2222222222222222,123456789012345';
IF NOT #CreditCardList IS NULL
BEGIN
SELECT #x = CAST('<A>'+ REPLACE(#CreditCardList,#Divider,'</A><A>')+ '</A>' AS XML);
INSERT INTO
#CreditCards
SELECT
t.value('.', 'varchar(16)') AS inVal
FROM
#x.nodes('/A') AS x(t) ;
END
INSERT INTO
#FinalTable
SELECT
CreditCardNumber
FROM
#CreditCards
SELECT * FROM #FinaLTable
XML isn't the fastest way to do the conversion, but it has the benefit of being relatively straightforward. Jeff Moden provides several pretty inspired approaches to the problem in his blog post Tally OH! An Improved SQL 8K “CSV Splitter” Function.
Hope that helps!
Use XML to pass all customer information in one document. This way you can pass any number of parameters and query it on server as a table.
Related
Is there a graceful way to handle passing a list of ids as a parameter to a stored procedure?
For instance, I want departments 1, 2, 5, 7, 20 returned by my stored procedure. In the past, I have passed in a comma delimited list of ids, like the below code, but feel really dirty doing it.
SQL Server 2005 is my only applicable limitation I think.
create procedure getDepartments
#DepartmentIds varchar(max)
as
declare #Sql varchar(max)
select #Sql = 'select [Name] from Department where DepartmentId in (' + #DepartmentIds + ')'
exec(#Sql)
Erland Sommarskog has maintained the authoritative answer to this question for the last 16 years: Arrays and Lists in SQL Server.
There are at least a dozen ways to pass an array or list to a query; each has their own unique pros and cons.
Table-Valued Parameters. SQL Server 2008 and higher only, and probably the closest to a universal "best" approach.
The Iterative Method. Pass a delimited string and loop through it.
Using the CLR. SQL Server 2005 and higher from .NET languages only.
XML. Very good for inserting many rows; may be overkill for SELECTs.
Table of Numbers. Higher performance/complexity than simple iterative method.
Fixed-length Elements. Fixed length improves speed over the delimited string
Function of Numbers. Variations of Table of Numbers and fixed-length where the number are generated in a function rather than taken from a table.
Recursive Common Table Expression (CTE). SQL Server 2005 and higher, still not too complex and higher performance than iterative method.
Dynamic SQL. Can be slow and has security implications.
Passing the List as Many Parameters. Tedious and error prone, but simple.
Really Slow Methods. Methods that uses charindex, patindex or LIKE.
I really can't recommend enough to read the article to learn about the tradeoffs among all these options.
Yeah, your current solution is prone to SQL injection attacks.
The best solution that I've found is to use a function that splits text into words (there are a few posted here, or you can use this one from my blog) and then join that to your table. Something like:
SELECT d.[Name]
FROM Department d
JOIN dbo.SplitWords(#DepartmentIds) w ON w.Value = d.DepartmentId
One method you might want to consider if you're going to be working with the values a lot is to write them to a temporary table first. Then you just join on it like normal.
This way, you're only parsing once.
It's easiest to use one of the 'Split' UDFs, but so many people have posted examples of those, I figured I'd go a different route ;)
This example will create a temporary table for you to join on (#tmpDept) and fill it with the department id's that you passed in. I'm assuming you're separating them with commas, but you can -- of course -- change it to whatever you want.
IF OBJECT_ID('tempdb..#tmpDept', 'U') IS NOT NULL
BEGIN
DROP TABLE #tmpDept
END
SET #DepartmentIDs=REPLACE(#DepartmentIDs,' ','')
CREATE TABLE #tmpDept (DeptID INT)
DECLARE #DeptID INT
IF IsNumeric(#DepartmentIDs)=1
BEGIN
SET #DeptID=#DepartmentIDs
INSERT INTO #tmpDept (DeptID) SELECT #DeptID
END
ELSE
BEGIN
WHILE CHARINDEX(',',#DepartmentIDs)>0
BEGIN
SET #DeptID=LEFT(#DepartmentIDs,CHARINDEX(',',#DepartmentIDs)-1)
SET #DepartmentIDs=RIGHT(#DepartmentIDs,LEN(#DepartmentIDs)-CHARINDEX(',',#DepartmentIDs))
INSERT INTO #tmpDept (DeptID) SELECT #DeptID
END
END
This will allow you to pass in one department id, multiple id's with commas in between them, or even multiple id's with commas and spaces between them.
So if you did something like:
SELECT Dept.Name
FROM Departments
JOIN #tmpDept ON Departments.DepartmentID=#tmpDept.DeptID
ORDER BY Dept.Name
You would see the names of all of the department IDs that you passed in...
Again, this can be simplified by using a function to populate the temporary table... I mainly did it without one just to kill some boredom :-P
-- Kevin Fairchild
You could use XML.
E.g.
declare #xmlstring as varchar(100)
set #xmlstring = '<args><arg value="42" /><arg2>-1</arg2></args>'
declare #docid int
exec sp_xml_preparedocument #docid output, #xmlstring
select [id],parentid,nodetype,localname,[text]
from openxml(#docid, '/args', 1)
The command sp_xml_preparedocument is built in.
This would produce the output:
id parentid nodetype localname text
0 NULL 1 args NULL
2 0 1 arg NULL
3 2 2 value NULL
5 3 3 #text 42
4 0 1 arg2 NULL
6 4 3 #text -1
which has all (more?) of what you you need.
A superfast XML Method, if you want to use a stored procedure and pass the comma separated list of Department IDs :
Declare #XMLList xml
SET #XMLList=cast('<i>'+replace(#DepartmentIDs,',','</i><i>')+'</i>' as xml)
SELECT x.i.value('.','varchar(5)') from #XMLList.nodes('i') x(i))
All credit goes to Guru Brad Schulz's Blog
Is there a graceful way to handle passing a list of ids as a parameter to a stored procedure?
For instance, I want departments 1, 2, 5, 7, 20 returned by my stored procedure. In the past, I have passed in a comma delimited list of ids, like the below code, but feel really dirty doing it.
SQL Server 2005 is my only applicable limitation I think.
create procedure getDepartments
#DepartmentIds varchar(max)
as
declare #Sql varchar(max)
select #Sql = 'select [Name] from Department where DepartmentId in (' + #DepartmentIds + ')'
exec(#Sql)
Erland Sommarskog has maintained the authoritative answer to this question for the last 16 years: Arrays and Lists in SQL Server.
There are at least a dozen ways to pass an array or list to a query; each has their own unique pros and cons.
Table-Valued Parameters. SQL Server 2008 and higher only, and probably the closest to a universal "best" approach.
The Iterative Method. Pass a delimited string and loop through it.
Using the CLR. SQL Server 2005 and higher from .NET languages only.
XML. Very good for inserting many rows; may be overkill for SELECTs.
Table of Numbers. Higher performance/complexity than simple iterative method.
Fixed-length Elements. Fixed length improves speed over the delimited string
Function of Numbers. Variations of Table of Numbers and fixed-length where the number are generated in a function rather than taken from a table.
Recursive Common Table Expression (CTE). SQL Server 2005 and higher, still not too complex and higher performance than iterative method.
Dynamic SQL. Can be slow and has security implications.
Passing the List as Many Parameters. Tedious and error prone, but simple.
Really Slow Methods. Methods that uses charindex, patindex or LIKE.
I really can't recommend enough to read the article to learn about the tradeoffs among all these options.
Yeah, your current solution is prone to SQL injection attacks.
The best solution that I've found is to use a function that splits text into words (there are a few posted here, or you can use this one from my blog) and then join that to your table. Something like:
SELECT d.[Name]
FROM Department d
JOIN dbo.SplitWords(#DepartmentIds) w ON w.Value = d.DepartmentId
One method you might want to consider if you're going to be working with the values a lot is to write them to a temporary table first. Then you just join on it like normal.
This way, you're only parsing once.
It's easiest to use one of the 'Split' UDFs, but so many people have posted examples of those, I figured I'd go a different route ;)
This example will create a temporary table for you to join on (#tmpDept) and fill it with the department id's that you passed in. I'm assuming you're separating them with commas, but you can -- of course -- change it to whatever you want.
IF OBJECT_ID('tempdb..#tmpDept', 'U') IS NOT NULL
BEGIN
DROP TABLE #tmpDept
END
SET #DepartmentIDs=REPLACE(#DepartmentIDs,' ','')
CREATE TABLE #tmpDept (DeptID INT)
DECLARE #DeptID INT
IF IsNumeric(#DepartmentIDs)=1
BEGIN
SET #DeptID=#DepartmentIDs
INSERT INTO #tmpDept (DeptID) SELECT #DeptID
END
ELSE
BEGIN
WHILE CHARINDEX(',',#DepartmentIDs)>0
BEGIN
SET #DeptID=LEFT(#DepartmentIDs,CHARINDEX(',',#DepartmentIDs)-1)
SET #DepartmentIDs=RIGHT(#DepartmentIDs,LEN(#DepartmentIDs)-CHARINDEX(',',#DepartmentIDs))
INSERT INTO #tmpDept (DeptID) SELECT #DeptID
END
END
This will allow you to pass in one department id, multiple id's with commas in between them, or even multiple id's with commas and spaces between them.
So if you did something like:
SELECT Dept.Name
FROM Departments
JOIN #tmpDept ON Departments.DepartmentID=#tmpDept.DeptID
ORDER BY Dept.Name
You would see the names of all of the department IDs that you passed in...
Again, this can be simplified by using a function to populate the temporary table... I mainly did it without one just to kill some boredom :-P
-- Kevin Fairchild
You could use XML.
E.g.
declare #xmlstring as varchar(100)
set #xmlstring = '<args><arg value="42" /><arg2>-1</arg2></args>'
declare #docid int
exec sp_xml_preparedocument #docid output, #xmlstring
select [id],parentid,nodetype,localname,[text]
from openxml(#docid, '/args', 1)
The command sp_xml_preparedocument is built in.
This would produce the output:
id parentid nodetype localname text
0 NULL 1 args NULL
2 0 1 arg NULL
3 2 2 value NULL
5 3 3 #text 42
4 0 1 arg2 NULL
6 4 3 #text -1
which has all (more?) of what you you need.
A superfast XML Method, if you want to use a stored procedure and pass the comma separated list of Department IDs :
Declare #XMLList xml
SET #XMLList=cast('<i>'+replace(#DepartmentIDs,',','</i><i>')+'</i>' as xml)
SELECT x.i.value('.','varchar(5)') from #XMLList.nodes('i') x(i))
All credit goes to Guru Brad Schulz's Blog
Is there a graceful way to handle passing a list of ids as a parameter to a stored procedure?
For instance, I want departments 1, 2, 5, 7, 20 returned by my stored procedure. In the past, I have passed in a comma delimited list of ids, like the below code, but feel really dirty doing it.
SQL Server 2005 is my only applicable limitation I think.
create procedure getDepartments
#DepartmentIds varchar(max)
as
declare #Sql varchar(max)
select #Sql = 'select [Name] from Department where DepartmentId in (' + #DepartmentIds + ')'
exec(#Sql)
Erland Sommarskog has maintained the authoritative answer to this question for the last 16 years: Arrays and Lists in SQL Server.
There are at least a dozen ways to pass an array or list to a query; each has their own unique pros and cons.
Table-Valued Parameters. SQL Server 2008 and higher only, and probably the closest to a universal "best" approach.
The Iterative Method. Pass a delimited string and loop through it.
Using the CLR. SQL Server 2005 and higher from .NET languages only.
XML. Very good for inserting many rows; may be overkill for SELECTs.
Table of Numbers. Higher performance/complexity than simple iterative method.
Fixed-length Elements. Fixed length improves speed over the delimited string
Function of Numbers. Variations of Table of Numbers and fixed-length where the number are generated in a function rather than taken from a table.
Recursive Common Table Expression (CTE). SQL Server 2005 and higher, still not too complex and higher performance than iterative method.
Dynamic SQL. Can be slow and has security implications.
Passing the List as Many Parameters. Tedious and error prone, but simple.
Really Slow Methods. Methods that uses charindex, patindex or LIKE.
I really can't recommend enough to read the article to learn about the tradeoffs among all these options.
Yeah, your current solution is prone to SQL injection attacks.
The best solution that I've found is to use a function that splits text into words (there are a few posted here, or you can use this one from my blog) and then join that to your table. Something like:
SELECT d.[Name]
FROM Department d
JOIN dbo.SplitWords(#DepartmentIds) w ON w.Value = d.DepartmentId
One method you might want to consider if you're going to be working with the values a lot is to write them to a temporary table first. Then you just join on it like normal.
This way, you're only parsing once.
It's easiest to use one of the 'Split' UDFs, but so many people have posted examples of those, I figured I'd go a different route ;)
This example will create a temporary table for you to join on (#tmpDept) and fill it with the department id's that you passed in. I'm assuming you're separating them with commas, but you can -- of course -- change it to whatever you want.
IF OBJECT_ID('tempdb..#tmpDept', 'U') IS NOT NULL
BEGIN
DROP TABLE #tmpDept
END
SET #DepartmentIDs=REPLACE(#DepartmentIDs,' ','')
CREATE TABLE #tmpDept (DeptID INT)
DECLARE #DeptID INT
IF IsNumeric(#DepartmentIDs)=1
BEGIN
SET #DeptID=#DepartmentIDs
INSERT INTO #tmpDept (DeptID) SELECT #DeptID
END
ELSE
BEGIN
WHILE CHARINDEX(',',#DepartmentIDs)>0
BEGIN
SET #DeptID=LEFT(#DepartmentIDs,CHARINDEX(',',#DepartmentIDs)-1)
SET #DepartmentIDs=RIGHT(#DepartmentIDs,LEN(#DepartmentIDs)-CHARINDEX(',',#DepartmentIDs))
INSERT INTO #tmpDept (DeptID) SELECT #DeptID
END
END
This will allow you to pass in one department id, multiple id's with commas in between them, or even multiple id's with commas and spaces between them.
So if you did something like:
SELECT Dept.Name
FROM Departments
JOIN #tmpDept ON Departments.DepartmentID=#tmpDept.DeptID
ORDER BY Dept.Name
You would see the names of all of the department IDs that you passed in...
Again, this can be simplified by using a function to populate the temporary table... I mainly did it without one just to kill some boredom :-P
-- Kevin Fairchild
You could use XML.
E.g.
declare #xmlstring as varchar(100)
set #xmlstring = '<args><arg value="42" /><arg2>-1</arg2></args>'
declare #docid int
exec sp_xml_preparedocument #docid output, #xmlstring
select [id],parentid,nodetype,localname,[text]
from openxml(#docid, '/args', 1)
The command sp_xml_preparedocument is built in.
This would produce the output:
id parentid nodetype localname text
0 NULL 1 args NULL
2 0 1 arg NULL
3 2 2 value NULL
5 3 3 #text 42
4 0 1 arg2 NULL
6 4 3 #text -1
which has all (more?) of what you you need.
A superfast XML Method, if you want to use a stored procedure and pass the comma separated list of Department IDs :
Declare #XMLList xml
SET #XMLList=cast('<i>'+replace(#DepartmentIDs,',','</i><i>')+'</i>' as xml)
SELECT x.i.value('.','varchar(5)') from #XMLList.nodes('i') x(i))
All credit goes to Guru Brad Schulz's Blog
In this procedure, I want to delete those number which coming in #msisdn from Master Table and insert into tblDeactive:
ALTER PROCEDURE [dbo].[msisdn_deactivition](#msisdn varchar(1024))
AS
BEGIN
SET NOCOUNT ON;
insert into tblDeactive SELECT * from msisdn_master where msisdn in (#msisdn);
delete from msisdn_master where msisdn in (#msisdn);
END
...but it does not work?
You can't use a single query parameter in place of a list of values. One parameter = one value.
See Parameterize an SQL IN clause for several solutions and lots of discussion about using parameters with the IN( ) predicate.
You need to use a split function, that will split your delimitted #msisdn into a table to select from.
Have a look at this link
How to pass a list of values or array to SQL Server stored procedure?
IN in TSQL needs to take multiple arguments per item. At the moment, if you pass it "123,456,789", it is looking for the single row with value "123,456,789". It isn't looking for "123", "456" or "789".
Look for something like a split udf - plenty on the internet. This takes your delimited string and returns a column of values. Typically you then join the the udf results (rather than use IN).
DON'T use EXEC here, as you shouldn't trust the input.
Is there a graceful way to handle passing a list of ids as a parameter to a stored procedure?
For instance, I want departments 1, 2, 5, 7, 20 returned by my stored procedure. In the past, I have passed in a comma delimited list of ids, like the below code, but feel really dirty doing it.
SQL Server 2005 is my only applicable limitation I think.
create procedure getDepartments
#DepartmentIds varchar(max)
as
declare #Sql varchar(max)
select #Sql = 'select [Name] from Department where DepartmentId in (' + #DepartmentIds + ')'
exec(#Sql)
Erland Sommarskog has maintained the authoritative answer to this question for the last 16 years: Arrays and Lists in SQL Server.
There are at least a dozen ways to pass an array or list to a query; each has their own unique pros and cons.
Table-Valued Parameters. SQL Server 2008 and higher only, and probably the closest to a universal "best" approach.
The Iterative Method. Pass a delimited string and loop through it.
Using the CLR. SQL Server 2005 and higher from .NET languages only.
XML. Very good for inserting many rows; may be overkill for SELECTs.
Table of Numbers. Higher performance/complexity than simple iterative method.
Fixed-length Elements. Fixed length improves speed over the delimited string
Function of Numbers. Variations of Table of Numbers and fixed-length where the number are generated in a function rather than taken from a table.
Recursive Common Table Expression (CTE). SQL Server 2005 and higher, still not too complex and higher performance than iterative method.
Dynamic SQL. Can be slow and has security implications.
Passing the List as Many Parameters. Tedious and error prone, but simple.
Really Slow Methods. Methods that uses charindex, patindex or LIKE.
I really can't recommend enough to read the article to learn about the tradeoffs among all these options.
Yeah, your current solution is prone to SQL injection attacks.
The best solution that I've found is to use a function that splits text into words (there are a few posted here, or you can use this one from my blog) and then join that to your table. Something like:
SELECT d.[Name]
FROM Department d
JOIN dbo.SplitWords(#DepartmentIds) w ON w.Value = d.DepartmentId
One method you might want to consider if you're going to be working with the values a lot is to write them to a temporary table first. Then you just join on it like normal.
This way, you're only parsing once.
It's easiest to use one of the 'Split' UDFs, but so many people have posted examples of those, I figured I'd go a different route ;)
This example will create a temporary table for you to join on (#tmpDept) and fill it with the department id's that you passed in. I'm assuming you're separating them with commas, but you can -- of course -- change it to whatever you want.
IF OBJECT_ID('tempdb..#tmpDept', 'U') IS NOT NULL
BEGIN
DROP TABLE #tmpDept
END
SET #DepartmentIDs=REPLACE(#DepartmentIDs,' ','')
CREATE TABLE #tmpDept (DeptID INT)
DECLARE #DeptID INT
IF IsNumeric(#DepartmentIDs)=1
BEGIN
SET #DeptID=#DepartmentIDs
INSERT INTO #tmpDept (DeptID) SELECT #DeptID
END
ELSE
BEGIN
WHILE CHARINDEX(',',#DepartmentIDs)>0
BEGIN
SET #DeptID=LEFT(#DepartmentIDs,CHARINDEX(',',#DepartmentIDs)-1)
SET #DepartmentIDs=RIGHT(#DepartmentIDs,LEN(#DepartmentIDs)-CHARINDEX(',',#DepartmentIDs))
INSERT INTO #tmpDept (DeptID) SELECT #DeptID
END
END
This will allow you to pass in one department id, multiple id's with commas in between them, or even multiple id's with commas and spaces between them.
So if you did something like:
SELECT Dept.Name
FROM Departments
JOIN #tmpDept ON Departments.DepartmentID=#tmpDept.DeptID
ORDER BY Dept.Name
You would see the names of all of the department IDs that you passed in...
Again, this can be simplified by using a function to populate the temporary table... I mainly did it without one just to kill some boredom :-P
-- Kevin Fairchild
You could use XML.
E.g.
declare #xmlstring as varchar(100)
set #xmlstring = '<args><arg value="42" /><arg2>-1</arg2></args>'
declare #docid int
exec sp_xml_preparedocument #docid output, #xmlstring
select [id],parentid,nodetype,localname,[text]
from openxml(#docid, '/args', 1)
The command sp_xml_preparedocument is built in.
This would produce the output:
id parentid nodetype localname text
0 NULL 1 args NULL
2 0 1 arg NULL
3 2 2 value NULL
5 3 3 #text 42
4 0 1 arg2 NULL
6 4 3 #text -1
which has all (more?) of what you you need.
A superfast XML Method, if you want to use a stored procedure and pass the comma separated list of Department IDs :
Declare #XMLList xml
SET #XMLList=cast('<i>'+replace(#DepartmentIDs,',','</i><i>')+'</i>' as xml)
SELECT x.i.value('.','varchar(5)') from #XMLList.nodes('i') x(i))
All credit goes to Guru Brad Schulz's Blog