How can I do a 'for each' and convert 3 columns into 1 row in SQL Server? - sql-server

I have this table in SQL Server:
Process
ProcessedActivity
BusinessDefinition
SystemError
LADG3
2
1
3
TEF2020
1
4
1
I need to convert it to this format:
Process
Status
LADG3
ProcessedActivity
LADG3
ProcessedActivity
LADG3
BusinessDefinition
LADG3
SystemError
LADG3
SystemError
LADG3
SystemError
TEF2020
ProcessedActivity
TEF2020
BusinessDefinition
TEF2020
BusinessDefinition
TEF2020
BusinessDefinition
TEF2020
BusinessDefinition
TEF2020
SystemError
I expect to convert each number from the columns ProcessedActivity, BusinessDefinition and SystemError into a message and join this information into only one new column.
How can I do this double conversion in SQL Server?

You need a numbers or tally table for this. If you are using sql server 2022 this is easily done with GENERATE_SERIES. But since that is really new at this point I will assume you are using an older version. I tackle that using this view which I keep on nearly all my databases.
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
Regardless of how you generate your tally table this becomes fairly simple. I did this using three queries. There are other ways you could tackle this as well but this will perform reasonably quick and the coding is simple. I used the ExplodedRows cte here so it would allow adding a sort order to all the rows once they are materialized.
with ExplodedRows as
(
select s.Process
, ProcessName = 'ProcessedActivity'
, SortVal = 1
from #Something s
join cteTally t on t.N <= s.ProcessedActivity
union all
select s.Process
, 'BusinessDefinition'
, 2
from #Something s
join cteTally t on t.N <= s.BusinessDefinition
union all
select s.Process
, 'SystemError'
, 3
from #Something s
join cteTally t on t.N <= s.SystemError
)
select er.Process
, er.ProcessName
from ExplodedRows er
order by er.Process
, SortVal

Related

Recurcive cte to identify circular reference in data

I am trying to identify recursive/circular reference in my data for which I need recursive cte.
For example I have table that contains Product_ID and Inner_Product_ID. I want results when Product_ID A is inner to Product_ID B, which is inner to Product_ID C, which is inner to Product_ID A.
Sample data
PRODUCT_ID INNER_PRODUCT_ID
12 36
24 12
36 24
1 2
3 4
Expected output
PRODUCT_ID INNER_PRODUCT_ID
12 36
24 12
36 24
I have tried basic query with cte but not sure how to implement recursive cte for this problem:
;WITH RNCTE
AS ( SELECT *,
ROW_NUMBER() OVER (PARTITION BY pr1.PRODUCT_ID
ORDER BY pr1.PRODUCT_ID
) rn
FROM
TableName pr1),
cte
AS ( SELECT *
FROM RNCTE
WHERE RNCTE.rn = 1
UNION ALL
SELECT *
FROM cte c
JOIN RNCTE r
ON r.PRODUCT_ID = c.PRODUCT_ID
AND r.rn = c.rn + 1)
SELECT *
FROM cte;
try this - it walks through the linked records, and finds if the 'walk' eventually terminates, or not. If it lasts for more than the number of records in the table, then it must be a loop. 'Efficient' I am not sure of that!
;WITH UCNT AS (SELECT count(0) c from products),
RNCTE
AS (SELECT 1 as Levle, Product_ID, INNER_PRODUCT_ID FROM Products
UNION ALL
SELECT levle + 1, P.Product_ID, P.INNER_PRODUCT_ID
FROM RNCTE R
JOIN Products P
ON P.PRODUCT_ID = R.INNER_PRODUCT_ID
WHERE levle <= (SELECT c + 2 FROM UCNT))
--when the recursion count levle exceeds the count of records in the table,
--we must have recursion, because
--termination has to otherwise occur. The most extreme case is
--that all records are linked, with termination
--after this, we have to be in a 'loop'
SELECT TOP 1 with ties * FROM RNCTE order by levle desc
option (maxrecursion 0)
I think you don't need to use CTE or RECUSRIVE CTE :
SELECT pr1.*
FROM TableName pr1
WHERE EXISTS (SELECT 1 FROM TableName pr2 WHERE pr2.INNER_PRODUCT_ID = pr1.PRODUCT_ID);

Remove special characters and numbers from column

How to remove all special characters and numbers except spaces from specific column in microsoft sql server?
The links above all use loops to solve this. There is no need to resort to loops for this type of thing. Instead we can use a tally table. I keep a tally table as a view in my system like this.
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
GO
Now we can leverage that tally as a set. This is a LOT faster than those loop based solutions.
create function GetOnlyCharacters
(
#SearchVal varchar(8000)
) returns table as return
with MyValues as
(
select substring(#SearchVal, N, 1) as number
, t.N
from cteTally t
where N <= len(#SearchVal)
and substring(#SearchVal, N, 1) like '[a-z]'
)
select distinct NumValue = STUFF((select number + ''
from MyValues mv2
order by mv2.N
for xml path('')), 1, 0, '')
from MyValues mv

Transforming Data in SQL Server

I am new here so please bear with me if this type of question has already been asked, and I am going to try and be as clear as possible. With that said, I am at whits end trying to transform the data table below into the format required. All ideas are helpful.
PhType PhNumber Sequence
-------------------------------------
Cell Phone 111-222-3333 2
Cell Phone 222-333-4444 5
Home Phone 999-222-1111 6
Home Phone 555-444-3333 8
And I am trying to transform the data into this table.
**CellPhone1#** **Sequence** **CellPhone2#** **Sequence** **HomePhone1#** **Sequence** **HomePhone2#** **Sequence**
111-222-3333 ---- 2 ------ 222-333-4444 ----- 5 --------- 999-222-1111 ----- 6 ------- 555-444-3333 ---------- 8
The idea is turning rows of data into columns, but because the sequences on the numbers aren't in sequential order I am having a hard time doing this without gaps in my data. Currently I am transforming the information into column on the join level
LEFT OUTER JOIN
Reports.dbo.BorrowerTelephones BT with(nolock) ON B1.HHNbr = BT.HHNbr
AND B1.NamSeq = BT.NamSeq
AND BT.Seq = 0
AND BT.PhType = 'Cell Phone'
LEFT OUTER JOIN
Reports.dbo.BorrowerTelephones BT1 with(nolock) ON B1.HHNbr = BT1.HHNbr
AND B1.NamSeq = BT1.NamSeq
AND BT1.Seq = 1
AND BT1.PhType = 'Cell Phone'
However if there is not a phone number in sequence 0 or 1 of the cell phone phtype it will leave those columns blank. I need the query to take the sequences out of the equation altogether without repeating the phone number in the next column over. Please help.
Thanks,
Mitch
So, as I understand it, you're just wanting to collapse the data in your table down to a single row, correct? I have written a SQL query that tests what I think you're wanting using ROW_NUMBER() to handle the gaps in your sequence numbers. I hope this gives you an idea of what you'll need.
CREATE TABLE #testPhones (
PhType VARCHAR(20),
PhNumber VARCHAR(12),
PhSequence INT)
INSERT INTO #testPhones(PhType, PhNumber, PhSequence)
VALUES('Cell Phone', '111-222-3333', 3),
('Cell Phone', '222-333-4444', 5),
('Home Phone', '999-222-1111', 6),
('Home Phone', '555-444-3333', 8)
SELECT ROW_NUMBER() OVER(ORDER BY PhSequence) AS Row, *
FROM #testPhones
SELECT A.PhNumber AS CellPhone1#, A.PhSequence AS PhSequence,
B.PhNumber AS CellPhone2#, B.PhSequence AS PhSequence,
C.PhNumber AS HomePhone1#, C.PhSequence AS PhSequence,
D.PhNumber AS HomePhone2#, D.PhSequence AS PhSequence
FROM (SELECT PhNumber, PhSequence FROM (SELECT ROW_NUMBER() OVER(ORDER BY PhSequence) AS RowNo, * FROM #testPhones) AS A WHERE RowNo = 1) AS A
LEFT OUTER JOIN (SELECT PhNumber, PhSequence FROM (SELECT ROW_NUMBER() OVER(ORDER BY PhSequence) AS RowNo, * FROM #testPhones) AS B WHERE RowNo = 2) AS B ON 1=1
LEFT OUTER JOIN (SELECT PhNumber, PhSequence FROM (SELECT ROW_NUMBER() OVER(ORDER BY PhSequence) AS RowNo, * FROM #testPhones) AS C WHERE RowNo = 3) AS C ON 1=1
LEFT OUTER JOIN (SELECT PhNumber, PhSequence FROM (SELECT ROW_NUMBER() OVER(ORDER BY PhSequence) AS RowNo, * FROM #testPhones) AS D WHERE RowNo = 4) AS D ON 1=1
DROP TABLE #testPhones
This outputs the two following tables:
Output Tables

SQL, Split by 1 column and duplicate other columns

Good day!
Maybe you can help me, or tell me if what I want to do is impossible or totally wrong...
I was trying to create a sqlfiddle but it seems the page is down at the moment.
(SQL Server 2008) I have a table, lets say that it has 3 columns, but the person who designed it didn't normalize, so one column holds multiple values, it's something like this:
IdCol Col1 Col2 Col3
1 a1 b1 a, b, c
2 a2 b2 d, e, f
As you can see, Col3 holds multiple values separated by ","
what I want to achieve, is to create a view (can't modify the table because they won't allow me to modify the application) that is something similar to this:
NewIdCol IdCol Col1 Col2 Col3
1 1 a1 b1 a
2 1 a1 b1 b
3 1 a1 b1 c
4 2 a2 b2 d
5 2 a2 b2 e
6 2 a2 b2 f
So the final result has Col3 values split into a different row and every other column's value copied. (the actual table has about 20 columns, and 2 of those columns hold multiple values, so I would need to do it for both columns)
At first I thought it would be easy... but then I hit a block on how to split that string... first I thought about using a split function, but then I didn't know how to join it back with the rest of the columns...
Thanks in advance.
You need to have a function for splitting comma-delimited strings into separate rows. Then you call the function like this:
SELECT
NewIdCol = ROW_NUMBER() OVER(ORDER BY t.IdCol, x.ItemNumber),
t.IdCol,
t.Col1,
t.Col2,
x.Item
FROM Test t
CROSS APPLY [dbo].[DelimitedSplit8K](t.Col3, ',') x
Here is the DelimitedSplit8K function by Jeff Moden.
CREATE FUNCTION [dbo].[DelimitedSplit8K](
#pString NVARCHAR(4000), #pDelimiter NCHAR(1)
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH E1(N) AS (
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)
,E4(N) AS (SELECT 1 FROM E2 a, E2 b)
,cteTally(N) AS(
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
,cteStart(N1) AS(
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(
SELECT
s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
SELECT
ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;

split string in to rows in sql server

I have a string in sql ABCDEF . This is coming under First_Name column in a EMP table.
I want to split this string into rows as give below.
Please note there is no delimiter or Comma or space. Its a string without any special characters and special symbols and space.
First_Name
A
B
C
D
E
F
A loop obviously comes to mind but we can do much better. This is where a tally or numbers table is perfect. I have a view in my system like this which creates such a table with 10,000 rows on demand. There are plenty of ways to create such a table, or you could create a persistent table for a slight performance gain.
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
Now that we have the view this type of thing is super painless using a totally set based approach and abandoning the idea of looping.
declare #Something varchar(20) = 'ABCDEF'
select SUBSTRING(#Something, N, 1)
from cteTally t
where t.N < LEN(#Something)
Solving this is trivial if you have a Numbers table. No loops or cursors are necessary, resulting in performance that is orders of magnitude better than other solutions:
declare #name varchar(10)='ABCDEF'
select #name,SUBSTRING(#name,n,1)
from numbers
where n<=LEN(#name)
Or
select EMP.First_Name,SUBSTRING(EMP.First_Name,n,1)
from EMP,numbers
where n<=LEN(EMP.First_Name)
A Numbers table contains only numbers from 1 to a sufficiently large number. You can create such a table with the following statement (borrowed from the linked article):
SELECT TOP (1000000) n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
INTO dbo.Numbers
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
OPTION (MAXDOP 1);
CREATE UNIQUE CLUSTERED INDEX n ON dbo.Numbers(n)
You can use a CTE to parse out each character of your string onto a new row:
DECLARE #data varchar(200) = 'ABCDEF'
;WITH CTE AS (
SELECT
1 as CharacterPosition,
SUBSTRING(#data,1,1) as Character
UNION ALL
SELECT
CharacterPosition + 1,
SUBSTRING(#data,CharacterPosition + 1,1)
FROM
CTE
WHERE CharacterPosition < LEN(#data)
)
SELECT Character
FROM CTE

Resources