SQL Server : get a substring (first name, last name, surname) - sql

I'm looking for a way to get table below:
CREATE TABLE [dbo].[#temp]
(
[ID_TASK] [NVARCHAR](300) NULL,
[CHNAME_NAME] [NVARCHAR](300) NULL,
[CHNAME_PHONE] [NVARCHAR](300) NULL
) ON [PRIMARY]
INSERT INTO [dbo].[#temp]
VALUES ('ID005', 'Anderson Abreu Oliveira', '68157120'),
('ID006', 'Gonzalez-IV', '64106929'),
('ID009', 'Parker W.H.', '60994308')
I'm using this SQL query:
SELECT
ID_TASK, CHNAME_NAME,
RTRIM(LTRIM(SUBSTRING(CHNAME_NAME, 1, CHARINDEX(' ', CHNAME_NAME)))) AS SURNAME,
RTRIM(LTRIM(SUBSTRING(CHNAME_NAME, CHARINDEX(' ', CHNAME_NAME) + 1, LEN(CHNAME_NAME) - (CHARINDEX(' ', CHNAME_NAME) - 1)))) AS FIRSTNAME,
REPLACE((RTRIM(LTRIM(CHNAME_PHONE))), '8-', '') AS CHNAME_PHONE
FROM
[dbo].[#temp]
but I get this result:
How do I fix this SQL query to get the desired result? Thank you

You might try to use case..when structure
with CHARINDEX('-', CHNAME_NAME) and CHARINDEX(' ', CHNAME_NAME)
as in the following statement :
SELECT
ID_TASK, CHNAME_NAME,
RTRIM(LTRIM(SUBSTRING(CHNAME_NAME, 1,
(Case CHARINDEX(' ', CHNAME_NAME) When 0 Then CHARINDEX('-', CHNAME_NAME)-1
Else CHARINDEX(' ', CHNAME_NAME) End)))) AS SURNAME,
RTRIM(LTRIM(SUBSTRING(CHNAME_NAME,
(Case CHARINDEX(' ', CHNAME_NAME) When 0 Then CHARINDEX('-', CHNAME_NAME)
Else CHARINDEX(' ', CHNAME_NAME) End) + 1,
LEN(CHNAME_NAME) - (CHARINDEX(' ', CHNAME_NAME) - 1)))) AS FIRSTNAME,
REPLACE((RTRIM(LTRIM(CHNAME_PHONE))), '8-', '') AS CHNAME_PHONE
FROM
[dbo].[#temp]

This is quite an advanced thing to achieve I've done some SQL to get you there, but not tidied it up...
DECLARE #temp TABLE (
[ID_TASK] [nvarchar](300) NULL,
[CHNAME_NAME] [nvarchar](300) NULL,
[CHNAME_PHONE] [nvarchar](300) NULL
)
INSERT INTO #temp
SELECT 'ID005','Anderson Abreu Oliveira','68157120' UNION ALL
SELECT 'ID006','Gonzalez-IV','64106929' UNION ALL
SELECT 'ID009','Parker W.H.','60994308'
select ID_TASK, col1, col2, col3
from
(
select a.ID_TASK, Item , 'col' + CONVERT(NVARCHAR(10), ItemNumber) ItemNumber
from #temp a
CROSS APPLY [dbo].[DelimitedSplit] ( REPLACE(a.CHNAME_NAME, '-', ' '), ' ' ) b
) d
pivot
(
MAX(item)
for itemNumber in (col1, col2, col3)
) piv
GROUP BY ID_TASK, col1, col2, col3
Helper function:
CREATE FUNCTION [dbo].[DelimitedSplit] (
#pString VARCHAR(MAX),
#pDelimiter CHAR(1)
)
RETURNS TABLE WITH SCHEMABINDING
AS
RETURN
WITH E1(N) AS ( -- 10
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
E2(N) AS (SELECT 1 FROM E1 a, E1 b),
E3(N) AS (SELECT 1 FROM E2 a, E2 b, E2 c),
E4(N) AS (SELECT 1 FROM E3 a, E3 b, E3 c, E3 d),
cteTally(N) AS (
SELECT 0 UNION ALL
SELECT TOP ( DATALENGTH( ISNULL( #pString, 1 ) ) ) ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL ) ) FROM E4
),
cteStart(N1) AS (
SELECT t.N + 1
FROM cteTally t
WHERE ( SUBSTRING( #pString, t.N, 1 ) = #pDelimiter OR t.N = 0 )
)
-- ------
SELECT
ItemNumber = ROW_NUMBER() OVER( ORDER BY s.N1 ),
Item = SUBSTRING( #pString, s.N1, ISNULL( NULLIF( CHARINDEX( #pDelimiter, #pString, s.N1 ), 0) - s.N1, 1000000 ) )
FROM cteStart s
GO

Here you go:
CREATE TABLE [dbo].[#temp]
(
[ID_TASK] [NVARCHAR](300) NULL,
[CHNAME_NAME] [NVARCHAR](300) NULL,
[CHNAME_PHONE] [NVARCHAR](300) NULL
) ON [PRIMARY]
INSERT INTO [dbo].[#temp]
VALUES ('ID005', 'Anderson Abreu Oliveira', '68157120'),
('ID006', 'Gonzalez-IV', '64106929'),
('ID009', 'Parker W.H.', '60994308');
WITH CTE AS
(
SELECT [ID_TASK],
LEFT([CHNAME_NAME], IIF( CHARINDEX(' ', [CHNAME_NAME]) = 0 , CHARINDEX('-', [CHNAME_NAME])-1, CHARINDEX(' ', [CHNAME_NAME]))) AS T,
[CHNAME_NAME],
[CHNAME_PHONE]
FROM #Temp
)
SELECT [ID_TASK],
[CHNAME_NAME],
T AS SURNAME,
LTRIM(REPLACE(CASE WHEN (T IS NULL) OR (T = '') THEN [CHNAME_NAME] ELSE SUBSTRING([CHNAME_NAME], LEN(T)+1, LEN([CHNAME_NAME])) END, '-', '')) FirstName,
[CHNAME_PHONE]
FROM CTE;
Results:
+---+---------+-------------------------+-----------+-----------------+--------------+
| | ID_TASK | CHNAME_NAME | SURNAME | FirstName | CHNAME_PHONE |
+---+---------+-------------------------+-----------+-----------------+--------------+
| 1 | ID005 | Anderson Abreu Oliveira | Anderson | Abreu Oliveira | 68157120 |
| 2 | ID006 | Gonzalez-IV | Gonzalez | IV | 64106929 |
| 3 | ID009 | Parker W.H. | Parker | W.H. | 60994308 |
+---+---------+-------------------------+-----------+-----------------+--------------+
Demo

Related

Split comma separated values of a multiple column in row in SQL query

I have data Like this
Code address phno
123 test1,test2,test3 123,456,789
And I want output
Code address phno
123 test1 123
123 test2 456
123 test3 789
My code is this
declare #address VARCHAR(500) = 'test1,test2,test3', #phoneno VARCHAR(500) = '123,456,789'
select *
from STRING_SPLIT(#address, ','), STRING_SPLIT(#phoneno, ',')
but it returns multiple values
You can use recursive CTEs for this:
with cte as (
select code, cast(NULL as varchar(max)) as address, cast(NULL as varchar(max)) as phno,
cast(address + ',' as varchar(max)) as rest_address, cast(phno + ',' as varchar(max)) as rest_phno, 0 as lev
from t
union all
select code, left(rest_address, charindex(',', rest_address) - 1), left(rest_phno, charindex(',', rest_phno) - 1),
stuff(rest_address, 1, charindex(',', rest_address), ''), stuff(rest_phno, 1, charindex(',', rest_phno), ''), lev + 1
from cte
where rest_address <> ''
)
select code, address, phno
from cte
where lev > 0;
Here is a db<>fiddle.
If you can have more than 100 elements in the lists, you'll need option (maxrecursion 0).
Please try the following solution.
SQL
DECLARE #tbl TABLE (Code CHAR(3), address VARCHAR(100), phno VARCHAR(100));
INSERT INTO #tbl (Code, address, phno) VALUES
('123', 'test1,test2,test3', '123,456,789');
;WITH cte1 AS
(
SELECT Code, value AS address
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS seq
FROM #tbl
CROSS APPLY STRING_SPLIT(address, ',')
), cte2 AS
(
SELECT Code, value AS phno
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS seq
FROM #tbl
CROSS APPLY STRING_SPLIT(phno, ',')
)
SELECT cte1.code, cte1.address, cte2.phno
FROM cte1 INNER JOIN cte2
ON cte2.seq = cte1.seq;
SQL #2, JSON based
;WITH rs AS
(
SELECT *
, ar1 = '["' + Replace(address, ',', '","') + '"]'
, ar2 = '["' + Replace(phno, ',', '","') + '"]'
FROM #tbl
)
SELECT Code, address.[value] AS [address], phno.[value] AS phno
FROM rs
CROSS APPLY OPENJSON (ar1, N'$') AS address
CROSS APPLY OPENJSON (ar2, N'$') AS phno
WHERE address.[key] = phno.[key];
Output
+------+---------+------+
| code | address | phno |
+------+---------+------+
| 123 | test1 | 123 |
| 123 | test2 | 456 |
| 123 | test3 | 789 |
+------+---------+------+

SQL extract string where it starts with specific character

I have a column that includes strings that are separated by space and commas. It looks like:
Column1
----------------------------
T1234, C1234, D1234, C1234
E1234, C1234
I need a SQL query to extract anything that starts with C. So the result would look like:
Column1
--------------
C1234, C1234
C1234
This is also an opportunity to use a recursive CTE:
with t as (
select 'T1234, C1234, D1234, C1234' as col1 union all
select 'E1234, C1234'
),
cte as (
select col1,
convert(varchar(max), (case when col1 like 'C%' then ', ' + left(col1, charindex(',', col1 + ',') - 1 ) else '' end)) as c_list,
convert(varchar(max), stuff(col1, 1, charindex(',', col1 + ',') + 1, '')) as rest,
1 as lev
from t
union all
select col1,
c_list + (case when rest like 'C%' then ', ' + left(rest, charindex(',', rest + ',') - 1 ) else '' end) ,
convert(varchar(max), stuff(rest, 1, charindex(',', rest + ',') + 1, '')) as rest,
lev + 1
from cte
where rest > '' and lev < 10
)
select stuff(c_list, 1, 2, '') as c_list
from (select cte.*, row_number() over (partition by col1 order by lev desc) as seqnum
from cte
) cte
where seqnum = 1;
This approach does not require extracting strings and then reaggregating. It also guarantees that the values remain in the original order as in the original data.
Here is a db<>fiddle.
declare #t table (c varchar(200) PRIMARY KEY)
insert into #t values ('T1234, C1234, D1234, C1234'), ('E1234, C1234')
;with cte1 as ( -- cte1 - number the rows. Order by PK
select row_number() over(order by c) rn, *
from #t
), cte2 as ( -- cte2 - turn comma delimited lists into rows of trimmed values
select rn, replace(ca.value, ' ', '') val
from cte1
cross apply (
select value from string_split((select c from cte1 cte1_inner where cte1_inner.rn = cte1.rn), N',')
)ca
), cte3 as ( -- cte3 - get distinct row numbers and re-concat vals in a subquery
select distinct rn, (
SELECT STUFF(
(
select ', ' + val
from cte2 cte2_inner
where val like 'c%' and cte2_inner.rn = cte2.rn
for xml path('')
), 1, 2, '')
)concatenated
from cte2
)
select concatenated
from cte3
Returns:
concatenated
C1234
C1234, C1234
Here's what each CTE returns:
cte1:
rn c
1 E1234, C1234
2 T1234, C1234, D1234, C1234
cte2:
rn val
1 E1234
1 C1234
2 T1234
2 C1234
2 D1234
2 C1234
cte3:
rn concatenated
1 C1234
2 C1234, C1234

Extracting data between two delimiters in SQL Server?

I have a column that contains data in the format of aaa|bbb|ccc and need to extract aaa, bbb & ccc from the data separately.
I tried
SELECT
SUBSTRING(Column1, 0, CHARINDEX('|', Column1)) AS [First],
SUBSTRING(Column1, CHARINDEX('|', Column1) + 1, LEN(Column1)) AS [Second]
FROM
Table1
OUTPUT:
aaa [FIRST],bbb|ccc [Second]
but I need aaa [FIRST],bbb [Second],ccc [Third]
If you have a string that are exactly same format (three times) delimited by | then you can PARSENAME() :
select col1, parsename(cols, 3) fisrt, parsename(cols, 2) second, parsename(cols, 1) third
from table1 t1 cross apply
( values (replace(col1, '|', '.'))
) t2 (cols);
You can try below way where function charindex,SUBSTRING and REVERSE used
with t as (select 'aaa|bbb|ccc' as val)
select t.*,
LEFT(val, charindex('|', val) - 1),
SUBSTRING(val, charindex('|', val)+1, len(val) - CHARINDEX('|', reverse(val)) - charindex('|', val)),
REVERSE(LEFT(reverse(val), charindex('|', reverse(val)) - 1))
from t;
val
aaa|bbb|ccc aaa bbb ccc
You can split the value in the column using a common table expression (CTE) since split function is not available in your version of SQL Server
WITH CTE(userString,startIndex,EndIndex)
AS
(
SELECT Column1,1,CHARINDEX('|',Column1)
FROM Table1
UNION ALL
SELECT Column1, EndIndex+1,CHARINDEX('|',Column1,EndIndex+1)
FROM CTE
WHERE EndIndex !=0
)
SELECT SUBSTRING(userString,
startIndex,
CASE WHEN EndIndex > 0
THEN EndIndex - startIndex ELSE LEN(Column1) END)
as splitVALUES
FROM CTE
I do this all the time and it works well for me:
DECLARE #delimString VARCHAR(255) = 'aaa|bbb|ccc';
DECLARE #xml XML = '<val>' + REPLACE( #delimString, '|', '</val><val>' ) + '</val>'
SELECT
x.f.value( '.', 'VARCHAR(50)' ) AS val
FROM #xml.nodes( '//val' ) x( f );
Returns
+-----+
| val |
+-----+
| aaa |
| bbb |
| ccc |
+-----+
If you're looking for a columnar return and know you will always only have three values to parse, you might be able to get away with something like the below example. You can run it in SSMS.
DECLARE #table TABLE ( [value] VARCHAR(255) );
INSERT INTO #table ( [value] ) VALUES
( 'aaa|bbb|ccc' )
, ( '0A-PRDS|JQLM-1|1967' )
, ( 'J1658|G-1|2003' );
SELECT
[value]
, SUBSTRING( [value], 0, CHARINDEX( '|', [value] ) ) AS Column1
, SUBSTRING(
[value]
, ( CHARINDEX( '|', [value]) + 1 ) -- starting position of column 2.
, CHARINDEX( '|', [value], ( CHARINDEX( '|', [value] ) + 1 ) ) - ( CHARINDEX( '|', [value]) + 1 ) -- length of column two is the number of characters between the two delimiters.
) AS Column2
, SUBSTRING(
[value]
, CHARINDEX( '|', [value], ( CHARINDEX( '|', [value] ) + 1 ) ) + 1
, LEN( [value] )
) AS Column3
FROM #table;
Returns
+---------------------+---------+---------+---------+
| value | Column1 | Column2 | Column3 |
+---------------------+---------+---------+---------+
| aaa|bbb|ccc | aaa | bbb | ccc |
| 0A-PRDS|JQLM-1|1967 | 0A-PRDS | JQLM-1 | 1967 |
| J1658|G-1|2003 | J1658 | G-1 | 2003 |
+---------------------+---------+---------+---------+

SQL count ids in fields

I have a table contains IDs in field. It looks like:
FieldName
-------------------------
1,8,2,3,4,10,5,9,6,7
-------------------------
1,8
-------------------------
1,8
I need to count these IDs to get result:
ID | Count
---|------
1 | 3
8 | 3
2 | 1
3 | 1
Any ideas?
Thanks!
Try this :
Declare #demo table(FieldName varchar(100))
insert into #demo values('1,8,2,3,4,10,5,9,6,7')
insert into #demo values('1,8')
insert into #demo values('1,8')
select ID, COUNT(id) ID_count from
(SELECT
CAST(Split.a.value('.', 'VARCHAR(100)') AS INT) AS ID
FROM
(
SELECT CAST ('<M>' + REPLACE(FieldName, ',', '</M><M>') + '</M>' AS XML) AS ID
FROM #demo
) AS A CROSS APPLY ID.nodes ('/M') AS Split(a)) q1
group by ID
I like Devart's answer because of the faster execution. Here is a modified earlier answer to suite your need :
Declare #col varchar(200)
SELECT
#col=(
SELECT FieldName + ','
FROM #demo c
FOR XML PATH('')
);
;with demo as(
select cast(substring(#col,1,charindex(',',#col,1)-1) AS INT) cou,charindex(',',#col,1) pos
union all
select cast(substring(#col,pos+1,charindex(',',#col,pos+1)-pos-1)AS INT) cou,charindex(',',#col,pos+1) pos
from demo where pos<LEN(#col))
select cou ID, COUNT(cou) id_count from demo
group by cou
Try this one -
Query:
SET NOCOUNT ON;
DECLARE #temp TABLE (txt VARCHAR(8000))
INSERT INTO #temp (txt)
VALUES ('1,8,2,3,4,10,5,9,6,7'), ('1,8'), ('1,8')
SELECT
t.ID
, [Count] = COUNT(1)
FROM (
SELECT
ID =
SUBSTRING(
t.string
, number + 1
, ABS(CHARINDEX(',', t.string, number + 1) - number - 1)
)
FROM (
SELECT string = (
SELECT ',' + txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)')
) t
CROSS JOIN [master].dbo.spt_values n
WHERE [type] = 'p'
AND number <= LEN(t.string) - 1
AND SUBSTRING(t.string, number, 1) = ','
) t
GROUP BY t.ID
ORDER BY [Count] DESC
Output:
ID Count
----- -----------
1 3
8 3
9 1
10 1
2 1
3 1
4 1
5 1
6 1
7 1
Query cost:

Problem in counting nulls and then merging them with the existing rows

Input:
ID groupId RowID Data
1 1 1 W
2 1 1 NULL
3 1 1 NULL
4 1 1 Z
5 1 2 NULL
6 1 2 NULL
7 1 2 X
8 1 2 NULL
9 1 3 NULL
10 1 3 NULL
11 1 3 Y
12 1 3 NULL
Expected Output
GroupId NewData
1 2Y1,2X1,W2Z
For every Null there will be a numeric count. That is if there are two nulls then the numeric value will be 2.
The ddl is as under
DECLARE #t TABLE(ID INT IDENTITY(1,1) , GroupId INT, RowID INT, Data VARCHAR(10))
INSERT INTO #t (GroupId, RowID,DATA)
SELECT 1,1,'W' UNION ALL SELECT 1,1,NULL UNION ALL SELECT 1,1,NULL UNION ALL SELECT 1,1,'Z' UNION ALL SELECT 1,2,NULL UNION ALL
SELECT 1,2,NULL UNION ALL SELECT 1,2,'X' UNION ALL SELECT 1,2,NULL UNION ALL SELECT 1,3,NULL UNION ALL SELECT 1,3,NULL UNION ALL
SELECT 1,3,'Y' UNION ALL SELECT 1,3,NULL
select * from #t
My version is as under but not the correct output
;with t as (
select GroupID, id, RowID, convert(varchar(25), case when Data is null then '' else Data end) Val,
case when Data is null then 1 else 0 end NullCount from #t where id = 1
union all
select t.GroupID, a.id,a.RowID, convert(varchar(25), Val +
case when Data is not null or (t.RowID <> a.RowID and NullCount > 0) then ltrim(NullCount) else '' end +
case when t.RowID <> a.RowID then ',' else '' end + isnull(Data, '')),
case when Data is null then NullCount + 1 else 0 end NullCount
from t inner join #t a on t.GroupID = a.GroupID and t.id + 1 = a.id
)
select GroupID, Data = Val + case when NullCount > 0 then ltrim(NullCount) else '' end from t
where id = (select max(id) from #t where GroupID = t.GroupId)
Is yielding the below output
GroupID Data
1 W2Z,2X1,3Y1
Please help me out
Thanks in advance
Kind of messy and most likely can be improved
;With RawData AS
(
select * from #t
)
,Ranked1 as
(
select *, RANK() OVER (PARTITION BY GroupId, RowID ORDER BY ID, GroupId, RowID) R from #t
)
,Ranked2 as
(
select *, R - RANK() OVER (PARTITION BY GroupId, RowID ORDER BY ID, GroupId, RowID) R2 from Ranked1
where Data is null
)
,Ranked3 as
(
select MIN(ID) as MinID, GroupId, RowID, R2, COUNT(*) C2 from Ranked2
group by GroupId, RowID, R2
)
,Ranked4 as
(
select RD.ID, RD.GroupId, RD.RowID, ISNULL(Data, C2) as C3 from RawData RD
left join Ranked3 R3 on RD.ID = R3.MinID and RD.GroupId = R3.GroupId and RD.RowID = R3.RowID
where ISNULL(Data, C2) is not null
)
,Grouped as
(
select GroupId, RowID,
(
select isnull(C3, '') from Ranked4 as R41
where R41.GroupId = R42.GroupId and R41.RowID = R42.RowID
order by GroupId, RowID for xml path('')
) as C4
from Ranked4 as R42
group by GroupId, RowID
)
select GroupId,
stuff((
select ',' + C4 from Grouped as G1
where G1.GroupId = G2.GroupId
order by GroupId for xml path('')
), 1, 1, '')
from Grouped G2
group by GroupId