Regular expressions in TSQL - sql-server

In cells of column e_vis_name I have organization structure where divisions divided with \ symbol, e.g.
Moscow\Direction
Yaroslavl\Sales
Omsk\Commercial center\Sales
I need to cut everything after first \ symbol to get the following result:
Moscow
Yaroslavl
Omsk
How can I do it?

You can use a combination of LEFT and CHARINDEX like this:
SELECT LEFT(colname, CHARINDEX('\', colname)-1) FROM table
EDIT: In the case where you don't have a \ symbol, if you just want to grab the whole column instead you can do this:
SELECT
CASE WHEN CHARINDEX('\', colname) > 0 THEN LEFT(colname, CHARINDEX('\', colname)-1)
ELSE ISNULL(colname, '')
END
FROM table
This says, "If there is a \, then take the characters up to that point, otherwise take the whole column. And if the column is NULL then just set an empty string."
I'm sure you can adapt this to your purposes.

there are many options how you can achieve what you need, below several examples of them
--------------------------------------------------------------------------------
-- TEMP TABLE WITH DATA SAMPLE
DECLARE #table AS TABLE ( Division VARCHAR(100) )
INSERT INTO #table ( Division )
VALUES ( 'Moscow\Direction' )
, ( 'Yaroslavl\Sales' )
, ( 'Omsk\Commercial center\Sales' )
, ( 'Voronezh' )
--------------------------------------------------------------------------------
-- variant using PARSENAME
SELECT REVERSE(PARSENAME(REVERSE(REPLACE(Division, '\', '.')), 1)) AS Town
FROM #table AS T
-------------------------------------------------------------------------------
-- variant using SUBSTRING AND CHARINDEX
SELECT SUBSTRING(division, 1,
CASE WHEN CHARINDEX('\', division) = 0 THEN LEN(Division)
ELSE CHARINDEX('\', division) - 1
END) AS Town
FROM #table AS T
--------------------------------------------------------------------------------
-- variant using SUBSTRING AND PATINDEX
SELECT SUBSTRING(division, 1,
CASE WHEN PATINDEX('%\%', division) = 0 THEN LEN(Division)
ELSE PATINDEX('%\%', division) - 1
END) AS Town
FROM #table AS T
--------------------------------------------------------------------------------
-- variant using LEFT AND PATINDEX
SELECT LEFT(division,
CASE WHEN PATINDEX('%\%', division) = 0 THEN LEN(Division)
ELSE PATINDEX('%\%', division) - 1
END) AS Town
FROM #table AS T
--------------------------------------------------------------------------------
-- variant using LEFT AND CHARINDEX
SELECT LEFT(division,
CASE WHEN CHARINDEX('\', division) = 0 THEN LEN(Division)
ELSE CHARINDEX('\', division) - 1
END) AS Town
FROM #table AS T
--------------------------------------------------------------------------------
-- variant using recursive cte, substring, top with ties by Row_number()
;
WITH tally
AS (SELECT n = 1
UNION ALL
SELECT n = n + 1
FROM tally
WHERE n < 100)
SELECT TOP 1 WITH TIES SUBSTRING(A.Division,1,B.n-1) AS Town
FROM #table AS A
JOIN tally AS B ON SUBSTRING(A.Division + '\', B.n , 1)= '\'
ORDER BY ROW_NUMBER() OVER (PARTITION BY A.Division ORDER BY B.n)
--------------------------------------------------------------------------------
-- variant using recursive cte, substring, row_number, subquery
;
WITH tally
AS (SELECT n = 1
UNION ALL
SELECT n = n + 1
FROM tally
WHERE n < 100)
SELECT T.TOWN
FROM (SELECT SUBSTRING(A.Division,1,B.n-1) AS TOWN,
ROW_NUMBER() OVER (PARTITION BY A.Division ORDER BY B.n) AS RN
FROM #table AS A JOIN tally AS B ON SUBSTRING(A.Division + '\', B.n , 1)= '\'
) AS T
WHERE RN = 1

As some alternatives:
Using LEFT :
REPLACE(LEFT(e_vis_name, CHARINDEX('\', e_vis_name + '\', 1)), '\', '')
Using SUBSTRING :
REPLACE(SUBSTRING(e_vis_name, 1, CHARINDEX('\', e_vis_name + '\', 1)), '\', '')
Using STUFF :
STUFF(e_vis_name + '\', CHARINDEX('\', e_vis_name + '\', 1), 512, '')
Using PARSENAME :
REVERSE(PARSENAME(REVERSE(REPLACE(e_vis_name, '\','.')), 1))
-- or REPLACE(REVERSE(PARSENAME(REPLACE(REVERSE(REPLACE(e_vis_name, '.', CHAR(8))), '\','.'), 1)), CHAR(8), '.')
or
PARSENAME(REPLACE(e_vis_name, '\','.'), LEN(e_vis_name) - LEN(REPLACE(e_vis_name, '\', '')) + 1)
-- or REPLACE(PARSENAME(REPLACE(REPLACE(e_vis_name, '.', CHAR(8)), '\','.'), LEN(e_vis_name) - LEN(REPLACE(e_vis_name, '\', '')) + 1) , CHAR(8), '.')

Related

Remove all Non-Numeric Chars from String

I would like to truncate all characters in a column, no matter where they are.
Example:
"+49123/4567890(testnumber)"
Should be changed to
"491234567890"
Is there a way without doing a replace for each char?
I have tried to replace it with several, but it is very time-consuming.
As you mentioned, if you are expecting only [a-zA-z()/+], you can use the translate function which is available from 2017+
declare #table TABLE (str varchar(max))
insert into #table
select '+49123/4567890(estnumber)'
select replace(translate(str, '/+()abcdefghijklmnopqrstuvwxyz', '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'), '~', '') digits
from #table
For more complex scenarios where the characters are not known, you can try using recursive CTE on a string column to extract only digits like following query.
;with cte
as (
select v.txt originalstring
,v.txt
,convert(varchar(max), '') as digits
,1 as lev
from (
values ('+49123/4567890(testnumber)')
,('+234&*#$%!##')
) v(txt)
union all
select originalstring
,stuff(txt, 1, 1, '')
,(
case
when left(txt, 1) LIKE '[0-9]'
then digits + left(txt, 1)
else digits
end
)
,lev + 1
from cte
where txt > ''
)
select originalstring
,digits
from (
select c.originalstring
,c.digits
,row_number() over (partition by c.originalstring order by lev desc
) rn
from cte c
) t
where rn = 1
Output
originalstring digits
--------------- --------
+234&*#$%!## 234
+49123/4567890(testnumber) 491234567890
A set-based option that exists in SQL Server 2017+ is to utilise translate.
You can hopefully adapt the following to your specific use-case:
select col, Replace(Translate(col, r, Replicate('*', Len(r))), '*', '') Newcol
from t
cross apply(values(' ABCDEFGHIJKLMNOPQRSTUVWXYZ/\+()'))r(r);
Example DB<>Fiddle
Instead of hardcoding the list of "bad" characters you can use a double TRANSLATE to first get the unwanted characters and then plug that back into TRANSLATE.
DECLARE #table TABLE
(
str VARCHAR(max)
)
INSERT INTO #table
SELECT '+49123/4567890(testnumber) '
DECLARE #CharactersToKeep VARCHAR(30) = '0123456789'
SELECT REPLACE(TRANSLATE(str, bad_chars, REPLICATE('X', LEN(bad_chars + 'X') - 1)), 'X', '')
FROM #table
CROSS APPLY (SELECT REPLACE(TRANSLATE(str, #CharactersToKeep, REPLICATE(LEFT(#CharactersToKeep, 1), LEN(#CharactersToKeep))), LEFT(#CharactersToKeep, 1), '')) ca(bad_chars)

SQL Select all words in a string from the third word

My data set is changing and now includes two additional words at the start of my customer name field, I need to clean this data up before moving it to my main customer table.
What I need to be able to do is keep only the words after the second space in a select statement.
Can anyone suggest a way to do this
i.e. "ZENDUSER ABCABC S ROCCO AL PORTO" needs to be returned as "S ROCCO AL PORTO"
You can use CHARINDEX and SUBSTRING to do this:
declare #a varchar(200)
set #a = 'ZENDUSER ABCABC S ROCCO AL PORTO'
select #a, substring(#a, charindex(' ', #a, charindex(' ', #a, 1) + 1) + 1, 200)
DECLARE #cust NVARCHAR(MAX);
SET #cust = N'ZENDUSER ABCABC S ROCCO AL PORTO';
SELECT SUBSTRING(#cust, CHARINDEX(' ', #cust, CHARINDEX(' ', #cust, 0) + 1) + 1,
LEN(#cust) - CHARINDEX(' ', #cust, CHARINDEX(' ', #cust, 0) + 1));
GO
| (No column name) |
| :--------------- |
| S ROCCO AL PORTO |
dbfiddle here
Try this:
select substring(MyColumn, CHARINDEX(MyColumn, ' ', CHARINDEX(MyColumn, ' ', 1) + 1) + 1, Len(MyColumn)) from MyTable
I know, that this is very similair to MJH answer, but additionally, I take Len(MyColumn) in substring method, so we are sure that we include all characters after second space. The other answer takes only 200 characters.
If you'd like your trimming to be more dynamic i.e starting from the 5th word etc, you can use the following code snippet. I would have encapsulate this in an inline function for additional capabilities
DECLARE #Sentence NVARCHAR(200) = 'ZENDUSER ABCABC S ROCCO AL PORTO'
DECLARE #Del NVARCHAR(2)= ' '
DECLARE #WordStart INT = 5
;WITH Nums (n) as
(
SELECT TOP (LEN(#Sentence)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0)) a(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d(n)
)
, Main as
(
SELECT N, x.val , Ind , CASE WHEN n = 1 THEN 1 ELSE SUM(Ind) OVER (ORDER BY n) + 1 END Pos
FROM Nums
CROSS APPLY
(
VALUES (SUBSTRING(#Sentence,n,1),
CASE WHEN SUBSTRING(#Sentence,n,1) = #Del THEN 1 ELSE 0 END)
) x(val, Ind)
)
, Combine (StrOut) as
(
SELECT LTRIM(RTRIM(STUFF(
CAST((SELECT ''+ val
FROM Main
WHERE Pos >= #WordStart
FOR XML PATH (''),TYPE) AS NVARCHAR(MAX)),1,0,'')
)))
SELECT StrOut
FROM Combine
UPDATE: creating a function
CREATE FUNCTION dbo.SentenceSplitter
(
#Sentence NVARCHAR(2000),
#WordStart INT,
#Del NVARCHAR(2) = ' '
)
RETURNS TABLE AS RETURN
WITH Nums (n) as
(
SELECT TOP (LEN(#Sentence)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0)) a(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d(n)
)
, Main as
(
SELECT N, x.val , Ind , CASE WHEN n = 1 THEN 1 ELSE SUM(Ind) OVER (ORDER BY n) + 1 END Pos
FROM Nums
CROSS APPLY
(
VALUES (SUBSTRING(#Sentence,n,1),
CASE WHEN SUBSTRING(#Sentence,n,1) = #Del THEN 1 ELSE 0 END)
) x(val, Ind)
)
, Combine (StrOut) as
(
SELECT LTRIM(RTRIM(STUFF(
CAST((SELECT ''+ val
FROM Main
WHERE Pos >= #WordStart
FOR XML PATH (''),TYPE) AS NVARCHAR(MAX)),1,0,'')
)))
SELECT StrOut
FROM Combine
Use case:
SELECT StrOut
FROM dbo.SentenceSplitter ('ZENDUSER ABCABC S ROCCO AL PORTO', 5, ' ')
will result:
StrOut
AL PORTO

Split string by using CHARINDEX() in RIGHT() or SUBSTRING() return incorrect result

I want to split a column into two. I want to select values from where cell value has '(' So here is my requirement:
Input Strings:
col: mystr
----------
123(0)
233 (123)
23 (A)
2 (122)
Required Output:
Output
-------
(0)
(123)
(A)
(122)
I have done following:
SELECT right(mystr,LEN(mystr)-
CASE WHEN CHARINDEX('(',mystr)=0 THEN LEN(mystr)
ELSE CHARINDEX('(',mystr) END
+ 1)
FROM docs
How it works: I want to select index where I found first '(' and then select values next to it. As CHARINDEX() work from Left to right. So instead of:
select right(mystr,CHARINDEX('(',mystr))
I subtracted index from total length LEN(mystr)-CHARINDEX('(',mystr).
Here I found a scenario when '(' was not found and 'CHARINDEX()' returned 0 So in case '(' was not found I made the whole term 0 by:
CASE WHEN CHARINDEX('(',mystr)=0 THEN LEN(mystr)
ELSE CHARINDEX('(',mystr) END
Here first element is not selected so I added +1 to whole term but it results in an extra value:
mystr Out without +1, Out with +1, Out with +1 moved inside else; desired
----- ------------ ----------- ----------------- -------
112 '' 2 '' ''
1(0) 0) (0) ) (0)
1 (12) 12) (12) ) (12)
I have also tried with substring() but it has same issue:
SELECT substring(mystr,
CASE WHEN CHARINDEX('(',mystr)=0 THEN LEN(mystr)
ELSE CHARINDEX('(',mystr) END,
LEN(mystr)-CASE WHEN CHARINDEX('(',mystr)=0 THEN LEN(mystr)
ELSE CHARINDEX('(',mystr)END +1) FROM docs
If open to a Table-Valued Functions, consider the following:
Tired of extacting and parsing strings (left(), right(), charindex(), ...), I modified a parse function to accept two non-like delimiters.
Example
Declare #YourTable table (mystr varchar(50))
Insert Into #YourTable values
('122'),
('123(0)'),
('233 (123)'),
('23 (A)'),
('2 (122)')
Select A.*
,NewVal = IsNull('('+B.RetVal+')','') -- Adding back the ()'s
From #YourTable A
Outer Apply [dbo].[tvf-Str-Extract](A.mystr,'(',')') B
Returns
mystr NewVal
122
123(0) (0)
233 (123) (123)
23 (A) (A)
2 (122) (122)
The UDF if Interested
CREATE FUNCTION [dbo].[tvf-Str-Extract] (#String varchar(max),#Delimiter1 varchar(100),#Delimiter2 varchar(100))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 N1,cte1 N2,cte1 N3,cte1 N4,cte1 N5,cte1 N6) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter1) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter1)) = #Delimiter1),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter1,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By N)
,RetPos = N
,RetVal = left(RetVal,charindex(#Delimiter2,RetVal)-1)
From (
Select *,RetVal = Substring(#String, N, L)
From cte4
) A
Where charindex(#Delimiter2,RetVal)>1
)
/*
Max Length of String 1MM characters
Declare #String varchar(max) = 'Dear [[FirstName]] [[LastName]], ...'
Select * From [dbo].[tvf-Str-Extract] (#String,'[[',']]')
*/
Try this:
DECLARE #x NVARCHAR(20) = '123(A)';
SELECT CASE WHEN CHARINDEX('(', #x) = 0 THEN NULL ELSE RIGHT(#x, LEN(#x) - CHARINDEX('(', #x) + 1) END AS x
In your case you have nothing after the last ), so you just can use some large number to point how many symbols to get:
DECLARE #DataSource TABLE
(
[value] VARCHAR(48)
);
INSERT INTO #DataSource ([value])
VALUES ('123(0)')
,('233 (123)')
,('23 (A)')
,('2 (122)');
SELECT CASE WHEN CHARINDEX('(', [value]) <> 0 THEN SUBSTRING([value], CHARINDEX('(', [value]), 100) ELSE '' END
FROM #DataSource;
If there are values after the final ):
DECLARE #DataSource TABLE
(
[value] VARCHAR(48)
);
INSERT INTO #DataSource ([value])
VALUES ('123(0) test')
,('233 (123) test 12')
,('23 (A)')
,('2 (122) sometthing');
SELECT SUBSTRING([value], CHARINDEX('(', [value]), CHARINDEX(')', [value]) - CHARINDEX('(', [value]) + 1)
FROM #DataSource;

Find missing integers in a list of Values

Currently, I have 12 rows with column Named 'Value'. The sample like this (just sample data, real data will be more):
Value
1
2
3
4
6
7
8
9
10
11
12
14
What I want is select them to get result like this:
Result Result_Miss
1-4, 6-12, 14 5, 13
I want to avoid using a cursor to work row-by-row.
Dynamic, set-based approach using CTEs to hunt down the missing values, and write out the ranges available based on those missing values.
--(I can't seem to get SqlFiddle to work with CTE's or I'd post one up here)--
Reworked to be more dynamic for number of records:
This works provided you always have '1' in your set of value
CREATE TABLE #OneTen
(
Value INT NOT NULL
);
INSERT INTO #OneTen
VALUES (1), (2), (3), (4), (6), (8), (9), (10), (11), (12), (14);
WITH ExpectedActual AS
(
SELECT ot.Value AS Actual, ROW_NUMBER() OVER (ORDER BY Value) AS Expected
FROM #OneTen AS ot
)
, DegreesOff AS
(
SELECT ea.Expected, ea.Actual, (ea.Actual - ea.Expected) AS Change
FROM ExpectedActual AS ea
)
, Missing AS
(
SELECT CASE
WHEN MIN(do.Expected) = 1 THEN 0
ELSE MIN(do.Expected) + do.Change - 1
END AS Missing
, ROW_NUMBER() OVER (ORDER BY MIN(do.Expected)) AS RowNumber
FROM DegreesOff AS do
GROUP BY do.Change
UNION ALL
SELECT MAX(do.Actual + 1), MAX(do.Change + 2) --Adding Last Value 1 higher than Actual so the code below that takes mNext.Missing - 1 brings it down to the proper value:
--Change + 2 to account for 0 plus being 1 higher
FROM DegreesOff AS do
)
SELECT STUFF((
SELECT ', ' + CASE
WHEN m.Missing + 1 = mNext.Missing - 1 THEN CAST(m.Missing + 1 AS NVARCHAR(4))
ELSE CAST(m.Missing + 1 AS NVARCHAR(4)) + '-' + CAST(mNext.Missing - 1 AS NVARCHAR(4))
END
FROM Missing AS m
LEFT JOIN Missing AS mNext ON m.RowNumber = mNext.RowNumber - 1
FOR XML PATH('')), 1, 2, '') AS Result
, STUFF((
SELECT ', ' + CAST(MIN(do.Expected + do.Change - 1) AS NVARCHAR(4))
FROM DegreesOff AS do
WHERE do.Change > 0
GROUP BY do.Change
FOR XML PATH('')), 1, 2, '') AS Result_Miss
Try the following script:
DDL
CREATE TABLE Numbers
(
Value INT NOT NULL
);
INSERT INTO Numbers
VALUES (1), (2), (3), (4), (6), (7), (8), (9), (10), (12),(13);
Script
DECLARE #MinValue INT
DECLARE #MaxValue INT
DECLARE #Temp TABLE(MissingValues INT)
DECLARE #MissingValues VARCHAR(50)
SELECT #MinValue = MIN(Value),
#MaxValue = MAX(Value)
FROM Numbers
;WITH CTE AS
(
SELECT #MinValue Value
UNION ALL
SELECT Value + 1
FROM CTE
WHERE Value + 1 <= #MaxValue
)
INSERT INTO #Temp
SELECT CTE.Value
FROM CTE
LEFT JOIN Numbers N
ON CTE.Value = N.Value
WHERE N.Value IS NULL
OPTION (MAXRECURSION 1000)
SELECT #MissingValues =
STUFF(( SELECT ',' + CAST(MissingValues AS VARCHAR)
FROM #Temp
FOR XML PATH('')),1,1,'')
INSERT INTO #Temp
SELECT #MinValue - 1
UNION ALL
SELECT #MaxValue + 1
;WITH CTE AS
(
SELECT MissingValues,
ROW_NUMBER() OVER(ORDER BY MissingValues ASC) RN
FROM #Temp
)
,Ranges AS
(
SELECT CAST(T1.MissingValues + 1 AS VARCHAR) + '-' +
CAST(T2.MissingValues - 1 AS VARCHAR) Ranges
FROM CTE AS T1
INNER JOIN CTE AS T2
ON T1.RN = T2.RN - 1
)
SELECT STUFF(( SELECT ',' + R.Ranges
FROM Ranges R
FOR XML PATH('')),1,1,'') Result,
#MissingValues AS Result_Miss

How to parse a string and create several columns from it?

I have a varchar(max) field containing Name Value pairs, in every line I have Name UnderScore Value.
I need to do a query against it so that it returns the Name, Value pairs in two columns (so by parsing the text, removing the underscore and the "new line" char.
So from this
select NameValue from Table
where I get this text:
Name1_Value1
Name2_Value2
Name3_Value3
I would like to have this output
Names Values
===== ======
Name1 Value1
Name2 Value2
Name3 Value3
SELECT substring(NameValue, 1, charindex('_', NameValue)-1) AS Names,
substring(NameValue, charindex('_', NameValue)+1, LEN(NameValue)) AS Values
FROM Table
EDIT:
Something like this put in a function or stored procedure combined with a temp table should work for more than one line, depending on the line delimiter you should also remove CHAR(13) before you start:
DECLARE #helper varchar(512)
DECLARE #current varchar(512)
SET #helper = NAMEVALUE
WHILE CHARINDEX(CHAR(10), #helper) > 0 BEGIN
SET #current = SUBSTRING(#helper, 1, CHARINDEX(CHAR(10), #helper)-1)
SELECT SUBSTRING(#current, 1, CHARINDEX('_', #current)-1) AS Names,
SUBSTRING(#current, CHARINDEX('_', #current)+1, LEN(#current)) AS Names
SET #helper = SUBSTRING(#helper, CHARINDEX(CHAR(10), #helper)+1, LEN(#helper))
END
SELECT SUBSTRING(#helper, 1, CHARINDEX('_', #helper)-1) AS Names,
SUBSTRING(#helper, CHARINDEX('_', #helper)+1, LEN(#helper)) AS Names
DECLARE #TExt NVARCHAR(MAX)= '***[ddd]***
dfdf
fdfdfdfdfdf
***[fff]***
4545445
45454
***[ahaASSDAD]***
DFDFDF
***[SOME TEXT]***
'
DECLARE #Delimiter VARCHAR(1000)= CHAR(13) + CHAR(10) ;
WITH numbers
AS ( SELECT ROW_NUMBER() OVER ( ORDER BY o.object_id, o2.object_id ) Number
FROM sys.objects o
CROSS JOIN sys.objects o2
),
c AS ( SELECT Number CHARBegin ,
ROW_NUMBER() OVER ( ORDER BY number ) RN
FROM numbers
WHERE SUBSTRING(#text, Number, LEN(#Delimiter)) = #Delimiter
),
res
AS ( SELECT CHARBegin ,
CAST(LEFT(#text, charbegin) AS NVARCHAR(MAX)) Res ,
RN
FROM c
WHERE rn = 1
UNION ALL
SELECT c.CHARBegin ,
CAST(SUBSTRING(#text, res.CHARBegin,
c.CHARBegin - res.CHARBegin) AS NVARCHAR(MAX)) ,
c.RN
FROM c
JOIN res ON c.RN = res.RN + 1
)
SELECT *
FROM res
He is an example that you can use:
-- Creating table:
create table demo (dID int, dRec varchar(100));
-- Inserting records:
insert into demo (dID, dRec) values (1, 'BCQP1 Sam');
insert into demo (dID, dRec) values (2, 'BCQP2 LD');
-- Selecting fields to retrive records:
select * from demo;
Then I want to show in one single row both rows combined and display only the values from the left removing the name on the right side up to the space character.
/*
The STUFF() function puts a string in another string, from an initial position.
The LEFT() function returns the left part of a character string with the specified number of characters.
The CHARINDEX() string function returns the starting position of the specified expression in a character string.
*/
SELECT
DISTINCT
STUFF((SELECT ' ' + LEFT(dt1.dRec, charindex(' ', dt1.dRec) - 1)
FROM demo dt1
ORDER BY dRec
FOR XML PATH('')), 1, 1, '') [Convined values]
FROM demo dt2
--
GROUP BY dt2.dID, dt2.dRec
ORDER BY 1
As you can see here when you run the function the output will be:
BCQP1 BCQP2
On the top of the script I explained what each function is used for (STUFF(), LEFT(), CHARINDEX() functions) I also used DISTINCT in order to eliminate duplicate values.
NOTE: dt stands for "demo table", I used the same table and use two alias dt1 and dt2, and dRec stands for "demo Record"
If you want to learn more about STUFF() Function here is a link:
https://www.mssqltips.com/sqlservertip/2914/rolling-up-multiple-rows-into-a-single-row-and-column-for-sql-server-data/
With a CTE you will have a problem with Recursion if more that 100 items
Msg 530, Level 16, State 1, Line 20 The statement terminated. The
maximum recursion 100 has been exhausted before statement completion.
DECLARE #TExt NVARCHAR(MAX)
SET #TExt = '100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203'
DECLARE #Delimiter VARCHAR(1000)= ',';
WITH numbers
AS ( SELECT ROW_NUMBER() OVER ( ORDER BY o.object_id, o2.object_id ) Number
FROM sys.objects o
CROSS JOIN sys.objects o2
),
c AS ( SELECT Number CHARBegin ,
ROW_NUMBER() OVER ( ORDER BY number ) RN
FROM numbers
WHERE SUBSTRING(#text, Number, LEN(#Delimiter)) = #Delimiter
),
res
AS ( SELECT CHARBegin ,
CAST(LEFT(#text, charbegin) AS NVARCHAR(MAX)) Res ,
RN
FROM c
WHERE rn = 1
UNION ALL
SELECT c.CHARBegin ,
CAST(SUBSTRING(#text, res.CHARBegin,
c.CHARBegin - res.CHARBegin) AS NVARCHAR(MAX)) ,
c.RN
FROM c
JOIN res ON c.RN = res.RN + 1
)
SELECT *
FROM res

Resources