Recursive CTE Problem - sql-server

I am trying to use a recursive CTE in SQL Server to build up a predicate formula from a table containing the underlying tree structure.
For example, my table looks like:
Id | Operator/Val | ParentId
--------------------------
1 | 'OR' | NULL
2 | 'AND' | 1
3 | 'AND' | 1
4 | '>' | 2
5 | 'a' | 4
6 | 'alpha' | 4
...
...which represents ((a > alpha) AND (b > beta)) OR ((c > gamma) AND (a < delta)).
ParentId is a reference to the Id in the same table of the parent node.
I want to write a query which will build up this string from the table. Is it possible?
Thanks

For a production environment, you may want to go with a recursive function for simplicity if performance and recursion depth limits (32 levels) is not a problem.
However, here's a quite clean and pretty efficient solution with CTEs (note that it will accept any number of "trees" and return one result for each item which has no parent):
DECLARE #tbl TABLE
(
id int PRIMARY KEY
NOT NULL,
op nvarchar(max) NOT NULL,
parent int
) ;
INSERT INTO #tbl
SELECT 1, 'OR', NULL UNION ALL
SELECT 2, 'AND', 1 UNION ALL
SELECT 3, 'AND', 1 UNION ALL
SELECT 4, '>', 2 UNION ALL
SELECT 5, 'a', 4 UNION ALL
SELECT 6, 'alpha', 4 UNION ALL
SELECT 7, '>', 2 UNION ALL
SELECT 8, 'b', 7 UNION ALL
SELECT 9, 'beta', 7 UNION ALL
SELECT 10, '>', 3 UNION ALL
SELECT 11, 'c', 10 UNION ALL
SELECT 12, 'gamma', 10 UNION ALL
SELECT 13, '>', 3 UNION ALL
SELECT 14, 'd', 13 UNION ALL
SELECT 15, 'delta', 13 ;
WITH nodes -- A CTE which sets a flag to 1 for non-leaf nodes
AS (
SELECT t.*, CASE WHEN p.parent IS NULL THEN 0
ELSE 1
END node
FROM #tbl t
LEFT JOIN (
SELECT DISTINCT parent
FROM #tbl
) p ON p.parent = T.id
),
rec -- the main recursive run to determine the sort order and add meta information
AS (
SELECT id rootId, node lvl, CAST(0 AS float) sort, CAST(0.5 AS float) offset, *
FROM nodes
WHERE parent IS NULL
UNION ALL
SELECT r.rootId, r.lvl+t.node, r.sort+r.offset*CAST((ROW_NUMBER() OVER (ORDER BY t.id)-1)*2-1 AS float),
r.offset/2, t.*
FROM rec r
JOIN
nodes t ON r.id = t.parent
),
ranked -- ranking of the result to sort and find the last item
AS (
SELECT rootId, ROW_NUMBER() OVER (PARTITION BY rootId ORDER BY sort) ix,
COUNT(1) OVER (PARTITION BY rootId) cnt, lvl, op
FROM rec
),
concatenated -- concatenate the string, adding ( and ) as needed
AS (
SELECT rootId, ix, cnt, lvl, CAST(REPLICATE('(', lvl)+op AS nvarchar(max)) txt
FROM ranked
WHERE ix = 1
UNION ALL
SELECT r.rootId, r.ix, r.cnt, r.lvl,
c.txt+COALESCE(REPLICATE(')', c.lvl-r.lvl), '')+' '+COALESCE(REPLICATE('(', r.lvl-c.lvl), '')+r.op
+CASE WHEN r.ix = r.cnt THEN REPLICATE(')', r.lvl)
ELSE ''
END
FROM ranked r
JOIN
concatenated c ON (r.rootId = c.rootId)
AND (r.ix = c.ix+1)
)
SELECT rootId id, txt
FROM concatenated
WHERE ix = cnt
OPTION (MAXRECURSION 0);

I found something, but it looks pretty nasty. You would be able to do this a lot easier using a recursive fundtion...
DECLARE #Table TABLE(
ID INT,
Op VARCHAR(20),
ParentID INT
)
INSERT INTO #Table SELECT 1,'OR',NULL
INSERT INTO #Table SELECT 2,'AND',1
INSERT INTO #Table SELECT 3,'AND',1
INSERT INTO #Table SELECT 4,'>',2
INSERT INTO #Table SELECT 5,'a',4
INSERT INTO #Table SELECT 6,'alpha',4
INSERT INTO #Table SELECT 7,'>',2
INSERT INTO #Table SELECT 8,'b',7
INSERT INTO #Table SELECT 9,'beta',7
INSERT INTO #Table SELECT 10,'>',3
INSERT INTO #Table SELECT 11,'c',10
INSERT INTO #Table SELECT 12,'gamma',10
INSERT INTO #Table SELECT 13,'<',3
INSERT INTO #Table SELECT 14,'a',13
INSERT INTO #Table SELECT 15,'delta',13
;WITH Vals AS (
SELECT t.*,
1 Depth
FROM #Table t LEFT JOIN
#Table parent ON t.ID = parent.ParentID
WHERE parent.ParentID IS NULL
UNION ALL
SELECT t.*,
v.Depth + 1
FROM #Table t INNER JOIN
Vals v ON v.ParentID = t.ID
),
ValLR AS(
SELECT DISTINCT
vLeft.ID LeftID,
vLeft.Op LeftOp,
vRight.ID RightID,
vRight.Op RightOp,
vLeft.ParentID OperationID,
vLeft.Depth
FROM Vals vLeft INNER JOIN
Vals vRight ON vLeft.ParentID = vRight.ParentID
AND vLeft.ID < vRight.ID
WHERE (vRight.ID IS NOT NULL)
),
ConcatVals AS(
SELECT CAST('(' + LeftOp + ' ' + Op + ' ' + RightOp + ')' AS VARCHAR(500)) ConcatOp,
t.ID OpID,
v.Depth,
1 CurrentDepth
FROM ValLR v INNER JOIN
#Table t ON v.OperationID = t.ID
WHERE v.Depth = 1
UNION ALL
SELECT CAST('(' + cL.ConcatOp + ' ' + t.Op + ' {' + CAST(v.RightID AS VARCHAR(10)) + '})' AS VARCHAR(500)) ConcatOp,
t.ID OpID,
v.Depth,
cL.CurrentDepth + 1
FROM ValLR v INNER JOIN
#Table t ON v.OperationID = t.ID INNER JOIN
ConcatVals cL ON v.LeftID = cL.OpID
WHERE v.Depth = cL.CurrentDepth + 1
),
Replaces AS(
SELECT REPLACE(
c.ConcatOp,
SUBSTRING(c.ConcatOp,PATINDEX('%{%', c.ConcatOp), PATINDEX('%}%', c.ConcatOp) - PATINDEX('%{%', c.ConcatOp) + 1),
(SELECT ConcatOp FROM ConcatVals WHERE OpID = CAST(SUBSTRING(c.ConcatOp,PATINDEX('%{%', c.ConcatOp) + 1, PATINDEX('%}%', c.ConcatOp) - PATINDEX('%{%', c.ConcatOp) - 1) AS INT))
) ConcatOp,
1 Num
FROM ConcatVals c
WHERE Depth = (SELECT MAX(Depth) FROM ConcatVals)
UNION ALL
SELECT REPLACE(
r.ConcatOp,
SUBSTRING(r.ConcatOp,PATINDEX('%{%', r.ConcatOp), PATINDEX('%}%', r.ConcatOp) - PATINDEX('%{%', r.ConcatOp) + 1),
(SELECT ConcatOp FROM ConcatVals WHERE OpID = CAST(SUBSTRING(r.ConcatOp,PATINDEX('%{%', r.ConcatOp) + 1, PATINDEX('%}%', r.ConcatOp) - PATINDEX('%{%', r.ConcatOp) - 1) AS INT))
) ConcatOp,
Num + 1
FROM Replaces r
WHERE PATINDEX('%{%', r.ConcatOp) > 0
)
SELECT TOP 1
*
FROM Replaces
ORDER BY Num DESC
OUTPUT
ConcatOp
----------------------------------------------------------------
(((a > alpha) AND (b > beta)) OR ((c > gamma) AND (a < delta)))
If you would rather want to look at a recursive function, give me a shout and we can have a look.
EDIT: Recursive Function
Have a look at how much easier this is
CREATE TABLE TableValues (
ID INT,
Op VARCHAR(20),
ParentID INT
)
INSERT INTO TableValues SELECT 1,'OR',NULL
INSERT INTO TableValues SELECT 2,'AND',1
INSERT INTO TableValues SELECT 3,'AND',1
INSERT INTO TableValues SELECT 4,'>',2
INSERT INTO TableValues SELECT 5,'a',4
INSERT INTO TableValues SELECT 6,'alpha',4
INSERT INTO TableValues SELECT 7,'>',2
INSERT INTO TableValues SELECT 8,'b',7
INSERT INTO TableValues SELECT 9,'beta',7
INSERT INTO TableValues SELECT 10,'>',3
INSERT INTO TableValues SELECT 11,'c',10
INSERT INTO TableValues SELECT 12,'gamma',10
INSERT INTO TableValues SELECT 13,'<',3
INSERT INTO TableValues SELECT 14,'a',13
INSERT INTO TableValues SELECT 15,'delta',13
GO
CREATE FUNCTION ReturnMathVals (#ParentID INT, #Side VARCHAR(1))
RETURNS VARCHAR(500)
AS
BEGIN
DECLARE #RetVal VARCHAR(500)
IF (#ParentID IS NULL)
BEGIN
SELECT #RetVal = ' (' + dbo.ReturnMathVals(ID,'L') + Op + dbo.ReturnMathVals(ID,'R') + ') '
FROM TableValues
WHERE ParentID IS NULL
END
ELSE
BEGIN
SELECT TOP 1 #RetVal = ' (' + dbo.ReturnMathVals(ID,'L') + Op + dbo.ReturnMathVals(ID,'R') + ') '
FROM TableValues
WHERE ParentID = #ParentID
ORDER BY CASE WHEN #Side = 'L' THEN ID ELSE -ID END
SET #RetVal = ISNULL(#RetVal, (SELECT TOP 1 Op FROM TableValues WHERE ParentID = #ParentID ORDER BY CASE WHEN #Side = 'L' THEN ID ELSE -ID END))
END
RETURN #RetVal
END
GO
SELECT dbo.ReturnMathVals(NULL, NULL)
GO
DROP FUNCTION ReturnMathVals
DROP TABLE TableValues

Yes it is possible to do it but the problem is not the CTE, check it with PIVOT
read more about it from this link
http://msdn.microsoft.com/en-us/library/ms177410.aspx
some examples in this documentation is similar with your problem

I couldn't figure out how to do the double-recursion, but hopefully one of the intermediate CTEs in this will set you on the right track:
SET NOCOUNT ON
DECLARE #tree AS TABLE
(
Id int NOT NULL
,Operator varchar(10) NOT NULL
,ParentId int
)
INSERT INTO #tree
VALUES (1, 'OR', NULL)
INSERT INTO #tree
VALUES (2, 'AND', 1)
INSERT INTO #tree
VALUES (3, 'AND', 1)
INSERT INTO #tree
VALUES (4, '>', 2)
INSERT INTO #tree
VALUES (5, 'a', 4)
INSERT INTO #tree
VALUES (6, 'alpha', 4)
INSERT INTO #tree
VALUES (7, '>', 2)
INSERT INTO #tree
VALUES (8, 'b', 7)
INSERT INTO #tree
VALUES (9, 'beta', 7)
INSERT INTO #tree
VALUES (10, '>', 3)
INSERT INTO #tree
VALUES (11, 'c', 10)
INSERT INTO #tree
VALUES (12, 'gamma', 10)
INSERT INTO #tree
VALUES (13, '>', 3)
INSERT INTO #tree
VALUES (14, 'd', 13)
INSERT INTO #tree
VALUES (15, 'delta', 13) ;
WITH lhs_selector
AS (
SELECT ParentId
,MIN(Id) AS Id
FROM #tree
GROUP BY ParentId
),
rhs_selector
AS (
SELECT ParentId
,MAX(Id) AS Id
FROM #tree
GROUP BY ParentId
),
leaf_selector
AS (
SELECT Id
FROM #tree AS leaf
WHERE NOT EXISTS ( SELECT *
FROM #tree
WHERE ParentId = leaf.Id )
),
recurse
AS (
SELECT operator.Id
,CASE WHEN lhs_is_leaf.Id IS NOT NULL THEN NULL
ELSE lhs.Id
END AS LhsId
,CASE WHEN rhs_is_leaf.Id IS NOT NULL THEN NULL
ELSE rhs.Id
END AS RhsId
,CASE WHEN COALESCE(lhs_is_leaf.Id, rhs_is_leaf.Id) IS NULL
THEN '({' + CAST(lhs.Id AS varchar) + '} ' + operator.Operator + ' {'
+ CAST(rhs.Id AS varchar) + '})'
ELSE '(' + lhs.Operator + ' ' + operator.Operator + ' ' + rhs.Operator + ')'
END AS expression
FROM #tree AS operator
INNER JOIN lhs_selector
ON lhs_selector.ParentID = operator.Id
INNER JOIN rhs_selector
ON rhs_selector.ParentID = operator.Id
INNER JOIN #tree AS lhs
ON lhs.Id = lhs_selector.Id
INNER JOIN #tree AS rhs
ON rhs.Id = rhs_selector.Id
LEFT JOIN leaf_selector AS lhs_is_leaf
ON lhs_is_leaf.Id = lhs.Id
LEFT JOIN leaf_selector AS rhs_is_leaf
ON rhs_is_leaf.Id = rhs.Id
)
SELECT *
,REPLACE(REPLACE(op.expression, '{' + CAST(op.LhsId AS varchar) + '}', lhs.expression),
'{' + CAST(op.RhsId AS varchar) + '}', rhs.expression) AS final_expression
FROM recurse AS op
LEFT JOIN recurse AS lhs
ON lhs.Id = op.LhsId
LEFT JOIN recurse AS rhs
ON rhs.Id = op.RhsId

Related

Possible Permutations for Source and Destination Lat/Long

I have the following table in SQL Server called #Places1
A->B
C->D
E->F
I am trying to get all the possible permutations for this table but the Origin has to be IN before the Destination.
The issue is that I am generating some destination without the Origin. Can you please guide/help me to find a solution.
create table #places1
(
idgeneric int identity(1,1)
, id AS cast(CONCAT('0',cast(idgeneric as varchar(10))) as nvarchar(max))
, CatCode as cast(char(64+idgeneric) as nvarchar(max))
,OB_FREIGHT_ORDER_ID bigint
, lat decimal(9,6)
, long decimal(9,6)
, source nvarchar(max))
insert into #places1(OB_FREIGHT_ORDER_ID, lat, long, source)
select 25, 40.066863, -76.776681, 'Origin'
union all
select 25, 41.671810, -72.834214, 'Destination'
union all
select 26, 40.140015,-74.191128, 'Origin'
union all
select 26,41.738134, -72.198242, 'Destination'
union all
select 35, 41.488488,-74.234148, 'Origin'
union all
select 35,40.818434, -73.883721, 'Destination'
select * from #places1
Below the code that I have so far
Declare #counter int
Select #counter = COUNT(*)
from #places1;
DECLARE #Terminus TABLE (OD INT, node nvarchar(max))
INSERT INTO #Terminus
SELECT 1, id
FROM (
SELECT id FROM #places1
where source = 'origin'
EXCEPT
SELECT id
FROM #places1
where source = 'Destination') x
UNION ALL
SELECT 2, id
FROM (
SELECT id
FROM #places1
where source = 'Destination'
EXCEPT
SELECT id
FROM #places1
where source = 'origin') x
;With Permutations (permutation, IDs, source, Depth, count_origin, count_destination)
as
(
Select c.CatCode
,c.ID + ';'
, cast(source as nvarchar(max))
--,lat
--,long
--,lat
--,long
--,parent
,Depth = 1
,case when source = 'Origin' then 1 else 0 end
,case when source = 'Destination' then 1 else 0 end
From #places1 c
WHERE id IN (SELECT node FROM #Terminus WHERE OD = 1)
union all
SELECT permutation + c.CatCode
,IDs + c.ID + ';' as IDs
,cast(p.source + '->'+ c.source as nvarchar(max)) as source
--,c.lat
--,c.long
--,p.d_lat
--,p.d_long
--,p.parent
,Depth = Depth + 1
,count_origin + case when c.source = 'Origin' then 1 else 0 end
,count_destination + case when c.source = 'Destination' then 1 else 0 end
FROM Permutations p
INNER JOIN #places1 c
ON IDs not like '%' + ID + ';%'
where (count_destination + case when c.source = 'Destination' then 1 else 0 end <= count_origin + case when c.source = 'Origin' then 1 else 0 end ))
Select distinct *
from Permutations
where depth = #counter
see result issue..06 shouldn't be generated without its origin 05

count and remove individual values of a single column

I have a table and the values like this
create table items_table(url varchar(max),counttotal_urls int,countduplicate_urls int,Unique_urls varchar(max),
countUnique_urls int)
insert into items_table(url) values('ht,ha,hb,ha|hc|hy')
insert into items_table(url) values('ht,hb,hb|hb|hx|hx')
insert into items_table(url) values('hz,hy,hx,hm|hm,hy')
insert into items_table(url) values('hz,hy,hx,hm|hm,hy')
I need to replace ,h with |h , for this I will use replace(url,',h','|h')
I need to count total_urls present by considering the separation '|'
I want to check or count how may duplicate url's are present
I want unique_urls to be updated in the Unique_url's column by removing the duplicates
Finally I want to count the Unique_urls which will be updated in another column
Desired output:
This is a bit complicated. But I tried to achieve it in Set based methodology.
Your Schema:
CREATE TABLE #items_table (
id INT identity
,url VARCHAR(max)
,counttotal_urls INT
,countduplicate_urls INT
,Unique_urls VARCHAR(max)
,countUnique_urls INT
)
INSERT INTO #items_table (url)
VALUES ('ht,ha,hb,ha|hc|hy')
INSERT INTO #items_table (url)
VALUES ('ht,hb,hb|hb|hx|hx')
INSERT INTO #items_table (url)
VALUES ('hz,hy,hx,hm|hm,hy')
INSERT INTO #items_table (url)
VALUES ('hy,hx,hm|hm,hy')
I have used several CTE's and XML methods
;WITH CTE
AS (
SELECT url
,REPLACE(',' + url, ',h', '|h') AS url2
,CAST('<M>'
+ REPLACE(REPLACE(',' + url, ',h', '|h'), '|', '</M><M>')
+ '</M>' AS XML) AS XML_FLD
FROM #items_table
)
,CTE2
AS (
SELECT url
,SUM(CASE
WHEN SUBSTRING(url2, number, 1) > '|'
THEN 1
ELSE 0
END) / 2 AS counttotal_urls
FROM CTE C
CROSS APPLY (
SELECT *
FROM master.dbo.spt_values
WHERE type = 'P'
AND number BETWEEN 1
AND LEN(C.url2)
) CA
GROUP BY url
)
,CTE3
AS (
SELECT C2.url
,C2.counttotal_urls
,SPLITS.ABC.value('.', 'varchar(MAX)') DUP_URLS
FROM CTE2 C2
INNER JOIN CTE C ON C2.url = C.url
CROSS APPLY C.XML_FLD.nodes('/M') AS SPLITS(ABC)
)
SELECT url
,counttotal_urls
,counttotal_urls - (COUNT(DISTINCT DUP_URLS) - 1) AS countduplicate_urls
,STUFF((
SELECT DISTINCT '|' + DUP_URLS
FROM CTE3 C
WHERE C3.url = C.url
FOR XML PATH('')
), 1, 1, '') AS Unique_urls
FROM CTE3 C3
GROUP BY url
,counttotal_urls
Result will be
+-------------------+-----------------+---------------------+-----------------+
| url | counttotal_urls | countduplicate_urls | Unique_urls |
+-------------------+-----------------+---------------------+-----------------+
| ht,ha,hb,ha|hc|hy | 6 | 1 | |ha|hb|hc|ht|hy |
| ht,hb,hb|hb|hx|hx | 6 | 3 | |hb|ht|hx |
| hy,hx,hm|hm,hy | 5 | 2 | |hm|hx|hy |
| hz,hy,hx,hm|hm,hy | 6 | 2 | |hm|hx|hy|hz |
+-------------------+-----------------+---------------------+-----------------+
You will have to create a string split table value function (Found one at aspsnippets)
as below
CREATE FUNCTION ufn_SplitString
(
#Input NVARCHAR(MAX),
#Character CHAR(1)
)
RETURNS #Output TABLE (
Item NVARCHAR(1000)
)
AS
BEGIN
DECLARE #StartIndex INT, #EndIndex INT
SET #StartIndex = 1
IF SUBSTRING(#Input, LEN(#Input) - 1, LEN(#Input)) <> #Character
BEGIN
SET #Input = #Input + #Character
END
WHILE CHARINDEX(#Character, #Input) > 0
BEGIN
SET #EndIndex = CHARINDEX(#Character, #Input)
INSERT INTO #Output(Item)
SELECT SUBSTRING(#Input, #StartIndex, #EndIndex - 1)
SET #Input = SUBSTRING(#Input, #EndIndex + 1, LEN(#Input))
END
RETURN
END
GO
Once the Function is in place the below code can be used to achieve the desired result
;WITH cte_OriginalTable(url) as
(
SELECT 'ht,ha,hb,ha|hc|hy' UNION ALL
SELECT 'ht,hb,hb|hb|hx|hx' UNION ALL
SELECT 'hz,hy,hx,hm|hm,hy' UNION ALL
SELECT 'hz,hy,hx,hm|hm,hy'
)
,cte_SaperaterFix AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS ID, replace(url, ',h', '|h') AS url
FROM cte_OriginalTable
)
,cte_Split as
(
SELECT o.*,
y.Item
FROM cte_SaperaterFix o
CROSS APPLY dbo.ufn_SplitString(o.url, '|') y
)
,cte_TotalCount AS
(
SELECT ID,COUNT(ID) AS counttotal_urls, COUNT(DISTINCT Item) AS Unique_urls, COUNT(ID) - COUNT(DISTINCT Item) AS countduplicate_urls
FROM cte_Split
GROUP BY ID
)
SELECT DISTINCT b.ID, b.url AS URLs, a.CountTotal_URLs, a.CountDuplicate_URLS, STUFF(( SELECT DISTINCT '|' + b1.Item AS [text()]
FROM cte_Split b1
WHERE
b.ID = b1.ID
FOR XML PATH('')
), 1, 1, '' ) AS Unique_URLs, a.Unique_URLs AS CountUnique_URLs
FROM cte_TotalCount a
JOIN cte_Split b
ON a.ID = b.ID

need help in forming sql query

ItemId Name parentId
1 A null
2 b null
3 c 1
4 d 2
5 e 3
6 f 4
7 g 2
hi i need help in create sql query. I have a table that contain 3 column itemid ,name ,parentitemid. i need a sql query that result parent child relation.if parentitemid id null then it means root .please help
i need data like.
<1><3><5></5> </3></1>
For example you can use:
WITH HierarchicalTable
AS
(
SELECT Id, ParentId, Name, 0 as [Level]
FROM YourTable
WHERE ParentId IS NULL
UNION ALL
SELECT YourTable.Id, YourTable.ParentId, YourTable.Name, [Level] + 1
FROM YourTable
JOIN HierarchicalTable ON HierarchicalTable.Id = YourTable.ParentId
)
SELECT [Level], Name FROM HierarchicalTable
This is excessively complicated solution, but it will work for your problem:
DECLARE #temp TABLE (ItemId int, Name char(1), parentId int,l int)
DECLARE #xml TABLE (s nvarchar(max), e nvarchar(max), parentId int, itemid int)
DECLARE #l int
;WITH cte AS (
SELECT *
FROM (VALUES
(1, 'a', NULL),(2, 'b', NULL),(3, 'c', 1),(4, 'd', 2),(5, 'e', 3),(6, 'f', 4),(7, 'g', 2)
) as t(ItemId, Name, parentId)
--Here we create recursive cte to obtain levels of nesting
), res AS (
SELECT *,
1 [Level]
FROM cte c
where parentId IS null
UNION ALL
SELECT c.*,
[Level]+1
FROM res r
INNER JOIN cte c
ON c.parentId = r.ItemId
)
--put results into temp table
INSERT INTO #temp
SELECT *
FROM res
--obtain max level
SELECT #l = MAX(l)
FROM #temp
--from max level to 1 begin
WHILE #l > 0
BEGIN
--if there is nodes with same parentid - concatinating them
UPDATE x
SET x.e = x.e + v.s + v.e
FROM #xml x
INNER JOIN #xml v
ON v.parentId = x.parentId and v.e !=x.e;
--here we merge table with results
-- first run <e></e>
-- next run <c><e></e></c>
-- next run <a><c><e></e></c></a>
MERGE #xml AS target
USING (
SELECT '<'+ Name +'>' as s,'</'+ Name + '>' as e, parentId, ItemId
FROM #temp
WHERE l = #l
) as source
ON target.parentid = source.itemid
WHEN NOT MATCHED THEN INSERT VALUES (source.s, source.e, source.parentId, source.ItemId)
WHEN MATCHED THEN
UPDATE
SET target.s = source.s + target.s,
target.e = target.e + source.e,
target.parentid = source.parentid,
target.itemid = source.itemid;
--next level down
SET #l = #l - 1
END
SELECT x --CAST(x as xml)
FROM (
SELECT s+e as x,
DENSE_RANK() OVER (PARTITION BY itemid ORDER BY s ASC) as rn
--need that column to obtain first one of every string for itemid
FROM #xml
) as c
WHERE c.rn = 1
--FOR XML PATH ('')
Output will be:
x
<a><c><e></e></c></a>
<b><d><f></f></d><g></g></b>
If you remove -- near FOR XML PATH ('') and change this SELECT x --CAST(x as xml) to this SELECT CAST(x as xml) in last query you will get this:
<a>
<c>
<e />
</c>
</a>
<b>
<d>
<f />
</d>
<g />
</b>

How to sort string alphabetically

I have a table which have the following data
Item
......
xzypq
abdcfe
How can I sort the string in the column and get the following result?
Item
......
pqxyz
abcdef
May be try the below link which might help http://social.technet.microsoft.com/wiki/contents/articles/19492.sort-letters-in-a-phrase-using-t-sql.aspx
/*Create sample table*/
IF OBJECT_ID('tempdb..#Text', 'U') IS NOT NULL
DROP TABLE #Test;
CREATE TABLE #Test
(
ID INT IDENTITY(1, 1) ,
Phrase VARCHAR(255)
);
/*Populate the table with sample data*/
INSERT #Test
( Phrase )
VALUES
( 'CHICAGO' ),
( 'NEW YORK' ),
( 'HOUSTON' ),
( 'SAN FRANCISCO' );
/*This is the final solution*/;
WITH base
AS ( SELECT L.[char] ,
T.ID ,
T.Phrase
FROM #Test T
CROSS APPLY ( SELECT SUBSTRING(T.Phrase, 1 + Number, 1) [char]
FROM master..spt_values
WHERE Number < DATALENGTH(T.Phrase)
AND type = 'P'
) L
)
SELECT DISTINCT
b1.Phrase ,
REPLACE(( SELECT '' + [char]
FROM base b2
WHERE b1.Phrase = b2.Phrase
ORDER BY [char]
FOR
XML PATH('')
), ' ', ' ') AS columns2
FROM base AS b1;
Using Recursive CTE also you can do this.
SELECT 'xzypq' NAME
INTO #temp
UNION ALL
SELECT 'abdcfe'
Recursive CTE
;WITH cte
AS (SELECT Cast(NAME AS VARCHAR(50)) AS st,NAME AS name1,1 AS rn
FROM #temp
UNION ALL
SELECT Cast(Substring(NAME, rn, 1) AS VARCHAR(50)),name1,rn + 1
FROM cte a
JOIN #temp b
ON a.name1 = b.NAME
AND rn < Len(a.name1) + 1)
SELECT DISTINCT (SELECT '' + st
FROM cte b
WHERE a.name1 = b.name1
AND rn <> 1
ORDER BY st
FOR XML PATH ('')) AS Ordered_String
FROM cte a
WHERE rn <> 1
Result
Ordered_String
--------------
abcdef
pqxyz

How to sum data from string

Have table :
id name
1 A1=7|A5=1|A10=5|A20=12|A50=8
2 A1=10|A5=2|A10=10|A20=14|A50=4
3 A1=3|A5=3|A10=5|A20=12|A50=8
.
.
Want sum all A1,A5,A10,A20,A50
Response must be like :
A1=20|A5=6|A10=20|A20=38|A50=20
How to do it ?
I also upvoted comment about changing table design but there are situations when somebody cannot do this. So here is the solution for this case and it's not so difficult. We call XML for assistance as usual when need to process formatted strings in XML columns.
-- Prepare data for solution testing
DECLARE #srctable TABLE (
id INT,
name VARCHAR(999),
namexml XML
)
INSERT INTO #srctable
SELECT id, name, namexml FROM ( VALUES
(1, 'A1=7|A5=1|A10=5|A20=12|A50=8', null),
(2, 'A1=10|A5=2|A10=10|A20=14|A50=4', null),
(3, 'A1=3|A5=3|A10=5|A20=12|A50=8', null)
) v (id, name, namexml)
-- Transform source formatted string to XML string
UPDATE #srctable
SET namexml = CAST('<row><data ' + REPLACE(REPLACE(name, '|', '"/><data '), '=', '="') + '"/></row>' AS XML)
-- Final select from XML data
SELECT SUM(x.data.value('(#A1)[1]', 'INT')) AS SUMA1,
SUM(x.data.value('(#A5)[1]', 'INT')) AS SUMA5,
SUM(x.data.value('(#A10)[1]', 'INT')) AS SUMA10,
SUM(x.data.value('(#A20)[1]', 'INT')) AS SUMA20,
SUM(x.data.value('(#A50)[1]', 'INT')) AS SUMA50
FROM #srctable AS t
CROSS APPLY t.namexml.nodes('/row/data') x (data)
You need to format your resulting string in any way you wish.
Variant without xml:
--------------------------------------------------------------------------------
-- Prepare data for solution testing
DECLARE #srctable TABLE ( id INT
, NAME VARCHAR(999) )
INSERT INTO #srctable
VALUES ( 1, 'A1=7|A5=1|A10=5|A20=12|A50=8' )
, ( 2, 'A1=10|A5=2|A10=10|A20=14|A50=4' )
, ( 3, 'A1=3|A5=3|A10=5|A20=12|A50=8' )
--------------------------------------------------------------------------------
-- prepare temp table for using in split string
DECLARE #Tally TABLE ( N INT )
DECLARE #i AS INT = 1
WHILE #i != 1000
BEGIN
INSERT INTO #Tally ( N )
VALUES ( #i )
SET #i = #i + 1
END
--------------------------------------------------------------------------------
--final query
;WITH cte AS
(
SELECT id,
(CASE WHEN CHARINDEX('|', S.string) > 0
THEN left(S.string, CHARINDEX('|', S.string) - 1)
ELSE string END ) NAME
FROM #srctable AS E
INNER JOIN #Tally AS T ON SUBSTRING('|' + NAME, T.N, 1) = '|'
AND T.N <= LEN(NAME)
CROSS APPLY (SELECT String = (CASE WHEN T.N = 1
THEN LEFT(E.NAME, CHARINDEX('|', E.NAME) - 1)
ELSE SUBSTRING(E.NAME, T.N, 1000) END )
) AS S
)
SELECT LEFT(NAME, CHARINDEX('=', NAME) - 1) AS NAME,
SUM(convert(float,RIGHT(NAME, CHARINDEX('=', REVERSE(NAME)) - 1))) AS Value
FROM cte
GROUP BY LEFT(NAME, CHARINDEX('=', NAME) - 1)

Resources