How to solve Syntax error in FROM clause? - c#

I'm getting this error but my sql statements aren't complicated so I don't know what I'm doing wrong, they seem fine to me. I though it might be my connection string but that looks fine too. Help would be appriciated.
public string conn = #"Provider = Microsoft.Jet.OLEDB.4.0; Data Source = C:\Users\Callum\Documents\College\Computer Science\MAZE GAME\MAZE GAME\Database2.mdb";
string getLevel = "SELECT * FROM Level WHERE Level.LevelID = #LevelID";
string getWall = "SELECT * FROM Wall WHERE Wall.LevelID = #LevelID";
string getMonster = "SELECT * FROM Monster WHERE Monster.LevelID = #LevelID";
//get values to build levels
using (OleDbConnection Conn = new OleDbConnection(conn))
{
try
{
Conn.Open();
using (OleDbCommand levelCmd = new OleDbCommand(getLevel, Conn))
{
levelCmd.Parameters.AddWithValue("#LevelID", LevelID);
levelCmd.Parameters["#LevelID"].Value = LevelID;
using (OleDbDataReader levelReader = levelCmd.ExecuteReader())//this is where the error is shown

Level is a reserved-word in JET Red SQL (MS Access SQL) and T-SQL for MS SQL Server.
As is Description, Memo, Name, and a few others.
You can escape reserved words by surrounding them in square-brackets, like this: [Level].
So change your SQL string to this:
string getLevel = "SELECT * FROM [Level] WHERE [Level].LevelID = #LevelID";
For posterity (because I'll bet $50 that MS will let MS Access documentation slowly die, considering MS Access has not been significantly updated since 2007, and the JET Red SQL language hasn't been changed since 2002), here's a list of all the documented reserved words from https://support.office.com/en-us/article/learn-about-access-reserved-words-and-symbols-ae9d9ada-3255-4b12-91a9-f855bdd9c5a2:
ADD
ALL
Alphanumeric
ALTER
AND
ANY
Application
AS
ASC
Assistant
AUTOINCREMENT
Avg
BETWEEN
BINARY
BIT
BOOLEAN
BY
BYTE
CHAR, CHARACTER
COLUMN
CompactDatabase
CONSTRAINT
Container
Count
COUNTER
CREATE
CreateDatabase
CreateField
CreateGroup
CreateIndex
CreateObject
CreateProperty
CreateRelation
CreateTableDef
CreateUser
CreateWorkspace
CURRENCY
CurrentUser
DATABASE
DATE
DATETIME
DELETE
DESC
Description
DISALLOW
DISTINCT
DISTINCTROW
Document
DOUBLE
DROP
Echo
Else
End
Eqv
Error
EXISTS
Exit
FALSE
Field, Fields
FillCache
FLOAT, FLOAT4, FLOAT8
FOREIGN
Form, Forms
FROM
Full
FUNCTION
GENERAL
GetObject
GetOption
GotoPage
GROUP
GROUP BY
GUID
HAVING
Idle
IEEEDOUBLE, IEEESINGLE
If
IGNORE
Imp
IN
INDEX
Index, Indexes
INNER
INSERT
InsertText
INT, INTEGER, INTEGER1, INTEGER2, INTEGER4
INTO
IS
JOIN
KEY
LastModified
LEFT
Level
Like
LOGICAL, LOGICAL1
LONG, LONGBINARY, LONGTEXT
Macro
Match
Max, Min, Mod
MEMO
Module
MONEY
Move
NAME
NewPassword
NO
Not
Note
NULL
NUMBER, NUMERIC
Object
OLEOBJECT
OFF
ON
OpenRecordset
OPTION
OR
ORDER
Orientation
Outer
OWNERACCESS
Parameter
PARAMETERS
Partial
PERCENT
PIVOT
PRIMARY
PROCEDURE
Property
Queries
Query
Quit
REAL
Recalc
Recordset
REFERENCES
Refresh
RefreshLink
RegisterDatabase
Relation
Repaint
RepairDatabase
Report
Reports
Requery
RIGHT
SCREEN
SECTION
SELECT
SET
SetFocus
SetOption
SHORT
SINGLE
SMALLINT
SOME
SQL
StDev, StDevP
STRING
Sum
TABLE
TableDef, TableDefs
TableID
TEXT
TIME, TIMESTAMP
TOP
TRANSFORM
TRUE
Type
UNION
UNIQUE
UPDATE
USER
VALUE
VALUES
Var, VarP
VARBINARY, VARCHAR
WHERE
WITH
Workspace
Xor
Year
YES
YESNO
The following symbols must not be used as part of a field name or as part of an object name:
.
/
*
;
:
!
#
&
-
?
"
'
$
%
The Access database engine runs in different modes, depending on whether it is called from Access, data access objects, the Microsoft OLE Provider for the Access database engine, or the Microsoft Access ODBC driver. It can be run in either ANSI mode or non-ANSI (traditional) mode.
Because using these two modes results in two slightly different sets of reserved words, a query that uses a reserved word might work in one mode and fail in another mode.
The following is a list of reserved words to avoid when choosing identifier names.
ABSOLUTE
ACTION
ADD
ADMINDB
ALL
ALLOCATE
ALPHANUMERIC
ALTER
AND
ANY
ARE
AS
ASC
ASSERTION
AT
AUTHORIZATION
AUTOINCREMENT
AVG
BAND
BEGIN
BETWEEN
BINARY
BIT
BIT_LENGTH
BNOT
BOR
BOTH
BXOR
BY
BYTE
CASCADE
CASCADED
CASE
CAST
CATALOG
CHAR
CHARACTER
CHAR_LENGTH
CHARACTER_LENGTH
CHECK
CLOSE
COALESCE
COLLATE
COLLATION
COLUMN
COMMIT
COMP
COMPRESSION
CONNECT
CONNECTION
CONSTRAINT
CONSTRAINTS
CONTAINER
CONTINUE
CONVERT
CORRESPONDING
COUNT
COUNTER
CREATE
CREATEDB
CROSS
CURRENCY
CURRENT
CURRENT_DATE
CURRENT_TIME
CURRENT_TIMESTAMP
CURRENT_USER
CURSOR
DATABASE
DATE
DATETIME
DAY
DEALLOCATE
DEC
DECIMAL
DECLARE
DEFAULT
DEFERRABLE
DEFERRED
DELETE
DESC
DESCRIBE
DESCRIPTOR
DIAGNOSTICS
DISALLOW
DISCONNECT
DISTINCT
DOMAIN
DOUBLE
DROP
ELSE
END
END-EXEC
ESCAPE
EXCEPT
EXCEPTION
EXCLUSIVECONNECT
EXEC
EXECUTE
EXISTS
EXTERNAL
EXTRACT
FALSE
FETCH
FIRST
FLOAT
FLOAT4
FLOAT8
FOR
FOREIGN
FOUND
FROM
FULL
GENERAL
GET
GLOBAL
GO
GOTO
GRANT
GROUP
GUID
HAVING
HOUR
IDENTITY
IEEEDOUBLE
IEEESINGLE
IGNORE
IMAGE
IMMEDIATE
ININDEX
INDICATOR
INHERITABLE
INITIALLY
INNER
INPUT
INSENSITIVE
INSERT
INT
INTEGER
INTEGER1
INTEGER2
INTEGER4
INTERSECT
INTERVAL
INTO
IS
ISOLATION
JOIN
KEY
LANGUAGE
LAST
LEADING
LEFT
LEVEL
LIKE
LOCAL
LOGICAL
LOGICAL1
LONG
LONGBINARY
LONGCHAR
LONGTEXT
LOWER
MATCH
MAX
MEMO
MIN
MINUTE
MODULE
MONEY
MONTH
NAMES
NATIONAL
NATURAL
NCHAR
NEXT
NO
NOT
NOTE
NULL
NULLIF
NUMBER
NUMERIC
OBJECT
OCTET_LENGTH
OFOLEOBJECT
ONONLY
OPEN
OPTION
ORORDER
OUTER
OUTPUT
OVERLAPS
OWNERACCESS
PAD
PARAMETERS
PARTIAL
PASSWORD
PERCENT
PIVOT
POSITION
PRECISION
PREPARE
PRESERVE
PRIMARY
PRIOR
PRIVILEGES
PROC
PROCEDURE
PUBLIC
READ
REAL
REFERENCES
RELATIVE
RESTRICT
REVOKE
RIGHT
ROLLBACK
ROWS
SCHEMA
SCROLL
SECOND
SECTION
SELECT
SELECTSCHEMA
SELECTSECURITY
SESSION
SESSION_USER
SET
SHORT
SINGLE
SIZE
SMALLINT
SOME
SPACE
SQL
SQLCODE
SQLERROR
SQLSTATE
STRING
SUBSTRING
SUM
SYSTEM_USER
TABLE
TABLEID
TEMPORARY
TEXT
THEN
TIME
TIMESTAMP
TIMEZONE_HOUR
TIMEZONE_MINUTE
TO
TOP
TRAILING
TRANSACTION
TRANSFORM
TRANSLATE
TRANSLATION
TRIM
TRUE
UNION
UNIQUE
UNIQUEIDENTIFIER
UNKNOWN
UPDATE
UPDATEIDENTITY
UPDATEOWNER
UPDATESECURITY
UPPER
USAGE
USER
USING
VALUE
VALUES
VARBINARY
VARCHAR
VARYING
VIEW
WHEN
WHENEVER
WHERE
WITH
WORK
WRITE
YEAR
YESNO
ZONE

Related

Check if a combination of cell values already exists on a user input table

CI have found similar problems to the one I am having but can't quite find the right answer.
I have a C# web form with a table which allows user inputs. The table has 3 columns - ccy1, ccy2 and Rate.
When the user inserts values, they then click a 'save' button which inserts the entered values into an SQL database via an "Insert into table..." statement.
However, I need to produce an error statement when they click the 'save' button if the following is true:
- the combination of ccy1 and ccy2 already exists in the table
Example table:
ccy1 ccy2 Rate
EUR USD 1.3
GBP USD 1.7
Now if a user was to try and add a third line where ccy1 = EUR and ccy2 = USD, this should produce an error as this combination already exists (in line 1 of the table).
So I want my C# code to be something like:
if (***combination of ccy1 and ccy2 already exists then***)
{ "ERROR MSG" }
else
{
sql += "INSERT INTO table values (ccy1, ccy2, rate)"
}
It is the line marked with stars I can't get my head around after attempting several methods.
Any help would be greatly appreciated.
Thanks,
John
declare #exists bit = 1
if not Exist(select 1 from Table)
begin
#exists = 0
insert into TABLEX (s, s, s, s, s)
Values (#ccy1, #ccy2, #rate, etc...)
end
select #exists
This returns whether or not the file exists, and inserts it if it does not.
In your code, you can check the return value, and then throw an error if it returns a 1.
Normally, you would do this with a unique constraint on the database table. Then, just try the insert. If your column combination exists, you'll get a constraint violation exception. It's a bit painful working out which constraint has been violated (if there's more than one on your table), but possible.
If you need more control, or want to format the exception a bit better, you could do the "if exists (select * from table where col1 = ... and col2 = ...)" test as suggested (but either return a result variable or an output parameter).

Reinserting values into table after validation in sql via c#

I've a varbinary column profile_name in my accounts table. Initially, I thought my users will not enter special characters and will keep it short and sweet.
But, that was my terrible mistake. They started keeping long profile names with lots of special characters, which is causing me trouble.
Now, I've limited their options to alphanumerics, underscore and hyphen and should be 3 to 10 characters. In other words this regex: [\w-]{3,10}
The task is uphill now.
I've 570 users already and I want to update their profile names to fit into the above rules.
I've some barebone code:
public NpMaintainance()
{
UTF8Encoder encode = new UTF8Encoder();
List<string> names = new List<string>();
using (con)
{
con.Open();
using (cmd)
{
cmd.Parameters.Clear();
cmd.Connection = con;
cmd.CommandText = "select profile_name from accounts";
using (dr = cmd.ExecuteReader())
{
names.Add((dr[0] as byte[]).ToUnicodeString(encode));
}
}
}
}
I've just the code, which I've not tried, because it is a production site. Now names contains the profile names, which I've to reinsert into the table. And to add to my problem, profile_name is unique, so no two names should be same.
I've provided all the details I could, and hoping to get an answer!
What should I do? Is there any other way(using only sql?), or if not please help me with the way I'm currently doing!
UPDATE:
Here is MySQL version, with the help of cleanString function written by Shay Anderson with just a minor change to accept numbers an letters.
IF ASCII(c) > 31 AND ASCII(c) < 127 THEN
replaced with
IF (ASCII(c) > 47 AND ASCII(c) < 57)
OR (ASCII(c) > 64 AND ASCII(c) < 91)
OR (ASCII(c) > 96 AND ASCII(c) < 122) THEN
The basic idea is the same create a temp table with profile_name converted to varchar, cleaning it and updateing it back to accounts table.
-- for testing purposes create and populate test table
CREATE TABLE accounts(id INT, profile_name VARBINARY(8000));
INSERT INTO accounts(id, profile_name)
SELECT 1, CAST('User|%$&&/(/' AS BINARY);
-- check what's in there
SELECT id, profile_name, CAST(profile_name AS CHAR) FROM accounts;
CREATE TEMPORARY TABLE IF NOT EXISTS tmp_accounts AS (
SELECT id, profile_name, CAST(profile_name AS CHAR) AS 'converted_name'
FROM accounts
);
-- clean converted profile_name in temp table using function udf_cleanString
UPDATE tmp_accounts
SET converted_name = UDF_CLEANSTRING(converted_name);
/* do any other change necessary.... length etc.
when satisfied with changes in temp table update accounts table with new profile_name
*/
UPDATE accounts a
INNER JOIN tmp_accounts t ON t.id = a.id
SET a.profile_name = CAST(t.converted_name AS BINARY);
-- check what was done with profile_name after changes
SELECT id, profile_name, CAST(profile_name AS CHAR) FROM accounts;
-- DROP TABLE tmp_accounts
In SQL you could try converting varbinary(max) to nvarchar(max) type something like this (assuming accountid is primary key of accounts table)
SELECT accountid, profile_name, CONVERT(NVARCHAR(MAX),profile_name) as converted_name
INTO #tmpaccounts
FROM accounts
then use some character stripping function like the one from How to strip all non-alphabetic characters from string in SQL Server?. It might need some tweaking to allow hyphens and underscores, but it shouldn't be too hard
UPDATE #tmpaccounts SET converted_name = [dbo].[RemoveNonAlphaCharacters](converted_name)
that should take care of the character limitations, if you have longer profile names then 10 trim them, hopefully there shouldn't be a lot of them. (I'm am somewhat skeptical of changing profile names without permission or at least awareness from the account owners)
After that you can update the accounts table from #tmpaccounts.
UPDATE a
SET profile_name = CONVERT(VARBINARY(max), converted_name)
FROM accounts a
INNER JOIN #tmpaccounts t on t.accountid = a.accountid
Of course DON'T do this on production database, use demo or training DB.
sqlmysql

Creating a SQLParameter from an array for an IN clause [duplicate]

How do I parameterize a query containing an IN clause with a variable number of arguments, like this one?
SELECT * FROM Tags
WHERE Name IN ('ruby','rails','scruffy','rubyonrails')
ORDER BY Count DESC
In this query, the number of arguments could be anywhere from 1 to 5.
I would prefer not to use a dedicated stored procedure for this (or XML), but if there is some elegant way specific to SQL Server 2008, I am open to that.
You can parameterize each value, so something like:
string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
string cmdText = "SELECT * FROM Tags WHERE Name IN ({0})";
string[] paramNames = tags.Select(
(s, i) => "#tag" + i.ToString()
).ToArray();
string inClause = string.Join(", ", paramNames);
using (SqlCommand cmd = new SqlCommand(string.Format(cmdText, inClause))) {
for(int i = 0; i < paramNames.Length; i++) {
cmd.Parameters.AddWithValue(paramNames[i], tags[i]);
}
}
Which will give you:
cmd.CommandText = "SELECT * FROM Tags WHERE Name IN (#tag0, #tag1, #tag2, #tag3)"
cmd.Parameters["#tag0"] = "ruby"
cmd.Parameters["#tag1"] = "rails"
cmd.Parameters["#tag2"] = "scruffy"
cmd.Parameters["#tag3"] = "rubyonrails"
No, this is not open to SQL injection. The only injected text into CommandText is not based on user input. It's solely based on the hardcoded "#tag" prefix, and the index of an array. The index will always be an integer, is not user generated, and is safe.
The user inputted values are still stuffed into parameters, so there is no vulnerability there.
Edit:
Injection concerns aside, take care to note that constructing the command text to accomodate a variable number of parameters (as above) impede's SQL server's ability to take advantage of cached queries. The net result is that you almost certainly lose the value of using parameters in the first place (as opposed to merely inserting the predicate strings into the SQL itself).
Not that cached query plans aren't valuable, but IMO this query isn't nearly complicated enough to see much benefit from it. While the compilation costs may approach (or even exceed) the execution costs, you're still talking milliseconds.
If you have enough RAM, I'd expect SQL Server would probably cache a plan for the common counts of parameters as well. I suppose you could always add five parameters, and let the unspecified tags be NULL - the query plan should be the same, but it seems pretty ugly to me and I'm not sure that it'd worth the micro-optimization (although, on Stack Overflow - it may very well be worth it).
Also, SQL Server 7 and later will auto-parameterize queries, so using parameters isn't really necessary from a performance standpoint - it is, however, critical from a security standpoint - especially with user inputted data like this.
Here's a quick-and-dirty technique I have used:
SELECT * FROM Tags
WHERE '|ruby|rails|scruffy|rubyonrails|'
LIKE '%|' + Name + '|%'
So here's the C# code:
string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
const string cmdText = "select * from tags where '|' + #tags + '|' like '%|' + Name + '|%'";
using (SqlCommand cmd = new SqlCommand(cmdText)) {
cmd.Parameters.AddWithValue("#tags", string.Join("|", tags);
}
Two caveats:
The performance is terrible. LIKE "%...%" queries are not indexed.
Make sure you don't have any |, blank, or null tags or this won't work
There are other ways to accomplish this that some people may consider cleaner, so please keep reading.
For SQL Server 2008, you can use a table valued parameter. It's a bit of work, but it is arguably cleaner than my other method.
First, you have to create a type
CREATE TYPE dbo.TagNamesTableType AS TABLE ( Name nvarchar(50) )
Then, your ADO.NET code looks like this:
string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
cmd.CommandText = "SELECT Tags.* FROM Tags JOIN #tagNames as P ON Tags.Name = P.Name";
// value must be IEnumerable<SqlDataRecord>
cmd.Parameters.AddWithValue("#tagNames", tags.AsSqlDataRecord("Name")).SqlDbType = SqlDbType.Structured;
cmd.Parameters["#tagNames"].TypeName = "dbo.TagNamesTableType";
// Extension method for converting IEnumerable<string> to IEnumerable<SqlDataRecord>
public static IEnumerable<SqlDataRecord> AsSqlDataRecord(this IEnumerable<string> values, string columnName) {
if (values == null || !values.Any()) return null; // Annoying, but SqlClient wants null instead of 0 rows
var firstRecord = values.First();
var metadata= new SqlMetaData(columnName, SqlDbType.NVarChar, 50); //50 as per SQL Type
return values.Select(v =>
{
var r = new SqlDataRecord(metadata);
r.SetValues(v);
return r;
});
}
Update
As Per #Doug
Please try to avoid var metadata = SqlMetaData.InferFromValue(firstRecord, columnName);
It's set first value length, so if first value is 3 characters then its set max length 3 and other records will truncated if more then 3 characters.
So, please try to use: var metadata= new SqlMetaData(columnName, SqlDbType.NVarChar, maxLen);
Note: -1 for max length.
The original question was "How do I parameterize a query ..."
This is not an answer to that original question. There are some very good demonstrations of how to do that, in other answers.
See the first answer from Mark Brackett (the first answer starting "You can parameterize each value") and Mark Brackett's second answer for the preferred answer that I (and 231 others) upvoted. The approach given in his answer allows 1) for effective use of bind variables, and 2) for predicates that are sargable.
Selected answer
I am addressing here the approach given in Joel Spolsky's answer, the answer "selected" as the right answer.
Joel Spolsky's approach is clever. And it works reasonably, it's going to exhibit predictable behavior and predictable performance, given "normal" values, and with the normative edge cases, such as NULL and the empty string. And it may be sufficient for a particular application.
But in terms generalizing this approach, let's also consider the more obscure corner cases, like when the Name column contains a wildcard character (as recognized by the LIKE predicate.) The wildcard character I see most commonly used is % (a percent sign.). So let's deal with that here now, and later go on to other cases.
Some problems with % character
Consider a Name value of 'pe%ter'. (For the examples here, I use a literal string value in place of the column name.) A row with a Name value of `'pe%ter' would be returned by a query of the form:
select ...
where '|peanut|butter|' like '%|' + 'pe%ter' + '|%'
But that same row will not be returned if the order of the search terms is reversed:
select ...
where '|butter|peanut|' like '%|' + 'pe%ter' + '|%'
The behavior we observe is kind of odd. Changing the order of the search terms in the list changes the result set.
It almost goes without saying that we might not want pe%ter to match peanut butter, no matter how much he likes it.
Obscure corner case
(Yes, I will agree that this is an obscure case. Probably one that is not likely to be tested. We wouldn't expect a wildcard in a column value. We may assume that the application prevents such a value from being stored. But in my experience, I've rarely seen a database constraint that specifically disallowed characters or patterns that would be considered wildcards on the right side of a LIKE comparison operator.
Patching a hole
One approach to patching this hole is to escape the % wildcard character. (For anyone not familiar with the escape clause on the operator, here's a link to the SQL Server documentation.
select ...
where '|peanut|butter|'
like '%|' + 'pe\%ter' + '|%' escape '\'
Now we can match the literal %. Of course, when we have a column name, we're going to need to dynamically escape the wildcard. We can use the REPLACE function to find occurrences of the % character and insert a backslash character in front of each one, like this:
select ...
where '|pe%ter|'
like '%|' + REPLACE( 'pe%ter' ,'%','\%') + '|%' escape '\'
So that solves the problem with the % wildcard. Almost.
Escape the escape
We recognize that our solution has introduced another problem. The escape character. We see that we're also going to need to escape any occurrences of escape character itself. This time, we use the ! as the escape character:
select ...
where '|pe%t!r|'
like '%|' + REPLACE(REPLACE( 'pe%t!r' ,'!','!!'),'%','!%') + '|%' escape '!'
The underscore too
Now that we're on a roll, we can add another REPLACE handle the underscore wildcard. And just for fun, this time, we'll use $ as the escape character.
select ...
where '|p_%t!r|'
like '%|' + REPLACE(REPLACE(REPLACE( 'p_%t!r' ,'$','$$'),'%','$%'),'_','$_') + '|%' escape '$'
I prefer this approach to escaping because it works in Oracle and MySQL as well as SQL Server. (I usually use the \ backslash as the escape character, since that's the character we use in regular expressions. But why be constrained by convention!
Those pesky brackets
SQL Server also allows for wildcard characters to be treated as literals by enclosing them in brackets []. So we're not done fixing yet, at least for SQL Server. Since pairs of brackets have special meaning, we'll need to escape those as well. If we manage to properly escape the brackets, then at least we won't have to bother with the hyphen - and the carat ^ within the brackets. And we can leave any % and _ characters inside the brackets escaped, since we'll have basically disabled the special meaning of the brackets.
Finding matching pairs of brackets shouldn't be that hard. It's a little more difficult than handling the occurrences of singleton % and _. (Note that it's not sufficient to just escape all occurrences of brackets, because a singleton bracket is considered to be a literal, and doesn't need to be escaped. The logic is getting a little fuzzier than I can handle without running more test cases.)
Inline expression gets messy
That inline expression in the SQL is getting longer and uglier. We can probably make it work, but heaven help the poor soul that comes behind and has to decipher it. As much of a fan I am for inline expressions, I'm inclined not use one here, mainly because I don't want to have to leave a comment explaining the reason for the mess, and apologizing for it.
A function where ?
Okay, so, if we don't handle that as an inline expression in the SQL, the closest alternative we have is a user defined function. And we know that won't speed things up any (unless we can define an index on it, like we could with Oracle.) If we've got to create a function, we might better do that in the code that calls the SQL statement.
And that function may have some differences in behavior, dependent on the DBMS and version. (A shout out to all you Java developers so keen on being able to use any database engine interchangeably.)
Domain knowledge
We may have specialized knowledge of the domain for the column, (that is, the set of allowable values enforced for the column. We may know a priori that the values stored in the column will never contain a percent sign, an underscore, or bracket pairs. In that case, we just include a quick comment that those cases are covered.
The values stored in the column may allow for % or _ characters, but a constraint may require those values to be escaped, perhaps using a defined character, such that the values are LIKE comparison "safe". Again, a quick comment about the allowed set of values, and in particular which character is used as an escape character, and go with Joel Spolsky's approach.
But, absent the specialized knowledge and a guarantee, it's important for us to at least consider handling those obscure corner cases, and consider whether the behavior is reasonable and "per the specification".
Other issues recapitulated
I believe others have already sufficiently pointed out some of the other commonly considered areas of concern:
SQL injection (taking what would appear to be user supplied information, and including that in the SQL text rather than supplying them through bind variables. Using bind variables isn't required, it's just one convenient approach to thwart with SQL injection. There are other ways to deal with it:
optimizer plan using index scan rather than index seeks, possible need for an expression or function for escaping wildcards (possible index on expression or function)
using literal values in place of bind variables impacts scalability
Conclusion
I like Joel Spolsky's approach. It's clever. And it works.
But as soon as I saw it, I immediately saw a potential problem with it, and it's not my nature to let it slide. I don't mean to be critical of the efforts of others. I know many developers take their work very personally, because they invest so much into it and they care so much about it. So please understand, this is not a personal attack. What I'm identifying here is the type of problem that crops up in production rather than testing.
You can pass the parameter as a string
So you have the string
DECLARE #tags
SET #tags = ‘ruby|rails|scruffy|rubyonrails’
select * from Tags
where Name in (SELECT item from fnSplit(#tags, ‘|’))
order by Count desc
Then all you have to do is pass the string as 1 parameter.
Here is the split function I use.
CREATE FUNCTION [dbo].[fnSplit](
#sInputList VARCHAR(8000) -- List of delimited items
, #sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
) RETURNS #List TABLE (item VARCHAR(8000))
BEGIN
DECLARE #sItem VARCHAR(8000)
WHILE CHARINDEX(#sDelimiter,#sInputList,0) <> 0
BEGIN
SELECT
#sItem=RTRIM(LTRIM(SUBSTRING(#sInputList,1,CHARINDEX(#sDelimiter,#sInputList,0)-1))),
#sInputList=RTRIM(LTRIM(SUBSTRING(#sInputList,CHARINDEX(#sDelimiter,#sInputList,0)+LEN(#sDelimiter),LEN(#sInputList))))
IF LEN(#sItem) > 0
INSERT INTO #List SELECT #sItem
END
IF LEN(#sInputList) > 0
INSERT INTO #List SELECT #sInputList -- Put the last item in
RETURN
END
I heard Jeff/Joel talk about this on the podcast today (episode 34, 2008-12-16 (MP3, 31 MB), 1 h 03 min 38 secs - 1 h 06 min 45 secs), and I thought I recalled Stack Overflow was using LINQ to SQL, but maybe it was ditched. Here's the same thing in LINQ to SQL.
var inValues = new [] { "ruby","rails","scruffy","rubyonrails" };
var results = from tag in Tags
where inValues.Contains(tag.Name)
select tag;
That's it. And, yes, LINQ already looks backwards enough, but the Contains clause seems extra backwards to me. When I had to do a similar query for a project at work, I naturally tried to do this the wrong way by doing a join between the local array and the SQL Server table, figuring the LINQ to SQL translator would be smart enough to handle the translation somehow. It didn't, but it did provide an error message that was descriptive and pointed me towards using Contains.
Anyway, if you run this in the highly recommended LINQPad, and run this query, you can view the actual SQL that the SQL LINQ provider generated. It'll show you each of the values getting parameterized into an IN clause.
If you are calling from .NET, you could use Dapper dot net:
string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = dataContext.Query<Tags>(#"
select * from Tags
where Name in #names
order by Count desc", new {names});
Here Dapper does the thinking, so you don't have to. Something similar is possible with LINQ to SQL, of course:
string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = from tag in dataContext.Tags
where names.Contains(tag.Name)
orderby tag.Count descending
select tag;
In SQL Server 2016+ you could use STRING_SPLIT function:
DECLARE #names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';
SELECT *
FROM Tags
WHERE Name IN (SELECT [value] FROM STRING_SPLIT(#names, ','))
ORDER BY [Count] DESC;
or:
DECLARE #names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';
SELECT t.*
FROM Tags t
JOIN STRING_SPLIT(#names,',')
ON t.Name = [value]
ORDER BY [Count] DESC;
LiveDemo
The accepted answer will of course work and it is one of the way to go, but it is anti-pattern.
E. Find rows by list of values
This is replacement for common anti-pattern such as creating a dynamic SQL string in application layer or Transact-SQL, or by using LIKE operator:
SELECT ProductId, Name, Tags
FROM Product
WHERE ',1,2,3,' LIKE '%,' + CAST(ProductId AS VARCHAR(20)) + ',%';
Addendum:
To improve the STRING_SPLIT table function row estimation, it is a good idea to materialize splitted values as temporary table/table variable:
DECLARE #names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails,sql';
CREATE TABLE #t(val NVARCHAR(120));
INSERT INTO #t(val) SELECT s.[value] FROM STRING_SPLIT(#names, ',') s;
SELECT *
FROM Tags tg
JOIN #t t
ON t.val = tg.TagName
ORDER BY [Count] DESC;
SEDE - Live Demo
Related: How to Pass a List of Values Into a Stored Procedure
Original question has requirement SQL Server 2008. Because this question is often used as duplicate, I've added this answer as reference.
This is possibly a half nasty way of doing it, I used it once, was rather effective.
Depending on your goals it might be of use.
Create a temp table with one column.
INSERT each look-up value into that column.
Instead of using an IN, you can then just use your standard JOIN rules. ( Flexibility++ )
This has a bit of added flexibility in what you can do, but it's more suited for situations where you have a large table to query, with good indexing, and you want to use the parametrized list more than once. Saves having to execute it twice and have all the sanitation done manually.
I never got around to profiling exactly how fast it was, but in my situation it was needed.
We have function that creates a table variable that you can join to:
ALTER FUNCTION [dbo].[Fn_sqllist_to_table](#list AS VARCHAR(8000),
#delim AS VARCHAR(10))
RETURNS #listTable TABLE(
Position INT,
Value VARCHAR(8000))
AS
BEGIN
DECLARE #myPos INT
SET #myPos = 1
WHILE Charindex(#delim, #list) > 0
BEGIN
INSERT INTO #listTable
(Position,Value)
VALUES (#myPos,LEFT(#list, Charindex(#delim, #list) - 1))
SET #myPos = #myPos + 1
IF Charindex(#delim, #list) = Len(#list)
INSERT INTO #listTable
(Position,Value)
VALUES (#myPos,'')
SET #list = RIGHT(#list, Len(#list) - Charindex(#delim, #list))
END
IF Len(#list) > 0
INSERT INTO #listTable
(Position,Value)
VALUES (#myPos,#list)
RETURN
END
So:
#Name varchar(8000) = null // parameter for search values
select * from Tags
where Name in (SELECT value From fn_sqllist_to_table(#Name,',')))
order by Count desc
This is gross, but if you are guaranteed to have at least one, you could do:
SELECT ...
...
WHERE tag IN( #tag1, ISNULL( #tag2, #tag1 ), ISNULL( #tag3, #tag1 ), etc. )
Having IN( 'tag1', 'tag2', 'tag1', 'tag1', 'tag1' ) will be easily optimized away by SQL Server. Plus, you get direct index seeks
I would pass a table type parameter (since it's SQL Server 2008), and do a where exists, or inner join. You may also use XML, using sp_xml_preparedocument, and then even index that temporary table.
In my opinion, the best source to solve this problem, is what has been posted on this site:
Syscomments. Dinakar Nethi
CREATE FUNCTION dbo.fnParseArray (#Array VARCHAR(1000),#separator CHAR(1))
RETURNS #T Table (col1 varchar(50))
AS
BEGIN
--DECLARE #T Table (col1 varchar(50))
-- #Array is the array we wish to parse
-- #Separator is the separator charactor such as a comma
DECLARE #separator_position INT -- This is used to locate each separator character
DECLARE #array_value VARCHAR(1000) -- this holds each array value as it is returned
-- For my loop to work I need an extra separator at the end. I always look to the
-- left of the separator character for each array value
SET #array = #array + #separator
-- Loop through the string searching for separtor characters
WHILE PATINDEX('%' + #separator + '%', #array) <> 0
BEGIN
-- patindex matches the a pattern against a string
SELECT #separator_position = PATINDEX('%' + #separator + '%',#array)
SELECT #array_value = LEFT(#array, #separator_position - 1)
-- This is where you process the values passed.
INSERT into #T VALUES (#array_value)
-- Replace this select statement with your processing
-- #array_value holds the value of this element of the array
-- This replaces what we just processed with and empty string
SELECT #array = STUFF(#array, 1, #separator_position, '')
END
RETURN
END
Use:
SELECT * FROM dbo.fnParseArray('a,b,c,d,e,f', ',')
CREDITS FOR: Dinakar Nethi
The proper way IMHO is to store the list in a character string (limited in length by what the DBMS support); the only trick is that (in order to simplify processing) I have a separator (a comma in my example) at the beginning and at the end of the string. The idea is to "normalize on the fly", turning the list into a one-column table that contains one row per value. This allows you to turn
in (ct1,ct2, ct3 ... ctn)
into an
in (select ...)
or (the solution I'd probably prefer) a regular join, if you just add a "distinct" to avoid problems with duplicate values in the list.
Unfortunately, the techniques to slice a string are fairly product-specific.
Here is the SQL Server version:
with qry(n, names) as
(select len(list.names) - len(replace(list.names, ',', '')) - 1 as n,
substring(list.names, 2, len(list.names)) as names
from (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' names) as list
union all
select (n - 1) as n,
substring(names, 1 + charindex(',', names), len(names)) as names
from qry
where n > 1)
select n, substring(names, 1, charindex(',', names) - 1) dwarf
from qry;
The Oracle version:
select n, substr(name, 1, instr(name, ',') - 1) dwarf
from (select n,
substr(val, 1 + instr(val, ',', 1, n)) name
from (select rownum as n,
list.val
from (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val
from dual) list
connect by level < length(list.val) -
length(replace(list.val, ',', ''))));
and the MySQL version:
select pivot.n,
substring_index(substring_index(list.val, ',', 1 + pivot.n), ',', -1) from (select 1 as n
union all
select 2 as n
union all
select 3 as n
union all
select 4 as n
union all
select 5 as n
union all
select 6 as n
union all
select 7 as n
union all
select 8 as n
union all
select 9 as n
union all
select 10 as n) pivot, (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val) as list where pivot.n < length(list.val) -
length(replace(list.val, ',', ''));
(Of course, "pivot" must return as many rows as the maximum number of
items we can find in the list)
If you've got SQL Server 2008 or later I'd use a Table Valued Parameter.
If you're unlucky enough to be stuck on SQL Server 2005 you could add a CLR function like this,
[SqlFunction(
DataAccessKind.None,
IsDeterministic = true,
SystemDataAccess = SystemDataAccessKind.None,
IsPrecise = true,
FillRowMethodName = "SplitFillRow",
TableDefinintion = "s NVARCHAR(MAX)"]
public static IEnumerable Split(SqlChars seperator, SqlString s)
{
if (s.IsNull)
return new string[0];
return s.ToString().Split(seperator.Buffer);
}
public static void SplitFillRow(object row, out SqlString s)
{
s = new SqlString(row.ToString());
}
Which you could use like this,
declare #desiredTags nvarchar(MAX);
set #desiredTags = 'ruby,rails,scruffy,rubyonrails';
select * from Tags
where Name in [dbo].[Split] (',', #desiredTags)
order by Count desc
I think this is a case when a static query is just not the way to go. Dynamically build the list for your in clause, escape your single quotes, and dynamically build SQL. In this case you probably won't see much of a difference with any method due to the small list, but the most efficient method really is to send the SQL exactly as it is written in your post. I think it is a good habit to write it the most efficient way, rather than to do what makes the prettiest code, or consider it bad practice to dynamically build SQL.
I have seen the split functions take longer to execute than the query themselves in many cases where the parameters get large. A stored procedure with table valued parameters in SQL 2008 is the only other option I would consider, although this will probably be slower in your case. TVP will probably only be faster for large lists if you are searching on the primary key of the TVP, because SQL will build a temporary table for the list anyway (if the list is large). You won't know for sure unless you test it.
I have also seen stored procedures that had 500 parameters with default values of null, and having WHERE Column1 IN (#Param1, #Param2, #Param3, ..., #Param500). This caused SQL to build a temp table, do a sort/distinct, and then do a table scan instead of an index seek. That is essentially what you would be doing by parameterizing that query, although on a small enough scale that it won't make a noticeable difference. I highly recommend against having NULL in your IN lists, as if that gets changed to a NOT IN it will not act as intended. You could dynamically build the parameter list, but the only obvious thing that you would gain is that the objects would escape the single quotes for you. That approach is also slightly slower on the application end since the objects have to parse the query to find the parameters. It may or may not be faster on SQL, as parameterized queries call sp_prepare, sp_execute for as many times you execute the query, followed by sp_unprepare.
The reuse of execution plans for stored procedures or parameterized queries may give you a performance gain, but it will lock you in to one execution plan determined by the first query that is executed. That may be less than ideal for subsequent queries in many cases. In your case, reuse of execution plans will probably be a plus, but it might not make any difference at all as the example is a really simple query.
Cliffs notes:
For your case anything you do, be it parameterization with a fixed number of items in the list (null if not used), dynamically building the query with or without parameters, or using stored procedures with table valued parameters will not make much of a difference. However, my general recommendations are as follows:
Your case/simple queries with few parameters:
Dynamic SQL, maybe with parameters if testing shows better performance.
Queries with reusable execution plans, called multiple times by simply changing the parameters or if the query is complicated:
SQL with dynamic parameters.
Queries with large lists:
Stored procedure with table valued parameters. If the list can vary by a large amount use WITH RECOMPILE on the stored procedure, or simply use dynamic SQL without parameters to generate a new execution plan for each query.
May be we can use XML here:
declare #x xml
set #x='<items>
<item myvalue="29790" />
<item myvalue="31250" />
</items>
';
With CTE AS (
SELECT
x.item.value('#myvalue[1]', 'decimal') AS myvalue
FROM #x.nodes('//items/item') AS x(item) )
select * from YourTable where tableColumnName in (select myvalue from cte)
If we have strings stored inside the IN clause with the comma(,) delimited, we can use the charindex function to get the values. If you use .NET, then you can map with SqlParameters.
DDL Script:
CREATE TABLE Tags
([ID] int, [Name] varchar(20))
;
INSERT INTO Tags
([ID], [Name])
VALUES
(1, 'ruby'),
(2, 'rails'),
(3, 'scruffy'),
(4, 'rubyonrails')
;
T-SQL:
DECLARE #Param nvarchar(max)
SET #Param = 'ruby,rails,scruffy,rubyonrails'
SELECT * FROM Tags
WHERE CharIndex(Name,#Param)>0
You can use the above statement in your .NET code and map the parameter with SqlParameter.
Fiddler demo
EDIT:
Create the table called SelectedTags using the following script.
DDL Script:
Create table SelectedTags
(Name nvarchar(20));
INSERT INTO SelectedTags values ('ruby'),('rails')
T-SQL:
DECLARE #list nvarchar(max)
SELECT #list=coalesce(#list+',','')+st.Name FROM SelectedTags st
SELECT * FROM Tags
WHERE CharIndex(Name,#Param)>0
I'd approach this by default with passing a table valued function (that returns a table from a string) to the IN condition.
Here is the code for the UDF (I got it from Stack Overflow somewhere, i can't find the source right now)
CREATE FUNCTION [dbo].[Split] (#sep char(1), #s varchar(8000))
RETURNS table
AS
RETURN (
WITH Pieces(pn, start, stop) AS (
SELECT 1, 1, CHARINDEX(#sep, #s)
UNION ALL
SELECT pn + 1, stop + 1, CHARINDEX(#sep, #s, stop + 1)
FROM Pieces
WHERE stop > 0
)
SELECT
SUBSTRING(#s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
FROM Pieces
)
Once you got this your code would be as simple as this:
select * from Tags
where Name in (select s from dbo.split(';','ruby;rails;scruffy;rubyonrails'))
order by Count desc
Unless you have a ridiculously long string, this should work well with the table index.
If needed you can insert it into a temp table, index it, then run a join...
For a variable number of arguments like this the only way I'm aware of is to either generate the SQL explicitly or do something that involves populating a temporary table with the items you want and joining against the temp table.
Another possible solution is instead of passing a variable number of arguments to a stored procedure, pass a single string containing the names you're after, but make them unique by surrounding them with '<>'. Then use PATINDEX to find the names:
SELECT *
FROM Tags
WHERE PATINDEX('%<' + Name + '>%','<jo>,<john>,<scruffy>,<rubyonrails>') > 0
Use the following stored procedure. It uses a custom split function, which can be found here.
create stored procedure GetSearchMachingTagNames
#PipeDelimitedTagNames varchar(max),
#delimiter char(1)
as
begin
select * from Tags
where Name in (select data from [dbo].[Split](#PipeDelimitedTagNames,#delimiter)
end
Here is another alternative. Just pass a comma-delimited list as a string parameter to the stored procedure and:
CREATE PROCEDURE [dbo].[sp_myproc]
#UnitList varchar(MAX) = '1,2,3'
AS
select column from table
where ph.UnitID in (select * from CsvToInt(#UnitList))
And the function:
CREATE Function [dbo].[CsvToInt] ( #Array varchar(MAX))
returns #IntTable table
(IntValue int)
AS
begin
declare #separator char(1)
set #separator = ','
declare #separator_position int
declare #array_value varchar(MAX)
set #array = #array + ','
while patindex('%,%' , #array) <> 0
begin
select #separator_position = patindex('%,%' , #array)
select #array_value = left(#array, #separator_position - 1)
Insert #IntTable
Values (Cast(#array_value as int))
select #array = stuff(#array, 1, #separator_position, '')
end
return
end
In ColdFusion we just do:
<cfset myvalues = "ruby|rails|scruffy|rubyonrails">
<cfquery name="q">
select * from sometable where values in <cfqueryparam value="#myvalues#" list="true">
</cfquery>
Here's a technique that recreates a local table to be used in a query string. Doing it this way eliminates all parsing problems.
The string can be built in any language. In this example I used SQL since that was the original problem I was trying to solve. I needed a clean way to pass in table data on the fly in a string to be executed later.
Using a user defined type is optional. Creating the type is only created once and can be done ahead of time. Otherwise just add a full table type to the declaration in the string.
The general pattern is easy to extend and can be used for passing more complex tables.
-- Create a user defined type for the list.
CREATE TYPE [dbo].[StringList] AS TABLE(
[StringValue] [nvarchar](max) NOT NULL
)
-- Create a sample list using the list table type.
DECLARE #list [dbo].[StringList];
INSERT INTO #list VALUES ('one'), ('two'), ('three'), ('four')
-- Build a string in which we recreate the list so we can pass it to exec
-- This can be done in any language since we're just building a string.
DECLARE #str nvarchar(max);
SET #str = 'DECLARE #list [dbo].[StringList]; INSERT INTO #list VALUES '
-- Add all the values we want to the string. This would be a loop in C++.
SELECT #str = #str + '(''' + StringValue + '''),' FROM #list
-- Remove the trailing comma so the query is valid sql.
SET #str = substring(#str, 1, len(#str)-1)
-- Add a select to test the string.
SET #str = #str + '; SELECT * FROM #list;'
-- Execute the string and see we've pass the table correctly.
EXEC(#str)
In SQL Server 2016+ another possibility is to use the OPENJSON function.
This approach is blogged about in OPENJSON - one of best ways to select rows by list of ids.
A full worked example below
CREATE TABLE dbo.Tags
(
Name VARCHAR(50),
Count INT
)
INSERT INTO dbo.Tags
VALUES ('VB',982), ('ruby',1306), ('rails',1478), ('scruffy',1), ('C#',1784)
GO
CREATE PROC dbo.SomeProc
#Tags VARCHAR(MAX)
AS
SELECT T.*
FROM dbo.Tags T
WHERE T.Name IN (SELECT J.Value COLLATE Latin1_General_CI_AS
FROM OPENJSON(CONCAT('[', #Tags, ']')) J)
ORDER BY T.Count DESC
GO
EXEC dbo.SomeProc #Tags = '"ruby","rails","scruffy","rubyonrails"'
DROP TABLE dbo.Tags
I have an answer that doesn't require a UDF, XML
Because IN accepts a select statement
e.g. SELECT * FROM Test where Data IN (SELECT Value FROM TABLE)
You really only need a way to convert the string into a table.
This can be done with a recursive CTE, or a query with a number table (or Master..spt_value)
Here's the CTE version.
DECLARE #InputString varchar(8000) = 'ruby,rails,scruffy,rubyonrails'
SELECT #InputString = #InputString + ','
;WITH RecursiveCSV(x,y)
AS
(
SELECT
x = SUBSTRING(#InputString,0,CHARINDEX(',',#InputString,0)),
y = SUBSTRING(#InputString,CHARINDEX(',',#InputString,0)+1,LEN(#InputString))
UNION ALL
SELECT
x = SUBSTRING(y,0,CHARINDEX(',',y,0)),
y = SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y))
FROM
RecursiveCSV
WHERE
SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y)) <> '' OR
SUBSTRING(y,0,CHARINDEX(',',y,0)) <> ''
)
SELECT
*
FROM
Tags
WHERE
Name IN (select x FROM RecursiveCSV)
OPTION (MAXRECURSION 32767);
I use a more concise version of the top voted answer:
List<SqlParameter> parameters = tags.Select((s, i) => new SqlParameter("#tag" + i.ToString(), SqlDbType.NVarChar(50)) { Value = s}).ToList();
var whereCondition = string.Format("tags in ({0})", String.Join(",",parameters.Select(s => s.ParameterName)));
It does loop through the tag parameters twice; but that doesn't matter most of the time (it won't be your bottleneck; if it is, unroll the loop).
If you're really interested in performance and don't want to iterate through the loop twice, here's a less beautiful version:
var parameters = new List<SqlParameter>();
var paramNames = new List<string>();
for (var i = 0; i < tags.Length; i++)
{
var paramName = "#tag" + i;
//Include size and set value explicitly (not AddWithValue)
//Because SQL Server may use an implicit conversion if it doesn't know
//the actual size.
var p = new SqlParameter(paramName, SqlDbType.NVarChar(50) { Value = tags[i]; }
paramNames.Add(paramName);
parameters.Add(p);
}
var inClause = string.Join(",", paramNames);
Here is another answer to this problem.
(new version posted on 6/4/13).
private static DataSet GetDataSet(SqlConnectionStringBuilder scsb, string strSql, params object[] pars)
{
var ds = new DataSet();
using (var sqlConn = new SqlConnection(scsb.ConnectionString))
{
var sqlParameters = new List<SqlParameter>();
var replacementStrings = new Dictionary<string, string>();
if (pars != null)
{
for (int i = 0; i < pars.Length; i++)
{
if (pars[i] is IEnumerable<object>)
{
List<object> enumerable = (pars[i] as IEnumerable<object>).ToList();
replacementStrings.Add("#" + i, String.Join(",", enumerable.Select((value, pos) => String.Format("#_{0}_{1}", i, pos))));
sqlParameters.AddRange(enumerable.Select((value, pos) => new SqlParameter(String.Format("#_{0}_{1}", i, pos), value ?? DBNull.Value)).ToArray());
}
else
{
sqlParameters.Add(new SqlParameter(String.Format("#{0}", i), pars[i] ?? DBNull.Value));
}
}
}
strSql = replacementStrings.Aggregate(strSql, (current, replacementString) => current.Replace(replacementString.Key, replacementString.Value));
using (var sqlCommand = new SqlCommand(strSql, sqlConn))
{
if (pars != null)
{
sqlCommand.Parameters.AddRange(sqlParameters.ToArray());
}
else
{
//Fail-safe, just in case a user intends to pass a single null parameter
sqlCommand.Parameters.Add(new SqlParameter("#0", DBNull.Value));
}
using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand))
{
sqlDataAdapter.Fill(ds);
}
}
}
return ds;
}
Cheers.
The only winning move is not to play.
No infinite variability for you. Only finite variability.
In the SQL you have a clause like this:
and ( {1}==0 or b.CompanyId in ({2},{3},{4},{5},{6}) )
In the C# code you do something like this:
int origCount = idList.Count;
if (origCount > 5) {
throw new Exception("You may only specify up to five originators to filter on.");
}
while (idList.Count < 5) { idList.Add(-1); } // -1 is an impossible value
return ExecuteQuery<PublishDate>(getValuesInListSQL,
origCount,
idList[0], idList[1], idList[2], idList[3], idList[4]);
So basically if the count is 0 then there is no filter and everything goes through. If the count is higher than 0 the then the value must be in the list, but the list has been padded out to five with impossible values (so that the SQL still makes sense)
Sometimes the lame solution is the only one that actually works.

Passing multiple rows of data to a stored procedure

I have a list of objects (created from several text files) in C#.net that I need to store in a SQL2005 database file. Unfortunately, Table-Valued Parameters began with SQL2008 so they won't help. I found from MSDN that one method is to "Bundle multiple data values into delimited strings or XML documents and then pass those text values to a procedure or statement" but I am rather new to stored procedures and need more help than that. I know I could create a stored procedure to create one record then loop through my list and add them, but that's what I'm trying to avoid. Thanks.
Input file example (Other files contain pricing and availability):
Matnr ShortDescription LongDescription ManufPartNo Manufacturer ManufacturerGlobalDescr GTIN ProdFamilyID ProdFamily ProdClassID ProdClass ProdSubClassID ProdSubClass ArticleCreationDate CNETavailable CNETid ListPrice Weight Length Width Heigth NoReturn MayRequireAuthorization EndUserInformation FreightPolicyException
10000000 A&D ENGINEERING SMALL ADULT CUFF FOR UA-767PBT UA-279 A&D ENGINEERING A&D ENG 093764011542 GENERAL General TDINTERNL TD Internal TDINTERNL TD Internal 2012-05-13 12:18:43 N 18.000 .350 N N N N
10000001 A&D ENGINEERING MEDIUM ADULT CUFF FOR UA-767PBT UA-280 A&D ENGINEERING A&D ENG 093764046070 GENERAL General TDINTERNL TD Internal TDINTERNL TD Internal 2012-05-13 12:18:43 N 18.000 .450 N N N N
Some DataBase File fields:
EffectiveDate varchar(50)
MfgName varchar(500)
MfgPartNbr varchar(500)
Cost varchar(200)
QtyOnHand varchar(200)
You can split multiple values from a single string quite easily. Say you can bundle the string like this, using a comma to separate "columns", and a semi-colon to separate "rows":
foo, 20120101, 26; bar, 20120612, 32
(This assumes that colons and semi-colons can't appear naturally in the data; if they can, you'll need to choose other delimiters.)
You can build a split routine like this, which includes an output column that allows you to determine the order the value appeared in the original string:
CREATE FUNCTION dbo.SplitStrings
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
AS
RETURN (SELECT Number = ROW_NUMBER() OVER (ORDER BY Number),
Item FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(#List, Number,
CHARINDEX(#Delimiter, #List + #Delimiter, Number) - Number)))
FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects) AS n(Number)
WHERE Number <= CONVERT(INT, LEN(#List))
AND SUBSTRING(#Delimiter + #List, Number, 1) = #Delimiter
) AS y);
GO
Then you can query it like this (for simplicity and illustration I'm only handling 3 properties but you can extrapolate this for 11 or n):
DECLARE #x NVARCHAR(MAX); -- a parameter to your stored procedure
SET #x = N'foo, 20120101, 26; bar, 20120612, 32';
;WITH x AS
(
SELECT ID = s.Number, InnerID = y.Number, y.Item
-- parameter and "row" delimiter here:
FROM dbo.SplitStrings(#x, ';') AS s
-- output and "column" delimiter here:
CROSS APPLY dbo.SplitStrings(s.Item, ',') AS y
)
SELECT
prop1 = x.Item,
prop2 = x2.Item,
prop3 = x3.Item
FROM x
INNER JOIN x AS x2
ON x.InnerID = x2.InnerID - 1
AND x.ID = x2.ID
INNER JOIN x AS x3
ON x2.InnerID = x3.InnerID - 1
AND x2.ID = x3.ID
WHERE x.InnerID = 1
ORDER BY x.ID;
Results:
prop1 prop2 prop3
------ -------- -------
foo 20120101 26
bar 20120612 32
We use XML data types like this...
declare #contentXML xml
set #contentXML=convert(xml,N'<ROOT><V a="124694"/><V a="124699"/><V a="124701"/></ROOT>')
SELECT content_id,
FROM dbo.table c WITH (nolock)
JOIN #contentXML.nodes('/ROOT/V') AS R ( v ) ON c.content_id = R.v.value('#a', 'INT')
Here is what it would look like if calling a stored procedure...
DbCommand dbCommand = database.GetStoredProcCommand("MyStroredProcedure);
database.AddInParameter(dbCommand, "dataPubXML", DbType.Xml, dataPublicationXml);
CREATE PROC dbo.usp_get_object_content
(
#contentXML XML
)
AS
BEGIN
SET NOCOUNT ON
SELECT content_id,
FROM dbo.tblIVContent c WITH (nolock)
JOIN #contentXML.nodes('/ROOT/V') AS R ( v ) ON c.content_id = R.v.value('#a', 'INT')
END
SQL Server does not parse XML very quickly so the use of the SplitStrings function might be more performant. Just wanted to provide an alternative.
I can think of a few options, but as I was typing one of them (the Split option) was posted by Mr. #Bertrand above. The only problem with it is that SQL just isn't that good at string manipulation.
So, another option would be to use a #Temp table that your sproc assumes will be present. Build dynamic SQL to the following effect:
Start a transaction, CREATE TABLE #InsertData with the shape you need, then loop over the data you are going to insert, using INSERT INTO #InsertData SELECT <values> UNION ALL SELECT <values>....
There are some limitations to this approach, one of which is that as the data set becomes very large you may need to split the INSERTs into batches. (I don't recall the specific error I got when I learned this myself, but for very long lists of values I have had SQL complain.) The solution, though, is simple: just generate a series of INSERTs with a smaller number of rows each. For instance, you might do 10 INSERT SELECTs with 1000 UNION ALLs each instead of 1 INSERT SELECT with 10000 UNION ALLs. You can still pass the entire batch as a part of a single command.
The advantage of this (despite its various disadvantages-- the use of temporary tables, long command strings, etc) is that it offloads all the string processing to the much more efficient C# side of the equation and doesn't require an additional persistent database object (the Split function; though, again, who doesn't need one of these sometimes)?
If you DO go with a Split() function, I'd encourage you to offload this to a SQLCLR function, and NOT a T-SQL UDF (for the performance reasons illustrated by the link above).
Finally, whatever method you choose, note that you'll have more problems if your data can include strings that contain the delimiter (for instance, In Aaron's answer you run into problems if the data is:
'I pity the foo!', 20120101, 26; 'bar, I say, bar!', 20120612, 32
Again, because C# is better at string handling than T-SQL, you'll be better off without using a T-SQL UDF to handle this.
Edit
Please note the following additional point to think about for the dynamic INSERT option.
You need to decide whether any input here is potentially dangerous input and would need to be cleaned before use. You cannot easily parameterize this data, so this is a significant one. In the place I used this strategy, I already had strong guarantees about the type of the data (in particular, I have used it for seeding a table with a list of integer IDs to process, so I was iterating over integers and not arbitrary, untrusted strings). If you don't have similar assurances, be aware of the dangers of SQL injection.

MSQL Query Get the Int value of a Hex value of an Integer

Background
Just to give you come context I have included a little background as to why I'm trying to do this:
I have a table 'cp' which has an [id] field which is the identifier for each registration I have come realize that this is to be moved from a deployed solution to a cloud based as a result multiple accounts will be accessing the same objects and it would be best if they all start at 1 so rather than use the id field I have created an int field called label which will be used in conjunction with the account id to find the unique record per account.
field | type
________________________
id | int
label | int
account_id | int
When the registrations are printed out on the screen I want them to be displayed as a 5 digit HEX number and I can simply do the following in the c# code
value.ToString("X")
However My problem exists with the existing entries int he database before I added the new label field.
I have found several questions with a similar but do not answer my question
Converting a paragraph to hex notatation, then back to string
Converting a String to HEX in SQL
Convert integer to hex and hex to integer
The Problem
i have the fields with ids (1,2,3,----,11,12,13,---etc) values 1-9 can simply be copied over as the value.ToString("X") will simply do nothing with those values however if I simply copied value 11 would return "B" which correctly is the HEX value of 11 however I want it to return "11" so I need an SQL script which will convert 11 => 17 (which is the hex value of 0x11) so that when the application reads it it will output the value 11 to the screen.
This is only to occur once to convert the exiting id's which is why I'm wanting an SQL script to do it all in one batch rather than build it into the application.
What I would Like would be something like below so that it assumes the value in would be a hex value and convert it back to an Int.
SELECT CONVERT(INT, '0x' + id) FROM cp;
I can get the correct effect in C# by doing the following
int.Parse(value.ToString(),System.Globalization.NumberStyles.HexNumber).ToString()
UPDATE: I have found the solution to this problem for my use-case but some confusion has been raised that I am going to be doing all of my conversion in the database, I assume by the SELECT statement above. To clarify that this is a one-time event to convert existing records. Note the below statement does not work but stackoverflow will not let me edit the original statement as the change is less than 15 characters.
UPDATE [dbname].[dbo].[cp] set [Label] = CONVERT(INT, '0x' + id);
Found the Answer, it's not pretty but it solves my problem so I thought I would share it with you. If anyone has a simpler solution that would be great but in the mean time here we go.
Create a MSQL Function
CREATE FUNCTION ConvertFromBase
(
#value AS VARCHAR(MAX),
#base AS BIGINT
) RETURNS BIGINT AS BEGIN
-- just some variables
DECLARE #characters CHAR(36),
#result BIGINT,
#index SMALLINT;
-- initialize our charater set, our result, and the index
SELECT #characters = '0123456789abcdefghijklmnopqrstuvwxyz',
#result = 0,
#index = 0;
-- make sure we can make the base conversion. there can't
-- be a base 1, but you could support greater than base 36
-- if you add characters to the #charater string
IF #base < 2 OR #base > 36 RETURN NULL;
-- while we have characters to convert, convert them and
-- prepend them to the result. we start on the far right
-- and move to the left until we run out of digits. the
-- conversion is the standard (base ^ index) * digit
WHILE #index < LEN(#value)
SELECT #result = #result + POWER(#base, #index) *
(CHARINDEX
(SUBSTRING(#value, LEN(#value) - #index, 1)
, #characters) - 1
),
#index = #index + 1;
-- return the result
RETURN #result;
END
Then simply execute the following query
UPDATE [cp] SET label = dbo.ConvertFromBase(id,16);
This solution was discovered in http://dpatrickcaldwell.blogspot.com/2009/05/converting-hexadecimal-or-binary-to.html
You should NOT be doing your number formatting in SQL. Let the ints come out of the database as ints, and format them in your presentation layer. You completely haven't mentioned what that presentation layer is, so it's impossible to say more, but if you do this, you're DOOMED when your boss comes over and says that some users want to see the hexadecimal, and some want to see base-10.

Categories

Resources