TLDNR: how do I update a table depending on a column?
Problem situation: the current column SortingNumber is full of bad data.
Solution: reassign new values to SortingNumber based on their Parent. The SortingNumber shall be 1 for the lowest current SortingNumber (by Parent) and be incremented by 1 for every subsequent dataset.
Current data: Desired result:
ID | Parent | SortingNumber >> ID | Parent | SortingNumber
1 | 1 | 3 >> 1 | 1 | 1
2 | 1 | 4 >> 2 | 1 | 2
3 | 1 | 5 >> 3 | 1 | 3
4 | 2 | 8 >> 4 | 2 | 1
5 | 2 | 10 >> 5 | 2 | 2
6 | 2 | 13 >> 6 | 2 | 3
Actual problem: I'm having trouble figuring out how to update the datasets corresponding to their parents.
My script currently updates all the values incrementally and doesn't group it by Parent.
My current solution:
DECLARE #lastSN INTEGER = 0;
WITH toUpdate AS
(
SELECT
T1.*,
-- "calculate" the sorting number from the row above
LAG(T1.SortingNumber + 1, 1, 1) OVER (ORDER BY T1.SortingNumber) AS [newSortNumber]
FROM
T AS T1
INNER JOIN
T AS T2 ON T1.Parent = T2.ID
)
UPDATE toUpdate
SET
#lastSN = CASE WHEN [newSortNumber] = 1 AND #lastSN = 0 THEN 1 ELSE #lastSN + 1 END,
toUpdate.SortingNumber = #lastSN
;
Result is:
ID | Parent | SortingNumber
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 4
5 | 2 | 5
6 | 2 | 6
I guess my question could be phrased as: how do I update datasets depending on the Parent column?
PS: here is the CREATE statement if you wish to try it out yourself
CREATE TABLE T
(
ID INT IDENTITY(1,1) PRIMARY KEY,
Parent INT FOREIGN KEY REFERENCES T(ID),
SortingNumber INT
);
GO
INSERT INTO T (Parent, SortingNumber)
VALUES (1, 3), (1, 4), (1, 5), (2, 8), (2, 10), (2, 13);
You can employ row_number to achieve this using partitioning by Parent and ordering by SortingNumber.
WITH cte AS (
SELECT
* ,
ROW_NUMBER() OVER (PARTITION BY Parent ORDER BY SortingNumber) AS NewSortingNumber
FROM T
)
UPDATE cte
SET SortingNumber = NewSortingNumber
A window function creates small tables within the table using Parent, so we have two subsets, one for Parent = 1 and the another for Parent = 2. Then it uses ORDER BY to know from which row it should start count (starting from 1). The first row is for Parent = 1 and ID =1 so it gets 1, the next row gets 2 etc. Please look here for more details.
As an alternative you can just rank, ordering by patient then ID:
UPDATE tt
SET sortingnumber = drank from (select *, DENSE_RANK() OVER (order by Parent, ID) as drank from tt ) a where tt.ID=a.id and tt.parent=a.parent
select * from tt
Related
Let's say I have table below:
ID | Name | Active | ParentID
1 | Foo1 | 1 | 0
2 | Foo2 | 1 | 1
3 | Foo3 | 1 | 2
4 | Foo4 | 1 | 3
5 | Foo5 | 1 | 3
6 | Foo6 | 0 | 5
7 | Foo7 | 1 | 2
7 | Foo7 | 1 | 6
8 | Foo8 | 1 | 7
9 | Foo9 | 1 | 5
(I have indeed duplicate ID's, on which I expressed my thoughts but to no result)
As you can see, once child can have multiple parents. ID's with ParentID 0 have no parent. I need to select all ID's that are active and do not have an inactive parent above them, however high in the tree that might be.
So with the data set above, my result would be:
ID | Name |
1 | Foo1 |
2 | Foo2 |
3 | Foo3 |
4 | Foo4 |
5 | Foo5 |
9 | Foo9 |
ID 6 got removed because it was Inactive
ID 7 got removed because one of its parents (6) is inactive
ID 8 got removed because a parent (6) of its parent (7) is inactive
ID 9 is fine because its parent (5) is active and so are 5 his parents etc
I attempted this with a subquery in the where
SELECT *
FROM table
WHERE ID not in (SELECT ID FROM table where Active = 0)
But that only solves it for the current record.
I've also tried a typical self-join as used for employee/manager, but that only goes one layer deep, while here I also need to check for the parent of the parent etc
Any suggestions/ideas?
One method would be to use an rCTE to work through the hierachy, with a column that retains the initial ID. Then you can use an EXISTS to ensure there are no rows with a value of 0 for Active:
WITH rCTE AS(
SELECT ID,
Name,
Active,
ParentID,
ID AS InitialID
FROM dbo.YourTable YT
UNION ALL
SELECT YT.ID,
YT.Name,
YT.Active,
YT.ParentID,
r.InitialID
FROM rCTE r
JOIN dbo.YourTable YT ON r.ParentID = YT.ID)
SELECT *
FROM dbo.YourTable YT
WHERE NOT EXISTS (SELECT 1
FROM rCTE r
WHERE r.InitialID = YT.ID
AND r.Active = 0);
I would use a recursive CTE to identify IDs where the chain is continuous, using both conditional and unconditional increment by 1 as follows:
With A As
(Select ID, [Name], Active, ParentID, 0 As NUM_1, 0 As NUM_2
From Tbl Where ParentID=0
Union All
Select Tbl.ID, Tbl.[Name], Tbl.Active, Tbl.ParentID,
NUM_1 + 1 As NUM_1,
NUM_2 + IIF(Tbl.Active=1,1,0) As NUM_2
From Tbl Inner Join A On (Tbl.ParentID=A.ID)
)
Select ID, [Name]
From A
Where ID Not In (Select ID From A Where NUM_1<>NUM_2)
Order by ID
Result:
ID
Name
1
Foo1
2
Foo2
3
Foo3
4
Foo4
5
Foo5
9
Foo9
db<>fiddle
I have a table that contains tree-like data (hierarchic design). Here is a small sample:
+----+----------+-----------+-------+----------+---------+
| ID | ParentID | Hierarchy | Order | FullPath | Project |
+----+----------+-----------+-------+----------+---------+
| 1 | null | 1 | 1 | 1 | 1 |
| 2 | null | 2 | 2 | 2 | 1 |
| 3 | 1 | 1.1 | 1 | 1-3 | 1 |
| 4 | 1 | 1.2 | 2 | 1-4 | 1 |
| 5 | 4 | 1.2.1 | 1 | 1-4-5 | 1 |
| 6 | 2 | 2.1 | 1 | 2-6 | 1 |
| 7 | null | 3 | 1 | 1 | 2 |
+----+----------+-----------+-------+----------+---------+
Project indicates which project owns the hierarchic dataset
ParentID is the ID of the parent node, it has a foreign key on ID.
Order is the rank of the element in one branch. For example, IDs 1, 2 and 7 are on the same node while 3 and 4 are in another.
FullPath shows the order using the ID (it's for system use and performance reasons).
Hierarchy is the column displayed to the user, which displays the hierarchy to the UI. It auto calculates after every insert, update and delete, and it's the one I'm having issues.
I created a procedure for deletion elements in the table. It receives as input the ID of the element to delete and deletes it, along with it's children if any. Then, it recalculates the FullPath and the Order Column .That works.
Problems is when I try to update the Hierarchy column. I use this procedure:
SELECT T.ID,
T.ParentID,
CASE WHEN T.ParentID IS NOT NULL THEN
CONCAT(T1.Hierarchy, '.', CAST(T.Order AS NVARCHAR(255)))
ELSE
CAST(T.Order AS NVARCHAR(255))
END AS Hierarchy
INTO #tmp
FROM t_HierarchyTable T
LEFT JOIN t_HierarchyTable T1
ON T1.ID = T.ParentID
WHERE Project = #Project --Variable to only update the current project for performance
ORDER BY T.FullPath
--Update the table with ID as key on tmp table
This fails when I delete items that have lower order than others and they have children.
For example, if I delete the item 3, item 4 Hierachy will be corrected (1.1), BUT its child won't (it will stay at 1.2.1, while it should be 1.1.1). I added the order by to make sure parents where updated first, but no change.
What is my error, I really don't know how to fix this.
I managed to update the hierarchy with a CTE. Since I have the order, I can append it to Hierarchy, based on the previous branch (parent) who is already updated.
;WITH CODES(ID, sCode, iLevel) AS
(
SELECT
T.[ID] AS [ID],
CONVERT(VARCHAR(8000), T.[Order]) AS [Hierarchy],
1 AS [iLevel]
FROM
[dbo].[data] AS T
WHERE
T.[ParentID] IS NULL
UNION ALL
SELECT
T.[ID] AS [ID],
P.[Hierarchy] + IIF(RIGHT(P.[Hierarchy], 1) <> '-', '-', '') + CONVERT(VARCHAR(8000), T.[Order]) AS [Hierarchy],
P.[iLevel] + 1 AS [iLevel]
FROM
[dbo].[data] AS T
INNER JOIN CODES AS P ON
P.[ID] = T.[ParentID]
WHERE
P.[iLevel] < 100
)
SELECT
[ID], [Hierarchy], [iLevel]
INTO
#CODES
FROM
CODES
I have a test table with an ActionId column. The column contains an increasing and random number of rows with values of 1 to 5 and then it starts again with another subset of values from 1 to 5. The data can have one or more subsets like that.
I am interested in rows which contain ActionId of values 4 or 5 but only the last one in each subset. So in this sample, I want to return rows 7 and 11. Row id 7 because 5 is the last value before the value goes down and row id 11 because 4 is the last value before the value goes down again. For the last subset, the value doesn't need to go down again. The value 4 or 5 could be in the last row.
I can program this in a procedural language but I can't think of set based SQL solution.
CREATE TABLE test (
id [int] IDENTITY(1,1)
,ActionId INT)
INSERT INTO [test] (ActionId ) VALUES
(1), (2), (3), (3), (4), (4), (5), (3), (3), (3), (4), (1),(2)
select * from test
http://sqlfiddle.com/#!18/4ffe71/3
The solution I came up with involves a simple correlated subquery and a common table expression:
;with cte as
(
select id,
ActionId,
isnull((
select top 1 ActionId
from test as t1
where t0.id < t1.id
order by t1.id
), 0) as nextActionId
from test As t0
)
select id, ActionId
from cte
where actionId IN(4,5)
and actionId > nextActionId
The subquery gets the next actionId for each row, based on the order of the id column. The isnull is there for the last row - to return 0 instead of null.
Then, all you have to do is query the cte where the actionId is either 4 or 5 and it is larger than the next action id.
If I guess correct, You need all values where the next row's value is less that the current value. If I am correct, You can use self join for your purpose. The following script will give you the desired output-
DECLARE #test TABLE
(
id [int] IDENTITY(1,1),
Actionid INT
)
INSERT INTO #test (Actionid )
VALUES
(1), (2), (3), (3), (4), (4), (5), (3), (3), (3), (4), (1),(2)
SELECT A.*
FROM #test A LEFT JOIN #test B ON A.id = B.id-1
WHERE B.Actionid < A.Actionid
The output is-
id Actionid
7 5
11 4
If you also need the last row's value without considering any condition, just change the script with below. This will include the last value 2 in the output.
SELECT A.*
FROM #test A LEFT JOIN #test B ON A.id = B.id-1
WHERE B.Actionid < A.Actionid
OR B.Actionid IS NULL
A recursive CTE can help you here:
--Your mockup table
DECLARE #test TABLE
(
id [int] IDENTITY(1,1),
Actionid INT
)
INSERT INTO #test (Actionid )
VALUES (1), (2), (3), (3), (4), (4), (5), (3), (3), (3), (4), (1),(2);
--the query
WITH recCTE AS
(
SELECT id
,Actionid
,1 AS GroupKey
,1 AS GroupStep
FROM #test t WHERE id=1 --the IDENTITY is the sorting key obviously and will start with a 1 in this test case.
UNION ALL
SELECT t.id
,t.Actionid
,CASE WHEN t.Actionid<=r.Actionid THEN r.GroupKey+1 ELSE r.GroupKey END
,CASE WHEN t.Actionid<=r.Actionid THEN 1 ELSE r.GroupStep+1 END
FROM #test t
INNER JOIN recCTE r ON t.id=r.id+1
)
SELECT *
FROM recCTE;
The idea in short:
We start with the first row and iterate through the set row-by-row. Each row we test, if the ActionId is not increasing and set corresponding values to the GroupKey and the GroupStep.
The result
+----+----------+----------+-----------+
| id | Actionid | GroupKey | GroupStep |
+----+----------+----------+-----------+
| 1 | 1 | 1 | 1 |
+----+----------+----------+-----------+
| 2 | 2 | 1 | 2 |
+----+----------+----------+-----------+
| 3 | 3 | 1 | 3 |
+----+----------+----------+-----------+
| 4 | 3 | 2 | 1 |
+----+----------+----------+-----------+
| 5 | 4 | 2 | 2 |
+----+----------+----------+-----------+
| 6 | 4 | 3 | 1 |
+----+----------+----------+-----------+
| 7 | 5 | 3 | 2 |
+----+----------+----------+-----------+
| 8 | 3 | 4 | 1 |
+----+----------+----------+-----------+
| 9 | 3 | 5 | 1 |
+----+----------+----------+-----------+
| 10 | 3 | 6 | 1 |
+----+----------+----------+-----------+
| 11 | 4 | 6 | 2 |
+----+----------+----------+-----------+
| 12 | 1 | 7 | 1 |
+----+----------+----------+-----------+
| 13 | 2 | 7 | 2 |
+----+----------+----------+-----------+
Solving your issue
We can proceed from there by changing the final SELECT to this
SELECT TOP 1 WITH TIES *
FROM recCTE
ORDER BY ROW_NUMBER() OVER(PARTITION BY GroupKey ORDER BY GroupStep DESC);
The result shows the last entry per sub-set
+----+----------+----------+-----------+
| id | Actionid | GroupKey | GroupStep |
+----+----------+----------+-----------+
| 3 | 3 | 1 | 3 |
+----+----------+----------+-----------+
| 5 | 4 | 2 | 2 |
+----+----------+----------+-----------+
| 8 | 3 | 4 | 1 |
+----+----------+----------+-----------+
| 9 | 3 | 5 | 1 |
+----+----------+----------+-----------+
| 11 | 4 | 6 | 2 |
+----+----------+----------+-----------+
| 7 | 5 | 3 | 2 |
+----+----------+----------+-----------+
| 13 | 2 | 7 | 2 |
+----+----------+----------+-----------+
You can filter to the sub-sets where the last entry is a 4 or a 5. In this case I see the rows 7 and 11 but also the row 5. Might be I did not get the logic correctly...
This is the query I came up with:
WITH cte
AS
(SELECT id, Actionid, ROW_NUMBER() OVER (ORDER BY id) rn FROM test)
SELECT
prev.id
,prev.Actionid prevActionId
,cur.Actionid curActionId
FROM cte cur
JOIN cte prev
ON prev.rn = cur.rn - 1
WHERE
prev.Actionid > cur.Actionid
AND prev.Actionid IN (4, 5)
I cannot summarize numbers in the table (SQL-Server) after pivoting and I will be very grateful for your advice.
Better if I explain the problem on the example:
Existing table:
+-------+-----------+-----------+-------------------+
| # | $$$$$ | Fire | Water |
+-------+-----------+-----------+-------------------+
| 1 | 5 | 1 | 5 |
| 1 | 4 | 1 | 5 |
| 1 | 10 | 1 | 5 |
| 2 | 3 | 3 | 8 |
| 2 | 4 | 3 | 8 |
+-------+-----------+-----------+-------------------+
Desired output:
+-------+-----------+-----------+-------------------+
| # | $$$$$ | Fire | Water |
+-------+-----------+-----------+-------------------+
| 1 | 19 | 1 | 5 |
| 2 | 7 | 3 | 8 |
+-------+-----------+-----------+-------------------+
I tend to believe that I already tried all the solutions I found with summarizing and grouping by, but it was not solved, so I rely on you. Thanks in advance. The code I used to create the table:
WITH Enerc AS
(
SELECT
a1.[#],
a1.[$$$$$],
a2.[cause_of_loss]
FROM
data1 AS a1
LEFT JOIN
data2 AS a2 ON a1.[id] = a2.[id]
)
SELECT *
FROM Enerc
PIVOT
(SUM(gross_claim) FOR [cause_of_loss] IN ([Fire], [Water])) AS PivotTable;
No need to pivot. Your desired result should be got by grouping and using SUM:
SELECT
a1.[#],
SUM(a1.[$$$$$]),
a1.[Fire]
a1.[Water]
from data1 as a1
group by a1.[#], a1.[Fire], a1.[Water]
Let me show an example:
DECLARE #Hello TABLE
(
[#] INT,
[$$$$$] INT,
[Fire] INT,
[Water] INT
)
INSERT INTO #Hello
(
#,
[$$$$$],
Fire,
Water
)
VALUES
( 1, -- # - int
5, -- $$$$$ - int
1, -- Fire - int
5 -- Water - int
)
, (1, 4, 1, 5)
, (1, 10, 1, 5)
, (2, 3, 3, 8)
, (2, 4, 3, 8)
SELECT
h.#,
SUM(h.[$$$$$]),
h.Fire,
h.Water
FROM #Hello h
GROUP BY h.#, h.Fire, h.Water
try group by after the pivot.
With Enerc as
(SELECT
a1.[#],
a1.[$$$$$],
a2.[cause_of_loss]
from data1 as a1
left join data2 as a2
on a1.[id] = a2.[id]
)
select *
into tmp
from Enerc
PIVOT
(sum(gross_claim)
FOR [cause_of_loss] in (
[Fire], [Water]))
as PivotTable
select [#], sum([$$$$$])as [$$$$$], Fire, Water
from #tmp
group by [#],Fire, Water
EDIT: in case of permission denied:
With Enerc as
(SELECT
a1.[#],
a1.[$$$$$],
a2.[cause_of_loss]
from data1 as a1
left join data2 as a2
on a1.[id] = a2.[id]
),phase2 as(
select *
from Enerc
PIVOT
(sum(gross_claim)
FOR [cause_of_loss] in (
[Fire], [Water]))
as PivotTable)
select [#], sum([$$$$$])as [$$$$$], Fire, Water
from phase2
group by [#],Fire, Water
the table is very simple,pid means the parent id,and cid means the child id.And there may be more than one trees in the table.So my question is:
knowing several cid,how can we get the root ancestors
here is an example
pid cid
1 2
2 3
3 4
5 6
6 7
7 8
given cid = 4 or cid = 8,I want to get their root ancestors whose pid is 1 ro 5
finally,I'm using an oracle 10g
In a a database environment the foreign keys at the top level will most likely be nulls like so:
| pid | cid |
|------*------|
| null | 2 |
| 2 | 3 |
| 3 | 4 |
| null | 6 |
| 6 | 7 |
| 7 | 8 |
So I'd recommend using something like:
select connect_by_root(t1.cid) as startpoint,
t1.cid as rootnode
from your_table t1
where connect_by_isleaf = 1
start with t1.cid in (8, 4)
connect by prior t1.pid = t1.cid;
fiddle
select
t1.cid,
connect_by_root(t1.pid) as root
from
your_table t1
left join your_table t2
on t2.cid = t1.pid
where t1.cid in (4, 8)
start with t2.cid is null
connect by t1.pid = prior t1.cid
fiddle