We all know that prepared statements are one of the best way of fending of SQL injection attacks. What is the best way of creating a prepared statement with an "IN" clause. Is there an easy way to do this with an unspecified number of values? Take the following query for example.
SELECT ID,Column1,Column2 FROM MyTable WHERE ID IN (1,2,3)
Currently I'm using a loop over my possible values to build up a string such as.
SELECT ID,Column1,Column2 FROM MyTable WHERE ID IN (#IDVAL_1,#IDVAL_2,#IDVAL_3)
Is it possible to use just pass an array as the value of the query paramter and use a query as follows?
SELECT ID,Column1,Column2 FROM MyTable WHERE ID IN (#IDArray)
In case it's important I'm working with SQL Server 2000, in VB.Net
Here you go - first create the following function...
Create Function [dbo].[SeparateValues]
(
#data VARCHAR(MAX),
#delimiter VARCHAR(10)
)
RETURNS #tbldata TABLE(col VARCHAR(10))
As
Begin
DECLARE #pos INT
DECLARE #prevpos INT
SET #pos = 1
SET #prevpos = 0
WHILE #pos > 0
BEGIN
SET #pos = CHARINDEX(#delimiter, #data, #prevpos+1)
if #pos > 0
INSERT INTO #tbldata(col) VALUES(LTRIM(RTRIM(SUBSTRING(#data, #prevpos+1, #pos-#prevpos-1))))
else
INSERT INTO #tbldata(col) VALUES(LTRIM(RTRIM(SUBSTRING(#data, #prevpos+1, len(#data)-#prevpos))))
SET #prevpos = #pos
End
RETURN
END
then use the following...
Declare #CommaSeparated varchar(50)
Set #CommaSeparated = '112,112,122'
SELECT ID,Column1,Column2 FROM MyTable WHERE ID IN (select col FROM [SeparateValues](#CommaSeparated, ','))
I think sql server 2008 will allow table functions.
UPDATE
You'll squeeze some extra performance using the following syntax...
SELECT ID,Column1,Column2 FROM MyTable
Cross Apply [SeparateValues](#CommaSeparated, ',') s
Where MyTable.id = s.col
Because the previous syntax causes SQL Server to run an extra "Sort" command using the "IN" clause. Plus - in my opinion it looks nicer :D!
If you would like to pass an array, you will need a function in sql that can turn that array into a sub-select.
These functions are very common, and most home grown systems take advantage of them.
Most commercial, or rather professional ORM's do ins by doing a bunch of variables, so if you have that working, I think that is the standard method.
You could create a temporary table TempTable with a single column VALUE and insert all IDs. Then you could do it with a subselect:
SELECT ID,Column1,Column2 FROM MyTable WHERE ID IN (SELECT VALUE FROM TempTable)
Go with the solution posted by digiguru. It's a great reusable solution and we use the same technique as well. New team members love it, as it saves time and keeps our stored procedures consistent. The solution also works well with SQL Reports, as the parameters passed to stored procedures to create the recordsets pass in varchar(8000). You just hook it up and go.
In SQL Server 2008, they finally got around to addressing this classic problem by adding a new "table" datatype. Apparently, that lets you pass in an array of values, which can be used in a sub-select to accomplish the same as an IN statement.
If you're using SQL Server 2008, then you might look into that.
Here's one technique I use
ALTER Procedure GetProductsBySearchString
#SearchString varchar(1000),
as
set nocount on
declare #sqlstring varchar(6000)
select #sqlstring = 'set nocount on
select a.productid, count(a.productid) as SumOf, sum(a.relevence) as CountOf
from productkeywords a
where rtrim(ltrim(a.term)) in (''' + Replace(#SearchString,' ', ''',''') + ''')
group by a.productid order by SumOf desc, CountOf desc'
exec(#sqlstring)
Related
This is what I am trying to do
EXEC sp1 1
SELECT * FROM x
UNION
if(#num <= 1)
EXEC sp1(2)
else
null //want to return null to stop
I could do this is with a programming language but I don't have an idea what is that I am doing wrong with programming in SQL?
This, honestly, makes no sense, and I still suggest that you use an inline Table-value function here, instead of a procedure, but you can do something like this using OPENROWSET to return the dataset from a stored procedure within a SELECT statement. It can't be parametrised though (not in the traditional sense), and if you don't understand this, don't use it.
This is pseudo SQL as well as there's a lack of enough information to provide a complete solution, such as the columns needed in the SELECTs, but it might get you there if you can comprehend it:
EXEC dbo.sp1 1;
SELECT {Columns}
FROM dbo.x
UNION ALL
SELECT {Same Columns again} --This dataset's definition must be IDENTICAL to the above against your table dbo.x
FROM OPENROWSET('SQLNCLI', 'Server=localhost;Trusted_Connection=Yes;Database={YourDatabase}','EXEC dbo.sp1(2);') ORS; --Assumes you are using Windows Authentication
WHERE #Num <= 1
UNION ALL
SELECT NULL,NULL,NULL{,NULL...} --Until you have you enough NULL columns
Note that in this example I am using the deprecated SQLNCLI connection manager. You should really be using MSOLEDBSQL, however, the only instance I currently have access to with a trusted connection is a 2012 instance which doesn't have that driver installed; so I didn't want to post code that I hadn't minimally tested.
You can achieve this in SQL this way:
SELECT * into #temp FROM x
if(#num <= 1)
begin
insert into #temp
EXEC sp1(2)
select * from #temp
end
else
begin
select null
end
First you create a temp table and insert x table records into it and after that you check your condition and then insert records from procedure and then select * from #temp and other case it will return null.
say if I have a column containing
hello;world;how;are;you;
how do I write a sql command to just delete "hello world" or "how are you".
thanks
trojanfoe wrote what you need to do with a correct database design. If you can't change this I may have a possible way todo it.
I doubt it is the fastest way to do so, but you can fetch the data, split it into a new virtual table and then remove what you need, the result you can add again in your column.
There is no split in SQL, so here is a split function (MS SQL, maybe need to modify for mySQL):
CREATE FUNCTION dbo.fn_Split(#text nvarchar(4000), #delimiter char(1) = ',')
RETURNS #Strings TABLE (
position int IDENTITY PRIMARY KEY,
value nvarchar(4000)
)
AS
BEGIN
DECLARE #index int
SET #index = -1
SET #text = RTRIM(LTRIM(#text))
WHILE (LEN(#text) > 0)
BEGIN
SET #index = CHARINDEX(#delimiter , #text)
IF (#index = 0) AND (LEN(#text) > 0)
BEGIN
INSERT INTO #Strings VALUES (#text)
BREAK
END
IF (#index > 1)
BEGIN
INSERT INTO #Strings VALUES (LEFT(#text, #index - 1))
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
ELSE
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
RETURN
END
With this you can do stuff like this:
SELECT * FROM dbo.fn_Split(<yourcolumn>,';')
Which gives you then your content as single rows.
Then you can remove items by using a WHERE statement like
SELECT * FROM dbo.fn_Split(<yourcolumn>,';') WHERE [value] LIKE '%hello%'
You might need to write a PROCEDURE then or a TABLE/SCALAR FUNCTION to handle you data with this.
So isn't a good way to do but the only way I found out so far :) Hope it helps a but.
If this column contains multiple items that you want to manipulate then putting them into a single column is a schema design flaw.
You are not taking advantage of the relational nature of the database and you should store these items in a child table of the main table. You will then be able to define ordering in this child table (with a separate column) and will be able to manipulate (i.e. SELECT, DELETE, etc) individual elements far easier.
Guess if it's a legacy database structure it cannot be changed easily.
Btw the answer is updating the field. Since almost every database engine nowadays allows you to write a stored function, I guess that's a good solution.
Using Oracle's Syntax something like this would work:
UPDATE TABLE SET FIELD_NAME = STORED_FUNCTION_MANIPULATING_THE_FIELD(FIELD_NAME) WHERE [where condition]
but before executing the query, you should define the stored function.
FUNCTION STORED_FUNCTION_MANIPULATING_THE_FIELD(FIELD_NAME VARCHAR2) RETURN VARCHAR2 IS
BEGIN
-- Do whatever you want on field, then return it.
END STORED_FUNCTION_MANIPULATING_THE_FIELD;
However this is conceptually wrong, multiple data values, shall use multiple columns in order to take fully advantage of relational engines (as stated in previous answers).
Regards
M.
UPDATE tablename SET field=replace(replace(field,'hello;world;',''),';',' ') [WHERE condition]
UPDATE tablename SET field=replace(mid(field,locate('how',field)),';',' ') [WHERE condition]
UPDATE tablename SET field=replace(mid(field,1,locate(';how',field)),';',' ') [WHERE condition]
UPDATE tablename SET field=replace(substring_index(field,';',2),';',' ') [WHERE condition]
(If you use a -2 in the substring_index function, you get the other part of the text.)
One of those will give you what you want. The use of substring_index is probably the most flexible way.
Consider the table SAMPLE:
id integer
name nvarchar(10)
There is a stored proc called myproc. It takes only one paramater ( which is id)
Given a name as parameter, find all rows with the name = #nameparameter and pass all those ids
to myproc
eg:
sample->
1 mark
2 mark
3 stu
41 mark
When mark is passed, 1 ,2 and 41 are to be passed to myproc individually.
i.e. the following should happen:
execute myproc 1
execute myproc 2
execute myproc 41
I can't touch myproc nor see its content. I just have to pass the values to it.
If you must iterate(*), use the construct designed to do it - the cursor. Much maligned, but if it most clearly expresses your intentions, I say use it:
DECLARE #ID int
DECLARE IDs CURSOR LOCAL FOR select ID from SAMPLE where Name = #NameParameter
OPEN IDs
FETCH NEXT FROM IDs into #ID
WHILE ##FETCH_STATUS = 0
BEGIN
exec myproc #ID
FETCH NEXT FROM IDs into #ID
END
CLOSE IDs
DEALLOCATE IDs
(*) This answer has received a few upvotes recently, but I feel I ought to incorporate my original comment here also, and add some general advice:
In SQL, you should generally seek a set-based solution. The entire language is oriented around set-based solutions, and (in turn) the optimizer is oriented around making set-based solutions work well. In further turn, the tools that we have available for tuning the optimizer is also set-oriented - e.g. applying indexes to tables.
There are a few situations where iteration is the best approach. These are few are far between, and may be likened to Jackson's rules on optimization - don't do it - and (for experts only) don't do it yet.
You're far better served to first try to formulate what you want in terms of the set of all rows to be affected - what is the overall change to be achieved? - and then try to formulate a query that encapsulates that goal. Only if the query produced by doing so is not performing adequately (or there's some other component that is unable to do anything other than deal with each row individually) should you consider iteration.
I just declare the temporary table #sample and insert the all rows which have the name='rahul' and also take the status column to check that the row is iterated.and using while loop i iterate through the all rows of temporary table #sample which have all the ids of name='rahul'
use dumme
Declare #Name nvarchar(50)
set #Name='Rahul'
DECLARE #sample table (
ID int,
Status varchar(500)
)
insert into #sample (ID,status) select ID,0 from sample where sample=#name
while ((select count(Id) from #sample where status=0 )>0)
begin
select top 1 Id from #sample where status=0 order by Id
update #sample set status=1 where Id=(select top 1 Id from #sample where status=0 order by Id)
end
Declare #retStr varchar(100)
select #retStr = COALESCE(#retStr, '') + sample.ID + ', '
from sample
WHERE sample.Name = #nameparameter
select #retStr = ltrim(rtrim(substring(#retStr , 1, len(#retStr )- 1)))
Return ISNULL(#retStr ,'')
I've used dynamic SQL for many tasks and continuously run into the same problem: Printing values of variables used inside the Dynamic T-SQL statement.
EG:
Declare #SQL nvarchar(max), #Params nvarchar(max), #DebugMode bit, #Foobar int
select #DebugMode=1,#Foobar=364556423
set #SQL='Select #Foobar'
set #Params=N'#Foobar int'
if #DebugMode=1 print #SQL
exec sp_executeSQL #SQL,#Params
,#Foobar=#Foobar
The print results of the above code are simply "Select #Foobar". Is there any way to dynamically print the values & variable names of the sql being executed? Or when doing the print, replace parameters with their actual values so the SQL is re-runnable?
I have played with creating a function or two to accomplish something similar, but with data type conversions, pattern matching truncation issues, and non-dynamic solutions. I'm curious how other developers solve this issue without manually printing each and every variable manually.
I dont believe the evaluated statement is available, meaning your example query 'Select #FooBar' is never persisted anywhere as 'Select 364556243'
Even in a profiler trace you would see the statement hit the cache as '(#Foobar int)select #foobar'
This makes sense, since a big benefit of using sp_executesql is that it is able to cache the statement in a reliable form without variables evaluated, otherwise if it replaced the variables and executed that statement we would just see the execution plan bloat.
updated: Here's a step in right direction:
All of this could be cleaned up and wrapped in a nice function, with inputs (#Statement, #ParamDef, #ParamVal) and would return the "prepared" statement. I'll leave some of that as an exercise for you, but please post back when you improve it!
Uses split function from here link
set nocount on;
declare #Statement varchar(100), -- the raw sql statement
#ParamDef varchar(100), -- the raw param definition
#ParamVal xml -- the ParamName -to- ParamValue mapping as xml
-- the internal params:
declare #YakId int,
#Date datetime
select #YakId = 99,
#Date = getdate();
select #Statement = 'Select * from dbo.Yak where YakId = #YakId and CreatedOn > #Date;',
#ParamDef = '#YakId int, #Date datetime';
-- you need to construct this xml manually... maybe use a table var to clean this up
set #ParamVal = ( select *
from ( select '#YakId', cast(#YakId as varchar(max)) union all
select '#Date', cast(#Date as varchar(max))
) d (Name, Val)
for xml path('Parameter'), root('root')
)
-- do the work
declare #pStage table (pName varchar(100), pType varchar(25), pVal varchar(100));
;with
c_p (p)
as ( select replace(ltrim(rtrim(s)), ' ', '.')
from dbo.Split(',', #ParamDef)d
),
c_s (pName, pType)
as ( select parsename(p, 2), parsename(p, 1)
from c_p
),
c_v (pName, pVal)
as ( select p.n.value('Name[1]', 'varchar(100)'),
p.n.value('Val[1]', 'varchar(100)')
from #ParamVal.nodes('root/Parameter')p(n)
)
insert into #pStage
select s.pName, s.pType, case when s.pType = 'datetime' then quotename(v.pVal, '''') else v.pVal end -- expand this case to deal with other types
from c_s s
join c_v v on
s.pName = v.pName
-- replace pName with pValue in statement
select #Statement = replace(#Statement, pName, isnull(pVal, 'null'))
from #pStage
where charindex(pName, #Statement) > 0;
print #Statement;
On the topic of how most people do it, I will only speak to what I do:
Create a test script that will run the procedure using a wide range of valid and invalid input. If the parameter is an integer, I will send it '4' (instead of 4), but I'll only try 1 oddball string value like 'agd'.
Run the values against a data set of representative size and data value distribution for what I'm doing. Use your favorite data generation tool (there are several good ones on the market) to speed this up.
I'm generally debugging like this on a more ad hoc basis, so collecting the results from the SSMS results window is as far as I need to take it.
The best way I can think of is to capture the query as it comes across the wire using a SQL Trace. If you place something unique in your query string (as a comment), it is very easy to apply a filter for it in the trace so that you don't capture more than you need.
However, it isn't all peaches & cream.
This is only suitable for a Dev environment, maybe QA, depending on how rigid your shop is.
If the query takes a long time to run, you can mitigate that by adding "TOP 1", "WHERE 1=2", or a similar limiting clause to the query string if #DebugMode = 1. Otherwise, you could end up waiting a while for it to finish each time.
For long queries where you can't add something the query string only for debug mode, you could capture the command text in a StmtStarted event, then cancel the query as soon as you have the command.
If the query is an INSERT/UPDATE/DELETE, you will need to force a rollback if #DebugMode = 1 and you don't want the change to occur. In the event you're not currently using an explicit transaction, doing that would be extra overhead.
Should you go this route, there is some automation you can achieve to make life easier. You can create a template for the trace creation and start/stop actions. You can log the results to a file or table and process the command text from there programatically.
Here's a problem I've been trying to solve at work. I'm not a database expert, so that perhaps this is a bit sophomoric. All apologies.
I have a given database D, which has been duplicated on another machine (in a perhaps dubious manner), resulting in database D'. It is my task to check that database D and D' are in fact exactly identical.
The problem, of course, is what to actually do if they are not. For this purpose, my thought was to run a symmetric difference on each corresponding table and see the differences.
There is a "large" number of tables, so I do not wish to run each symmetric difference by hand. How do I then implement a symmetric difference "function" (or stored procedure, or whatever you'd like) that can run on arbitrary tables without having to explicitly enumerate the columns?
This is running on Windows, and your hedge fund will explode if you don't follow through. Good luck.
Here is the solution. The example data is from the ReportServer database that comes with SSRS 2008 R2, but you can use it on any dataset:
SELECT s.name, s.type
FROM
(
SELECT s1.name, s1.type
FROM syscolumns s1
WHERE object_name(s1.id) = 'executionlog2'
UNION ALL
SELECT s2.name, s2.type
FROM syscolumns s2
WHERE object_name(s2.id) = 'executionlog3'
) AS s
GROUP BY s.name, s.type
HAVING COUNT(s.name) = 1
You can achieve this by doing something like this.
I have used a function to split comma separated value into a table to demostrate.
CREATE FUNCTION [dbo].[Split]
(
#RowData nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Data nvarchar(100)
)
AS
BEGIN
Declare #Cnt int
Set #Cnt = 1
While (Charindex(#SplitOn,#RowData)>0)
Begin
Insert Into #RtnValue (data)
Select
Data = ltrim(rtrim(Substring(#RowData,1,Charindex(#SplitOn,#RowData)-1)))
Set #RowData = Substring(#RowData,Charindex(#SplitOn,#RowData)+1,len(#RowData))
Set #Cnt = #Cnt + 1
End
Insert Into #RtnValue (data)
Select Data = ltrim(rtrim(#RowData))
Return
END
GO
DECLARE #WB_LIST varchar(1024) = '123,125,764,256,157';
DECLARE #WB_LIST_IN_DB varchar(1024) = '123,125,795,256,157,789';
DECLARE #TABLE_UPDATE_LIST_IN_DB TABLE ( id varchar(20));
DECLARE #TABLE_UPDATE_LIST TABLE ( id varchar(20));
INSERT INTO #TABLE_UPDATE_LIST
SELECT data FROM dbo.Split(#WB_LIST,',');
INSERT INTO #TABLE_UPDATE_LIST_IN_DB
SELECT data FROM dbo.Split(#LIST_IN_DB,',');
SELECT * FROM #TABLE_UPDATE_LIST
EXCEPT
SELECT * FROM #TABLE_UPDATE_LIST_IN_DB
UNION
SELECT * FROM #TABLE_UPDATE_LIST_IN_DB
EXCEPT
SELECT * FROM #TABLE_UPDATE_LIST;
My first reaction is to suggest duplicating to the other machine again in a non-dubious manner.
If that is not an option, perhaps some of the tools available from Red Gate could do what you need.
(I am in no way affliated with Red Gate, just remember Joel mentioning how good their tools were on the podcast.)
SQL Server 2000 Added the "EXCEPT" keyword, which is almost exactly the same as Oracle's "minus"
SELECT * FROM TBL_A WHERE ...
EXCEPT
SELECT * FROM TBL_B WHERE ...
Use the SQL Compare tools by Red Gate. It compares scheamas, and the SQL Data Compare tool compares data. I think that you can get a free trial for them, but you might as well buy them if this is a recurring problem. There may be open source or free tools like this, but you might as well just get this one.