Insert results of SELECT Statement into SQL input Parameter - sql-server

I have a comma delimited list of column names that I am casting to XML and then selecting. I would like to then insert these into the #selectedRows input parameter, which will be called in a stored procedure. How do you insert these multiple values into the #selectedRows parameter? Thanks!
Convert Comma Delimited List to XML and Select:
DECLARE #xml as xml,#string as varchar(1000),#delimiter as varchar(10)
SET #string='Column1,Column2,Column3,Column4,Column5'
SET #delimiter =','
SET #xml = cast(('<X>'+replace(#string,#delimiter ,'</X><X>')+'</X>') as xml)
SELECT N.value('.', 'varchar(25)') as value FROM #xml.nodes('X') as T(N)
This will be in stored procedure:
Select #selectedRows
from Test
where TestField > TestField

you do not need to split the comma delimited string of column names, but you would need to use dynamic sql for this something like this....
Declare #Sql NVarChar(Max);
Declare #string VarChar(1000) = 'Column1,Column2,Column3,Column4,Column5';
SET #Sql = N' SELECT ' + #string
+ N' From Test '
+ N' where TestField > TestField'
Exec sp_executesql #Sql

Related

How to select alias name from a table in sql server?

I have a language table and I want to select aliases from that table according to the specified language.
ALTER PROCEDURE sp_executesql
(#parameter1 NVARCHAR(MAX)
,#parameter2 NVARCHAR(MAX)
,#code NVARCHAR(MAX),#language NVARCHAR(MAX))
DECLARE #sql NVARCHAR(MAX)
SET #sql = 'SELECT '+#parameter1+' AS (SELECT #language FROM Languages WHERE code=somecolumn) '+#paramter2+' AS (SELECT #language FROM Languages WHERE code='+#code+') FROM mytable'
EDIT:
in Stored Procedure, I need something like that.
Thanks for answers..
You cannot use a subquery to build an alias in that way, you would need to use dynamic sql to do this.
DECLARE #language NVARCHAR(255) -- or whatever type your field is
SELECT #language=language FROM Languages WHERE code=#code
DECLARE #sql NVARCHAR(MAX) = 'SELECT ' + #parameter1 + ' AS ' + QUOTENAME(#language) + ' FROM MyTable'
EXEC sp_executesql #sql
(Note the inclusion of QUOTENAME around the alias - this is a safety feature in case of your alias names having invalid characters.)
You can repeat the code above for the second parameter inside your stored procedure.
Try this:
CREATE PROCEDURE sp_NameOfSP
(#parameter1 NVARCHAR(MAX)
,#parameter2 NVARCHAR(MAX)
,#code NVARCHAR(MAX)
,#language NVARCHAR(MAX))
AS
BEGIN
DECLARE #sql NVARCHAR(MAX)
SELECT TOP(1) #language=LanguageColumn FROM Languages WHERE code=somecolumn
SET #sql = 'SELECT '+#parameter1+' AS '+#language+', '
SELECT TOP(1) #language=LanguageColumn FROM Languages WHERE code=#code
SET #sql=#sql+#paramter2+' AS '+#language+' FROM mytable'
EXEC(#SQL)
END
Replace LanguageColumn with proper column name from Languages table

How to use local-name(.) within dynamic sql statement

I have the following code to create a SQL function that will parse an XML string and create a table of key value pairs that represent the nodes and values. This works fine for me in my use cases.
CREATE FUNCTION XmlToKeyValue
(
#rootName AS varchar(256),
#xml AS Xml
)
RETURNS #keyval TABLE ([key] varchar(max), [value] varchar(max))
AS
BEGIN
DECLARE #input TABLE (XmlData XML NOT NULL)
INSERT INTO #input VALUES(#xml)
INSERT #keyval ([key], [value])
SELECT
XC.value('local-name(.)', 'varchar(max)') AS [key],
XC.value('(.)[1]', 'varchar(max)') AS [value]
FROM
#input
CROSS APPLY
XmlData.nodes('/*[local-name()=sql:variable("#rootName")]/*') AS XT(XC)
RETURN
END
What I am trying to do is have a stored procedure in my main database that will create another database with all the appropriate functions/procedures/etc. So in that stored procedure I am trying to do something like this:
SET #cmd = '
CREATE FUNCTION XmlToKeyValue
(
#rootName AS varchar(256),
#xml AS Xml
)
RETURNS #keyval TABLE ([key] varchar(max), [value] varchar(max))
AS
BEGIN
DECLARE #input TABLE (XmlData XML NOT NULL)
INSERT INTO #input VALUES(#xml)
INSERT #keyval ([key], [value])
SELECT
XC.value(''local-name(.)'', ''varchar(max)'') AS [key],
XC.value(''(.)[1]'', ''varchar(max)'') AS [value]
FROM
#input
CROSS APPLY
XmlData.nodes(''/*[local-name()=sql:variable("#rootName")]/*'') AS XT(XC)
RETURN
END
'
BEGIN TRY
EXEC(N'USE '+#dbName+'; EXEC sp_executesql N''' + #cmd + '''; USE master')
END TRY
BEGIN CATCH
PRINT 'Error creating XmlToKeyValue'
Print Error_Message();
RETURN
END CATCH
However, I am getting the following error that I can't figure out how to resolve.
Error creating XmlToKeyValue
Incorrect syntax near 'local'.
Can I use local-name in a dynamic sql statement? If not, how can I achieve my goal? Thank you.
The problem is not the local-name function. It is entirely the fact that you are concatenating in the #cmd variable into your Dynamic SQL without properly escaping the embedded single-quotes.
This line:
EXEC(N'USE '+#dbName+'; EXEC sp_executesql N''' + #cmd + '''; USE master')
should be:
SET #cmd = REPLACE(#cmd, N'''', N'''''');
EXEC(N'USE ' + #dbName + N'; EXEC sp_executesql N''' + #cmd + N''';');
Else you are embedding:
XC.value(''local-name(
into the string, but using the same number of escape sequences, hence the XC.value( now becomes the end of the string and the local-name(.) is technically unescaped SQL and not part of a string.
Also:
You don't need the USE master at the end of the Dynamic SQL (so I removed it).
You prefixed the first string literal with N but none of the others (I added the N in for the others so that they all have that prefix).

How to parse a comma-delimited string variable in SQL

I am working in SQL Server 2008. I have a stored proc that takes a parameter, called #test. This parameter is varchar(255). In this stored proc, I need to parse this string, convert each value into a string itself (there will be a variable number of values), and build a list to use in a NOT IN statement.
For example, suppose #test = 'a, b, c, d'. I need to send this parameter into my stored proc. There is a SELECT query in my stored proc that uses a NOT IN statement. For this example, I need this NOT IN statement to read NOT IN('a', 'b', 'c', 'd').
How do I accomplish this? Or, is this a bad practice?
Use Split function something along with NOT EXISTS operator almost always faster than NOT IN operator
Split Function
CREATE FUNCTION [dbo].[split]
(
#delimited NVARCHAR(MAX),
#delimiter NVARCHAR(100)
)
RETURNS #t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
AS
BEGIN
DECLARE #xml XML
SET #xml = N'<t>' + REPLACE(#delimited,#delimiter,'</t><t>') + '</t>'
INSERT INTO #t(val)
SELECT r.value('.','varchar(MAX)') as item
FROM #xml.nodes('/t') as records(r)
RETURN
END
Test Data
DECLARE #Table TABLE (Vals INT)
INSERT INTO #Table VALUES (1), (2), (3), (4)
DECLARE #test VARCHAR(256) = '3,4,5,6'
SELECT * FROM #Table
WHERE NOT EXISTS (SELECT 1
FROM [dbo].[split](#test , ',')
WHERE val = Vals)
Result
Vals
1
2
You can use dynamic sql for this. Use the replace function to get the correct NOT IN() argument:
DECLARE #test VARCHAR(10) = 'a,b,c,d'
DECLARE #sql NVARCHAR(MAX)
SELECT #sql = 'SELECT *
FROM #temp
WHERE label NOT IN (''' + REPLACE( #test ,',',''',''') + ''')'
EXEC sp_executesql #sql
you can find the char or string in the whole string having delimiter as comma (,)
DECLARE #code VARCHAR(50) = 'c', #text varchar(100) = 'a,b,c,d,e'
SELECT CHARINDEX(',' + #code + ',', ',' + #text + ',') > 0

Create a sql query that can handle multiple check box selections

I have a form with 3 check box dropdown lists enabling multiple selection from each control.
Lets say for talking sake its an accommodation table I am querying and the check box dropdown lists are 'AccommodationName', 'Company', and 'Nights'.
So potentially I could be passing in multiple values from each control and I want to return an aggregated query relevant to all data input.
How should I be going about this query?
Is the query going to have to be dynamic sql?
Please note, I am using sql server 2005.
You will need to create a split function inside you database,
Definition Of Split Function
CREATE FUNCTION [dbo].[split]
(
#delimited NVARCHAR(MAX),
#delimiter NVARCHAR(100)
)
RETURNS #t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
AS
BEGIN
DECLARE #xml XML
SET #xml = N'<t>' + REPLACE(#delimited,#delimiter,'</t><t>') + '</t>'
INSERT INTO #t(val)
SELECT r.value('.','varchar(MAX)') as item
FROM #xml.nodes('/t') as records(r)
RETURN
END
Stored Procedure
Then you need to create a stored procedure which will build sql query dynamically and use this split function to handle multiple values passed as a comma deliminated list.
CREATE PROCEDURE GetData
#AccommodationName VARCHAR(1000) = NULL,
#Company VARCHAR(1000) = NULL,
#Nights VARCHAR(1000) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX);
SET #SQL = N' SELECT * FROM TableName WHERE 1 = 1 '
+ CASE WHEN #AccommodationName IS NOT NULL
THEN N' AND AccommodationName IN (SELECT Val FROM dbo.split(#AccommodationName )) '
ELSE N'' END
+ CASE WHEN #Company IS NOT NULL
THEN N' AND Company IN (SELECT Val FROM dbo.split(#Company)) '
ELSE N'' END
+ CASE WHEN #Nights IS NOT NULL
THEN N' AND Nights IN (SELECT Val FROM dbo.split(#Nights)) '
ELSE N'' END
EXECUTE sp_executesql #SQL
,N'#AccommodationName VARCHAR(1000), #Company VARCHAR(1000), #Nights VARCHAR(1000)'
,#AccommodationName
,#Company
,#Nights
END

Deleting Multiple Nodes in Single XQuery for SQL Server

I have:
a table with an xml type column (list of IDs)
an xml type parameter (also list of IDs)
What is the best way to remove nodes from the column that match the nodes in the parameter, while leaving any unmatched nodes untouched?
e.g.
declare #table table (
[column] xml
)
insert #table ([column]) values ('<r><i>1</i><i>2</i><i>3</i></r>')
declare #parameter xml
set #parameter = '<r><i>1</i><i>2</i></r>'
-- this is the problem
update #table set [column].modify('delete (//i *where text() matches #parameter*)')
The MSDN documentation indicates it should be possible (in Introduction to XQuery in SQL Server 2005):
This stored procedure can easily be
modified to accept an XML fragment
which contains one or more skill
elements thereby allowing the user to
delete multiple skill nodes with a
single invocation of stored procedure.
While the delete is a little awkward to do this way, you can instead do an update to change the data, provided your data is simple (such as the example you gave). The following query will basically split the two XML strings into tables, join them, exclude the non-null (matching) values, and convert it back to XML:
UPDATE #table
SET [column] = (
SELECT p.i.value('.','int') AS c
FROM [column].nodes('//i') AS p(i)
OUTER APPLY (
SELECT x.i.value('.','bigint') AS i
FROM #parameter.nodes('//i') AS x(i)
WHERE p.i.value('.','bigint') = x.i.value('.','int')
) a
WHERE a.i IS NULL
FOR XML PATH(''), TYPE
)
You need something in the form:
[column].modify('delete (//i[.=(1, 2)])') -- like SQL IN
-- or
[column].modify('delete (//i[.=1 or .=2])')
-- or
[column].modify('delete (//i[.=1], //i[.=2])')
-- or
[column].modify('delete (//i[contains("|1|2|",concat("|",.,"|"))])')
XQuery doesn't support xml SQL types in SQL2005, and the modify method only accepts string literals (no variables allowed).
Here's an ugly hack w/ the contains function:
declare #table table ([column] xml)
insert #table ([column]) values ('<r><i>1</i><i>2</i><i>3</i></r>')
declare #parameter xml
set #parameter = '<r><i>1</i><i>2</i></r>'
-- build a pipe-delimited string
declare #in nvarchar(max)
set #in = convert(nvarchar(max),
#parameter.query('for $i in (/r/i) return concat(string($i),"|")')
)
set #in = '|'+replace(#in,'| ','|')
update #table set [column].modify ('
delete (//i[contains(sql:variable("#in"),concat("|",.,"|"))])
')
select * from #table
Here's another w/ dynamic SQL:
-- replace table variable with temp table to get around variable scoping
if object_id('tempdb..#table') is not null drop table #table
create table #table ([column] xml)
insert #table ([column]) values ('<r><i>1</i><i>2</i><i>3</i></r>')
declare #parameter xml
set #parameter = '<r><i>1</i><i>2</i></r>'
-- we need dymamic SQL because the XML modify method only permits string literals
declare #sql nvarchar(max)
set #sql = convert(nvarchar(max),
#parameter.query('for $i in (/r/i) return concat(string($i),",")')
)
set #sql = substring(#sql,1,len(#sql)-1)
set #sql = 'update #table set [column].modify(''delete (//i[.=('+#sql+')])'')'
print #sql
exec (#sql)
select * from #table
if you are updating an xml variable rather than a column, use sp_executesql and output parameters:
declare #xml xml
set #xml = '<r><i>1</i><i>2</i><i>3</i></r>'
declare #parameter xml
set #parameter = '<r><i>1</i><i>2</i></r>'
declare #sql nvarchar(max)
set #sql = convert(nvarchar(max),
#parameter.query('for $i in (/r/i) return concat(string($i),",")')
)
set #sql = substring(#sql,1,len(#sql)-1)
set #sql = 'set #xml.modify(''delete (//i[.=('+#sql+')])'')'
exec sp_executesql #sql, N'#xml xml output', #xml output
select #xml
Alternate method using a cursor to iterate through delete values, probably less efficient due to multiple updates:
declare #table table ([column] xml)
insert #table ([column]) values ('<r><i>1</i><i>2</i><i>3</i></r>')
declare #parameter xml
set #parameter = '<r><i>1</i><i>2</i></r>'
/*
-- unfortunately, this doesn't work:
update t set [column].modify('delete (//i[.=sql:column("p.i")])')
from #table t, (
select i.value('.', 'nvarchar')
from #parameter.nodes('//i') a (i)
) p (i)
select * from #table
*/
-- so we have to use a cursor
declare #cursor cursor
set #cursor = cursor for
select i.value('.', 'varchar') as i
from #parameter.nodes('//i') a (i)
declare #i int
open #cursor
while 1=1 begin
fetch next from #cursor into #i
if ##fetch_status <> 0 break
update #table set [column].modify('delete (//i[.=sql:variable("#i")])')
end
select * from #table
More information on the limitations of XML variables in SQL2005 here:
http://blogs.msdn.com/denisruc/archive/2006/05/17/600250.aspx
Does anyone have a better way?

Resources