I have a database column in a SQL Server 2008 database that contains VARCHAR data composed of four elements separated by an underscore; for example:
01_1234_ABC_TESTFOO
02_2234_YES_FOO
03_77653234_NO_BAR
04_10922234_BLUE_TESTBAR
05_8372_SKY_FOOBAR
I need a query to return the first three elements for each row as separate columns. I have managed to separate out the first two elements with the following code:
SELECT DISTINCT SUBSTRING(BarType, 1, CHARINDEX('_', BarType) - 1) as element1,
SUBSTRING(BarType,CHARINDEX('_',BarType)+1,
CHARINDEX('_', SUBSTRING(BarType,CHARINDEX('_',BarType)+1,LEN(BarType)))-1) as element2
FROM dbo.TestDataFoo
This returns:
element1 element2
01 1234
02 2234
03 77653234
04 10922234
05 8372
Could someone help me to get the third element of the data please? I'd also be interested to learn of any alternate methods for dealing with this.
See below for code to generate test data.
CREATE TABLE TestDataFoo (
id int PRIMARY KEY IDENTITY,
DateFoo datetime NOT NULL,
BarType varchar(50) NOT NULL)
INSERT INTO TestDataFoo (DateFoo, BarType)
VALUES(GetDate(), '01_1234_ABC_TESTFOO')
INSERT INTO TestDataFoo (DateFoo, BarType)
VALUES(GetDate(), '02_2234_YES_FOO')
INSERT INTO TestDataFoo (DateFoo, BarType)
VALUES(GetDate(), '03_77653234_NO_BAR')
INSERT INTO TestDataFoo (DateFoo, BarType)
VALUES(GetDate(), '04_10922234_BLUE_TESTBAR')
INSERT INTO TestDataFoo (DateFoo, BarType)
VALUES(GetDate(), '05_8372_SKY_FOOBAR')
Thanks
Nimi
Edit: Ideally, I'd like to achieve this without using a function but any solutions welcome!
;with cte as
(
select cast('<r><i>'+replace(BarType,'_','</i><i>')+'</i></r>' as xml) as xmlcol
from TestDataFoo
)
select
r.value('i[1]', 'varchar(50)') as element1,
r.value('i[2]', 'varchar(50)') as element2,
r.value('i[3]', 'varchar(50)') as element3
from cte
cross apply xmlcol.nodes('r') r(r)
Here's a handy function that I think would do the trick.
create function [dbo].[fn_split]
(
#String nvarchar (4000)
,#Delimiter nvarchar (10)= ','
)
returns #ValueTable table ([Value] nvarchar(4000))
as
/******************************************************************************
** name: fn_split
** desc: splits a delimited list into a table
** select * from dbo.fn_split('01_1234_ABC_TESTFOO', '_')
*******************************************************************************/
begin
declare #NextString nvarchar(4000)
declare #Pos int
declare #NextPos int
declare #CommaCheck nvarchar(1)
--Initialize
set #NextString = ''
set #CommaCheck = right(#String,1)
--Check for trailing Comma, if not exists, INSERT
--if (#CommaCheck <> #Delimiter )
set #String = #String + #Delimiter
--Get position of first Comma
set #Pos = charindex(#Delimiter,#String)
set #NextPos = 1
--Loop while there is still a comma in the String of levels
while (#pos <> 0)
begin
set #NextString = substring(#String,1,#Pos - 1)
insert into #ValueTable ( [Value]) Values (#NextString)
set #String = substring(#String,#pos +1,len(#String))
set #NextPos = #Pos
set #pos = charindex(#Delimiter,#String)
end
return
end
GO
Just use the old PARSENAME trick:
SQL
SELECT
PARSENAME(REPLACE(MEGASTRING,'_','.'),4) AS COL1,
PARSENAME(REPLACE(MEGASTRING,'_','.'),3) AS COL2,
PARSENAME(REPLACE(MEGASTRING,'_','.'),2) AS COL3,
PARSENAME(REPLACE(MEGASTRING,'_','.'),1) AS COL4
FROM (
SELECT '01_1234_ABC_TESTFOO' AS MEGASTRING
UNION ALL SELECT '02_2234_YES_FOO' AS MEGASTRING
UNION ALL SELECT '03_77653234_NO_BAR' AS MEGASTRING
UNION ALL SELECT '04_10922234_BLUE_TESTBAR' AS MEGASTRING
UNION ALL SELECT '05_8372_SKY_FOOBAR' AS MEGASTRING
) data
Results
COL1 COL2 COL3 COL4
01 1234 ABC TESTFOO
02 2234 YES FOO
03 77653234 NO BAR
04 10922234 BLUE TESTBAR
05 8372 SKY FOOBAR
Related
I have a Table Family like the following
Family_Name | Family_Members_Age
Johnson | 45,60,56
Ken | 78,67,40
David | 40
Here is a proc I have
CREATE PROCEDURE getFamilyRowsByAge #Age nvarchar(30)
AS
SELECT *
FROM Family
WHERE Family_members_age LIKE FILL_IN -- need to get this fill_in dynamically
The #Age param is supplied with comma separated String like 45,67.
FILL_IN would be something like this for input String of "45,67" LIKE '%45%' OR LIKE '%67%'. I want this to be dynamically created by splitting input String for comma and joining with LIKE OR. Is there a way in MSSQL to do this?
Output:
Johnson | 45,60,56
Ken | 78,67,40
Here is another input and output:
input : 40, 67, 69
Output:
Johnson | 45,60,56
Ken | 78,67,40
David | 40
Based on those comments, try this:
USE tempdb;
GO
DROP TABLE IF EXISTS dbo.Family;
GO
CREATE TABLE dbo.Family
(
FamilyID int IDENTITY(1,1)
CONSTRAINT PK_dbo_Family PRIMARY KEY,
Family_Name varchar(100),
Family_Members_Age varchar(max)
);
GO
INSERT dbo.Family (Family_Name, Family_Members_Age)
VALUES ('Johnson', '45,60,56'),
('Ken', '78,67,40'),
('David', '40');
GO
CREATE PROCEDURE dbo.GetFamilyRowsByAge
#RequiredAges varchar(100)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
WITH FamilyAges
AS
(
SELECT f.Family_Name, fma.Age
FROM dbo.Family AS f
CROSS APPLY (SELECT value AS Age FROM STRING_SPLIT(f.Family_Members_Age,',')) AS fma
)
SELECT fa.Family_Name, fa.Age
FROM FamilyAges AS fa
WHERE fa.Age IN (SELECT value FROM STRING_SPLIT(#RequiredAges, ','));
END;
GO
EXEC dbo.GetFamilyRowsByAge '45,67,40';
GO
You can achieve it by this simple way, Live demo here
strSplit
CREATE FUNCTION [dbo].[strSplit] ( #string nvarchar( MAX), #splitter CHAR( 1) )
RETURNS #res TABLE (id INT PRIMARY KEY, rank INT, val nvarchar( MAX) )
AS
BEGIN
IF SUBSTRING ( #string, len ( #string), 1)<>#splitter
SET #string= #string+#splitter
DECLARE #start INT, #word nvarchar(MAX), #charindex INT, #i INT
SET #i=1
SET #start=1
SET #charindex= CHARINDEX( #splitter, #string, #start)
WHILE (#charindex <> 0)BEGIN
SET #word= SUBSTRING( #string, #start, #charindex - #start)
SET #start= #charindex +1
SET #charindex= CHARINDEX( #splitter, #string, #start)
INSERT INTO #res VALUES ( #start, #i, #word)
SET #i=#i+1
END
RETURN
END
ContainString
CREATE FUNCTION [dbo].[ContainString] (#string1 nvarchar( MAX), #string2 nvarchar( MAX))
RETURNS BIT
AS
BEGIN
IF(EXISTS(SELECT TOP 1 a.val From strSplit(#string1, ',') a
INNER JOIN strSplit(#string2, ',') b on a.val = b.val ))
BEGIN
RETURN 1
END
RETURN 0
END
Select result
SELECT * FROM Family WHERE [dbo].ContainString(Family_Members_Age, '45,67') = 1
SELECT * FROM Family WHERE [dbo].ContainString(Family_Members_Age, '40,67,69') = 1
The whole query within the proc will need to be dynamic. Then you can use something like STRING_SPLIT (2016 or later) to pull apart the comma-delimited value and build the string.
However, I'm unconvinced that's what you need. If you use LIKE, how are you going to avoid %9% matching a value like 91 ?
You might want to consider normalizing the table correctly instead. Why are there 3 ages within a single column? Why not just store it as 3 rows?
Probably you need something like this .
SELECT 'Johnson'Family_Name,'45,60,56' Family_Members_Age
into #YourTable
union all select 'Ken','78,67,40'
union all select 'David','40'
DECLARE #WhereQuery varchar(250)='40, 67, 69', #Query nvarchar(250)
select #Query = 'select * from #YourTable'
SET #WhereQuery = ' WHERE Family_Members_Age LIKE ''%'+ REPLACE (#WhereQuery,',','%'' OR Family_Members_Age LIKE ''%') + '%'''
SET #Query = #Query + #WhereQuery
EXEC SP_EXECUTESQL #Query
Here is my table structure:
id PaymentCond
1 ZBE1, AP1, LST2, CC1
2 VB3, CC1, ZBE1
I need to split the column PaymentCond, and would love to do that with a simple sql query since I have no clue how to use functions and would love to keep it all simple.
Here is what I already found:
SELECT id,
Substring(PaymentConditions, 1, Charindex(',', PaymentConditions)-1) as COND_1,
Substring(PaymentConditions, Charindex(',', PaymentConditions)+1, LEN(ANGEBOT.STDTXT)) as COND_2
from Payment
WHERE id = '1'
But this only outputs
id COND_1 COND_2
1 ZBE1 AP1, LST2, CC1
Is there a way to split everything from PaymentConditions to COND_1, COND_2, COND_3 and so on?
Thanks in advance.
first create function to split values
create function [dbo].[udf_splitstring] (#tokens varchar(max),
#delimiter varchar(5))
returns #split table (
token varchar(200) not null )
as
begin
declare #list xml
select #list = cast('<a>'
+ replace(#tokens, #delimiter, '</a><a>')
+ '</a>' as xml)
insert into #split
(token)
select ltrim(t.value('.', 'varchar(200)')) as data
from #list.nodes('/a') as x(t)
return
end
CREATE TABLE #Table1
([id] int, [PaymentCond] varchar(20))
;
INSERT INTO #Table1
([id], [PaymentCond])
VALUES
(1, 'ZBE1, AP1, LST2, CC1'),
(2, 'VB3, CC1, ZBE1')
;
select id, token FROM #Table1 as t1
CROSS APPLY [dbo].UDF_SPLITSTRING([PaymentCond],',') as t2
output
id token
1 ZBE1
1 AP1
1 LST2
1 CC1
2 VB3
2 CC1
2 ZBE1
declare #SchoolYearList nvarchar(max)='2014,2015,2016'
declare #start int=1
declare #length int=4
create table #TempFY(SchoolYear int)
while #start<len(#SchoolYearList)
BEGIN
Insert into #TempFY
select SUBSTRING(#SchoolYearList,#start,#length)
set #start=#start+5
END
Select SchoolYear from #TempFY
There is a new table-valued function in SQL Server STRING_SPLIT:
DECLARE #tags NVARCHAR(400) = 'aaaa,bbb,,cc,d'
SELECT *
FROM STRING_SPLIT(#tags, ',')
You will get:
But be careful its availability in your DB: The STRING_SPLIT function is available only under compatibility level 130
I have a string field in which csv row is inserted
'6 33','318011385','3183300153','Z','21.11.2011 13:33:22','51','51','2','0','032425','','','','','8 50318011100 318069332','','21.11.2011','21.11.2011','','0','','','GOT','0','0','0','0','0','0','0','0','0','0','0','21.11.2011','4','','','','','','','','','','','','',''
I need to extract several fields from this csv format using t-sql.
My main approach was to count colons (,) and based on the colon num to parse the data between two colons:
select min(SUBSTRING(field,charindex(''',''',recorddata,charindex(''',''',recorddata)+1)+3,CHARINDEX(''',''',field,charindex(''',''',field,charindex(''',''',field)+1)+3) - (charindex(''',''',field,charindex(''',''',field)+1)+3))) as fld from TBLSYNCEXPORT where SUBSTRING(field,2,CHARINDEX(''',''',field,0)-2) = #type and substring(field,CHARINDEX(''',''',field)+3,3) = #person and SUBSTRING(field,charindex(''',''',field,charindex(''',''',field)+1)+3,CHARINDEX(''',''',field,charindex(''',''',field,charindex(''',''',field)+1)+3) - (charindex(''',''',field,charindex(''',''',field)+1)+3)) > #prev_type
is there a better method that this one?
If you prefer a more clear way, at least for me, you can do something like this:
CREATE TABLE #destination_table(
value varchar(10)
)
DECLARE #position INT
DECLARE #source_string VARCHAR( 1000 )
SELECT #source_string = "'6 33','318011385','3183300153','Z','21.11.2011 13:33:22','51','51','2','0','032425','','','','','8 50318011100 318069332','','21.11.2011','21.11.2011','','0','','','GOT','0','0','0','0','0','0','0','0','0','0','0','21.11.2011','4','','','','','','','','','','','','',''"
SELECT #position = CHARINDEX(',', #source_string )
WHILE #position <> 0
BEGIN
INSERT INTO #destination_table VALUES( LEFT( #source_string, #position-1 ) )
SELECT #source_string = STUFF( #source_string, 1, #position, NULL )
SELECT #position = CHARINDEX(',', #source_string )
END
INSERT INTO #destination_table VALUES( #source_string)
SELECT * FROM #destination_table
-- or select what you need
select value from #destination_table where id = 2
drop table #destination_table
It'll insert the different values in a table and then you can choose the needed values.
I am having more issues with splitting stacked columns, and would love some help to complete this last part. I was trying to apply another solution I had, but with no luck.
DB Table:
ID INT,
SN varchar(100),
Types varchar(1000)
Sample:
ID SN Types
1 123 ABC,XYZ,TEST
2 234 RJK,CDF,TTT,UMB,UVX
3 345 OID,XYZ
Desired output:
ID SN Types
1 123 ABC
1 123 XYZ
1 123 TEST
....
here's a cte i have to break up a delimited string
declare #table table (ID int identity(1,1), String varchar(max))
declare #delim varchar(max)
insert into #table values ('abc,def')
insert into #table values ('ghij,klmn,opqrst')
set #delim=','
;with c as
(
select
ID,
--String,
CHARINDEX(#delim,String,1) as Pos,
case when CHARINDEX(#delim,String,1)>0 then SUBSTRING(String,1,CHARINDEX(#delim,String,1)-1) else String end as value,
case when CHARINDEX(#delim,String,1)>0 then SUBSTRING(String,CHARINDEX(#delim,String,1)+1,LEN(String)-CHARINDEX(#delim,String,1)) else '' end as String
from #table
union all
select
ID,
CHARINDEX(#delim,String,1) as Pos,
case when CHARINDEX(#delim,String,1)>0 then SUBSTRING(String,1,CHARINDEX(#delim,String,1)-1) else String end as Value,
case when CHARINDEX(#delim,String,1)>0 then SUBSTRING(String,CHARINDEX(#delim,String,1)+1,LEN(String)-CHARINDEX(#delim,String,1)) else '' end as String
from c
where LEN(String)>0
)
select ID, Value from c
declare #T table(ID int, SN varchar(100), Types varchar(1000))
insert into #T
select 1, 123, 'ABC,XYZ,TEST' union all
select 2, 234, 'RJK,CDF,TTT,UMB,UVX' union all
select 4, 234, 'XXX' union all
select 3, 345, 'OID,XYZ'
;with cte(ID, SN, Types, Rest) as
(
select ID,
SN,
cast(substring(Types+',', 1, charindex(',', Types+',')-1) as varchar(100)),
stuff(Types, 1, charindex(',', Types), '')+','
from #T
where len(Types) > 0
union all
select ID,
SN,
cast(substring(Rest, 1, charindex(',', Rest)-1) as varchar(100)),
stuff(Rest, 1, charindex(',', Rest), '')
from cte
where len(Rest) > 0
)
select ID, SN, Types
from cte
order by ID
I use a recursive CTE to split the string. The third column Types is populated with the first word in the Types column of #T. Stuff will then remove the first word and populate the Rest column that then will contain everything but the first word. After UNION ALL is the recursive part that basically do the exact same thing but it uses the CTE as a source and it uses the rest column to pick the first word. The first word of the rest column is removed with stuff and then ..... well it is recursive so I think I will stop here with the explanation. The recursive part will end when there are no more words left
where len(Rest) > 0.
You will need to use a cursor and a while statement as far as I can tell... Some of these indexes may be off by one, but I think this should get you there...
DECLARE MY_CURSOR Cursor
FOR
SELECT ID, SN, Types
FROM Tbl1
Open My_Cursor
DECLARE #ID int, #SN varchar(100), #types varchar(1000)
Fetch NEXT FROM MY_Cursor INTO #ID, #SN, #types
While (##FETCH_STATUS <> -1)
BEGIN
DECLARE #Pos int
WHILE #Pos < LEN(#types)
BEGIN
DECLARE #type varchar(25)
DECLARE #nextpos int
set #nextpos = CHARINDEX(#types, ',', #pos)
SET #type = SUBSTRING(#types, #pos, #nextpos-#pos)
INSERT INTO tbl2 (ID, SN, type) VALUES (#ID, #SN, #Type)
SET #Pos = #nextpos+1
END
FETCH NEXT FROMMY_CURSOR INTO #VAR1Number, #VAR2DateTime ,#VarLongText
END
CLOSE MY_CURSOR
DEALLOCATE MY_CURSOR
First of all many thanks to the site creator and most importantly helping guru's on this site.
I have the same problem splitting string from a field and displaying it in multiple columns example my table has got three columns
dbo.tests
Fname ID wTest Loc
ABC 1 "XYZ,PTO,LKMD,HGGFFD," R1
BCE 2 "PTO,XYZ,LKMD,," R1
LKJ 3 "XYZ" R3
JKL 4 "XYZ,PTO,LKMD,HGGFFD,PKL" R2
The output for the select statement should display the data as follows: (Dynamically generate number of columns based on maximum columns required from wTest string and fill the empty columsn with null or some value.
Returns:
Fname ID Loc wTest wTest1 wTest2,wTest3,Wtest4...
ABC 1 R1 XYZ PTO LKMD HGGFFD Null
BCE 2 R1 PTO XYZ LKMD Null Null
LKJ 3 R3 XYZ Null Null Null Null
JKL 4 R2 XYZ PTO LKMD HGGFFD PKL
Two close function I came accross are as follows:
CREATE FUNCTION dbo.Split (#sep char(1), #s varchar(512))
RETURNS table AS RETURN
(
WITH Pieces (pn, start, stop) AS
(
SELECT 1, 1, CHARINDEX(#sep, #s)
UNION ALL
SELECT pn + 1, stop + 1,
CHARINDEX(#sep, #s, stop + 1)
FROM Pieces
WHERE stop > 0 )
SELECT pn, SUBSTRING(#s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s FROM Pieces )
with testTable AS
( SELECT 1 AS Id, N'how now brown cow' AS txt
UNION ALL
SELECT 2, N'she sells sea shells upon the sea shore' UNION ALL
SELECT 3, N'red lorry yellow lorry' UNION ALL
SELECT 4, N'the quick brown fox jumped over the lazy dog' )
SELECT display_term, COUNT(*) As Cnt
FROM testTable CROSS APPLY sys.dm_fts_parser('"' + txt + '"', 1033, 0,0)
GROUP BY display_term
HAVING COUNT(*) > 1 ORDER BY Cnt DESC
Any help in this regard is highly appreciated.
Zain...
zainali2006#hotmail.co.uk
Someone suggested this, but I am having difficulties applying for my purpose....
Returns #Tbl_IDs
Table (Id int identity(1,1),
Data Varchar(500)) As
Begin
--Remove the leading delimiter if any
while (substring(#IDs,1,1) =#Delimiter)
set #IDs = substring(#IDs, 2,len(#IDs)-1)
-- Append comma
--Set #IDs = #IDs + #Delimiter
set #IDs = REPLACE(RTRIM(LTRIM(REPLACE(#IDs,#Delimiter,' '))),' ',#Delimiter)
-- Indexes to keep the position of searching
Declare #Pos1 Int
Declare #pos2 Int
Declare #RowNum Int
-- Start from first character
Set #Pos1=1
Set #Pos2=1
While #Pos1>0
Begin
Set #Pos1 = CharIndex(#Delimiter,#IDs,#Pos1)
Insert #Tbl_IDs Values (Substring(#IDs,#Pos2,#Pos1-#Pos2))
-- Go to next non comma character
Set #Pos2=#Pos1+1
-- Search from the next charcater
Set #Pos1 = #Pos1+1
End
Return
End
Another one I came across quite interesting and simple but not sure how to use in my select statement:
DECLARE #NextString NVARCHAR(40)
DECLARE #Pos INT
DECLARE #NextPos INT
DECLARE #String NVARCHAR(40)
DECLARE #Delimiter NVARCHAR(40)
SET #String ='SQL,TUTORIALS,,TCF'
SET #Delimiter = ','
SET #String = #String + #Delimiter
SET #Pos = charindex(#Delimiter,#String)
WHILE (#pos <> 0)
BEGIN
SET #NextString = substring(#String,1,#Pos - 1)
SELECT #NextString -- Show Results
SET #String = substring(#String,#pos+1,len(#String))
SET #pos = charindex(#Delimiter,#String)
END