Query everything that comes after the '#' - sql

I am setting up a new query but unfortunately I got stuck in some kind of functions in SQL. I have some records with specific emails. All I want is bringing everything that comes after the '#'.
For example:
cesarcastillo88#hotmail.com ==> as a result I should get the following: hotmail.com.
This was not complicated at all because of the fact that the record shows one email only.
But...what if that record includes the following emails:
cesarcastillo88#hotmail.com ; laura23#gmail.com ; test#compliance.com
I did it perfectly for those cases with only 1 email in a single record
I used the following formula:
substring(**columnName**, charindex('#', sfe.**columnName**), len(sfe.**columnName**))
However, how am I suppose to do it with 3 emails in a single record?
My desired outcome is the following:
hotmail.com ; gmail.com ; compliance.com

Here is a possible solution based on the assumption that you have some sort of ID column that could help to identify each unique row:
;with smpl as (
select *
from (values
(1, 'cesarcastillo88#hotmail.com ; laura23#gmail.com ; test#compliance.com'),
(2, 'abc#cde.net'),
(3, 'laura23#gmail.com ; test#compliance.com')) x(id, email)
), split(id, A, B) as (
select distinct id, CAST(LEFT(email, CHARINDEX(';',email+';')-1) as varchar(100)),
CAST(STUFF(email, 1, CHARINDEX(';',email+';'), '') as varchar(100))
from smpl
union all
select id, CAST(LEFT(B, CHARINDEX(';',B+';')-1) as varchar(100)),
CAST(STUFF(B, 1, CHARINDEX(';',B+';'), '') as varchar(100))
from split
where B > ''
), clr as (
select ID, substring(LTRIM(RTRIM(A)), charindex('#', LTRIM(RTRIM(A))) + 1, len(LTRIM(RTRIM(A)))) cleanEmail
--into #tempTbl
from split
), ccat as (
SELECT DISTINCT ST2.ID,
SUBSTRING(
(
SELECT ';'+ST1.cleanEmail AS [text()]
FROM clr ST1
WHERE ST1.ID = ST2.ID
ORDER BY ST1.ID
FOR XML PATH ('')
), 2, 1000) Emails
FROM clr ST2
)
select * from ccat
And here is some explanation on how this all works:
First CTE expression splits emails into separate rows using ; as a separator
Second CTE is based on your function to remove the recipient from email address and only leave the domain
The last one concatenates everything back and uses same ; as separator. Feel free to add extra spaces around if that's your preferred output.

You don't say what version of SQL Server, but I'll assume 2016 or newer. They key is the STRING_SPLIT function. To join it to your data, you'll want to use CROSS APPLY.
create table #a (
id int identity(1,1),
email varchar(max)
)
insert #a
values ('cesarcastillo88#hotmail.com ; laura23#gmail.com ; test#compliance.com')
, ('dannyboy#irish.com')
select id
, email
, substring(email, CHARINDEX('#', email) + 1, len(email)) as domain
from #a
select a.id
, substring(ltrim(rtrim(b.value)), CHARINDEX('#', ltrim(rtrim(b.value))) + 1, len(ltrim(rtrim(b.value)))) as domain
from #a a
cross apply string_split(email, ';') b
drop table #a

Related

Apply CTE function consolidating strings to every row in a H2 table

I have a table with long strings in one column which I want to consolidate into an easier to read format ('abc;abc;abc;efg;hij;klm;klm;klm' -> 'abc: 3, efg: 1, hij: 1, klm: 3').
I have written a function that consolidates the string, but now I want to apply it to every entry in a table. Any suggestion on how this can be achieved?
This is the code that splits and consolidates the string in #str:
SET #str = 'abc;abc;abc;efg;hij;klm;klm;klm;';
WITH cte1(token, remainder) AS (
SELECT LEFT(#str, LOCATE(';', #str)-1) AS token,
RIGHT(#str, LENGTH(#str)-LOCATE(';', #str)) as remainder -- anchor member
UNION ALL
SELECT LEFT(remainder, LOCATE(';', remainder)-1) AS token,
RIGHT(remainder, LENGTH(remainder)-LOCATE(';', remainder)) as remainder -- recursive member
FROM cte1
WHERE LENGTH(remainder)>0 -- terminator
), cte2 AS (
SELECT token, count(token) AS c
FROM cte1
GROUP BY token
HAVING LENGTH(token)>0
ORDER BY token
)
SELECT GROUP_CONCAT(CONCAT_WS(': ', token, c) SEPARATOR ', ') FROM cte2
GROUP BY 1
The first cte1 breaks the string into separate tokens, the second cte2 creates a pivot and counts each instance, and the final SELECT statement consolidates the resulting table into one single string.
How would I apply this to each entry in column S1 in the following setup, e.g. by updating the table and adding the result into S2?
CREATE TABLE T1 (
ID INT, S1 VARCHAR, S2 VARCHAR);
INSERT INTO T1
VALUES (1, 'abc;abc;abc;efg;hij;klm;klm;klm;', ''),
(2, '123;123;235;235;235;987;987;123;', '');
Thank you very much for any help!

Get a specific string

It's my data and every ThroughRouteSid record has the same pattern.
six number and five comma. then I just want to get three and five
number into two record to template Table and get the same Count()
value to these two record.
For example: First record in the picture.
ThroughRouteSid(3730,2428,2428,3935,3935,3938,) Count(32).
I want a result like this:
2428 32 3935 32
I get What number I want.become two record and both have same Count value into template table
you can use XML to get your result, please refer below sample code -
create table #t1( ThroughRouteSid varchar(500) , Cnt int)
insert into #t1
select '3730,2428,2428,3935,3935,3938,' , len('3730,2428,2428,3935,3935,3938,')
union all select '1111,2222,3333,4444,5555,6666,' , len('1111,2222,3333,4444,5555,6666,')
select cast( '<xml><td>' + REPLACE( SUBSTRING(ThroughRouteSid ,1 , len(ThroughRouteSid)-1),',','</td><td>') + '</td></xml>' as xml) XmlData , Cnt
into #t2 from #t1
select XmlData.value('(xml/td)[3]' ,'int' ), Cnt ,XmlData.value('(xml/td)[5]' ,'int' ), Cnt
from #t2
First create the function referring How to Split a string by delimited char in SQL Server. Then try Querying the following
select (SELECT CONVERT(varchar,splitdata) + ' '+ Convert(varchar, [Count])+' ' FROM (select splitdata, ROW_NUMBER() over (ORDER BY (SELECT 100)) row_no
from [dbo].[fnSplitString](ThroughRouteSid,',')
where splitdata != '') as temp where row_no in (2,5)
for xml path('')) as col1 from [yourtable]
If you are using SQL Server 2016 you can do something like this:
create table #temp (ThroughRouteSid varchar(1024),[Count] int)
insert into #temp values
('3730,2428,2428,3935,3935,3938,',32),
('730,428,428,335,935,938,',28)
select
spt.value,
t.[Count]
from #temp t
cross apply (
select value from STRING_SPLIT(t.ThroughRouteSid,',') where LEN(value) > 0
)spt

how to extract a particular id from the string using sql

I want to extract a particular ids from the records in a table.For example i have a below table
Id stringvalue
1 test (ID 123) where another ID 2596
2 next ID145 and the condition I(ID 635,897,900)
I want the result set as below
ID SV
1 123,2596
2 145,635,897,900
i have tried the below query which extracts only one ID from the string:
Select Left(substring(string,PATINDEX('%[0-9]%',string),Len(string)),3) from Table1
I seriously don't encourage the T-SQL approach (as SQL is not meant to do this), however, a working version is presented below -
Try this
DECLARE #T TABLE(ID INT IDENTITY,StringValue VARCHAR(500))
INSERT INTO #T
SELECT 'test (ID 123) where another ID 2596' UNION ALL
SELECT 'next ID145 and the condition I(ID 635,897,900)'
;WITH SplitCTE AS(
SELECT
F1.ID,
X.SplitData
,Position = PATINDEX('%[0-9]%', X.SplitData)
FROM (
SELECT *,
CAST('<X>'+REPLACE(REPLACE(StringValue,' ',','),',','</X><X>')+'</X>' AS XML) AS XmlFilter
FROM #T F
)F1
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(50)') AS SplitData
FROM f1.xmlfilter.nodes('X') AS fdata(D)) X
WHERE PATINDEX('%[0-9]%', X.SplitData) > 0),
numericCTE AS(
SELECT
ID
,AllNumeric = LEFT(SUBSTRING(SplitData, Position, LEN(SplitData)), PATINDEX('%[^0-9]%', SUBSTRING(SplitData, Position, LEN(SplitData)) + 't') - 1)
FROM SplitCTE
)
SELECT
ID
,STUFF(( SELECT ',' + c1.AllNumeric
FROM numericCTE c1
WHERE c1.ID = c2.ID
FOR XML PATH(''),TYPE)
.value('.','NVARCHAR(MAX)'),1,1,'') AS SV
FROM numericCTE c2
GROUP BY ID
/*
Result
ID SV
1 123,2596
2 145,635,897,900
*/
However, I completely agree with #Giorgi Nakeuri. It is better to use some programming language (if you have that at your disposal) and use regular expression for the same. You can figure out that, I have used REPLACE function two times, first to replace the blank space and second to replace the commas(,).
Hope you will get some idea to move on.

How to group through a string part?

I've a table which contains logs from a web portal, it contains the url visited, the request duration, the referer...
One of these columns is the path info and contains strings like following:
/admin/
/export/
/project2/
/project1/news
/project1/users
/user/id/1
/user/id/1/history
/user/id/2
/forum/topic/14/post/456
I would like to calculate with sql queries some stats based on this column, so I would like to know how can I create aggregate based on the first part of the path info?
It'd let me count number of url starting by /admin/, /export/, /project1/, /project2/, /user/, /forum/, ...
Making it with a programming language would be easy with regex, but I read that similar function does not exists on SQLServer.
I would use CHARINDEX() to find the first occurrence of the "/" starting AFTER the leading first character '/', so anything AFTER the second is stripped off.
select
LEFT( pathInfo, CHARINDEX( '/', pathInfo, 2 )) as RootLevelPath,
count(*) as Hits
from
temp
group by
LEFT( pathInfo, CHARINDEX( '/', pathInfo, 2 ))
Working result from SQLFiddle
DRapp's is perfect for grouping on the first fragment of the URL. If you need to group by other levels it might get unwieldy to manage the nested LEFT/CHARINDEX statements.
Here's one way to group by a parameterized level:
declare #t table (pathId int identity(1,1) primary key, somePath varchar(100));
insert into #t
select '/admin/' union all
select '/export/' union all
select '/project2/' union all
select '/project1/news' union all
select '/project1/users' union all
select '/user/id/1' union all
select '/user/id/1/history' union all
select '/user/id/2' union all
select '/forum/topic/14/post/456' union all
select '/forum/topic/14/post/789' union all
select '/forum/topic/14/post/789'
declare #level int =1;
;with fragments as
( select pathId,
[n] = x.query('.'),
[Fragment] = x.value('.', 'varchar(100)')
from ( select PathId,
cast('<r>' + replace(stuff(somePath, 1, 1, ''), '/', '</r><r>') + '</r>' as xml)
.query('r[position()<=sql:variable("#level")]')
from #t
) d (PathId, X)
)
select count(*), [path] = max(r.v)
from fragments f
cross
apply ( select '/' + p.n.value('.', 'varchar(100)')
from fragments
cross
apply n.nodes('r')p(n)
where PathId = f.PathId
for xml path('')
) r(v)
group
by fragment;

SQL Server query with multiple values in one column relating to another column

Situation: This table holds the relation information between a Documents table and an Users table. Certain Users need to review or approve documents (Type). I would like to have it to where I could get all of the reviewers on one line if needed. So if three users review Document 1, then a row would have 346, 394, 519 as the value, since those are the reviewers
Table: xDocumentsUsers
DocID..UserID....Type...
1........386......approver
1........346......reviewer
1........394......reviewer..
1........519......reviewer..
4........408......reviewer..
5........408......reviewer..
6........408......reviewer..
7........386......approver..
7........111......readdone..
7........346......reviewer..
8........386......approver..
8........346......reviewer..
9........386......approver..
9........346......reviewer..
10.......386......approver..
11.......386......approver..
11......346......reviewer..
12......386......approver..
12......346......reviewer..
13......386......approver..
13......346......reviewer..
14......386......approver..
14......346......reviewer..
15......386......approver
So desired result would be...
DocID..UserID................Type...
1........386....................approver
1........346,394,519......reviewer.
4........408....................reviewer..
5........408....................reviewer..
6........408....................reviewer..
7........386....................approver..
7........111....................readdone..
7........346....................reviewer..
8........386....................approver..
8........346....................reviewer..
9........386....................approver..
9........346....................reviewer..
10......386....................approver..
11......386....................approver..
11......346....................reviewer..
12......386....................approver..
12......346....................reviewer..
13......386....................approver..
13......346....................reviewer..
14......386....................approver..
14......346....................reviewer..
15......386....................approver
The FOR XML PATH is a great solution. You need to be aware, though, that it will convert any special characters in the inner SELECTs result set into their xml equivalent - i.e., & will become & in the XML result set. You can easily revert back to the original character by using the REPLACE function around the inner result set. To borrow from astander's previous example, it would look like (note that the SELECT as the 1st argument to the REPLACE function is enclosed in ():
--Concat
SELECT t.ID,
REPLACE((SELECT tIn.Val + ','
FROM #Table tIn
WHERE tIn.ID = t.ID
FOR XML PATH('')), '&', '&'))
FROM #Table t
GROUP BY t.ID
Have a look at
Emulating MySQL’s GROUP_CONCAT() Function in SQL Server 2005
Is there a way to create a SQL Server function to “join” multiple rows from a subquery into a single delimited field?
A simple example is
DECLARE #Table TABLE(
ID INT,
Val VARCHAR(50)
)
INSERT INTO #Table (ID,Val) SELECT 1, 'A'
INSERT INTO #Table (ID,Val) SELECT 1, 'B'
INSERT INTO #Table (ID,Val) SELECT 1, 'C'
INSERT INTO #Table (ID,Val) SELECT 2, 'B'
INSERT INTO #Table (ID,Val) SELECT 2, 'C'
--Concat
SELECT t.ID,
(
SELECT tIn.Val + ','
FROM #Table tIn
WHERE tIn.ID = t.ID
FOR XML PATH('')
)
FROM #Table t
GROUP BY t.ID
Does this help?
SELECT DocID
, [Type]
, (SELECT CAST(UserID + ', ' AS VARCHAR(MAX))
FROM [xDocumentsUsers]
WHERE (UserID = x1.UserID)
FOR XML PATH ('')
) AS [UserIDs]
FROM [xDocumentsUsers] AS x1