SQL Data Hierarchy Mapping - sql-server

The first data table shows the hierarchy structure and Im trying to show how each level relates to all the children underneath. The output I'm looking for is the second table.
Any pointers of how to do this in T-SQL please?

A recursive CTE is what you use to get this output:
WITH reccte AS
(
/*Recursive Seed (first result set upon which we iterate)*/
SELECT CUSTOMERNO, CUSTOMER_PARENT, HIERARCHY
FROM yourtable
WHERE CUSTOMERNO NOT IN (SELECT CUSTOMER_PARENT FROM yourtable)
UNION ALL
/*Recursive Member - The part that refers to itself that iterates until the join fails*/
SELECT
reccte.CUSTOMERNO, yourtable.CUSTOMER_PARENT, yourtable.HIERARCHY
FROM reccte
INNER JOIN yourtable
ON reccte.CUSTOMER_PARENT = yourtable.CUSTOMERNO
)
/*select from the CTE output*/
SELECT * FROM reccte
UNION ALL
/*Union in those level 0 records (records that aren't a parent themselves*/
SELECT CUSTOMERNO, CUSTOMERNO, 0 FROM yourtable WHERE CUSTOMERNO NOT IN (SELECT CUSTOMER_PARENT FROM yourtable)

Related

sql server using recrusive cte to get the level in the same group

I have a sql server table showing the IDs and their previous IDs,
create table test2 ( ID varchar(10) ,
Pre_ID varchar(10)
)
insert into test2 values ('e','d')
, ('d','c')
, ('c','b')
, ('b','a')
, ('a',null)
, ('r','q')
, ('q','p')
, ('p',null)
the table is like this:
The result should be like this:
I have successfully got the levels using a recursive cte, but could not get the correct group for them. can anyone help? Thanks.
This is my code:
with cte as (
select id, Pre_ID, level
from #temp2
where Pre_ID is null
union all
select t2.id, t2.Pre_ID, t2.level
from cte
inner join #temp2 t2
on t2.Pre_ID=cte.id
)
select * from cte
order by id
What you need to do is start with the first level and add a ROW_NUMBER to that, then join all further levels recursively:
with cte as (
select id, Pre_ID, level, row_number() over (order by ID) as grp
from #temp2
where Pre_ID is null
union all
select t2.id, t2.Pre_ID, t2.level, cte.grp
from cte
inner join #temp2 t2
on t2.Pre_ID=cte.id
)
select * from cte
order by id;

Transforming and repeating multiple rows

I have a table that has two IDs within it named FamilyID and PersonID. I need to be able to repeat these rows with all combinations, as the below screenshot shows noting that each of the numbers get an extra row.
Here is some SQL to create the table with some sample data. There is no set number of occurrences that could occur.
Anyone aware of how we could be achieved?
CREATE TABLE #TempStackOverflow
(
FamilyID int,
PersonID int
)
insert into #TempStackOverflow
(
FamilyID,
PersonID
)
select
1012,
1
union
select
1013,
1
union
select
1014,
1
union
select
1015,
2
union
select
14774,
3
union
select
1019,
5
I understand that you need some sort of a complete list of matches within groups, but honestly, it would be much better if you would explain the business context, using plain English, in the first place.
The following query seems to produce your sample result:
with cte as (
select a.FamilyID, a.PersonID, a.PersonID as [GroupId] from #TempStackOverflow a
union all
select b.PersonID, b.FamilyID, b.PersonID from #TempStackOverflow b
)
select distinct c.FamilyID, s.PersonID
from cte c
inner join cte s on s.GroupId = c.GroupId
where c.FamilyID != s.PersonID;
Here is the simplest version I can come up with that groups the items by PersonId, as you do above. Obviously if you don't want that, then you can remove the outer query.
SELECT FamilyId,
PersonID
FROM (
SELECT FamilyId, PersonId, PersonID as SortBy
FROM #TempStackOverflow t1
UNION
SELECT PersonId, FamilyId, PersonId as SortBy
FROM #TempStackOverflow t1
UNION
SELECT t1.FamilyID, t2.FamilyID, t1.PersonID as SortBy
FROM #TempStackOverflow t1
FULL OUTER JOIN #TempStackOverflow t2
ON t1.PersonID = t2.PersonID
WHERE t1.FamilyID != t2.FamilyID
) as Src
ORDER BY SortBy

Updating multiple row with random data from another table?

Combining some examples, I came up with the following query (fields and table names have been anonymised soI hope I didn't insert typos).
UPDATE destinationTable
SET destinationField = t2.value
FROM destinationTable t1
CROSS APPLY (
SELECT TOP 1 'SomeRequiredPrefix ' + sourceField as value
FROM #sourceTable
WHERE sourceField <> ''
ORDER BY NEWID()
) t2
Problem
Currently, all records get the same value into destinationField , value needs to be random and different. I'm probably missing something here.
Here's a possible solution. Using CTE's assign row numbers to both tables based on random order. Join the tables together using that rownumber and update the rows accordingly.
;WITH
dt AS
(SELECT *, ROW_NUMBER() OVER (ORDER BY NEWID()) AS RowNum
FROM dbo.destinationtable),
st AS
(SELECT *, ROW_NUMBER() OVER (ORDER BY NEWID()) AS RowNum
FROM dbo.#sourcetable)
UPDATE dt
SET dt.destinationfield = 'SomeRequiredPrefix ' + st.sourcefield
FROM dt
JOIN st ON dt.RowNum = st.RowNum
UPDATED SOLUTION
I used CROSS JOIN to get all possibilities since you have less rows in source table. Then assign random rownumbers and only take 1 row for each destination field.
;WITH cte
AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY destinationfield ORDER BY NEWID()) AS Rownum
FROM destinationtable
CROSS JOIN #sourcetable
WHERE sourcefield <> ''
)
UPDATE cte
SET cte.destinationfield = 'SomeRequiredPrefix ' + sourcefield
WHERE cte.Rownum = 1
SELECT * FROM dbo.destinationtable

SQL Recursive CTE - Recursive member of a common table expression 'CTE' has multiple recursive references

I need to be able to "look back" during the execution of a CTE. Using the sample data below I need an additional column returned which represents the balance as of the BalanceDate column. So rowid 6 would return 30 because that is the balance as of 3/6/2015. Rowid 9 would return 80 because that is the last record closest to 9/30/2015. When I try to use the CTE as a derived table I receive the error
Msg 253, Level 16, State 1, Procedure
Recursive member of a common table expression 'CTE' has multiple recursive references.
--Sample data
CREATE TABLE #TEMP
(RowID int null,
TranDate date null,
Amount int null,
BalanceDate date null);
INSERT INTO #TEMP (RowID,TranDate, Amount, BalanceDate)
SELECT 1,'1/15/2015',10,null
UNION ALL
SELECT 2,'2/18/2015',10,null
UNION ALL
SELECT 3,'3/6/2015',10,null
UNION ALL
SELECT 4,'6/1/2015',10,null
UNION ALL
SELECT 5,'6/18/2015',10,null
UNION ALL
SELECT 6,'7/31/2015',10,'3/6/2015'
UNION ALL
SELECT 7,'8/2/2015',10,null
UNION ALL
SELECT 8,'9/13/2015',10,null
UNION ALL
SELECT 9,'11/15/2015',10,'9/30/2015';
with CTE
as
( SELECT RowID, TranDate, Amount, Balance=Amount, BalanceDate FROM #TEMP WHERE RowID = 1
UNION ALL
SELECT #TEMP.RowID,#TEMP.TranDate, #TEMP.Amount, Balance = #TEMP.Amount + CTE.Balance, #TEMP.BalanceDate
FROM #TEMP
INNER JOIN CTE on #Temp.RowID = CTE.RowID + 1
)
SELECT * FROM CTE;
I generally avoid recursive CTEs unless there's a reason you need to use them (for example, to find parents and children). Rather than using a recursive CTE, you can do something like this to get what you want:
EDIT:
If there are ever negative amounts, this would produce the correct result.
SELECT RowID, T1.TranDate, Amount
, (SELECT SUM(Amount) FROM #TEMP WHERE TranDate <= T1.TranDate) Balance
, BalanceDate
, (SELECT SUM(Amount) FROM #TEMP WHERE TranDate <= T1.BalanceDate) BalanceToDate
FROM #TEMP T1
Edited out other queries that were here before to make this less messy.
Using original recursive CTE:
; WITH CTE AS
(
SELECT RowID, TranDate, Amount, Balance=Amount, BalanceDate
FROM TEMP
WHERE RowID = 1
UNION ALL
SELECT TEMP.RowID,TEMP.TranDate, TEMP.Amount, Balance = TEMP.Amount + CTE.Balance, TEMP.BalanceDate
FROM TEMP
JOIN CTE on Temp.RowID = CTE.RowID + 1
)
SELECT C.RowID, C.TranDate, C.Amount, C.Balance, C.BalanceDate
, (SELECT SUM(Amount) FROM CTE WHERE TranDate <= C.BalanceDate) BalanceToDate
FROM CTE C

TSQL GROUP BY in recursive CTE

Is there a workaround to use GROUP BY inside a looping CTE or there is a workaround?
I need to group resultset of a CTE table and use it in another loop with the same CTE, but i get following error:
GROUP BY, HAVING, or aggregate functions are not allowed in the
recursive part of a recursive common table expression 'cte'.
Here's the query:
WITH cte
AS
(
SELECT
id,
dailyconsumption,
stock/dailyconsumption as cutoff
FROM items
WHERE father IS NULL
UNION ALL
SELECT
i.id,
SUM(father.dailyconsumption*i.num),
MAX(stock)/SUM(father.dailyconsumption*i.num)
FROM cte father
JOIN items i ON father.id=i.father
group by i.id
)
SELECT id, MIN(cutoff)
FROM cte
GROUP BY id
SQL-Fiddle (with sample data)
EDIT... this is the logical problem
I have a set of end-user items (father=NULL) and other sub-items made by a number of other items (field father and field num populated).
I got the dailyconsumption just for the end-user items (I start my cte with "WHERE father IS NULL"), and sub-items's dailyconsumption are calculate by SUM(father.dailyconsumption *item.num).
WITH cte AS(
SELECT
id,
dailyconsumption,
stock/dailyconsumption as cutoff
FROM items
WHERE father IS NULL
UNION ALL
SELECT
i.id,
father.dailyconsumption*i.num
0
FROM cte father
JOIN items i ON father.id=i.father
)
SELECT id, SUM(dailyconsumption)
FROM cte
GROUP BY id
http://sqlfiddle.com/#!3/f4f2a/95
With this valid query I'm going to have all dailyconsumption populated for all items (end-user and sub-items). Please mind that father-son relationship can be more than 1 level deep.
Now i need to calculate the cutoff (for how many days my stock is enought).
For end-use it is very easy and already calculated in first CTE: stock/dailyconsumption.
For sub-items it is a little more complicated:
subitem.stock/subitem.dailyconsumption + MIN(father.cutoff)
where MIN(father.cutoff) is the minimun cutoff from all fathers of this subitem.
This is because i need another group by.
May I need another CTE to loop in the same father-son relationship?
Thank you for your attention and sorry for my English.
;WITH cte AS
(
SELECT id, father,
dailyconsumption,
(stock / dailyconsumption) AS cutoff,
0 AS [Level]
FROM items
WHERE father IS NULL
UNION ALL
SELECT i.id, i.father,
c.dailyconsumption * i.num,
i.stock / (c.dailyconsumption * i.num),
[Level] + 1
FROM cte c JOIN items i ON c.id = i.father
)
SELECT c.id, c.dailyconsumption, c.cutoff AS subItemsCutoff,
MIN(ct.cutoff) OVER(PARTITION BY ct.[Level]) AS fatherCutoff,
(c.cutoff + ISNULL(MIN(ct.cutoff) OVER(PARTITION BY ct.[Level]), 0)) AS Cutoff
FROM cte c LEFT JOIN cte ct ON c.father = ct.id
Demo on SQLFiddle
I recommend using a variable table instead. Declare the table and then insert those records into it. You would need to figure out a way to loop through it on the second insert into command. I got this to get you started:
DECLARE #staging TABLE
(
id INT
,dailyconsumption FLOAT
,cutoff FLOAT
)
INSERT INTO #staging
SELECT
id,
dailyconsumption,
stock/dailyconsumption as cutoff
FROM
items
WHERE
father IS NULL
INSERT INTO #staging
SELECT
i.id,
SUM(father.dailyconsumption*i.num),
MAX(stock)/SUM(father.dailyconsumption*i.num)
FROM
#staging father
JOIN items i
ON father.id=i.father
group by
i.id
SELECT
id
,MIN(cutoff)
FROM
#staging
GROUP BY
id

Resources