SQL Query - Concatenating Results into One String [duplicate] - sql

This question already has answers here:
How to concatenate text from multiple rows into a single text string in SQL Server
(47 answers)
Closed 7 years ago.
I have a sql function that includes this code:
DECLARE #CodeNameString varchar(100)
SELECT CodeName FROM AccountCodes ORDER BY Sort
I need to concatenate all results from the select query into CodeNameString.
Obviously a FOREACH loop in C# code would do this, but how do I do it in SQL?

If you're on SQL Server 2005 or up, you can use this FOR XML PATH & STUFF trick:
DECLARE #CodeNameString varchar(100)
SELECT
#CodeNameString = STUFF( (SELECT ',' + CodeName
FROM dbo.AccountCodes
ORDER BY Sort
FOR XML PATH('')),
1, 1, '')
The FOR XML PATH('') basically concatenates your strings together into one, long XML result (something like ,code1,code2,code3 etc.) and the STUFF puts a "nothing" character at the first character, e.g. wipes out the "superfluous" first comma, to give you the result you're probably looking for.
UPDATE: OK - I understand the comments - if your text in the database table already contains characters like <, > or &, then my current solution will in fact encode those into <, >, and &.
If you have a problem with that XML encoding - then yes, you must look at the solution proposed by #KM which works for those characters, too. One word of warning from me: this approach is a lot more resource and processing intensive - just so you know.

DECLARE #CodeNameString varchar(max)
SET #CodeNameString=''
SELECT #CodeNameString=#CodeNameString+CodeName FROM AccountCodes ORDER BY Sort
SELECT #CodeNameString

#AlexanderMP's answer is correct, but you can also consider handling nulls with coalesce:
declare #CodeNameString nvarchar(max)
set #CodeNameString = null
SELECT #CodeNameString = Coalesce(#CodeNameString + ', ', '') + cast(CodeName as varchar) from AccountCodes
select #CodeNameString

For SQL Server 2005 and above use Coalesce for nulls and I am using Cast or Convert if there are numeric values -
declare #CodeNameString nvarchar(max)
select #CodeNameString = COALESCE(#CodeNameString + ',', '') + Cast(CodeName as varchar) from AccountCodes ORDER BY Sort
select #CodeNameString

from msdn Do not use a variable in a SELECT statement to concatenate values (that is, to compute aggregate values). Unexpected query results may occur. This is because all expressions in the SELECT list (including assignments) are not guaranteed to be executed exactly once for each output row
The above seems to say that concatenation as done above is not valid as the assignment might be done more times than there are rows returned by the select

Here is another real life example that works fine at least with 2008 release (and later).
This is the original query which uses simple max() to get at least one of the values:
SELECT option_name, Field_M3_name, max(Option_value) AS "Option value", max(Sorting) AS "Sorted"
FROM Value_list group by Option_name, Field_M3_name
ORDER BY option_name, Field_M3_name
Improved version, where the main improvement is that we show all values comma separated:
SELECT from1.keys, from1.option_name, from1.Field_M3_name,
Stuff((SELECT DISTINCT ', ' + [Option_value] FROM Value_list from2
WHERE COALESCE(from2.Option_name,'') + '|' + COALESCE(from2.Field_M3_name,'') = from1.keys FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'') AS "Option values",
Stuff((SELECT DISTINCT ', ' + CAST([Sorting] AS VARCHAR) FROM Value_list from2
WHERE COALESCE(from2.Option_name,'') + '|' + COALESCE(from2.Field_M3_name,'') = from1.keys FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'') AS "Sorting"
FROM ((SELECT DISTINCT COALESCE(Option_name,'') + '|' + COALESCE(Field_M3_name,'') AS keys, Option_name, Field_M3_name FROM Value_list)
-- WHERE
) from1
ORDER BY keys
Note that we have solved all possible NULL case issues that I can think of and also we fixed an error that we got for numeric values (field Sorting).

Related

How to fetch only a part of string

I have a column which has inconsistent data. The column named ID and it can have values such as
0897546321
ABC,0876455321
ABC,XYZ,0873647773
ABC,
99756
test only
The SQL query should fetch only Ids which are of 10 digit in length, should begin with a 08 , should be not null and should not contain all characters. And for those values, which have both digits and characters such as ABC,XYZ,0873647773, it should only fetch the 0873647773 . In these kind of values, nothing is fixed, in place of ABC, XYZ , it can be anything and can be of any length.
The column Id is of varchar type.
My try: I tried the following query
select id
from table
where id is not null
and id not like '%[^0-9]%'
and id like '[08]%[0-9]'
and len(id)=10
I am still not sure how should I deal with values like ABC,XYZ,0873647773
P.S - I have no control over the database. I can't change its values.
SQL Server generally has poor support regular expressions, but in this case a judicious use of PATINDEX is viable:
SELECT SUBSTRING(id, PATINDEX('%,08[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9],%', ',' + id + ','), 10) AS number
FROM yourTable
WHERE ',' + id + ',' LIKE '%,08[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9],%';
Demo
If you normalise your data, and split the delimited data into parts, you can achieve this some what more easily:
SELECT SS.value
FROM dbo.YourTable YT
CROSS APPLY STRING_SPLIT(YT.YourColumn,',') SS
WHERE LEN(SS.value) = 10
AND SS.value NOT LIKE '%[^0-9]%';
If you're on an older version of SQL Server, you'll have to use an alternative String Splitter method (such as a XML splitter or user defined inline table-value function); there are plenty of examples on these already on Stack Overflow.
db<>fiddle

SQL Sum of numbers from a string

I have a string like this:)
"<"FOSTIMON 75 M.J.>|4|4|4|3|3|3|3|3|3|3|3|3||||||||||||||||||||||||||||"
I need the sum of the numbers (4+4+4+3+...+3), expected result = 39
Thank you for the help!
THIS ANSWERS THE ORIGINAL QUESTION BEFORE EDITING.
Most databases support executing some form of prepared statement. In your case, probably the simplest method is to construct a SQL statement and then execute it dynamically.
The syntax for this varies dramatically from database to database. Out of randomness, I'm choosing SQL Server, but the functionality (although not the syntax) is available in almost any database:
declare #str nvarchar(max) = '|4|4|4|3|3|3|3|3|3|3|3|3||||||||||||||||||||||||||||';
set #str = 'select ' + replace(#str, '|', '+') + ' + 0';
exec sp_executesql #str;
Here is a rextester for this particular version.
Note that this works because + is a unary (numeric) operator, analogous to - but it does not change the sign.
Try this
Assuming the first part of the string upto the first "|" can be ignored
Not sure what version of SQL you're using but if its less than SQL2016 then use this string splitter
DECLARE #s NVARCHAR(50) = '<"FOSTIMON 75 M.J.>|4|4|4|3|3|3|3|3|3|3|3|3||||||||||||||||||||||||||||'
SELECT
SUM(CONVERT(INT, S.[Value]))
FROM dbo.DelimitedSplit8K(#s, '|') S
WHERE S.RN >= 2 AND S.RN <= 13
If you're on SQL2012 + then you can utilise TRY_PARSE and get rid of the WHERE clause altogether
SELECT
SUM(
CASE
WHEN TRY_PARSE(S.[Value] AS INT) IS NOT NULL
THEN CONVERT(INT, S.[Value])
ELSE 0
END
--CONVERT(INT, S.[Value])
)
FROM dbo.DelimitedSplit8K(#s, '|') S
If you're using SQL2016 + then use the inbuilt function, String_Split
SELECT
SUM(
CASE
WHEN TRY_PARSE(S.[Value] AS INT) IS NOT NULL
THEN CONVERT(INT, S.[Value])
ELSE 0
END
)
FROM STRING_SPLIT(#s, '|') S

SQL query with subquery and concatenating variable using CRM on-premise data

I am working on a report where I need to provide a summary of notes for particular "activities/tasks".
Since the activity can accept multiple notes, I have to search for all the notes related to that activity. I then order it by date (new to old), and concatenate them with some other strings as such:
[Tom Smith wrote on 9/23/2016 1:21 pm] Client was out of office, left message. [Jane Doe wrote on 9/21/2016 3:24 pm] Client called asking about pricing.
The data comes from replicated tables of our on-premise CRM system, and I'm using SQL Server 2012. The tables I'm using are: AnnotationBase (contains the notes), ActivityPointerBase (contains the activities/tasks), and SystemUserID (to lookup usernames). Due to Data mismatch, I have to do some converting of the data types so that I can concatenate them properly, so that's why there's a lot of CAST and CONVERT. In addition, not all Activities have a NoteText associated with them, and sometimes the NoteText field is NULL, so I have to catch and filter the NULLs out (or it'll break my concatenated string).
I have written the following query:
DECLARE #Notes VarChar(Max)
SELECT
( SELECT TOP 5 #Notes = COALESCE(#Notes+ ', ', '') + '[' + CONVERT(varchar(max), ISNULL(sUB.FullName, 'N/A')) + ' wrote on ' + CONVERT(varchar(10), CAST(Anno.ModifiedOn AS DATE), 101) + RIGHT(CONVERT(varchar(32),Anno.ModifiedOn,100),8) + '] ' + CONVERT(varchar(max), ISNULL(Anno.NoteText, '')) --+ CONVERT(varchar(max), CAST(ModifiedOn AS varchar(max)), 101)--+ CAST(ModifiedOn AS varchar(max))
FROM [CRM_rsd].[dbo].[AnnotationBase] AS Anno
LEFT OUTER JOIN [CRM_rsd].[dbo].[systemUserBase] AS sUB
ON Anno.ModifiedBy = sUB.SystemUserId
WHERE Anno.ObjectId = Task.ActivityId--'0B48AB28-C08F-419A-8D98-9916BDFFDE4C'
ORDER BY Anno.ModifiedOn DESC
SELECT LEFT(#Notes,LEN(#Notes)-1)
) AS Notes
,Task.*
FROM [CRM_rsd].[dbo].[ActivityPointerBase] AS Task
WHERE Task.Subject LIKE '%Project On Hold%'
I know the above method is probably not very efficient, but the list of "Projects On Hold" is rather small (less than 500), so performance isn't a priority. What is a priority is to be able to get a consolidated and concatenated list of notes for each activity. I have been searching all over the internet for a solution, and I have tried many different methods. But I get the following errors:
Msg 102, Level 15, State 1, Line 3
Incorrect syntax near '='.
Msg 102, Level 15, State 1, Line 10
Incorrect syntax near ')'.
I envision two possible solutions to my problem:
My subquery errors are fixed or
Create a "view" of just the concatenated NotesText, grouped by ActivityId (which would work as a key), and then just query from that.
Yet even though I'm pretty sure my ideas would work, I can't seem to figure out how to concatenate a column and group at the same time.
What you are trying to do is display the records from one table (in your case ActivityPointerBase) and inside you want to add a calculated column with information from multiple records from another table (in your case AnnotationBase) merged in the rows.
There are multiple ways how you could achieve this that are different in terms of performance impact:
Option 1. You could write a scalar function that would receive as parameter the id of the task and would inside select the top 5 records, concatenating them in a procedural fashion and returning a varchar(max)
Option 2: You could use a subquery in combination with the FOR XML clause.
SELECT
SUBSTRING(
CAST(
(SELECT TOP 5 ', ' +
'[' + CONVERT(varchar(max), ISNULL(FullName, 'N/A')) +
' wrote on ' +
CONVERT(varchar(10), CAST(ModifiedOn AS DATE), 101) +
RIGHT(CONVERT(varchar(32),ModifiedOn,100),8) + '] ' +
CONVERT(varchar(max), ISNULL(NoteText, ''))
FROM [CRM_rsd].[dbo].[AnnotationBase] AS Anno
LEFT OUTER JOIN [CRM_rsd].[dbo].[systemUserBase] AS sUB ON Anno.ModifiedBy = sUB.SystemUserId
WHERE Anno.ObjectId = Task.ActivityId
ORDER BY Anno.ModifiedOn DESC
FOR XML PATH(''),TYPE
) AS VARCHAR(MAX)
),3,99999) AS Notes
,Task.*
FROM [CRM_rsd].[dbo].[ActivityPointerBase] AS Task
WHERE Task.Subject LIKE '%Project On Hold%'
What here happens is that by using the construct inside the CAST() we fetch the top 5 lines and make SQL server produce an XML with no element names, resulting in concatenation of the element values, we add comma as separator. Then we convert the XML to varchar(max) and remove the initial separator before the first record.
I prefer option 2, it will perform much better then using a scalar function.

Find the total word count in each row of a table SQL [duplicate]

This question already has answers here:
Using SQL to determine word count stats of a text field
(5 answers)
Closed 8 years ago.
I have a table with 2 columns ,ID and comment. Is it possible to find the total word count of the comment column of each row?
and then find the TOP 10 word counts? I having been trying and failing all afternoon. Any help would be hugely appreciated. If you would like any more info please ask.
If you just need to do this quickly, you could try the query below. Note that it just crudely uses a space for word boundaries.
SELECT TOP(10)(LEN(comment) - LEN(REPLACE(comment, ' ', '')) + 1)
FROM tblComments
ORDER BY (LEN(comment) - LEN(REPLACE(comment, ' ', ''))) DESC
However, please note that this query isn't a particularly efficient solution and I would only use it if it was an ad-hoc problem I was trying to solve. If performance is an issue, such as the query needing to be built into a live transactional application of some sort, I suggest that a better approach would be to use some combination of a third column to store the word count and/or doing the word count in code. Doing so will also provide a better separation of logic and data storage, as well as giving you more flexibility in how words are recognized.
Try using the DATALENGTH() function in SQLSERVER:
DECLARE #String VARCHAR(100)
,#CharToFind VARCHAR(1)
SET #String = 'AAAA BBBCB NNNNN NEEEEE ERERERERERE '
SET #CharToFind = ' '
select DATALENGTH(#String)
SELECT CountOfWordsInTheString = DATALENGTH(#String) - (DATALENGTH(REPLACE(#String,#CharToFind,''))+1)
Your query would look like the below:
SELECT TOP 10 id,DATALENGTH(comment) - ( DATALENGTH(Replace(comment, ' ', '')) + 1 )
FROM tblname
ORDER BY DATALENGTH(comment) - ( DATALENGTH(Replace(comment, ' ', '')) + 1 ) DESC

T-SQL Split String Like Clause

I have declare #a varchar(100) = 'abc bcd cde def'. What I need is to select from a table where a column is like 'abc' or 'bcd' or 'cde' or 'def'. I can use a split function and a while to get what I want, but somewhere I saw a smart solution using replace or something similar and I just can't remember it.
I know I can use an xml variable, and parse it that way. However, the value is part of a large procedure, and the best way for me is to use it in string form.
I know I can solve this by building a dynamic sql query, but that is not an option in the domain I'm working in.
Damn, I just can remember the solution. Its a hack, a little dirty trick that do the job.
Anyways, I ll use the code bellow (Im over SQL Server 2008), is it a good idea? I prefer it over the dirty split. Is it more performatic?
declare #w varchar(100) = 'some word'
declare #f xml
set #f = '<word>' + replace(#w, ' ', '</word><word>') + '</word>'
select
template.item.value('.', 'varchar(100)') as word
from #f.nodes('/word') template(item)
Use a function to split the individual items into a table, one record per item. Then you simply join to that table.
insert into #FilterTable (filters)
select Items from dbo.Split(#YourFilterString)
select *
from YourTable yt
join #FilterTable f on f.filters = yt.YourColumn
Of course my example is using equality. It gets more complicated if you truly intend to use "like" with wildcards.
In tsql you can use a pattern col like '[abcd]'
http://msdn.microsoft.com/en-us/library/ms179859.aspx
For matching multiple words (not letter) and without dynamic SQL, you'll have to get the values into a temp table. For a split function try this page http://www.sommarskog.se/arrays-in-sql-2005.html#iterative and look at the List of Strings function iter_charlist_to_table.
Or maybe you are thinking of this little trick Parameterize an SQL IN clause from the SO CEO.
for 4 sections max
WHERE
PARSENAME(REPLACE(#a, ' ', '.'), 1) = 'xxx'
OR
PARSENAME(REPLACE(#a, ' ', '.'), 2) = 'xxx'
OR
PARSENAME(REPLACE(#a, ' ', '.'), 3) = 'xxx'
OR
PARSENAME(REPLACE(#a, ' ', '.'), 4) = 'xxx'