Stripping Values between two brackets {} - sql

Good Afternoon,
I'm trying to query a column that gets data between two brackets. there may be multiple sets in the column such as : {Abrasision} {None} {Bruise}
i use this and it doesn't do exactly what i want, because i think i only use one bracket in the query. i want to get each value in my result set and insert into a table variable. Just having a little bit of trouble.
SELECT
LEFT(InjuryCategory, CHARINDEX('{', InjuryCategory)-1),
SUBSTRING(InjuryCategory, CHARINDEX('{', InjuryCategory)+1, LEN(InjuryCategory)-CHARINDEX('{', InjuryCategory)-CHARINDEX('{',REVERSE(InjuryCategory ))),
RIGHT(InjuryCategory, CHARINDEX('{', REVERSE(InjuryCategory))-1)
FROM TblVictim

You may use STRING_SPLIT(), STUFF() and STRING_AGG() to get the expected results. Note, that STRING_SPLIT() orders the results (using enable_ordinal parameter) only in Azure SQL Database, Azure SQL Managed Instance, and Azure Synapse Analytics (serverless SQL pool only), so STRING_AGG() may aggregate differently.
Test data:
SELECT *
INTO tblVictim
FROM (
VALUES ('{Abrasision} {None} {Bruise}')
) t (InjuryCategory)
Statement:
SELECT STRING_AGG(STUFF(s.[value], 1, CHARINDEX('{', s.[value]), ''), ' ') AS Category
FROM tblVictim t
CROSS APPLY STRING_SPLIT(t.InjuryCategory, '}') s
WHERE s.[value] <> ''
Result:
Category
----------------------
Abrasision None Bruise

In newer versions of SQL Server, you can combine STRING_SPLIT and TRIM
SELECT TRIM('{}' FROM s.[value]) AS Category
FROM TblVictim v
CROSS APPLY STRING_SPLIT(v.InjuryCategory, ' ') s
WHERE s.[value] <> '';
db<>fiddle

Quick and dirty, since this is delimited data, pretend it's XML. Setup:
DECLARE #tblVictim TABLE(ID INT IDENTITY, InjuryCategory NVARCHAR(MAX));
INSERT #tblVictim(InjuryCategory)
VALUES
('{Abrasision} {None} {Bruise}'),
('{Abrasision} {<5} {Bruise; very severe}');
Query:
WITH data AS (
SELECT ID, xml = CAST(REPLACE(REPLACE(InjuryCategory,
'{', '<i><![CDATA['),
'}', ']]></i>') AS XML
)
FROM #tblVictim
)
SELECT ID, node.value('text()[1]', 'nvarchar(max)')
FROM data
CROSS APPLY xml.nodes('i') AS nodes(node)
Note that this completely breaks down (with no easy fixes) if there are unbalanced delimiters.

Related

STRING_AGG substitute for SQL Server 2008 [duplicate]

Can anyone help me make this query work for SQL Server 2014?
This is working on Postgresql and probably on SQL Server 2017. On Oracle it is listagg instead of string_agg.
Here is the SQL:
select
string_agg(t.id,',') AS id
from
Table t
I checked on the site some xml option should be used but I could not understand it.
In SQL Server pre-2017, you can do:
select stuff( (select ',' + cast(t.id as varchar(max))
from tabel t
for xml path ('')
), 1, 1, ''
);
The only purpose of stuff() is to remove the initial comma. The work is being done by for xml path.
Note that for some characters, the values will be escaped when using FOR XML PATH, for example:
SELECT STUFF((SELECT ',' + V.String
FROM (VALUES('7 > 5'),('Salt & pepper'),('2
lines'))V(String)
FOR XML PATH('')),1,1,'');
This returns the string below:
7 > 5,Salt & pepper,2
lines'
This is unlikely desired. You can get around this using TYPE and then getting the value of the XML:
SELECT STUFF((SELECT ',' + V.String
FROM (VALUES('7 > 5'),('Salt & pepper'),('2
lines'))V(String)
FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'');
This returns the string below:
7 > 5,Salt & pepper,2
lines
This would replicate the behaviour of the following:
SELECT STRING_AGG(V.String,',')
FROM VALUES('7 > 5'),('Salt & pepper'),('2
lines'))V(String);
Of course, there might be times where you want to group the data, which the above doesn't demonstrate. To achieve this you would need to use a correlated subquery. Take the following sample data:
CREATE TABLE dbo.MyTable (ID int IDENTITY(1,1),
GroupID int,
SomeCharacter char(1));
INSERT INTO dbo.MyTable (GroupID, SomeCharacter)
VALUES (1,'A'), (1,'B'), (1,'D'),
(2,'C'), (2,NULL), (2,'Z');
From this wanted the below results:
GroupID
Characters
1
A,B,D
2
C,Z
To achieve this you would need to do something like this:
SELECT MT.GroupID,
STUFF((SELECT ',' + sq.SomeCharacter
FROM dbo.MyTable sq
WHERE sq.GroupID = MT.GroupID --This is your correlated join and should be on the same columns as your GROUP BY
--You "JOIN" on the columns that would have been in the PARTITION BY
FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'')
FROM dbo.MyTable MT
GROUP BY MT.GroupID; --I use GROUP BY rather than DISTINCT as we are technically aggregating here
So, if you were grouping on 2 columns, then you would have 2 clauses your sub query's WHERE: WHERE MT.SomeColumn = sq.SomeColumn AND MT.AnotherColumn = sq.AnotherColumn, and your outer GROUP BY would be GROUP BY MT.SomeColumn, MT.AnotherColumn.
Finally, let's add an ORDER BY into this, which you also define in the subquery. Let's, for example, assume you wanted to sort the data by the value of the ID descending in the string aggregation:
SELECT MT.GroupID,
STUFF((SELECT ',' + sq.SomeCharacter
FROM dbo.MyTable sq
WHERE sq.GroupID = MT.GroupID
ORDER BY sq.ID DESC --This is identical to the ORDER BY you would have in your OVER clause
FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'')
FROM dbo.MyTable MT
GROUP BY MT.GroupID;
For would produce the following results:
GroupID
Characters
1
D,B,A
2
Z,C
Unsurprisingly, this will never be as efficient as a STRING_AGG, due to having the reference the table multiple times (if you need to perform multiple aggregations, then you need multiple sub queries), but a well indexed table will greatly help the RDBMS. If performance really is a problem, because you're doing multiple string aggregations in a single query, then I would either suggest you need to reconsider if you need the aggregation, or it's about time you conisidered upgrading.

SQL Server: display whole column only if substring found

Working with SQL Sever 2016. I am constrained by the fact we cannot create functions or stored procedures. I am trying to find %word% in many columns across a table (75). Right now, I have a very large clump of
and (fieldname1 like %word%
or fieldname2 like %word%
or fieldname3 like %word%) etc.
While cumbersome, this does provide me the correct results. However:
I am looking to simplify this and
in the select, I want to display the whole column if and only if it finds %word% (or even just the column name would work)
Thank you in advance for any thoughts.
--...slow...
declare #searchfor varchar(100) = '23';
select #searchfor as [thevalue],
thexml.query('for $a in (/*[contains(upper-case(.), upper-case(sql:variable("#searchfor")))])
return concat(local-name($a[1]), ",")').value('.', 'nvarchar(max)') as [appears_in_columns],
*
from
(
select *, (select o.* for xml path(''), type) as thexml
from sys.all_objects as o --table goes here
) as src
where thexml.exist('/*[contains(upper-case(.), upper-case(sql:variable("#searchfor")))]') = 1;
One option uses cross apply to unpivot the table and then search:
select v.*
from mytable t
cross apply (values
('fieldname1', fieldname1),
('fieldname2', fieldname2),
('fieldname3', fieldname3)
) v(fieldname, fieldvalue)
where v.fieldvalue like '%word%'
Note that if more than one column contains the search word, you will get several rows in the resultset. I am unsure how you want to handle this use case (there are options).
SELECT OBJECT_NAME(id) ObjectName , [Text]
FROM syscomments
WHERE TEXT LIKE '%word%'

Search for a particular value in a string with commas

I have a TEXT column in my Table T and contains some values separated by Commas.
Example
Columns BNFT has text values such as
B20,B30,B3,B13,B31,B14,B25,B29,B1,B2,B4,B5
OR
B1,B2,B34,B31,B8,B4,B5,B33,B30,B20,B3
I want to return result in my query only if B3 is present.
It should not consider B30-B39 or B[1-9]3 (i.e. B13, B23 .... B93).
I tried with below query, but want to implement REGEXP or REGEXP_LIKE/INSTR etc. Haven't used them before and unable to understand also.
Select *
FROM T
Where BNFT LIKE '%B3,%' or BNFT LIKE '%B3'
Pls advise
Procedures will not work. Query must start with Select as 1st statement.
The first advice is to fix your data structure. Storing lists of ids in strings is a bad idea:
You are storing numbers as strings. That is the wrong representation.
You are storing multiple values in a string column. That is not using SQL correctly.
These values are probably ids. You cannot declare proper foreign key relationships.
SQL does not have particularly strong string functions.
The resulting query cannot take advantage of indexes.
That said, sometimes we are stuck with other people's bad design decisions.
In SQL Server, you would do:
where ',' + BNFT + ',' LIKE '%,33,%'
This question was originally tagged MySQL, which offers find_in_set() for this purpose:
Where find_in_set(33, BNFT) > 0
Select *
FROM T
Where ',' + BNFT + ',' LIKE '%,B3,%';
or
Select *
FROM T
Where CHARINDEX (',B3,',',' + BNFT + ',') > 0;
This can be easily achieve by CTE, REGEXP/REGEXP_Like/INSTR works better with oracle, for MS SQL Server you can try this
DECLARE #CSV VARCHAR(100) ='B2,B34,B31,B8,B4,B5,B33,B30,B20,B3';
SET #CSV = #CSV+',';
WITH CTE AS
(
SELECT SUBSTRING(#CSV,1,CHARINDEX(',',#CSV,1)-1) AS VAL, SUBSTRING(#CSV,CHARINDEX(',',#CSV,1)+1,LEN(#CSV)) AS REM
UNION ALL
SELECT SUBSTRING(A.REM,1,CHARINDEX(',',A.REM,1)-1)AS VAL, SUBSTRING(A.REM,CHARINDEX(',',A.REM,1)+1,LEN(A.REM))
FROM CTE A WHERE LEN(A.REM)>=1
) SELECT VAL FROM CTE
WHERE VAL='B3'

Creating a lookup table from a column in table

I have a table in SQL Server that contains an ID and also a column with multiple values separated by a comma (like the example below)
ID Category_number
-------------------------
1 3,5,6,8
2 4,8,23
3 4,7,5,3
I need to make this into a lookup table with 1 category number per row, so like below;
ID Category_Number
-------------------------
1 3
1 5
1 6
I have been told that XPATH might be the solution to this. Does anyone have any sample code that will do this?
Thanks
See this answer Split
Create that function in your database.
Then you can create the results you want using:
SELECT
ID,
A.S Category_Number
FROM
MyCsvTable
CROSS APPLY dbo.Split (',', MyCsvTable.Category_Number) A
I know you just accepted the solution, but assuming you're using SQL Server, here is an alternative approach without building a function:
SELECT A.[id],
Split.a.value('.', 'VARCHAR(100)') AS Cat
FROM
(SELECT [id],
CAST ('<M>' + REPLACE(Cat, ',', '</M><M>') + '</M>' AS XML) AS String
FROM YourTable
) AS A
CROSS APPLY String.nodes ('/M') AS Split(a)
And some Fiddle: http://sqlfiddle.com/#!3/cf427/3
Best of luck!
You may also want to check HierarchyID in SQL SERVER. Some tutorial: http://www.codeproject.com/Articles/37171/HierarchyID-Data-Type-in-SQL-Server-2008
Basically all you need to do is to loop through string and find position of commas and replace them with chr(13) or such. In Oracle it can be done easily in a single query. I think the same can be done using HierarchyID in SQL Server starting from 2008 or maybe even earlier versions.

How can I pull a list of ID's from a SQL table as a comma-separated values string?

I have to pull a list of integer IDs from a table using only records that match some criteria. For example:
Select ProdID From Products Where (ProdType='XYZ');
The catch is that I have to return it as a set of comma separated values so I can use it to select items in a multi-select list:
111,231,554,112
rather than as records. I do not want to do this in my C# code - I'd like it to come right out of the database via a query this way. Any ideas?
MySQL
SELECT GROUP_CONCAT(t.prodid SEPARATOR ',')
FROM PRODUCTS t
WHERE t.prodtype = 'XYZ'
Oracle:
There is an excellent summary of the available string aggregation techniques on Tim Hall's site.
SQL Server 2005+
SELECT STUFF((SELECT ','+ t.prodid
FROM PRODUCTS t
WHERE t.prodtype = 'XYZ'
FOR XML PATH('')), 1, 1, '')
In addition to #OMG Ponies method, you could also try this COALESCE trick from:
Using COALESCE to Build Comma-Delimited Strings
declare #string nvarchar(255)
select #string = coalesce(#string + ', ', '') + cast(prodid as nvarchar(5))
from products
From SQL Server 2017 onwards, you can now use the STRING_AGG function.
This allows you to create the comma-separated list from within the SELECT statement (so works nicely with views). Given your example, it will become:
SELECT STRING_AGG(ProdID, ',')
FROM Products
WHERE (ProdType='XYZ');
This is a very old question but I'm adding an answer that applies the already-accepted answer using COALESCE by Justin Niessner. This application is how I would normally want to apply this technique where I'm querying a parent and I want to also have a single column which contains a comma-delimited list of child IDs.
These examples go against an AdventureWorksLT database as created in Azure SQL Database if you use the dropdown to select it when you provision a database. Nothing new here, just a convenient application that might help somebody.
The first query is how I'll normally use it:
SELECT
SalesLT.ProductCategory.*,
STUFF((SELECT ','+ cast(ProductID as nvarchar(10)) FROM SalesLT.Product WHERE ProductCategoryID=SalesLT.ProductCategory.ProductCategoryID ORDER BY ProductID FOR XML PATH('')), 1, 1, '') AS ProductIDs
FROM SalesLT.ProductCategory
The second query shows a self-referencing use of it:
SELECT
ParentCategory.*,
STUFF((SELECT ','+ cast(child.ProductCategoryID as nvarchar(10)) FROM SalesLT.ProductCategory child WHERE child.ParentProductCategoryID=ParentCategory.ProductCategoryID ORDER BY child.ProductCategoryID FOR XML PATH('')), 1, 1, '') AS ChildCategoryIDs
FROM SalesLT.ProductCategory ParentCategory
WHERE
EXISTS (SELECT ParentProductCategoryID FROM SalesLT.ProductCategory children WHERE children.ParentProductCategoryID=ParentCategory.ProductCategoryID)
For SQL server see here: Concatenate Values From Multiple Rows Into One Column
Theres a way to do it without additional functions:
DECLARE #Test nvarchar(max) = ''
SELECT #Test = #Test + ProdID + ', '
FROM Products
WHERE (ProdType='XYZ')
SELECT #Test
#Test will contain a list of your IDs, although I cannot explain why this works.
For the future PostgreSQL users, please find the solution below (it is the same as #Matt Tester answered.
SELECT STRING_AGG(cast(id as varchar), ',') from table1
where col1 = 'ABC';
Please note that the cast is required if the column you are selecting is not string (or varchar in database terms).