params[] in TSQL procedure? - sql-server

Can I force the procedure to use array of parameters?
Something similar to Console.WriteLine(String msg, params[]) in C#
I need it to pass column names of table in procedure, something similar to INSERT INTO operation, but need to pass n arguments

As said above, a Table Valued Parameter is probably your best option here.
I've also had some success with XML parameters (might be easier to generate from C#, depending on the host), and allows some more complexity on the parameter side.
Before SQL 2008, I used pipe (|) separated strings and a string splitter function, which also work fine.

Related

combine several queries into one

I would like to be able to query multiple of the same type of argument (for example, several IDs, just to keep the example simple) so I only have to execute a procedure once instead of one time for each individual ID. Where my single-instance proc returns, say, a name, my get-all proc would return a single-column table of names.
What I have now:
EXEC MyProc(123);
EXEC MyProc(456);
EXEC MyProc(789);
What I would like:
// Square brackets aren't correct syntax,
// they just represent a list that contains x number of IDs
EXEC MyProc([123, 456, 789]);
Can I do this, and if so, is there an easy mechanism for handling such a thing that doesn't involve cursors and various over-complicated things? Would this even be considered a good idea?
To execute the proc only once, you'll have to refactor your proc to work with multiple IDs, as there is no T-SQL function or syntactic sugar to do this for you.
If this is to be varadic in that there may be one or many IDs, you'll have to pass multiple IDs to your proc in one parameter. This passing of an array of sorts can be easier in more recent versions of SQL Server.
For example, you can try passing:
TVPs in SQL Server 2008+
delimited strings that are then split in the proc
xml which is then parsed in the proc
a table name which is then read by the proc dynamically
use a table name which is known by both the proc and the caller beforehand
A quick search for passing arrays is SQL Server will yield more results, among the best of them is Arrays and Lists in SQL Server as mentioned by #Andomar.

Using an IN clause with LINQ-to-SQL's ExecuteQuery

LINQ to SQL did a horrible job translating one of my queries, so I rewrote it by hand. The problem is that the rewrite necessarily involves an IN clause, and I cannot for the life of me figure out how to pass a collection to ExecuteQuery for that purpose. The only thing I can come up with, which I've seen suggested on here, is to use string.Format on the entire query string to kluge around it—but that will prevent the query from ever ending up in the query cache.
What's the right way to do this?
NOTE: Please note that I am using raw SQL passed to ExecuteQuery. I said that in the very first sentence. Telling me to use Contains is not helpful, unless you know a way to mix Contains with raw SQL.
Table-Valued Parameters
On Cheezburger.com, we often need to pass a list of AssetIDs or UserIDs into a stored procedure or database query.
The bad way: Dynamic SQL
One way to pass this list in was to use dynamic SQL.
IEnumerable<long> assetIDs = GetAssetIDs();
var myQuery = "SELECT Name FROM Asset WHERE AssetID IN (" + assetIDs.Join(",") + ")";
return Config.GetDatabase().ExecEnumerableSql(dr=>dr.GetString("Name"), myQuery);
This is a very bad thing to do:
Dynamic SQL gives attackers a weakness by making SQL injection attacks easier.
Since we are usually just concatenating numbers together, this is highly unlikely, but
if you start concatenating strings together, all it takes is one user to type ';DROP TABLE Asset;SELECT '
and our site is dead.
Stored procedures can't have dynamic SQL, so the query had to be stored in code instead of in the DB schema.
Every time we run this query, the query plan must be recalculated. This can be very expensive for complicated queries.
However, it does have the advantage that no additional decoding is necessary on the DB side, since the AssetIDs are found by the query parser.
The good way: Table-Valued Parameters
SQL Server 2008 adds a new ability: users can define a table-valued database type.
Most other types are scalar (they only return one value), but table-valued types can hold multiple values, as long as the values are tabular.
We've defined three types: varchar_array, int_array, and bigint_array.
CREATE TYPE bigint_array AS TABLE (Id bigint NOT NULL PRIMARY KEY)
Both stored procedures and programmatically defined SQL queries can use these table-valued types.
IEnumerable<long> assetIDs = GetAssetIDs();
return Config.GetDatabase().ExecEnumerableSql(dr=>dr.GetString("Name"),
"SELECT Name FROM Asset WHERE AssetID IN (SELECT Id FROM #AssetIDs)",
new Parameter("#AssetIDs", assetIDs));
Advantages
Can be used in both stored procedures and programmatic SQL without much effort
Not vulnerable to SQL injection
Cacheable, stable queries
Does not lock the schema table
Not limited to 8k of data
Less work done by both DB server and the Mine apps, since there is no concatenation or decoding of CSV strings.
"typical use" statistics can be derived by the query analyzer, which can lead to even better performance.
Disadvantages
Only works on SQL Server 2008 and above.
Rumors that TVP are prebuffered in their entirety before execution of the query, which means phenomenally large TVPs may be rejected by the server.
Further investigation of this rumor is ongoing.
Further reading
This article is a great resource to learn more about TVP.
If you can't use table-valued parameters, this option is a little faster than the xml option while still allowing you to stay away from dynamic sql: pass the joined list of values as a string parameter, and parse the delimited string back to values in your query. please see this article for instructions on how to do the parsing efficiently.
I have a sneaking suspicion that you're on SQL Server 2005. Table-valued parameters weren't added until 2008, but you can still use the XML data type to pass sets between the client and the server.
This works for SQL Server 2005 (and later):
create procedure IGetAListOfValues
#Ids xml -- This will recevie a List of values
as
begin
-- You can load then in a temp table or use it as a subquery:
create table #Ids (Id int);
INSERT INTO #Ids
SELECT DISTINCT params.p.value('.','int')
FROM #Ids.nodes('/params/p') as params(p);
...
end
You have to invoke this procedure with a parameter like this:
exec IGetAListOfValues
#Ids = '<params> <p>1</p> <p>2</p> </params>' -- xml parameter
The nodes function uses an xPath expression. In this case, it's /params/p and that's way the XML uses <params> as root, and <p> as element.
The value function cast the text inside each p element to int, but you can use it with other data types easily. In this sample there is a DISTINCT to avoid repeated values, but, of course, you can remove it depending on what you want to achieve.
I have an auxiliary (extension) method that converts an IEnumerable<T> in a string that looks like the one shown in the execute example. It's easy to create one, and have it do the work for you whenever you need it. (You have to test the data type of T and convert to an adequate string that can be parsed on SQL Server side). This way your C# code is cleaner and your SPs follow the same pattern to receive the parameters (you can pass in as many lists as needed).
One advantage is that you don't need to make anything special in your database for it to work.
Of course, you don't need to create a temp table as it's done in my example, but you can use the query directly as a subquery inside an IN predicate
WHERE MyTableId IN (SELECT DISTINCT params.p.value('.','int')
FROM #Ids.nodes('/params/p') as params(p) )
I am not 100% sure that I understand correctly the problem, but LinqToSql's ExecuteQuery has an overload for parameters, and the query is supposed to use a format similar to string.Format.
Using this overload is safe against SQL injection, and behind the scenes LinqToSql transalets it to use sp_executesql with parameters.
Here is an example:
string sql = "SELECT * FROM city WHERE city LIKE {0}";
db.ExecuteQuery(sql, "Lon%"); //Note that we don't need the single quotes
This way one can use the benefit of parameterized queries, even while using dynamic sql.
However when it comes to using IN with a dynamic number of parameters, there are two options:
Construct the string dynamically, and then pass the values as an array, as in:
string sql = "SELECT * FROM city WHERE zip IN (";
List<string> placeholders = new List<string>();
for(int i = 0; i < zips.Length;i++)
{
placeholders.Add("{"+i.ToString()+"}");
}
sql += string.Join(",",placeholders.ToArray());
sql += ")";
db.ExecuteQuery(sql, zips.ToArray());
We can use a more compact approach by using the Linq extension methods, as in
string sql = "SELECT * FROM city WHERE zip IN ("+
string.Join("," , zips.Select(z => "{" + zips.IndexOf(f).ToString() + "}"))
+")";
db.ExecuteQuery(sql, zips.ToArray());

Define a String constant in SQL Server?

Is it possible in SQL Server to define a String constant? I am rewriting some queries to use stored procedures and each has the same long string as part of an IN statement [a], [b], [c] etc.
It isn't expected to change, but could at some point in future. It is also a very long string (a few hundred characters) so if there is a way to define a global constant for this that would be much easier to work with.
If this is possible I would also be interested to know if it works in this scenario. I had tried to pass this String as a parameter, so I could control it from a single point within my application but the Stored Procedure didn't like it.
You can create a table with a single column and row and disallow writes on it.
Use that as you global string constant (or additional constants, if you wish).
You are asking for one thing (a string constant in MS SQL), but appear to maybe need something else. The reason I say this is because you have given a few hints at your ultimate objective, which appears to be using the same IN clause in multiple stored procedures.
The biggest clue is in the last sentence:
I had tried to pass this String as a
parameter, so I could control it from
a single point within my application
but the Stored Procedure didn't like
it.
Without details of your SQL scripts, I am going to attempt to use some psychic debugging techniques to see if I can get you to what I believe is your actual goal, and not necessarily your stated goal.
Given your Stored Procedure "didn't like that" when you tried to pass in a string as a parameter, I am guessing the composition of the string was simply a delimited list of values, something like "10293, 105968, 501940" or "Juice, Milk, Donuts" (pay no attention to the actual list values - the important part is the delimited list itself). And your SQL may have looked something like this (again, ignore the specific names and focus on the general concept):
SELECT Column1, Column2, Column3
FROM UnknownTable
WHERE Column1 IN (#parameterString);
If this approximately describes the path you tried to take, then you will need to reconsider your approach. Using a regular T-SQL statement, you will not be able to pass a string of parameter values to an IN clause - it just doesn't know what to do with them.
There are alternatives, however:
Dynamic SQL - you can build up the
whole SQL statement, parameters and
all, then execute that in the SQL
database. This probably is not what
you are trying to achieve, since you
are moving script to stored
procedures. But it is listed here
for completeness.
Table of values -
you can create a single-column table
that holds the specific values you
are interested in. Then your Stored
Procedure can simply use the column
from this table for the IN clause).
This way, there is no Dynamic SQL
required. Since you indicate that
the values are not likely to change,
you may just need to populate the
table once, and use it wherever
appropriate.
String Parsing to
derive the list of values - You can
pass the list of values as a string,
then implement code to parse the
list into a table structure on the
fly. An alternative form of this
technique is to pass an XML
structure containing the values, and
use MS SQL Server's XML
functionality to derive the table.
Define a table-value function that
returns the values to use - I have
not tried this one, so I may be
missing something, but you should be
able to define the values in a
table-value function (possibly using
a bunch of UNION statements or
something), and call that function
in the IN clause. Again - this is an
untested suggestion and would need
to be worked through to determine
it's feasibility.
I hope that helps (assuming I have guessed your underlying quandary).
For future reference, it would be extremely helpful if you could include SQL script showing
your table structure and stored procedure logic so we can see what you have actually attempted. This will considerably improve the effectiveness of the answers you receive. Thanks.
P.S. The link for String Parsing actually includes a large variety of techniques for passing arrays (i.e. lists) of information to Stored Procedures - it is a very good resource for this kind of thing.
In addition to string-constants tables as Oded suggests, I have used scalar functions to encapsulate some constants. That would be better for fewer constants, of course, but their use is simple.
Perhaps a combination - string constants table with a function that takes a key and returns the string. You could even use that for localization by having the function take a 'region' and combine that with a key to return a different string!

Stored procedure: Searching in a table when passing an array of values

I need to create a stored procedure which receives a parameter (named #codes).
This is a string which contains a list of codes separated by a semicolumn.
I'd need to look inside a table and return all rows that have a code (which is in the column EANcodes) which was passed in the #codes parameter.
Can anyone help me get started. My knowledge of stored procedures is very limited.
Thanks in advance.
Ideally, I'd prefer to see the parameter passed in another way, either using a table-value parameter (assuming SQL 2008) or XML which can be easily shredded into a table.
Alternatively, use a SQL split function (one example is here) to parse the string into a temp table, then join against that table in your select query.
Stored Procedures aren't really meant to handle a list of strings as a paramter. You'd be better off splitting it up in your App code and then calling the stored procedure many times with each one as a parameter.
However, if you feel the need to do it this way. You could loop through the string, and use CHARINDEX to find the next index of a semicolon and then use SUBSTRING to get the next code. Then you could use a CTE for the matching rows at each iteration and when the loop is done, simply return the CTE. This is pretty hacky, but I can't think of any other way to do this.
(Those are the T-SQL string functions)
For info on the string manipulation functions (in T-SQL): http://msdn.microsoft.com/en-us/library/ms186323.aspx
And here are similar functions in MySQL: http://dev.mysql.com/doc/refman/5.1/en/string-functions.html

How to refactor T-SQL stored procedure encapsulating it's parameters to a class

On my SQL Server 2008 I have a stored procedure with a large number of parameters.
The first part of them is used in every call and parameters from the second part are used rarely. And I can't move the logic to two different stored procedures.
Is there a way to encapsulate all this parameters to a class or struct and pass it as a stored procedure parameter?
Can I use SQL CLR. Are there other ways?
Have you tried Table Valued Parameters?
Actually, I wont use CLR function for anything which can be done effectively and easily in t-SQL. For example, last time I used CLR function is for updating a column based on some complex regex which I found pretty hard to do in t-SQL.
It sounds like your concern is with the need to specify values for each parameter. Would it work for you to just assign default values to the parameters that aren't used as often (so you don't need to pass every parameter each time the proc is called)?
A CLR type could be an option (as could XML) but I'm not sure it would be a good idea to go down that route.
If the volume of parameters is causing you problems in your application you could try one of the following two methods:
1) pass in a single parameter of XML data type that contains all of the parameters data. you could then parse out what you need. See xml (Transact-SQL).
2) create a table parameter, see Table-Valued Parameters (Database Engine), where the table is:
ParameterName sysname
DataValue sql_variant
With either of these methods, you'd more than likely need to expand them out into local variables to use them again.

Resources