Working with arrays in SQL Server - c#

I have a int array of ID's (a lot of checkboxes I can choose from) which I want to get in one database call though a stored procedure.
Is there a way to work with an array of these ID's in SQL Server? I believe it should be something with splitting the array and then loop it (in sql). I just don't know how?
SQL Server 2008

There are many ways to do this:
Pass in a varchar parameter of the values separated by commas and parse that out (not very efficient, but for a small amount of data, not too bad except for the parsing bit)
Pass in XML and use the built in XML functions (SQL Server 2005+ has better support for this than earlier versions)
Use table value parameters (SQL Server 2008+)
Since you are using SQL Server 2008, use table value parameters.

EDIT: Example below
As #Oded mentioned, table valued parameters is the best option.
However, if for some reason you can't use these (perhaps your calling framework's limitations), you can use the following to perform the split to table:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[SplitToTable]
(
#List varchar(max), #Delim varchar(1)
)
RETURNS TABLE
AS
RETURN
(
WITH csvtbl(Start, [Stop]) AS (
SELECT Start = convert(bigint, 1), [Stop] =
charindex(#Delim COLLATE Slovenian_BIN2, #list + #Delim)
UNION ALL
SELECT Start = [Stop] + 1, [Stop] = charindex(#Delim
COLLATE Slovenian_BIN2, #list + #Delim, [Stop] + 1)
FROM csvtbl
WHERE ([Stop] > 0)
)
SELECT substring(#list, Start, CASE WHEN [Stop] > 0 THEN [Stop] -
Start ELSE 0 END) AS Value
FROM csvtbl
WHERE ([Stop] > 0)
)
You need to be aware of the default recursion depth of 100. If this isn't enough, increase it by adding the following to your outer calling query:
OPTION (MAXRECURSION 1000) -- or 0 for unlimited
EXAMPLE
SELECT *
FROM MyTable as t
WHERE t.ID IN (
SELECT *
FROM dbo.SplitToTable('1,2,12,34,101', ',')
)
It can be used on joins, etc., too.

I think you need something like...
Declare #query as varchar(500)
Declare #valuesList as varchar(100)
set #valuesList = '1,2,3'
set #query = 'select * From tableName where id in ( ' + #valuesList + ')'
exec(#query)

TO REVERSE THE PROCESS
DECLARE #t TABLE
(
ID int
)
INSERT INTO #t
VALUES (1), (3), (5), (7), (9)
SELECT STUFF(
(
SELECT ',' + CAST(t.ID AS VARCHAR(10))
FROM #t t
FOR XML PATH('')
), 1, 1, '') AS CSV
Courtesy SQLAuthority.

Related

Batch delete operation procedure not working

I have a stored procedure which looks like following:
alter procedure [dbo].[zsp_deleteEndedItems]
(
#ItemIDList nvarchar(max)
)
as
delete from
SearchedUserItems
WHERE EXISTS (SELECT 1 FROM dbo.SplitStringProduction(#ItemIDList,',') S1 WHERE ItemID=S1.val)
The parameter IDList is passed like following:
124125125,125125125...etc etc
And the split string function look like following:
ALTER FUNCTION [dbo].[SplitStringProduction]
(
#string nvarchar(max),
#delimiter nvarchar(5)
) RETURNS #t TABLE
(
val nvarchar(500)
)
AS
BEGIN
declare #xml xml
set #xml = N'<root><r>' + replace(#string,#delimiter,'</r><r>') + '</r></root>'
insert into #t(val)
select
r.value('.','varchar(500)') as item
from #xml.nodes('//root/r') as records(r)
RETURN
END
This is supposed to delete all items from table "SearcheduserItems" under the IDs:
124125125 and 125125125
But for some reason after I do a select to check it out:
select * from SearchedUserItems
where itemid in('124125125','125125125')
The records are still there...
What am I doing wrong here? Can someone help me out?
As mentioned in the comments, a different option would be to use a table type parameter. This makes a couple of assumptions (some commented), however, should get you on the right path:
CREATE TYPE dbo.IDList AS TABLE (ItemID int NOT NULL); --Assumed int datatype;
GO
ALTER PROC dbo.zsp_deleteEndedItems #ItemIDList dbo.IDList READONLY AS
DELETE SUI
FROM dbo.SearchedUserItems SUI
JOIN #ItemIDList IDL ON SUI.ItemID = IDL.ItemID;
GO
--Example of usage
DECLARE #ItemList dbo.IDList;
INSERT INTO #ItemList
VALUES(123456),(123457),(123458);
EXEC dbo.zsp_deleteEndedItems #ItemList;
GO
In regards to the question of an inline table value function, one such example is the below, which I quickly wrote up, that provides a tally table of the next 1000 numbers:
CREATE FUNCTION dbo.NextThousand (#Start int)
RETURNS TABLE
AS RETURN
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)
)
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 + #Start AS I
FROM N N1 --10
CROSS JOIN N N2 --100
CROSS JOIN N N3; --1,000
GO
The important thing about an iTVF is that it has only one statement, and that is the RETURN statement. Declaring the table as a return type variable, inserting data into it, and returning that variable turns it into a multi-line TVF; which perform far slower.

Many SQL rows into one

I've got a stored procedure which joins a number of tables to produce a large resultset which is then returned to my application. The application in turn loops through the results and combines rows on a particular ID and chooses data per row to include in a new object. This is perhaps easiest to explain using an example:
Inspection, Desc, Value
1, Description1, 3
1, Description2, 2
1, Description3, 5
This is in code turned into
Inspection, Description1, Description2, Description3
1, 3, 2, 5
The point of this is to have one row per inspection item with item description as headers and value as the cell value for inspection row and header. This is then exported to Excel.
The question is: how do I do this in SQL Server, as in expanding my SP to return a lot fewer but "wider" rows with a lot more columns?
Another complication is that one inspection may have rows which another one lacks, in that case the solution is to add an empty value or a '-'.
P.S. This is using Sql Server 2012.
If you are using mssql 2005+. You can use a pivot like this:
Test data
DECLARE #tbl TABLE(Inspection INT, [Desc] VARCHAR(100),Value INT)
INSERT INTO #tbl
VALUES
(1,'Description1', 3),
(1,'Description2', 2),
(1,'Description3', 5)
Query
SELECT
*
FROM
(
SELECT
tbl.Inspection,
tbl.[Desc],
tbl.Value
FROM
#tbl AS tbl
) AS tbl
PIVOT
(
SUM(Value)
FOR [Desc] IN ([Description1],[Description2],[Description3])
)AS pvt
Result:
Inspection, Description1, Description2, Description3
1 3 2 5
Edit
As juharr said in the comment:
The resulting column names (values in the table) are when building the query. Which might require another initial query to get
Edit 2
If you are not using mssql 2005+. Or want to have and alternitive explanation. Please see the following query:
SELECT
tbl.Inspection,
SUM(CASE WHEN [Desc]='Description1' THEN tbl.Value ELSE 0 END) AS Description1,
SUM(CASE WHEN [Desc]='Description2' THEN tbl.Value ELSE 0 END) AS Description2,
SUM(CASE WHEN [Desc]='Description3' THEN tbl.Value ELSE 0 END) AS Description3
FROM
#tbl AS tbl
GROUP BY
tbl.Inspection
This do not requiere a pivot and can be use on most of RDMS out there
You should use Sql Server Pivot. It converts rows into columns. You can have an easiest start by this example.
If you'd like to do this dynamically, without having to know what all of the Desc values are, you can build your pivot query and use Exec() or Execute sp_executesql
DECLARE #Columns NVARCHAR(MAX),
#Sql NVARCHAR(MAX)
--Build your column headers based on Distinct Desc values
SELECT #Columns = COALESCE(#Columns + ',', '') + QUOTENAME([Desc])
FROM (SELECT DISTINCT [Desc] FROM tbl) t
ORDER BY [Desc]
--Build your pivot query
SET #Sql = '
SELECT
*
FROM
tbl
PIVOT
(
MAX([Value])
FOR [Desc] IN (' + #Columns + ')
) p
'
EXEC(#Sql)
If you want - for null values, you'll need to create another variable to hold the conversion scripts for the Select part of your sql.
DECLARE #Columns NVARCHAR(MAX),
#Sql NVARCHAR(MAX),
#ColumnAliases NVARCHAR(MAX)
--Build your pivot columns based on Distinct Desc values
SELECT #Columns = COALESCE(#Columns + ',', '') + QUOTENAME([Desc])
FROM (SELECT DISTINCT [Desc] FROM tbl) t
ORDER BY [Desc]
--Build your column headers, replacing NULL with -
SELECT #ColumnAliases = COALESCE(#ColumnAliases + ',', '')
+ 'COALESCE(CONVERT(VARCHAR,' + QUOTENAME([Desc]) + '),''-'') AS ' + QUOTENAME([Desc])
FROM (SELECT DISTINCT [Desc] FROM tbl) t
ORDER BY [Desc]
--Build your pivot query
SET #Sql = '
SELECT
Inspection,'
+ #ColumnAliases + '
FROM
tbl
PIVOT
(
MAX([Value])
FOR [Desc] IN (' + #Columns + ')
) p
'
EXEC(#Sql)

Passing array of values to SQL stored procedure in .net compact framework

Im working on a windows app (windows CE) and I need to send an array of ID's to stored procedure (Stored in SQL server 2012).
Im working on .net compact framework 3.5 and C#.
I checked the approaches like passing the list as XML attribute and table valued parameter but both give me the same error in compact framework.
Invalid Enumeration Type SqlDbType.XML
Invalid Enumeration Type SqlDbType.Structured
Havent found anything that says these are unsupported in compact framework but they dont work too.
What other approach can i folow.
If you need to pass only ID's then maybe this function will help you:
ALTER FUNCTION [dbo].[fn_SplitIds]
(
#text nvarchar(max)
)
RETURNS #Ids TABLE
(
Id int UNIQUE
)
AS
BEGIN
declare #iStart int,
#iPos int
if substring( #text, 1, 1 ) = ','
begin
set #iStart = 2
insert into #Ids
values( null )
end
else
set #iStart = 1
while 1=1
begin
set #iPos = charindex( ',', #text, #iStart )
if #iPos = 0
set #iPos = len( #text )+1
if #iPos - #iStart > 0
insert into #Ids
values ( cast(substring( #text, #iStart, #iPos-#iStart ) as int))
else
insert into #Ids
values( null )
set #iStart = #iPos+1
if #iStart > len( #text )
break
end
RETURN
END
we use it in our project to support some legacy stuff and it works fine. Just pass integers separated by commas ','.
You do not need to create function.

What is the best way to get rows from different databases?

I have 10 identical databases.
I get the database names at runtime.
I want to store rows into a collection of objects.
I also only want one hit on the database server.
My current approach:-
In a query (no stored procedures for X reason) I get list of databases and store in a temporary table.
Then I iterate through each database and create a dynamic query and execute it.
DECLARE #MaxRownum int SET #MaxRownum = (SELECT MAX(RowNum) FROM #Databases)
DECLARE #Iter int SET #Iter = 1
WHILE #Iter <= #MaxRownum
BEGIN
DECLARE #Database varchar(255) SELECT #Database = Databases FROM #Databases
WHERE RowNum = #Iter
IF HAS_DBACCESS(#Database) > 0
BEGIN
//appending query
END
SET #Iter = #Iter + 1
END
EXEC(#Query)
Can I use Linq + entity framework with one hit to server, without dynamic query and without hampering the performance? Is there any better solution?
Having no idea what your query is (I asked but you did not supply it), and not sure that you understand it is going to be extremely difficult to supply database names as variables without a "dynamic query", here is a much simpler way to do it IMHO:
DECLARE #sql NVARCHAR(MAX);
SELECT #sql = N'';
SELECT #sql = #sql + CHAR(13) + CHAR(10) + 'UNION ALL'
--// you will need to fill in your "//appending query" stuff here:
+ ' SELECT ... FROM ' + QUOTENAME(Databases) + '.dbo.tablename'
FROM #Databases
WHERE HAS_DBACCESS(Databases) = 1;
SET #sql = STUFF(#sql, 1, 9, '');
EXEC sp_executesql #sql;

SQL Server (2008) Pass ArrayList or String to SP for IN()

I was wondering how I can pass either an ArrayList, List<int> or StringBuilder comma delimited list to a stored procedure such that I find a list of IDs using IN():
#myList varchar(50)
SELECT *
FROM tbl
WHERE Id IN (#myList)
In C# I am currently building the list as a string which is comma delimeted; however when using nvarchar(50) for example, as the type for the param in the stored procedure - I get an error as it can't convert '1,2,3' to int which it expects between the IN().
Any ideas? Much appreciated.
Pete
You could use a User Defined function such as
CREATE function [dbo].[csl_to_table] ( #list nvarchar(MAX) )
RETURNS #list_table TABLE ([id] INT)
AS
BEGIN
DECLARE #index INT,
#start_index INT,
#id INT
SELECT #index = 1
SELECT #start_index = 1
WHILE #index <= DATALENGTH(#list)
BEGIN
IF SUBSTRING(#list,#index,1) = ','
BEGIN
SELECT #id = CAST(SUBSTRING(#list, #start_index, #index - #start_index ) AS INT)
INSERT #list_table ([id]) VALUES (#id)
SELECT #start_index = #index + 1
END
SELECT #index = #index + 1
END
SELECT #id = CAST(SUBSTRING(#list, #start_index, #index - #start_index ) AS INT)
INSERT #list_table ([id]) VALUES (#id)
RETURN
END
Which accepts an nvarchar comma separated list of ids and returns a table of those ids as ints. You can then join on the returned table in your stored procedure like so -
DECLARE #passed_in_ids TABLE (id INT)
INSERT INTO #passed_in_ids (id)
SELECT
id
FROM
[dbo].[csl_to_table] (#your_passed_in_csl)
SELECT *
FROM
myTable
INNER JOIN
#passed_in_ids ids
ON
myTable.id = ids.id
In SQL 2008 there are table-valued-parameters, that make a friendly alternative to parsing CSV; see here for an example.
Otherwise, another option is xml - the xml data type in SQL Server allows you to read this pretty easily (although it takes more transfer bytes).

Categories

Resources