Script to concatenate columns and remove leading/trailing delimiter - sql

I have a table like this and I want to return concatenated strings where the column values are in ('01', '02', '03', '04', '99'). Plus the values will be delimited by a ';'. So row 1 will be 01;04, row 3 will be 01;02;03;04 and row 5 will simply be 01. All leading/trailing ; should be removed. What script would do this successfully?
R_NOT_CUR R_NOT_CUR_2 R_NOT_CUR_3 R_NOT_CUR_4
01 NULL 04 NULL
98 56 45 22
01 02 03 04
NULL NULL NULL NULL
01 NULL NULL NULL

You can accomplish this using COALESCE / ISNULL and STUFF. Something like this.
SELECT STUFF(
COALESCE(';'+R_NOT_CUR,'')
+ COALESCE(';'+R_NOT_CUR_2,'')
+ COALESCE(';'+R_NOT_CUR_3,'')
+ COALESCE(';'+R_NOT_CUR_4,''),1,1,'')
FROM YourTable
Stuff will remove the first occurrence of ;

It's not recommended to store integer values in strings but here this should work. Try it out and let me know:
DECLARE #yourTable TABLE (R_NOT_CUR VARCHAR(10),R_NOT_CUR_2 VARCHAR(10),R_NOT_CUR_3 VARCHAR(10),R_NOT_CUR_4 VARCHAR(10));
INSERT INTO #yourTable
VALUES ('01',NULL,'04',NULL),
('98','56','45','22'),
('01','02','03','04'),
(NULL,NULL,NULL,NULL),
('01',NULL,NULL,NULL);
WITH CTE_row_id
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) row_id, --identifies each row
R_NOT_CUR,
R_NOT_CUR_2,
R_NOT_CUR_3,
R_NOT_CUR_4
FROM #yourTable
),
CTE_unpivot --puts all values in one column so your can apply your where logic
AS
(
SELECT *
FROM CTE_row_id
UNPIVOT
(
val FOR col IN (R_NOT_CUR,R_NOT_CUR_2,R_NOT_CUR_3,R_NOT_CUR_4)
) unpvt
WHERE val IN ('01','02','03','04','99')
)
SELECT STUFF(
COALESCE(';'+R_NOT_CUR,'') +
COALESCE(';'+R_NOT_CUR_2,'') +
COALESCE(';'+R_NOT_CUR_3,'') +
COALESCE(';'+R_NOT_CUR_4,'')
,1,1,'')
AS concat_columns
FROM CTE_unpivot
PIVOT
(
MAX(val) FOR col IN(R_NOT_CUR,R_NOT_CUR_2,R_NOT_CUR_3,R_NOT_CUR_4)
) pvt
Results:
concat_columns
--------------------------------------------
01;04
01;02;03;04
01

If you use a SUBSTRING there already is a parameter that allow you to remove character
SUBSTRING((SELECT CONCAT('/',p.YourValue)
FROM YourTable p
Where p.value2 = YourCondition
AND pc1.ProfilID = p.ProfilID
FOR XML PATH ('')),
2, 1000) [ColonneTitle],
Where the value '2' is the position of the beggining of the display, so you can choose the char position where the display will start.
(Sorry for my bad english, hope it help someone :) )

Related

SQL : extract next character from string where multiple separators exist

Azure MSSQL Database
I have a column that contains values stored per transaction. The string can contain up to 7 values, separated by a '-'.
I need to be able to extract the value that is stored after the 3rd '-'. The issue is that the length of this column (and the characters that come before the 3rd '-') can vary.
For example:
DIM VALUE
1. NHL--WA-S-MOSG-SER-
2. VDS----HAST-SER-
3. ---D---SER
Row 1 needs to return 'S'
Row 2 needs to return '-'
Row 3 needs to return 'D'
This is by no means an optimal solution, but it works in SQL Server. 😊
TempTable added for testing purposes. Maybe it gives you a hint as of where to start.
Edit: added reference for string_split function (works from SQL Server 2016 up).
CREATE TABLE #tempStrings (
VAL VARCHAR(30)
);
INSERT INTO #tempStrings VALUES ('NHL--WA-S-MOSG-SER-');
INSERT INTO #tempStrings VALUES ('VDS----HAST-SER-');
INSERT INTO #tempStrings VALUES ('---D---SER');
INSERT INTO #tempStrings VALUES ('A-V-D-C--SER');
SELECT
t.VAL,
CASE t.PART WHEN '' THEN '-' ELSE t.PART END AS PART
FROM
(SELECT
t.VAL,
ROW_NUMBER() OVER (PARTITION BY VAL ORDER BY (SELECT NULL)) AS IX,
value AS PART
FROM #tempStrings t
CROSS APPLY string_split(VAL, '-')) t
WHERE t.IX = 4; --DASH COUNT + 1
DROP TABLE #tempStrings;
Output is...
VAL PART
---D---SER D
A-V-D-C--SER C
NHL--WA-S-MOSG-SER- S
VDS----HAST-SER- -
If you always want the fourth element then using CHARINDEX is relatively straightforward:
DROP TABLE IF EXISTS #tmp;
CREATE TABLE #tmp (
rowId INT IDENTITY PRIMARY KEY,
xval VARCHAR(30) NOT NULL
);
INSERT INTO #tmp
VALUES
( 'NHL--WA-S-MOSG-SER-' ),
( 'VDS----HAST-SER-' ),
( '---D---SER' ),
( 'A-V-D-C--SER' );
;WITH cte AS
( -- Work out the position of the 3rd dash
SELECT
rowId,
xval,
CHARINDEX( '-', xval, CHARINDEX( '-', xval, CHARINDEX( '-', xval ) + 1 ) + 1 ) + 1 xstart
FROM #tmp t
), cte2 AS
( -- Work out the length for the substring function
SELECT rowId, xval, xstart, CHARINDEX( '-', xval, xstart) - (xstart) AS xlen
FROM cte
)
SELECT rowId, ISNULL( NULLIF( SUBSTRING( xval, xstart, xlen ), '' ), '-' ) xpart
FROM cte2
I also did a volume test at 1 million rows and this was by far the fastest method compared with STRING_SPLIT, OPENJSON, recursive CTE (the worst at high volume). As a downside this method is less extensible, say you want the second or fifth items for example.

How to SELECT string between second and third instance of ",,"?

I am trying to get string between second and third instance of ",," using SQL SELECT.
Apparently functions substring and charindex are useful, and I have tried them but the problem is that I need the string between those specific ",,"s and the length of the strings between them can change.
Can't find working example anywhere.
Here is an example:
Table: test
Column: Column1
Row1: cat1,,cat2,,cat3,,cat4,,cat5
Row2: dogger1,,dogger2,,dogger3,,dogger4,,dogger5
Result: cat3dogger3
Here is my closest attempt, it works if the strings are same length every time, but they aren't:
SELECT SUBSTRING(column1,LEN(LEFT(column1,CHARINDEX(',,', column1,12)+2)),LEN(column1) - LEN(LEFT(column1,CHARINDEX(',,', column1,20)+2)) - LEN(RIGHT(column1,CHARINDEX(',,', (REVERSE(column1)))))) AS column1
FROM testi
Just repeat sub-string 3 times, each time moving onto the next ",," e.g.
select
-- Substring till the third ',,'
substring(z.col1, 1, patindex('%,,%',z.col1)-1)
from (values ('cat1,,cat2,,cat3,,cat4,,cat5'),('dogger1,,dogger2,,dogger3,,dogger4,,dogger5')) x (col1)
-- Substring from the first ',,'
cross apply (values (substring(x.col1,patindex('%,,%',x.col1)+2,len(x.col1)))) y (col1)
-- Substring from the second ',,'
cross apply (values (substring(y.col1,patindex('%,,%',y.col1)+2,len(y.col1)))) z (col1);
And just to reiterate, this is a terrible way to store data, so the best solution is to store it properly.
Here is an alternative solution using charindex. The base idea is the same as in Dale K's an answer, but instead of cutting the string, we specify the start_location for the search by using the third, optional parameter, of charindex. This way, we get the location of each separator, and could slip each value off from the main string.
declare #vtest table (column1 varchar(200))
insert into #vtest ( column1 ) values('dogger1,,dogger2,,dogger3,,dogger4,,dogger5')
insert into #vtest ( column1 ) values('cat1,,cat2,,cat3,,cat4,,cat5')
declare #separetor char(2) = ',,'
select
t.column1
, FI.FirstInstance
, SI.SecondInstance
, TI.ThirdInstance
, iif(TI.ThirdInstance is not null, substring(t.column1, SI.SecondInstance + 2, TI.ThirdInstance - SI.SecondInstance - 2), null)
from
#vtest t
cross apply (select nullif(charindex(#separetor, t.column1), 0) FirstInstance) FI
cross apply (select nullif(charindex(#separetor, t.column1, FI.FirstInstance + 2), 0) SecondInstance) SI
cross apply (select nullif(charindex(#separetor, t.column1, SI.SecondInstance + 2), 0) ThirdInstance) TI
For transparency, I saved the separator string in a variable.
By default the charindex returns 0 if the search string is not present, so I overwrite it with the value null, by using nullif
IMHO, SQL Server 2016 and its JSON support in the best option here.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Tokens VARCHAR(500));
INSERT INTO #tbl VALUES
('cat1,,cat2,,cat3,,cat4,,cat5'),
('dogger1,,dogger2,,dogger3,,dogger4,,dogger5');
-- DDL and sample data population, end
WITH rs AS
(
SELECT *
, '["' + REPLACE(Tokens
, ',,', '","')
+ '"]' AS jsondata
FROM #tbl
)
SELECT rs.ID, rs.Tokens
, JSON_VALUE(jsondata, '$[2]') AS ThirdToken
FROM rs;
Output
+----+---------------------------------------------+------------+
| ID | Tokens | ThirdToken |
+----+---------------------------------------------+------------+
| 1 | cat1,,cat2,,cat3,,cat4,,cat5 | cat3 |
| 2 | dogger1,,dogger2,,dogger3,,dogger4,,dogger5 | dogger3 |
+----+---------------------------------------------+------------+
It´s the same as #"Yitzhak Khabinsky" but i think it looks clearer
WITH CTE_Data
AS(
SELECT 'cat1,,cat2,,cat3,,cat4,,cat5' AS [String]
UNION
SELECT 'dogger1,,dogger2,,dogger3,,dogger4,,dogger5' AS [String]
)
SELECT
A.[String]
,Value3 = JSON_VALUE('["'+ REPLACE(A.[String], ',,', '","') + '"]', '$[2]')
FROM CTE_Data AS A

How to extract the n-th value from the text field with delimitors

In SQL table I have a text column with value 'Yellow|Green|Blue' and another column with numeric value. This numeric value defines which part of the text column to be extracted. Values in the text column are separated with '|' separator.
For example:
If numeric value is 0, 1st part of the text field should be extracted: Yellow
If numeric value is 1, 2nd part of the text field should be extracted: Green
And so on.
Is there a way how to extract it dynamically ?
Meaning without using CASE statement like:
case when u.UD_2 =0 then 'Yellow' when u.UD_2=1 then 'Green' when u.UD_2=3 then 'Blue' end Kategorie
UPDATE: We are using SQL Server 2016
This should work for you, in the subquery extract each category to separate columns and after it, use a case statement to choose the needed category.
select case sep when 0 then x.[0] when 1 then x.[1] when 2 then x.[2] end as Kategorie
from (
select *
,LEFT(val, CHARINDEX('|', val) - 1) AS '0'
,LEFT(STUFF(SUBSTRING(val, CHARINDEX('|', val), LEN(val)), 1, 1, ''), CHARINDEX('|', STUFF(SUBSTRING(val, CHARINDEX('|', val), LEN(val)), 1, 1, '')) - 1) AS '1'
,SUBSTRING(SUBSTRING(val, CHARINDEX('|', val), LEN(val)), CHARINDEX('|', val) + 1, LEN(val)) AS '2'
from #test
)x
Sample data:
create table #test
(
val nvarchar(500),
sep int
)
insert into #test values
('Yellow|Green|Blue', 0),
('Yellow|Green|Blue', 1),
('Yellow|Green|Blue', 2)
Note: this only works if there are exact 3 values separated with |
UPDATE
And this is a dynamic way to achieve it, doesn't matter how many categories will be separated:
SELECT x.Kategorie
FROM (
SELECT DISTINCT node.s.value('.', 'NVARCHAR(500)') AS Kategorie
,ROW_NUMBER() OVER (PARTITION BY sep ORDER BY (SELECT NULL)) - 1 as rn
FROM (
SELECT sep
,CAST('<M>' + REPLACE(val, '|', '</M><M>') + '</M>' AS XML) AS Kategorie
FROM #test
) AS s
CROSS APPLY Kategorie.nodes('/M') AS node(s)
)x
JOIN #test AS t ON t.sep = x.rn
One possible approach is to split your text data into substrings and get each substring position.
Starting with SQL Server 2016 you may use STRING_SPLIT() to split a string, but in your case this is not an option, because this function returns a table with all substrings, but they are not ordered and the order of substrings is not guaranteed.
Again, if you use SQL Server 2016+, you may try to transform the text data into a valid JSON array using REPLACE() ('Yellow|Green|Blue' is transformed into '["Yellow","Green","Blue"]') and after that to use OPENJSON() with default schema to retrieve this JSON array as table, which has columns key, value and type (key column contains the index of the element in the specified array).
Input:
CREATE TABLE #Data (
TextValue nvarchar(max),
IndexValue int
)
INSERT INTO #Data
(TextValue, IndexValue)
VALUES
('Yellow|Green|Blue', 0),
('Yellow|Green|Blue', 1)
T-SQL:
SELECT d.TextValue, d.IndexValue, j.[value] AS [Value]
FROM #Data d
CROSS APPLY OPENJSON(CONCAT(N'["', REPLACE(d.TextValue, N'|', N'","'), N'"]')) j
WHERE d.IndexValue = j.[key]
Output:
---------------------------------------
TextValue IndexValue Value
---------------------------------------
Yellow|Green|Blue 0 Yellow
Yellow|Green|Blue 1 Green

How to concatenate columns without null value in sql

This question is asked many time here but i am not getting the proper output as i am expecting:
I have a table in which i need to concatenate columns but i don't want NULL value in it. I want it in sql server 2008 concate function does not work in 2008.
Example:
OrderTable
Customer_Number order1 order2 order3 order4
1 NULL X Y NULL
2 NULL A B NULL
3 V NULL H NULL
Now want i want is the data in concatenated manner for order only like this:
Customer_Number Order
1 X,Y
2 A,B
3 V,H
This is the code i used
Select Customer_number, ISNULL(NULLIF(order1,' ')+',','')+
ISNULL(NULLIF(order2,' ')+',','')+
ISNULL(NULLIF(order3,' ')+',','')+
ISNULL(NULLIF(order4,' ')+',','')
as Order from Ordertable
I got the below output
Customer_Number Order
1 NULL,X,Y,NULL
2 NULL,A,B,NULL
3 V,NULL,H,NULL
I already try Coalesce, Stuff, ISNULL, NULLIF but all have same result
Thanks in advance !!!
Another variation, for fun and profit, demonstrating the FOR XML trick to concatenate values pre-SQL Server 2012.
SELECT Customer_Number, STUFF(
(SELECT ',' + order1, ',' + order2, ',' + order3, ',' + order4 FOR XML PATH('')),
1, 1, ''
)
This is slight overkill for a constant number of columns (and not particularly efficient), but an easy to remember pattern for concatenation. Also, it shows off STUFF, a function any SQL developer should learn to love.
Example
Declare #YourTable Table ([Customer_Number] varchar(50),[order1] varchar(50),[order2] varchar(50),[order3] varchar(50),[order4] varchar(50))
Insert Into #YourTable Values
(1,NULL,'X','Y',NULL)
,(2,NULL,'A','B',NULL)
,(3,'V',NULL,'H',NULL)
Select Customer_Number
,[Order] = IsNull(stuff(
IsNull(','+order1,'')
+IsNull(','+order2,'')
+IsNull(','+order3,'')
+IsNull(','+order4,'')
,1,1,''),'')
From #YourTable
Returns
Customer_Number Order
1 X,Y
2 A,B
3 V,H
EDIT - IF the "NULL" are strings and NOT NULL Values
Declare #YourTable Table ([Customer_Number] varchar(50),[order1] varchar(50),[order2] varchar(50),[order3] varchar(50),[order4] varchar(50))
Insert Into #YourTable Values
(1,'NULL','X','Y','NULL')
,(2,'NULL','A','B','NULL')
,(3,'V','NULL','H','NULL')
Select Customer_Number
,[Order] = IsNull(stuff(
IsNull(','+nullif(order1,'NULL'),'')
+IsNull(','+nullif(order2,'NULL'),'')
+IsNull(','+nullif(order3,'NULL'),'')
+IsNull(','+nullif(order4,'NULL'),'')
,1,1,''),'')
From #YourTable
This is a bit unpleasant, it needs to be in a subquery to remove the trailing comma efficiently (though you could use a CTE):
SELECT
Customer_number,
SUBSTRING([Order], 0, LEN([Order])) AS [Order]
FROM(
SELECT
Customer_number,
COALESCE(order1+',', '') +
COALESCE(order2+',', '') +
COALESCE(order3+',', '') +
COALESCE(order4+',', '') AS [Order]
FROM
OrderTable) AS SubQuery
You were on the right track with the Coalesce, or IsNull. Instead of trying to track the length and use substring, left or stuff, I just used a replace to remove the trailing comma that would show up from the coalesce.
Select Customer_number,
Replace(
Coalesce(Order1 + ',','')+
Coalesce(Order2 + ',','')+
Coalesce(Order3 + ',','')+
Coalesce(Order4 + ',','')
+',',',,','') --Hack to remove the Last Comma
as [Order] from Ordertable

Replace all numbers with three digits or more

I have a field say "keywords" which contains random strings of numbers and I'd like to clean the field from any string of numbers which has more than 3 digits.
I have searched and know wildcards are not possible in replace. Any idea how I can go about that?
Here's a good place to start
Say you have a table called "test_test":
create table dbo.test_test (thisStuff varchar(100));
With a value like this in it:
insert into test_test values ('Hello123 this is 12 a test 22983o398r57298298347238');
You can do some limited pattern matching with patindex():
select substring(thisStuff,
1,
patindex('%[0-9][0-9][0-9]%',thisStuff)-1) +
substring(thisStuff,
patindex('%[0-9][0-9][0-9]%',thisStuff)+3,
len(thisStuff))
from test_test
Which converts this value:
Hello123 this is 12 a test 22983o398r57298298347238
Into this value:
Hello this is 12 a test 22983o398r57298298347238
In update form it would look like this:
update test_test set thisStuff =
substring(thisStuff,
1,
patindex('%[0-9][0-9][0-9]%',thisStuff)-1) +
substring(thisStuff,
patindex('%[0-9][0-9][0-9]%',thisStuff)+3,
len(thisStuff));
Which, when run over and over, gives you the progressive values:
Hello this is 12 a test 83o398r57298298347238
Hello this is 12 a test 83or57298298347238
Hello this is 12 a test 83or98298347238
Hello this is 12 a test 83or98347238
Hello this is 12 a test 83or47238
Hello this is 12 a test 83or38
Before erroring out
Msg 3621, Level 0, State 0.
The statement has been terminated.
Msg 537, Level 16, State 2.
Invalid length parameter passed to the LEFT or SUBSTRING function. (Line 35)
Since you are on 2016, you can use String_Split() in concert with Try_Convert()
Example
Declare #YourTable table (idproduct int,searchkeywords varchar(500))
Insert Into #YourTable values
(109070,'stands & cabinets kantec ams300 1010055 43212002 03906786808 7503 ktkams ltk ams 300')
Select A.idproduct
,NewString = B.S
From #YourTable A
Cross Apply (
Select S = Stuff((Select ' ' +Value
From (Select Value,Seq=Row_Number() over (Order by (select null))
From String_Split(A.searchkeywords,' ')
) B1
Where (try_convert(float,Value) is null)
or (try_convert(float,Value) is not null and len(Value)<=3)
Order by Seq
For XML Path ('')),1,1,'')
) B
Returns
idproduct NewString
109070 stands & cabinets kantec ams300 ktkams ltk ams 300
If you are satisfied with the results, you can apply an update like so:
Update A Set searchkeywords = B.S
From #YourTable A
Cross Apply (
Select S = Stuff((Select ' ' +Value
From (Select Value,Seq=Row_Number() over (Order by (select null))
From String_Split(A.searchkeywords,' ')
) B1
Where (try_convert(float,Value) is null)
or (try_convert(float,Value) is not null and len(Value)<=3)
Order by Seq
For XML Path ('')),1,1,'')
) B