SQL Server: I would like to create a function that removes specific characters from a string, based on parameters.
parameter1 is original string
parameter2 is characters want to removed from original string
For example :
call MyRemoveFunc('32.87.65.54.89', '87.65' ) -- this will return '32.54.89'
call MyRemoveFunc('11.23.45', '23' ) -- this will return '11.45'
call MyRemoveFunc('14.99.16.84', '84.14' ) -- this will return '99.16'
call MyRemoveFunc('11.23.45.65.31.90', '23' ) -- this will return 11.45.65.31.90
call MyRemoveFunc('34.35.36', '35' ) -- this will return 34.36
call MyRemoveFunc('34.35.36.76.44.22', '35' ) -- this will return 34.36.76.44.22
call MyRemoveFunc('34', '34' ) -- this will return blank
call MyRemoveFunc('45.23.11', '45.11' ) -- this will return 23
Thanks
Here is one way using Recursive CTE and Split string function
;WITH data
AS (SELECT org_string,
replace_with,
cs.Item,
cs.ItemNumber
FROM (VALUES ('32.87.65.54.89','87.65' ),
('11.23.45','23' ),
('14.99.16.84','84.14' ),
('11.23.45.65.31.90','23' ),
('34.35.36','35' ),
('34.35.36.76.44.22','35' ),
('34','34' ),
('45.23.11','45.11')) tc (org_string, replace_with)
CROSS apply [Delimitedsplit8k](replace_with, '.') cs),
cte
AS (SELECT org_string,
replace_with,
Item,
Replace('.' + org_string, + '.' + Item, '') AS result,
ItemNumber
FROM data
WHERE ItemNumber = 1
UNION ALL
SELECT d.org_string,
d.replace_with,
d.Item,
CASE
WHEN LEFT(Replace('.' + result, '.' + d.Item, ''), 1) = '.' THEN Stuff(Replace('.' + result, '.' + d.Item, ''), 1, 1, '')
ELSE Replace('.' + result, '.' + d.Item, '')
END,
d.ItemNumber
FROM cte c
JOIN data d
ON c.org_string = d.org_string
AND d.ItemNumber = c.ItemNumber + 1)
SELECT TOP 1 WITH ties org_string,
replace_with,
result = Isnull(Stuff(result, 1, 1, ''), '')
FROM cte
ORDER BY Row_number()OVER(partition BY org_string ORDER BY ItemNumber DESC)
Result :
╔═══════════════════╦══════════════╦════════════════╗
║ org_string ║ replace_with ║ result ║
╠═══════════════════╬══════════════╬════════════════╣
║ 11.23.45 ║ 23 ║ 11.45 ║
║ 11.23.45.65.31.90 ║ 23 ║ 11.45.65.31.90 ║
║ 14.99.16.84 ║ 84.14 ║ 99.16 ║
║ 34 ║ 34 ║ ║
║ 34.35.36 ║ 35 ║ 34.36 ║
║ 34.35.36.76.44.22 ║ 35 ║ 34.36.76.44.22 ║
║ 45.23.11 ║ 45.11 ║ 23 ║
║ 32.87.65.54.89 ║ 87.65 ║ 32.54.89 ║
╚═══════════════════╩══════════════╩════════════════╝
The above code can be converted to a user defined function. I will suggest to create Inline Table valued function instead of Scalar function if you have more records.
Split string Function code referred from http://www.sqlservercentral.com/articles/Tally+Table/72993/
CREATE FUNCTION [dbo].[DelimitedSplit8K]
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
-- enough to cover NVARCHAR(4000)
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
), --10E+1 or 10 rows
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 (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
GO
You could try this..
CREATE FUNCTION StringSpecialReplace(#TargetString VARCHAR(1000),
#InputString VARCHAR(1000))
returns VARCHAR(1000)
AS
BEGIN
declare #result as varchar(1000)
;with CTE1 AS
(
SELECT LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))) AS Certs
FROM (SELECT CAST('<XMLRoot><RowData>' + REPLACE(#TargetString,'.','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x)t
CROSS APPLY x.nodes('/XMLRoot/RowData')m(n)
),
CTE2 AS
(
SELECT LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))) AS Certs
FROM (SELECT CAST('<XMLRoot><RowData>' + REPLACE(#InputString,'.','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x)t
CROSS APPLY x.nodes('/XMLRoot/RowData')m(n)
)
SELECT
#Result = CASE
WHEN #Result IS NULL
THEN certs
ELSE #Result + '.' + certs
END
FROM CTE1 where certs not in (select certs from CTE2)
return #Result
END
CREATE FUNCTION MyRemoveFunc (
#OrigString VARCHAR(8000),
#RemovedString VARCHAR(max)
)
RETURNS varchar(max)
BEGIN
DECLARE #xml XML
SET #xml='<n>'+REPLACE(#RemovedString,'.','</n><n>')+'</n>'
SET #OrigString='.'+#OrigString+'.'
SELECT #OrigString=REPLACE(#OrigString,'.'+s.b.value('.','varchar(200)')+'.','.')
FROM #xml.nodes('n')s(b)
RETURN CASE WHEN LEN(#OrigString)>2 THEN SUBSTRING(#OrigString,2,LEN(#OrigString)-2) ELSE '' END
END
SELECT t.*,dbo.MyRemoveFunc(t.o,t.r)
FROM (VALUES ('32.87.65.54.89','87.65' ),
('11.23.45','23' ),
('14.99.16.84','84.14' ),
('11.23.45.65.31.90','23' ),
('34.35.36','35' ),
('34.35.36.76.44.22','35' ),
('34','34' ),
('45.23.11','45.11')) t (o, r)
o r
----------------- ----- -------------------------
32.87.65.54.89 87.65 32.54.89
11.23.45 23 11.45
14.99.16.84 84.14 99.16
11.23.45.65.31.90 23 11.45.65.31.90
34.35.36 35 34.36
34.35.36.76.44.22 35 34.36.76.44.22
34 34
45.23.11 45.11 23
Related
I have 3 columns in a table, in which 2 columns have string separated by a '|' pipe. Both these columns values depend on each other.
For an example: I have data in the table like this :
ID product quantity
1 A|B|C 1|2|3
2 X|Y|Z 7|8|9
I would like to change it to something like this :
ID product quantity
1 A 1
1 B 2
1 C 3
2 X 7
2 Y 8
2 Z 9
As i am working with SSMS, i don't have any other choice except SQL. I try to use cross apply but i am not getting right result. For 1 row i receive 9 rows instead of getting 3.
could anyone suggest me which method should i use?
Thank you in advance!!
JACK
This is rather tricky, because you need for the values to match up. The following takes a recursive CTE approach:
with cte as (
select id,
left(product, charindex('|', product + '|') - 1) as product,
left(quantity, charindex('|', quantity + '|') - 1) as quantity,
substring(product, charindex('|', product + '|') + 1, 1000) as products,
substring(quantity, charindex('|', quantity + '|') + 1, 1000) as quantities
from t
union all
select id,
left(products, charindex('|', products + '|') - 1) as product,
left(quantities, charindex('|', quantities + '|') - 1) as quantity,
substring(products, charindex('|', products + '|') + 1, 1000) as products,
substring(quantities, charindex('|', quantities + '|') + 1, 1000) as quantities
from cte
where products <> '' and quantities <> ''
)
select id, product, quantity
from cte;
Here is a little Rextester.
Test Data
CREATE TABLE #t (ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO #t VALUES
(1 ,'A|B|C' , '1|2|3'),
(2 ,'X|Y|Z' , '7|8|9');
Query
WITH Products AS (
SELECT ID
, Product_Split.a.value('.', 'VARCHAR(100)') Products
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) rn
FROM (
SELECT ID
,Cast ('<X>'
+ Replace(product, '|', '</X><X>')
+ '</X>' AS XML) AS Product_Data
FROM #t
) AS t
CROSS APPLY Product_Data.nodes ('/X') AS Product_Split(a)
),
Quantities AS (
SELECT ID
, Quantity_Split.a.value('.', 'VARCHAR(100)') Quantity
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) rn
FROM (
SELECT ID
,Cast ('<X>'
+ Replace(quantity, '|', '</X><X>')
+ '</X>' AS XML) AS Quantity_Data
FROM #t
) AS t
CROSS APPLY Quantity_Data.nodes ('/X') AS Quantity_Split(a)
)
SELECT t.ID
, P.Products
, Q.Quantity
FROM #t t
LEFT JOIN Products P ON t.ID = p.ID
LEFT JOIN Quantities Q ON Q.ID = t.ID
AND Q.rn = p.rn
Result Set
╔════╦══════════╦══════════╗
║ ID ║ Products ║ Quantity ║
╠════╬══════════╬══════════╣
║ 1 ║ A ║ 1 ║
║ 1 ║ B ║ 2 ║
║ 1 ║ C ║ 3 ║
║ 2 ║ X ║ 7 ║
║ 2 ║ Y ║ 8 ║
║ 2 ║ Z ║ 9 ║
╚════╩══════════╩══════════╝
splitting strings is easy, there are tons of examples. The tricky part here is to connect the fragments via their position. My suggestion uses XMLs abilities to target an element by its position:
DECLARE #tbl TABLE(ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO #tbl VALUES
(1 ,'A|B|C' , '1|2|3')
,(2 ,'X|Y|Z' , '7|8|9');
--This is the query
WITH CastedToXML AS
(
SELECT *
,CAST('<x>' + REPLACE(product,'|','</x><x>') + '</x>' AS XML) AS ProductXml
,CAST('<x>' + REPLACE(quantity,'|','</x><x>') + '</x>' AS XML) AS QuantityXml
FROM #tbl
)
SELECT *
,ProductXml.value('/x[sql:column("Nmbr")][1]','nvarchar(10)') AS ProductAtPosition
,QuantityXml.value('/x[sql:column("Nmbr")][1]','int') AS QuantityAtPosition
FROM CastedToXML
--Create a set of running numbers (spt_values is just a pre-filled table with many rows)
CROSS APPLY (SELECT TOP(CastedToXML.ProductXml.value('count(/x)','int'))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) AS Tally(Nmbr);
the result
+----+------+-------------------+--------------------+
| ID | Nmbr | ProductAtPosition | QuantityAtPosition |
+----+------+-------------------+--------------------+
| 1 | 1 | A | 1 |
+----+------+-------------------+--------------------+
| 1 | 2 | B | 2 |
+----+------+-------------------+--------------------+
| 1 | 3 | C | 3 |
+----+------+-------------------+--------------------+
| 2 | 1 | X | 7 |
+----+------+-------------------+--------------------+
| 2 | 2 | Y | 8 |
+----+------+-------------------+--------------------+
| 2 | 3 | Z | 9 |
+----+------+-------------------+--------------------+
Some explanation:
the cast to xml transfers your A|B|C to
<x>A</x>
<x>B</x>
<x>C</x>
This list is joined with a number set created on the fly using the count of <x> as the TOP limit.
Now it is easy to pick the <x> out of your XML by the position.
Try it out!
UPDATE: Non-unique IDs
DECLARE #tbl TABLE(ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO #tbl VALUES
(1 ,'A|B|C' , '1|2|3')
,(2 ,'X|Y|Z' , '7|8|9')
,(3 ,'a|b|c' , '7|8|9')
,(2 ,'D|e|f' , '7|8|9')
;
--This is the query
WITH CastedToXML AS
(
SELECT *
,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID) AS RowIndex
,CAST('<x>' + REPLACE(product,'|','</x><x>') + '</x>' AS XML) AS ProductXml
,CAST('<x>' + REPLACE(quantity,'|','</x><x>') + '</x>' AS XML) AS QuantityXml
FROM #tbl
)
SELECT *
,ProductXml.value('/x[sql:column("Nmbr")][1]','nvarchar(10)') AS ProductAtPosition
,QuantityXml.value('/x[sql:column("Nmbr")][1]','int') AS QuantityAtPosition
FROM CastedToXML
--Create a set of running numbers (spt_values is just a pre-filled table with many rows)
CROSS APPLY (SELECT TOP(CastedToXML.ProductXml.value('count(/x)','int'))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) AS Tally(Nmbr);
I have a table as below:
Order No | Customers | Amount
---------+----------------------+-------------
1 | Briant~~Luck | 23~~2122
2 | Mike~~Lee~~David | 10~~200~37
3 | Stak | 100
With format, each customer has one value in Amount.
I'm trying to figure out how to expand the ~~ delimited values to populate a new customers table, which should look like this:
Order No | Customer | Amount
---------+----------------------+---------
1 | Briant | 23
1 | Luck | 2122
2 | Mike | 10
2 | Lee | 200
2 | David | 37
3 | Stak | 100
How can I do?
Any solution in SQL query, function or cursor is appreciated.
Thanks
I think you could store data as your expected result structure. It is much better.
Btw you could use a split function to get your output
DECLARE #SampleData AS TABLE
(
OrderNo int,
Customers varchar(200),
Amount varchar(200)
)
INSERT INTO #SampleData
(
OrderNo,
Customers,
Amount
)
VALUES
( 1, 'Briant~~Luck','23~~2122'),
( 2, 'Mike~~Lee~~David','10~~200~~37'),
( 3, 'Stak','100')
SELECT sd.OrderNo, c.[Value] AS Customer, a.[Value] AS Amount
FROM #SampleData sd
CROSS APPLY
(
SELECT Pos, Value
FROM [dbo].[SplitString](sd.Customers,'~~')
) c
CROSS APPLY
(
SELECT Pos, Value
FROM [dbo].[SplitString](sd.Amount,'~~')
) a
WHERE c.Pos = a.Pos
ORDER BY sd.OrderNo
Split function
CREATE FUNCTION [dbo].[SplitString] (#Text varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos = Row_Number() over (Order By (Select null))
,Value = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#Text,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
Demo link: http://rextester.com/XRX32958
This solution uses XML, CROSS APPLY & ROW_NUMBER to deconstruct the '~~' seperated fields.
It doesn't require a UDF or the STRING_SPLIT function from SQL Server 2016.
-- Using a table variable for the test
declare #Orders table ([Order No] int, Customers varchar(30), Amount varchar(30));
insert into #Orders ([Order No], Customers, Amount) values
(1,'Briant~~Luck','23~~2122'),
(2,'Mike~~Lee~~David','10~~200~~37'),
(3,'Stak','100');
SELECT C.[Order No], C.Customer, A.Amount
FROM
(
SELECT
[Order No],
row_number() over (partition by [Order No] order by (select 1)) as rn,
Customers.Name.value('.', 'VARCHAR(100)') AS Customer
FROM (
SELECT [Order No], CAST ('<x>' + REPLACE(Customers, '~~', '</x><x>') + '</x>' AS XML) AS XCustomers
FROM #Orders
) AS Q1
CROSS APPLY Q1.XCustomers.nodes ('/x') AS Customers(Name)
) C
JOIN (
SELECT
[Order No],
row_number() over (partition by [Order No] order by (select 1)) as rn,
Amounts.Value.value('.', 'VARCHAR(100)') AS Amount
FROM (
SELECT [Order No], CAST ('<x>' + REPLACE(Amount, '~~', '</x><x>') + '</x>' AS XML) AS XAmounts
FROM #Orders
) AS Q1
CROSS APPLY Q1.XAmounts.nodes ('/x') AS Amounts(Value)
) A
ON (C.[Order No] = A.[Order No] AND C.RN = A.RN);
Or if you know there will be maximum 3 values in those strings.
Then the trick with PARSENAME could be used:
SELECT [Order No],
PARSENAME(REPLACE(Customers, '~~', '.'), v.n) as Customer,
PARSENAME(REPLACE(Amount, '~~', '.'), v.n) as Amount
FROM #Orders t
JOIN (VALUES (1),(2),(3)) AS v(n)
ON v.n <= (len(Customers) - len(replace(Customers, '~~', ','))+1);
If you are on SQL2016+ then try this:
drop table if exists dbo.Orders;
create table dbo.Orders (
ID int
, Customers varchar(100)
, Amount varchar(100)
);
insert into dbo.Orders (ID, Customers, Amount)
values (1,'Briant~~Luck', '23~~2122')
, (2, 'Mike~~Lee~~David', '10~~200~~37')
, (3, 'Stak', '100');
select
o.ID
, t01.value as Customer
, t02.value as Amount
from dbo.Orders o
outer apply (
select
ROW_NUMBER () over (order by o.Customers ASC) as Rbr
, t.value
from string_split (replace(o.Customers, '~~', '~'), '~') t
) t01
outer apply (
select
ROW_NUMBER () over (order by o.Amount ASC) as Rbr
, t.value
from string_split (replace(o.Amount, '~~', '~'), '~') t
) t02
where t01.Rbr = t02.Rbr
If you are on a version of SQL Server prior to 2016, you will need to create your own String Splitting function and reference that in your script. The version I use is as follows:
create function [dbo].[StringSplit]
(
#str nvarchar(4000) = ' ' -- String to split.
,#delimiter as nvarchar(1) = ',' -- Delimiting value to split on.
,#num as int = null -- Which value to return.
)
returns table
as
return
( -- Start tally table with 10 rows.
with n(n) as (select n from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n(n))
-- Select the same number of rows as characters in #str as incremental row numbers.
-- Cross joins increase exponentially to a max possible 10,000 rows to cover largest #str length.
,t(t) as (select top (select len(#str) a) row_number() over (order by (select null)) from n n1,n n2,n n3,n n4)
-- Return the position of every value that follows the specified delimiter.
,s(s) as (select 1 union all select t+1 from t where substring(#str,t,1) = #delimiter)
-- Return the start and length of every value, to use in the SUBSTRING function.
-- ISNULL/NULLIF combo handles the last value where there is no delimiter at the end of the string.
,l(s,l) as (select s,isnull(nullif(charindex(#delimiter,#str,s),0)-s,4000) from s)
select rn as ItemNumber
,Item
from(select row_number() over(order by s) as rn
,substring(#str,s,l) as item
from l
) a
where rn = #num -- Return a specific value where specified,
or #num is null -- Or the everything where not.
)
And is used as follows. Note that I have split the outer apply into separate queries to avoid row duplication:
declare #t table(OrderNo int,Customers nvarchar(500),Amount nvarchar(500));
insert into #t values
(1,'Briant~~Luck','23~~2122')
,(2,'Mike~~Lee~~David','10~~200~~37')
,(3,'Stak','100');
with c as
(
select t.OrderNo
,c.ItemNumber
,c.Item as Customers
from #t t
outer apply dbo.StringSplit(replace(t.Customers,'~~','|'),'|',null) c
),a as
(
select t.OrderNo
,a.ItemNumber
,a.Item as Amount
from #t t
outer apply dbo.StringSplit(replace(t.Amount,'~~','|'),'|',null) a
)
select c.OrderNo
,c.Customers
,a.Amount
from c
join a
on(c.OrderNo = a.OrderNo
and c.ItemNumber = a.ItemNumber
)
order by a.OrderNo
,c.Customers;
Output:
+---------+-----------+--------+
| OrderNo | Customers | Amount |
+---------+-----------+--------+
| 1 | Briant | 23 |
| 1 | Luck | 2122 |
| 2 | David | 37 |
| 2 | Lee | 200 |
| 2 | Mike | 10 |
| 3 | Stak | 100 |
+---------+-----------+--------+
Here solution as plsql
declare
type t_array_text is table of varchar2(30);
type t_array_number is table of number;
array_text t_array_text := t_array_text();
array_number t_array_number := t_array_number();
i number := 1;
cursor c1 is
select * from deneme;
begin
FOR c in c1
LOOP
i := 1;
array_text := t_array_text();
array_number := t_array_number();
for rec in (
SELECT regexp_substr(c.customer, '[[:alpha:]]+', 1, level) a frOM dual
CONNECT BY level<=regexp_count(c.customer,'~~')+1)
loop
array_text.extend();
array_text (i) := rec.a;
i := i + 1;
end loop;
i := 1;
for rec in (
SELECT regexp_substr(c.amount, '[0-9]+', 1, level) a frOM dual
CONNECT BY level<=regexp_count(c.amount,'~~')+1)
loop
array_number.extend();
array_number (i) := rec.a;
i := i + 1;
end loop;
for y in 1..array_text.count loop
dbms_output.put_line (c.order_no || ' ' || array_text(y) || ' ' || array_number(y));
end loop;
END LOOP;
end;
result as follows:
1 Briant 23
1 Luck 2122
2 Mike 10
2 Lee 200
2 David 37
3 Stak 10
I trying to write a query and I need some help.
I have some data that looks like this:
However, the end users would like a Pivot so that the data displays like this:
I wrote this code:
SELECT *
FROM
(
SELECT TOP 100
Log0.sn
,CONVERT(VARCHAR(50), Log0.capture_time) AS Instant
,CONVERT(VARCHAR(50),Log0.address) + '/' + Log0.[Key] AS 'Address/Key'
,Log0.average AS Command
,Log1.average AS Actual
FROM ih3_fan_speed_log_0 Log0
LEFT JOIN ih3_fan_speed_log_01 Log1 ON Log0.sn = Log1.sn AND
Log0.capture_time = Log1.capture_time AND Log0.address = Log1.address
WHERE Log0.sn = 'V300188' AND Log0.capture_time = '03/26/2017 13:05:00'
) Src
pivot
(
MAX(sn)
for Instant in ([1], [2], [3], [4])
) piv;
But that returns:
My questions are:
1) Why doesn't my Pivot work ?
2) Is it even possible to do what I need with a Pivot ?
Thanks for any help.
Per request, here is the data:
sn Instant Address/Key Command Actual
V300188 Mar 26 2017 1:05PM 1/EF-1 99.23 99.24
V300188 Mar 26 2017 1:05PM 2/EF-2 99.02 99.04
V300188 Mar 26 2017 1:05PM 3/EF-3 100 0
V300188 Mar 26 2017 1:05PM 4/EF-4 100 100
V300188 Mar 26 2017 1:05PM 41/MUA-1 74.58 74.58
V300188 Mar 26 2017 1:05PM 51/0-10VDC 74.58 74.58
Here is the result set:
Address/Key Command Actual 1 2 3 4
1/EF-1 99.23 99.24 NULL NULL NULL NULL
2/EF-2 99.02 99.04 NULL NULL NULL NULL
3/EF-3 100 0 NULL NULL NULL NULL
4/EF-4 100 100 NULL NULL NULL NULL
41/MUA-1 74.58 74.58 NULL NULL NULL NULL
51/0-10VDC 74.58 74.58 NULL NULL NULL NULL
Here is how you can leverage a dynamic crosstab to accomplish this type of thing. Jeff Moden has a fantastic article explaining this technique here. http://www.sqlservercentral.com/articles/Crosstab/65048/
if OBJECT_ID('tempdb..#Something') is not null
drop table #Something
create table #Something
(
sn varchar(10)
, Instant datetime
, [Address/Key] varchar(20)
, Command numeric(7,2)
, Actual numeric(7,2)
)
insert #Something
select 'V300188', 'Mar 26 2017 1:05PM', '1/EF-1', 99.23, 99.24 union all
select 'V300188', 'Mar 26 2017 1:05PM', '2/EF-2', 99.02, 99.04 union all
select 'V300188', 'Mar 26 2017 1:05PM', '3/EF-3', 100, 0 union all
select 'V300188', 'Mar 26 2017 1:05PM', '4/EF-4', 100, 100 union all
select 'V300188', 'Mar 26 2017 1:05PM', '41/MUA-1', 74.58, 74.58 union all
select 'V300188', 'Mar 26 2017 1:05PM', '51/0-10VDC', 74.58,74.58
declare #MaxCols int
declare #StaticPortion nvarchar(2000) =
'with OrderedResults as
(
select *, ROW_NUMBER() over(partition by sn order by instant) as RowNum
from #Something
)
select sn, instant';
declare #DynamicPortion nvarchar(max) = '';
declare #FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by sn, instant order by sn';
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 #DynamicPortion = #DynamicPortion +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then [Address/Key] end) as address' + CAST(N as varchar(6)) + CHAR(10) +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then Command end) as Command' + CAST(N as varchar(6)) + CHAR(10) +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then Actual end) as Actual' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 Count(*)
from #Something
group by sn, instant
order by COUNT(*) desc
)
select #StaticPortion + #DynamicPortion + #FinalStaticPortion
declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
exec sp_executesql #SqlToExecute
Another Option for a Dynamic Pivot
The twist was the non-distinct/repeating columns in the final results. As you can see, this was resolved with a simple replace (x3).
Declare #SQL varchar(max) = Stuff((Select Distinct ','+QuoteName(concat('A',ColGrp))
+','+QuoteName(concat('B',ColGrp))
+','+QuoteName(concat('C',ColGrp))
From (Select Distinct ColGrp=Row_Number() over (Partition By sn,Instant Order by [Address/Key]) From YourTable ) A
Order By 1
For XML Path('')),1,1,'')
Select #SQL = '
Select [SN],[Instant],' + replace(replace(replace(#SQL,'[A','[Address/Key]=[A'),'[C','[Actual]=[C'),'[B','[Command]=[B') + '
From (
Select [SN]
,[Instant]
,[Col] = Concat(B.Prefix,RN )
,B.Value
From (Select *,RN=Row_Number() over (Partition By sn,Instant Order by [Address/Key]) From YourTable) A
Cross Apply (values (''A'',[Address/Key])
,(''B'',cast(Command as varchar(25)))
,(''C'',cast(Actual as varchar(25)))
) B (PreFix,Value)
) A
Pivot (max([Value]) For [Col] in (' + #SQL + ') ) p'
--Print #SQL
Exec(#SQL);
Returns
EDIT - If it Helps with the Visualization
The generated SQL Looks like this
Select [SN],[Instant],[Address/Key]=[A1],[Command]=[B1],[Actual]=[C1],[Address/Key]=[A2],[Command]=[B2],[Actual]=[C2],[Address/Key]=[A3],[Command]=[B3],[Actual]=[C3],[Address/Key]=[A4],[Command]=[B4],[Actual]=[C4],[Address/Key]=[A5],[Command]=[B5],[Actual]=[C5],[Address/Key]=[A6],[Command]=[B6],[Actual]=[C6]
From (
Select [SN]
,[Instant]
,[Col] = Concat(B.Prefix,RN )
,B.Value
From (Select *,RN=Row_Number() over (Partition By sn,Instant Order by [Address/Key]) From YourTable) A
Cross Apply (values ('A',[Address/Key])
,('B',cast(Command as varchar(25)))
,('C',cast(Actual as varchar(25)))
) B (PreFix,Value)
) A
Pivot (max([Value]) For [Col] in ([A1],[B1],[C1],[A2],[B2],[C2],[A3],[B3],[C3],[A4],[B4],[C4],[A5],[B5],[C5],[A6],[B6],[C6]) ) p
I have 5 rows of data like as below
Now I need to find the position of every ',' from my input string.
My output should be like this:
Please try this one it will give output as yours.
Create table #Table (rowNo int identity(1,1), ID varchar(100))
insert into #Table values('32132')
insert into #Table values('32132,32132')
insert into #Table values('32132,32132,6456,654,645')
declare #TableRow int = (select count(*) from #Table),#Tableloop int = 1
while(#Tableloop <= #TableRow)
begin
Declare #var varchar(100) ;
SET #var = (select ID from #Table where rowNo=#Tableloop)
declare #count int = (select len(#var) - len(replace(#var, ',', '')))
declare #loop int = 1;
declare #location int = 0;
print 'Row' + cast(#Tableloop as varchar(5))
while (#loop <= #count)
begin
SET #location = (select charindex(',',#var,#location))
print cast(#loop as varchar(5)) + ' Comma at ' + cast(#location as varchar(5))
SET #location = #location +1
SET #loop = #loop + 1;
end
SET #Tableloop = #Tableloop + 1;
END
drop table #Table
This will show proper output just put it in temp table and display it.
It looks like you are trying to split your ID values out of comma separated lists. If this is the case you would be better served creating a table-valued function that splits your comma separated list into rows.
You can use Jeff Moden's function below to achieve this using the following:
select i.ID
,ItemNumber
,Item as IDSplit
from Input i
cross apply dbo.DelimitedSplit8K(i.ID,',') s;
Which will return the following:
ID | ItemNumber | IDSplit
````````````````````````````````````````````|````````````|`````````
21321,32154,84655,65465,65476,89799,65464 | 1 | 21321
21321,32154,84655,65465,65476,89799,65464 | 2 | 32154
21321,32154,84655,65465,65476,89799,65464 | 3 | 84655
21321,32154,84655,65465,65476,89799,65464 | 4 | 65465
21321,32154,84655,65465,65476,89799,65464 | 5 | 65476
21321,32154,84655,65465,65476,89799,65464 | 6 | 89799
21321,32154,84655,65465,65476,89799,65464 | 7 | 65464
21313,32156,31656,32132 | 1 | 21313
21313,32156,31656,32132 | 2 | 32156
21313,32156,31656,32132 | 3 | 31656
21313,32156,31656,32132 | 4 | 32132
Jeff Moden's String Split function:
/****** Object: UserDefinedFunction [dbo].[DelimitedSplit8K] Script Date: 24/11/2016 12:08:35 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== String Split function by Jeff Moden: http://www.sqlservercentral.com/articles/Tally+Table/72993/
--===== Define I/O parameters
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
-- enough to cover VARCHAR(8000)
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
), --10E+1 or 10 rows
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 (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
I have following data
I want to Convert Column to Row
I have use following Query ::
Select Distinct [ApplicantID],
Split.a.value('.', 'VARCHAR(100)') AS Subjects,
hoursperweek,[Days],[Time]
From ( Select
Cast ('<M>' + REPLACE([Subjects], ',', '</M><M>') + '</M>' AS XML)
AS Subjects , ApplicantID, hoursperweek,[Days],[Time]
From [dbo].[Progrm])
AS A CROSS APPLY Subjects.nodes ('/M') AS Split(a)
It give me following Result
but I want to seprate Subjects, Hoursperweek, days, time. How can I do that ?
ApplicantID Subjects HoursPerWeek Days Time
1 a 12 Sun 12:45:12
1 b 25 Mon 14:45:12
Here is a function to split the Comma separated values into Rows
CREATE FUNCTION dbo.Splitstrings (#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255))
RETURNS #split_tab TABLE (
RN INT IDENTITY(1, 1),
SPLIT_VAL VARCHAR(100))
WITH SCHEMABINDING
AS
BEGIN ;
;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),
E42(N) AS (SELECT 1 FROM E4 a, E2 b),
cteTally(N)
AS (SELECT 0
UNION ALL
SELECT TOP (Datalength(Isnull(#List, 1))) Row_number()OVER (
ORDER BY (SELECT NULL))
FROM E42),
cteStart(N1)
AS (SELECT t.N + 1
FROM cteTally t
WHERE ( Substring(#List, t.N, 1) = #Delimiter
OR t.N = 0 ))
INSERT INTO #split_tab
SELECT SPLIT_VAL= Substring(#LIST, S.N1, Isnull(NULLIF(Charindex(#DELIMITER, #LIST, S.N1), 0) - S.N1, 8000))
FROM CTESTART S;
RETURN
END
Now the ugly code to split the data
;WITH data
AS (SELECT 1 ApplicantID,
'a,b' Subjects,
'12,25' HoursPerWeek,
'sun,mon' Days,
'12:45:12,14:45:12' Time),
sub
AS (SELECT RN,
ApplicantID,
Subjects=SPLIT_VAL
FROM data
CROSS apply Splitstrings (Subjects, ',')),
HPW
AS (SELECT RN,
ApplicantID,
HoursPerWeek=SPLIT_VAL
FROM data
CROSS apply Splitstrings (HoursPerWeek, ',')),
Days
AS (SELECT RN,
ApplicantID,
Days=SPLIT_VAL
FROM data
CROSS apply Splitstrings (Days, ',')),
Time
AS (SELECT RN,
ApplicantID,
Time=SPLIT_VAL
FROM data
CROSS apply Splitstrings (Time, ','))
SELECT D.APPLICANTID,
S.SUBJECTS,
H.HOURSPERWEEK,
DA.DAYS,
T.TIME
FROM DATA D
FULL OUTER JOIN SUB S
ON D.APPLICANTID = S.APPLICANTID
FULL OUTER JOIN HPW H
ON D.APPLICANTID = H.APPLICANTID
AND H.RN = S.RN
FULL OUTER JOIN DAYS DA
ON D.APPLICANTID = DA.APPLICANTID
AND DA.RN = COALESCE(S.RN, H.RN)
FULL OUTER JOIN TIME T
ON D.APPLICANTID = T.APPLICANTID
AND T.RN = COALESCE(S.RN, H.RN, DA.RN)
Result:
╔═════════════╦══════════╦══════════════╦══════╦══════════╗
║ APPLICANTID ║ SUBJECTS ║ HOURSPERWEEK ║ DAYS ║ TIME ║
╠═════════════╬══════════╬══════════════╬══════╬══════════╣
║ 1 ║ a ║ 12 ║ sun ║ 12:45:12 ║
║ 1 ║ b ║ 25 ║ mon ║ 14:45:12 ║
╚═════════════╩══════════╩══════════════╩══════╩══════════╝
This is the reason we should never store comma separated values into single column. When you want to parse the data things get difficult and we need to such ugly codes to parse the data