Parse Columns in SQL by delimiter - sql-server

In SQL Server, I have a table/view that has multiple columns. The last column looks like this:
COL
---------------------------------
|test|test|test11|testing|final
|test|test|test1|testing2|final3
|test|test|test17|testing|final6
How do parse this column by | and combine it with the right side of the existing table like such:
COL1 COL2 COL Parse1 Parse2 Parse3 Parse4 Parse5
1 4 |test|test|test11|testing|final test test test11 testing final
2 6 |test|test|test1|testing2|final3 test test test1 testing2 final3
5 9 |test|test|test17|testing|final6 test test test17 testing final6
There are the same number of parsings for column COL.
Any help would be great thanks!

Not clear if you have a leading | in the field COL. If so, you may want to shift /x[n]
The pattern is pretty clear. Easy to expand or contract as necessary
Example
Declare #YourTable Table ([COL] varchar(50))
Insert Into #YourTable Values
('test|test|test11|testing|final')
,('test|test|test1|testing2|final3')
,('test|test|test17|testing|final6')
Select A.*
,B.*
From #YourTable A
Cross Apply (
Select Pos1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
,Pos2 = ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
,Pos3 = ltrim(rtrim(xDim.value('/x[3]','varchar(max)')))
,Pos4 = ltrim(rtrim(xDim.value('/x[4]','varchar(max)')))
,Pos5 = ltrim(rtrim(xDim.value('/x[5]','varchar(max)')))
,Pos6 = ltrim(rtrim(xDim.value('/x[6]','varchar(max)')))
,Pos7 = ltrim(rtrim(xDim.value('/x[7]','varchar(max)')))
,Pos8 = ltrim(rtrim(xDim.value('/x[8]','varchar(max)')))
,Pos9 = ltrim(rtrim(xDim.value('/x[9]','varchar(max)')))
From (Select Cast('<x>' + replace((Select replace(A.Col,'|','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as B1
) B
Returns

Related

Unable concate NULL value in SQL using CONCAT, COALESCE and ISNULL

I have a query with multiple joins where I want to combine records from two columns into one. If one column is empty then I want to show one column value as result. I tried with CONCAT, COALEASE and ISNULL but no luck. What am I missing here?
My objective is, create one column which has combination of s.Script AS Original and FromAnotherTable from query. Below query runs but throws Invalid column name 'Original' and Invalid column name 'FromAnotherTable'. when I try to use CONCAT, COALEASE or ISNULL .
SQL Query:
SELECT DISTINCT
c.Name AS CallCenter,
LTRIM(RTRIM(s.Name)) Name,
d.DNIS,
s.ScriptId,
s.Script AS Original,
(
SELECT TOP 5 CCSL.Line+'; '
FROM CallCenterScriptLine CCSL
WHERE CCSL.ScriptId = s.ScriptId
ORDER BY ScriptLineId FOR XML PATH('')
) AS FromAnotherTable,
--CONCAT(s.Script, SELECT TOP 5 CCSL.Line+'; ' FROM dbo.CallCenterScriptLine ccsl WHERE ccsl.ScriptId = s.ScriptId ORDER BY ccsl.ScriptLineId xml path(''))
--CONCAT(Original, FromAnotherTable) AS Option1,
--COALESCE(Original, '') + FromAnotherTable AS Option2,
--ISNULL(Original, '') + FromAnotherTable AS Option3,,
r.UnitName AS Store,
r.UnitNumber
FROM CallCenterScript s WITH (NOLOCK)
INNER JOIN CallCenterDNIS d WITH (NOLOCK) ON d.ScriptId = s.ScriptId
INNER JOIN CallCenter c WITH (NOLOCK) ON c.Id = s.CallCenterId
INNER JOIN CallCenterDNISRestaurant ccd WITH (NOLOCK) ON ccd.CallCenterDNISId = d.CallCenterDNISId
INNER JOIN dbo.Restaurant r WITH (NOLOCK) ON r.RestaurantID = ccd.CallCenterRestaurantId
WHERE c.Id = 5
AND (1 = 1)
AND (s.IsDeleted = 0 OR s.IsDeleted IS NULL)
ORDER BY DNIS ASC;
Output:
This works:
DECLARE #Column1 VARCHAR(50) = 'Foo',
#Column2 VARCHAR(50) = NULL;
SELECT CONCAT(#Column1,#Column2);
SELECT COALESCE(#Column2, '') + #Column1
SELECT ISNULL(#Column2, '') + #Column1
So I am not sure what I am missing in my original query.
Look at row 3 in the results you are getting. In your concatenated columns (Option1, 2, 3) you are getting the first script column twice. Not the first one + the second one like you expect.
The reason is because you've aliased your subquery "script" which is the same name as another column in your query, which makes it ambiguous.
Change the alias of the subquery and the problem should go away. I'm frankly surprised your query didn't raise an error.
EDIT: You can't use a column alias in another column's definition in the same level of the query. In other words, you can't do this:
SELECT
SomeColumn AS A
, (Subquery that returns a column) AS B
, A + B --this is not allowed
FROM ...
You can either create a CTE that returns the aliased columns and then concatenate them in the main query that selects from the CTE, or you have to use the original sources of the aliases, like so:
SELECT
SomeColumn AS A
, (Subquery that returns a column) AS B
, SomeColumn + (Subquery that returns a column) --this is fine
FROM ...
I took another approach where instead on creating separate column, I used ISNULL in my subQuery which returns my desired result.
Query:
SELECT DISTINCT
c.Name AS CallCenter,
LTRIM(RTRIM(s.Name)) Name,
d.DNIS,
s.ScriptId,
s.Script AS Original,
(
SELECT TOP 5 ISNULL(CCSL.Line, '')+'; ' + ISNULL(s.Script, '')
FROM CallCenterScriptLine CCSL
WHERE CCSL.ScriptId = s.ScriptId
ORDER BY ScriptLineId FOR XML PATH('')
) AS FromAnotherTable,
r.UnitName AS Store,
r.UnitNumber
FROM CallCenterScript s WITH (NOLOCK)
INNER JOIN CallCenterDNIS d WITH (NOLOCK) ON d.ScriptId = s.ScriptId
INNER JOIN CallCenter c WITH (NOLOCK) ON c.Id = s.CallCenterId
INNER JOIN CallCenterDNISRestaurant ccd WITH (NOLOCK) ON ccd.CallCenterDNISId = d.CallCenterDNISId
INNER JOIN dbo.Restaurant r WITH (NOLOCK) ON r.RestaurantID = ccd.CallCenterRestaurantId
WHERE c.Id = 5
AND (1 = 1)
AND (s.IsDeleted = 0 OR s.IsDeleted IS NULL)
ORDER BY DNIS ASC;
Here's a simplified example using table variables.
Instead of using a subquery for a field, it uses a CROSS APPLY.
And CONCAT in combination with STUFF is used to glue the strings together.
declare #Foo table (fooID int identity(1,1) primary key, Script varchar(30));
declare #Bar table (barID int identity(1,1) primary key, fooID int, Line varchar(30));
insert into #Foo (Script) values
('Test1'),('Test2'),(NULL);
insert into #Bar (fooID, Line) values
(1,'X'),(1,'Y'),(2,NULL),(3,'X'),(3,'Y');
select
f.fooID,
f.Script,
x.Lines,
CONCAT(Script+'; ', STUFF(x.Lines,1,2,'')) as NewScript
from #Foo f
cross apply (
select '; '+b.Line
from #Bar b
where b.fooID = f.fooID
FOR XML PATH('')
) x(Lines)
Result:
fooID Script Lines NewScript
----- ------- ------- -----------
1 Test1 ; X; Y Test1; X; Y
2 Test2 NULL Test2;
3 NULL ; X; Y X; Y

Dynamic Columns - SQL Server 2012

Having two tables, I want to convert some rows to columns. My database engine is Microsoft SQL Server. The image below illustrates my desired result.
Your question is not so clear, but it seems you want to use SQL PIVOT
Sample Data
DECLARE #tblModule TABLE(modId INT,name VARCHAR(200))
DECLARE #tblProfile TABLE(id INT,modId INT,profil VARCHAR(200))
INSERT INTO #tblModule
SELECT 1,'Manteniminento' UNION
SELECT 2 , 'Soporte'
INSERT INTO #tblProfile
SELECT 1,1,'Administrador' UNION
SELECT 2,2 , 'Empleado' UNION
SELECT 3,1 , 'Empleado' UNION
SELECT 4,1 , 'Empleado' UNION
SELECT 5,1 , 'Administrador' UNION
SELECT 6,1 , 'Administrador'
Main query
SELECT name,SUM([Administrador]) AS Administrador, SUM([Empleado]) AS Empleado
FROM
(SELECT id,p.modId,m.name,p.profil
FROM #tblProfile p
INNER JOIN #tblModule m ON m.modId = p.modId) AS SourceTable
PIVOT
(
COUNT(modId)
FOR profil IN ([Administrador], [Empleado])
) AS PivotTable
GROUP BY name
Result
name Administrador Empleado
Manteniminento 3 2
Soporte 0 1
I found the solution to what I needed. I share with you I made use of SQL STUFF:
SELECT A.codEmpresa
,A.nomEmpresa
,A.codSistema
,A.nomSistema
,A.codPerfil
,A.nomPerfil
,modulos = STUFF((SELECT DISTINCT ', ' + M.nomModulo
FROM smpseg.[0004] R
JOIN smpseg.[0014] SMOP
ON SMOP.codSistema = R.codSistema
AND SMOP.codModulo = R.codModulo
AND SMOP.codPerfil = A.codPerfil
AND SMOP.objDefault = CAST(1 AS BIT)
JOIN smpseg.[0011] O
ON O.codSistema = SMOP.codSistema
AND O.codModulo = SMOP.codModulo
AND O.codObjeto = SMOP.codObjeto
JOIN smpseg.[0010] M
ON M.codSistema = O.codSistema
AND M.codModulo = O.codModulo
WHERE R.codUsuario = #p_codUsuario
FOR XML PATH('')), 1, 2, '') FROM smpseg.[0004] A
JOIN smpseg.[0016] U
ON U.codUsuario = A.codUsuario

SQL Server - Need to correct the numbering pattern from ABC 1-1-1 to ABC 001-001-001

So we need to pad with zeros in the customer numbering pattern. The length of the numbers in between the dashes should always be 3. So, first it should find the first space and then find the length of the numbers in between the dashes and if it's less than 3 it should pad with zeros
Ex: XYZ 45-678-2
This should be corrected to XYZ 045-678-002.
> **Edit Updated for new patterns**
Declare #YourTable table (ID int,YourField varchar(50))
Insert Into #YourTable values
(1,'XYZ 45-678-2'),
(2,'XYZ 45-678'),
(3,'DA 1-1'),
(4,'XYZ 123-678-9-234')
--Update #YourTable Set YourField -- If Satisfied Remove Comment
Select A.*,NewField -- If Satisfied Remove This Line
= Concat(Pos1
,Format(Try_Convert(int,Pos2),' 000')
,Format(Try_Convert(int,Pos3),'-000')
,Format(Try_Convert(int,Pos4),'-000')
,Format(Try_Convert(int,Pos5),'-000')
,Format(Try_Convert(int,Pos6),'-000')
,Format(Try_Convert(int,Pos7),'-000')
,Format(Try_Convert(int,Pos8),'-000')
,Format(Try_Convert(int,Pos9),'-000')
)
From #YourTable A
Cross Apply (
Select Pos1 = xDim.value('/x[1]','varchar(50)')
,Pos2 = xDim.value('/x[2]','varchar(50)')
,Pos3 = xDim.value('/x[3]','varchar(50)')
,Pos4 = xDim.value('/x[4]','varchar(50)')
,Pos5 = xDim.value('/x[5]','varchar(50)')
,Pos6 = xDim.value('/x[6]','varchar(50)')
,Pos7 = xDim.value('/x[7]','varchar(50)')
,Pos8 = xDim.value('/x[8]','varchar(50)')
,Pos9 = xDim.value('/x[9]','varchar(50)')
From (Select Cast('<x>' + Replace(Replace(YourField,' ','-'),'-','</x><x>')+'</x>' as XML) as xDim) A
) B
Where YourField Like '% %-%'
The Updated Results Would Be
ID YourField
1 XYZ 045-678-002
2 XYZ 045-678
3 DA 001-001
4 XYZ 123-678-009-234

Issue in complicated join

I have 4 tables
tbLicenceTypesX (2 Fields)
LicenceTypes
LicenceTypesX
tbLicenceTypesX (Contains data like)
1 - Medical Licence
2 - Property
3 - Casualty
4 - Trainning Licence
tbProduct (3 feilds)
Product
ProductX
CompanyId (F.K)
LicenceTypes(F.K)
tbProduct (Contains data like)
1 - T.V - 10 - 2
2 - A.C - 30 - 3
3 - Mobiles - 40 -4
tbLicence (3 feilds)
Licence
LicenceTypesNames
AgentId
tbLicence (Contains data like)
1 - Property, Casualty - 23
2 - Trainning Licence, Casualty - 34
Now I have to Fetch Product and ProductX from tbProduct whose LicenceTypes matches with Agent's Licence in tbLicence in a Company.
For e.g: I have to fetch T.V Whose Licence Types is 2("Property") and Company Id is 10 which should be assigned to Agent where Agent Id is 23 and Whose LicenceTypesNames should also contains "Property"
I want to fetch something like
#CompanyId int,
#AgentId int
As
SELECT p.ProductX,p.Product
from tbProduct p
inner join tbLicence l on p.LicenceTypes = l.LicenceTypesNames<its corresponding Id>
inner join tbProduct c on c.Product =p.Product
where
c.CompanyId=#CompanyId
and l.AgentId=#AgentId
Please help me!!!
You can use XML and CROSS APPLY to Split the comma separated values and JOIN with tbProduct. The LTRIM and RTRIM functions are used to trim the comma separated values if they have excessive empty space. The below code gives you the desired output.
DECLARE #CompanyId int = 30, #AgentId int = 23
;WITH CTE AS
(
SELECT AgentId, TCT.LicenceTypes FROM
(
SELECT AgentId, LTRIM(RTRIM(Split.XMLData.value('.', 'VARCHAR(100)'))) LicenceTypesNames FROM
(
SELECT AgentID, Cast ('<M>' + REPLACE(LicenceTypesNames, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM tbLicence
) AS XMLData
CROSS APPLY Data.nodes ('/M') AS Split(XMLData)
)
AS LTN
JOIN tbLicenceTypesX TCT ON LTN.LicenceTypesNames = tct.LicenceTypesX
)
SELECT p.ProductX,p.Product
FROM tbProduct P
JOIN CTE c on p.LicenceTypes = c.LicenceTypes
WHERE CompanyId = #CompanyId
AND AgentId = #AgentId
Sql Fiddle Demo

SQL server: WHERE in xml field

Example: I have table TableA with 3 records:
record 1:
id = 1, value = '<Employee id='1' name='Employee1'></Employee><Employee id='2' name='Employee2'></Employee>'
record 2:
id = 2, value = '<Employee id='1' name='Employee1'></Employee><Employee id='2' name='Employee2'></Employee><Employee id='3' name='Employee3'></Employee>'
record 3:
id = 3, value = '<Employee id='1' name='Employee1'></Employee><Employee id='2' name='Employee2'></Employee><Employee id='3' name='Employee3'></Employee><Employee id='4' name='Employee4'></Employee>'
the query:
SELECT * FROM TableA
WHERE...
How can I put the where clause to get only record 1?
Many thanks,
The problem with the data is that it doesn't contain well formed xml - you will need to wrap it before you can use the xml tools in Sql like xquery.
SELECT *
FROM
(
SELECT
Nodes.node.value('(./#id)[1]', 'int') AS EmployeeId,
Nodes.node.value('(./#name)[1]', 'varchar(50)') AS EmployeeName
FROM
(
SELECT CAST('<xml>' + value + '</xml>' AS Xml) As WrappedXml
FROM TableA
) AS x
cross apply x.WrappedXml.nodes('//Employee') as Nodes(node)
) as y
WHERE
y.EmployeeId = 1;
Inner select -wraps the xml
Middle select - standard xquery
Outer select - where filter
You haven't clarified what you mean w.r.t. get only record 1, but if you mean just the first element of each row (which coincidentally also has id = 1), you can use ROW_NUMBER() to assign a sequence:
SELECT *
FROM
(
SELECT
Nodes.node.value('(./#id)[1]', 'int') AS EmployeeId,
Nodes.node.value('(./#name)[1]', 'varchar(50)') AS EmployeeName,
ROW_NUMBER() OVER (PARTITION BY x.Id ORDER BY ( SELECT 1 )) SequenceId
FROM
(
SELECT Id, CAST('<xml>' + value + '</xml>' AS Xml) As WrappedXml
FROM TableA
) AS x
cross apply x.WrappedXml.nodes('//Employee') as Nodes(node)
) as y
WHERE SequenceId = 1;
Both Fiddles here
I tried with this query and It returns record 1 as I expect:
SELECT * FROM TableA
WHERE value.exist('(Employee[#id = 1])') = 1 and value.exist('(Employee[#id = 2])') = 1 AND value.value('count(Employee[#id])', 'int') = 2
Do you have any comments for this query? Should I use it? :)

Resources