Creating a long string from a result set - sql

I have a result set in MS-SQL within a stored procedure, and lets say it has one VARCHAR column, but many rows. I want to create a comma separated string conataining all these values, Is there an easy way of doing this, or am I going to have to step through each result and build the string up manually?
Preferably I'd like to do this in the Stored Procedure itself.

Here is one way (using AdventureWorks2008 DB):
DECLARE #name varchar(255)
SET #name = NULL
select #Name = COALESCE(#Name + ',','') + LastName from Person.Person
Select #name
And here is another (for SQL 2005 onwards):
SELECT
LastName + ','
FROM
Person.Person
FOR XML PATH('')
In both cases you will need to remove the trailing comma ',' (can use STUFF() function)

Related

Find rows containing delimited words within nvarchar parameter

I have a procedure that selects an offset of rows from a table:
SELECT * --table contains ID and Name columns
FROM Names
ORDER BY ID
OFFSET #Start ROWS
FETCH NEXT #Length ROWS ONLY
In addition to #Start and #Length parameters, the procedure also receives #SearchValue NVARCHAR(255) parameter. #SearchValue contains a string of values delimited by a space, for example '1 ik mi' or 'Li 3'.
What I need is to query every record containing all of those values. So, if the #SearchValue is '1 ik mi', it should return any records that contain all three values: '1', 'mi', and 'ik'. Another way to understand this is by going here, searching the table (try searching 00 eer 7), and observing the filtered results.
I have the freedom to change the delimiter or run some function (in C#, in my case) that could format an array of those words.
Below are our FAILED attempts (we didn't try implementing it with OFFSET yet):
Select ID, Name
From Names
Where Cast(ID as nvarchar(255)) in (Select value from string_split(#SearchValue, ' ')) AND
Name in (Select value from string_split(#SearchValue, ' '))
SELECT ID, Name
FROM Names
WHERE #SearchValueLIKE '% ' + CAST(ID AS nvarchar(20)) + ' %' AND
#SearchValueLIKE '% ' + Name + ' %';
We used Microsoft docs on string_split for the ideas above.
Tomorrow, I will try to implement this solution, but I'm wondering if there's another way to do this in case that one doesn't work. Thank you!
Your best bet will be to use a FULL TEXT index. This is what they're built for.
Having said that you can work around it.. BUT! You're going to be building a query to do it. You can either build the query in C# and fire it at the database, or build it in the database. However, you're never going to be able to optimise the query very well because users being users could fire all sorts of garbage into your search that you'll need to watch out for, which is obviously a topic for another discussion.
The solution below makes use of sp_executesql, so you're going to have to watch out for SQL injection (before someone else picks apart this whole answer just to point out SQL injection):
DROP TABLE #Cities;
CREATE TABLE #Cities(id INTEGER IDENTITY PRIMARY KEY, [Name] VARCHAR(100));
INSERT INTO #Cities ([Name]) VALUES
('Cooktown'),
('South Suzanne'),
('Newcastle'),
('Leeds'),
('Podunk'),
('Udaipur'),
('Delhi'),
('Murmansk');
DECLARE #SearchValue VARCHAR(20) = 'ur an rm';
DECLARE #query NVARCHAR(1000);
SELECT #query = COALESCE(#query + '%'' AND [Name] LIKE ''%', '') + value
FROM (Select value from string_split(#SearchValue, ' ')) a;
SELECT #query = 'SELECT * FROM #Cities WHERE [Name] LIKE ''%' + #query + '%''';
EXEC sp_executesql #query;

Return multiple columns as single comma separated row in SQL Server 2005

I'm curious to see if this is possible.
I have a table or this could be specific to any old table with data. A simple SELECT will return the columns and rows as a result set. What I'm trying to find out if is possible to return rows but rather than columns, the columns concatenated and are comma separated. So expected amount of rows returned but only one varchar column holding comma separated results of all the columns just like a CSV file.
Thanks.
[UPDATE]
Here is a bit more detail why I'm asking. I don't have the option to do this on the client, this is a task I'm trying to do with SSIS.
Scenario: I have a table that is dynamically created in SSIS but the column names change each time it's built. The original package uses BCP to grab the data and put it into a flat file but due to permissions when run as a job BCP can't create the flat file at the required destination. We can't get this changed either.
The other issue is that with SSIS 2005, using the flat files destination, you have to map the column name from the input source which I can't do because the column names keep changing.
I've written a script task to grab all the data from the original tables and then use stream writer to write to the CSV but I have to loop through each row then through each column to produce the string built up of all the columns. I want to measure performance of this concatenation of columns on sql server against a nasty loop with vb.net.
If I can get sql to produce a single column for each row I can just write a single line to the text file instead of iterating though each column to build the row.
I Think You Should try This
SELECT UserName +','+ Password AS ColumnZ
FROM UserTable
Assuming you know what columns the table has, and you don't want to do something dynamic and crazy, you can do this
SELECT CONCAT(ColumnA, ',', ColumnB) AS ColumnZ
FROM Table
There is a fancy way to this using SQL Server's XML functions, but for starters could you just cast the contents of the columns you care about as varchar and concatenate them with commas?
SELECT cast(colA as varchar)+', '+cast(colB as varchar)+', '+cast(colC as varchar)
FROM table
Note, that this will get tripped up if any of your contents have a comma or double quotes in them, in which case you can also use a replace function on each cast to escape them.
This could stand to be cleaned up some, but you can do this by using the metadata stored in sys.objects and sys.columns along with dynamic SQL. Note that I am NOT a fan of dynamic SQL, but for reporting purposes it shouldn't be too much of a problem.
Some SQL to create test data:
if (object_id('test') is not null)
drop table test;
create table test
(
id uniqueidentifier not null default newId()
,col0 nvarchar(255)
,col1 nvarchar(255)
,col2 nvarchar(255)
,col3 nvarchar(255)
,col4 nvarchar(255)
);
insert into test (col0,col1,col2,col3,col4)
select 'alice','bob','charlie','dave','emily'
union
select 'abby','bill','charlotte','daniel','evan'
A stored proc to build CSV rows:
-- emit the contents of a table as a CSV.
-- #table_name: name of a permanent (in sys.objects) table
-- #debug: set to 1 to print the generated query
create procedure emit_csv(#table_name nvarchar(max), #debug bit = 0)
as
declare #object_id int;
set nocount on;
set #object_id = object_id(#table_name);
declare #name nvarchar(max);
declare db_cursor cursor for
select name
from sys.columns
where object_id = #object_id;
open db_cursor;
fetch next from db_cursor into #name
declare #query nvarchar(max);
set #query = '';
while ##FETCH_STATUS = 0
begin
-- TODO: modify appended clause to escape commas in addition to trimming
set #query = #query + 'rtrim(cast('+#name+' as nvarchar(max)))'
fetch next from db_cursor into #name;
-- add concatenation to the end of the query.
-- TODO: Rearrange #query construction order to make this unnecessary
if (##fetch_status = 0)
set #query = #query + ' + '','' +'
end;
close db_cursor;
deallocate db_cursor;
set #query = 'select rtrim('+#query+') as csvrow from '+#table_name;
if #debug != 0
begin
declare #newline nvarchar(2);
set #newline = char(13) + char(10)
print 'Generated SQL:' + #newline + #query + #newline + #newline;
end
exec (#query);
For my test table, this generates the query:
select
rtrim(rtrim(cast(id as nvarchar(max)))
+ ','
+rtrim(cast(col0 as nvarchar(max)))
+ ','
+rtrim(cast(col1 as nvarchar(max)))
+ ','
+rtrim(cast(col2 as nvarchar(max)))
+ ','
+rtrim(cast(col3 as nvarchar(max)))
+ ','
+rtrim(cast(col4 as nvarchar(max))))
as csvrow
from test
and the result set:
csvrow
-------------------------------------------------------------------------------------------
EEE16C3A-036E-4524-A8B8-7CCD2E575519,alice,bob,charlie,dave,emily
F1EE6C84-D6D9-4621-97E6-AA8716C0643B,abby,bill,charlotte,daniel,evan
Suggestions
Modify the cursor loop to escape commas
Make sure that #table_name refers to a valid table (if object_id(#table_name) is null) in the sproc
Some exception handling would be good
Set permissions on this so that only the account that runs the report can execute it. String concatenation in dynamic SQL can be a big security hole, but I don't see another way to do this.
Some error handling to ensure that the cursor gets closed and deallocated might be nice.
This can be used for any table that is not a #temp table. In that case, you'd have to use sys.objects and sys.columns from tempdb...
select STUFF((select ','+ convert(varchar,l.Subject) from tbl_Student B,tbl_StudentMarks L
where B.Id=L.Id FOR XML PATH('')),1,1,'') Subject FROM tbl_Student A where A.Id=10

T-SQL Newline String Concatenation For Exec()

So, I've come across a pretty annoying problem with T-SQL... Essentially, in a table, there are several T-SQL statements. I want a stored procedure to efficiently grab these rows and concatenate them to a variable. Then, execute the concatenated lines with EXEC(#TSQL)
The problem is, string concatenation with newlines seem to be stripped when calling Exec...
For example something like:
declare #sql nvarchar(max) = ''
select #sql += char(13) + char(10) + [sql] from [SqlTable]
exec(#sql) -- Won't always do the right thing
print #sql -- Looks good
I don't want to butcher the code with a Cursor, is there any way around this? Thanks!
Edit:
Okay, so it looks like the issue is in fact only with the GO statement, for example:
declare #test nvarchar(max) = 'create table #test4(i int) ' + char(10) + char(13) + 'GO' + char(10) + char(13) +'create table #test5(i int)'
exec(#test)
I guess this go will have to go (no pun intended) I just really didn't want to have to try and parse it in fear of special cases blowing up the whole thing.
A select statement without order by is free to return results in any order.
You'd have to specify the order in which your SQL snippets make sense:
select #sql += char(13) + char(10) + [sql]
from [SqlTable]
order by
SnippetSequenceNr
As #Bort suggested in the comments, if the snippets are stand-alone SQL, you can separate them with a semicolon. Carriage returns, newlines, tabs and spaces are all the same in T-SQL: they're whitespace.
select #sql += [sql] + ';'
from [SqlTable]
order by
SnippetSequenceNr
Just get rid of the GO "statements". As noted by others you also might need to ensure the string is constructing in the correct statement sequence. Using += is probably not the best idea, though I'm not sure about the dynamic sql idea in the first place. It might actually be more appropriate to use a cursor here.
Sam F,
Your method didn't work with FOR XML method of string concatenation (if you wanted to create a "line" delimited list, based on values found in different rows of a table). However, replace the char(13) with SPACE(13), then it works great.
SELECT PackageNote = SUBSTRING((SELECT (SPACE(13) + char(10) + PackageDescription)
FROM POPackageNote PN2
WHERE PN1.PurchaseOrderNumber = PN2.PurchaseOrderNumber
ORDER BY POPackageNoteID, PackageDescription
FOR XML PATH( '' )
), 3, 1000 )
FROM POPackageNote PN1
WHERE (PurchaseOrderNumber = #PurchaseOrderNumber)
GROUP BY PurchaseOrderNumber

SQL Dynamic SELECT statement from values stored in a table

I have been researching this for a couple of days and feel like I am going around in circles. I have basic knowledge of SQL but there are many areas I do not understand.
I have a table that stores the names and fields of all the other tables in my database.
tblFields
===================================================
TableName FieldName BookmarkName
---------------------------------------------------
Customer FirstName CustomerFirstName
Customer LastName CustomerLastName
Customer DOB CustomerDOB
I want to write a SELECT statement like the following but i am unable to get it work:
SELECT (SELECT [FieldName] FROM [TableName]) FROM tblFields
Is this possible? The application I have developed requires this for user customization of reports.
If i understand what you are trying to do, i think this will help you. It is not pretty and it works for SQL Server 2005 and above, but maybe this is what you are looking for:
declare #tableName nvarchar(100)
declare #sqlQuery nvarchar(max)
declare #fields varchar(500)
set #tableName = 'YourTableName'
set #fields = ''
select #fields = #fields + QUOTENAME(t.fieldname) + ',' from (
select distinct fieldname from tblfields where tablename = #tableName)t
set #sqlQuery = 'select ' + left(#fields, LEN(#fields)-1) + ' from ' + QUOTENAME(#tableName)
execute sp_executesql #sqlQuery
Edit: As Martin suggested, i edited so that the columns and tablename are using QUOTENAME
If I understand correctly what you are trying to do, you are probably better off doing this as two separate queries from your program. One which gets the fields you want to select which you then use in your program to build up the second query which actually gets the data.
If it must be done entirely in SQL, then you will need to tell us what database you are using. If it is SQL Server, you might be able to user a cursor over the first query to build up the second query which you then execute with the sp_executesql stored procedure. But doing doing it outside of SQL would be recommended.

Passing SQL stored procedure entirety of WHERE clause

I have a SQL stored procedure of the form
SELECT [fields] FROM [table] WHERE #whereSql
I want to pass the procedure an argument (#whereSql) which specifies the entire WHERE clause, but the following error is returned:
An expression of non-boolean type specified in a context where a condition is expected
Can this be done?
The short answer is that you can't do it like this -- SQL Server looks at the contents of a variable as a VALUE. It doesn't dynamically build up the string to execute (which is why this is the correct way to avoid SQL injection attacks).
You should make every effort to avoid a dynamic WHERE as you're trying to do, largely for this reason, but also for the sake of efficiency. Instead, try to build up the WHERE clause so that it short-circuits pieces with lots of ORs, depending on the situation.
If there's no way around it, you can still build a string of your own assembled from the pieces of the command, and then EXEC it.
So you could do this:
DECLARE #mywhere VARCHAR(500)
DECLARE #mystmt VARCHAR(1000)
SET #mywhere = ' WHERE MfgPartNumber LIKE ''a%'' '
SELECT #mystmt = 'SELECT TOP 100 * FROM Products.Product AS p ' + #mywhere + ';'
EXEC( #mystmt )
But I recommend instead that you do this:
SELECT TOP 100 *
FROM Products.Product AS p
WHERE
( MfgPartNumber LIKE 'a%' AND ModeMfrPartNumStartsWith=1)
OR ( CategoryID = 123 AND ModeCategory=1 )
I believe this can be done using Dynamic SQL. See below:
CREATE PROCEDURE [dbo].[myProc]
#whereSql nvarchar(256)
AS
EXEC('SELECT [fields] FROM [table] WHERE ' + #whereSql)
GO
That said, you should do some serious research on dynamic SQL before you actually use it.
Here are a few links that I came across after a quick search:
http://www.sommarskog.se/dynamic_sql.html
http://msdn.microsoft.com/en-us/library/aa224806%28SQL.80%29.aspx
http://www.itjungle.com/fhg/fhg100505-story02.html
Make sure you read this fully
www.sommarskog.se/dynamic_sql.html
Dynamic SQL listed in some of the Answers is definitely a solution. However, if Dynamic SQL needs to be avoided, one of the solutions that I prefer is to make use of table variables (or temp tables) to store the parameter value that is used for comparison in WHERE clause.
Here is an example Stored Procedure implementation.
CREATE PROCEDURE [dbo].[myStoredProc]
#parameter1 varchar(50)
AS
declare #myTempTableVar Table(param1 varchar(50))
insert into #myTempTableVar values(#parameter1)
select * from MyTable where MyColumn in (select param1 from #myTempTableVar)
GO
In case you want to pass in multiple values, then the comma separated values can be stored as rows in the table variable and used in the same way for comparison.
CREATE PROCEDURE [dbo].[myStoredProc]
#parameter1 varchar(50)
AS
--Code Block to Convert Comma Seperated Parameter into Values of a Temporary Table Variable
declare #myTempTableVar Table(param1 varchar(50))
declare #index int =0, #tempString varchar(10)
if charindex(',',#parameter1) > 0
begin
set #index = charindex(',',#parameter1)
while #index > 0
begin
set #tempString = SubString(#parameter1,1,#index-1)
insert into #myTempTableVar values (#tempString)
set #parameter1 = SubString(#parameter1,#index+1,len(#parameter1)-#index)
set #index = charindex(',',#parameter1)
end
set #tempString = #parameter1
insert into #myTempTableVar values (#tempString)
end
else
insert into #myTempTableVar values (#parameter1)
select * from MyTable where MyColumn in (select param1 from #myTempTableVar)
GO
http://sqlmag.com/t-sql/passing-multivalued-variables-stored-procedure
try this it works!!
CHARINDEX (',' + ColumnName + ',', ',' +
REPLACE(#Parameter, ' ', '') + ',') > 0
execute syntax set #Parameter= 'nc1,nc2'