T-SQL - Incremental update using subquery - sql-server

I'm trying to write a incremental update statement using SQL Server 2012.
Current Data:
RecNo Budget_ID Item_Code Revision
---------------------------------------
1 16 xxx 2
2 16 xxx NULL
3 16 xxx NULL
12 19 yyy 3
13 19 yyy NULL
14 19 yyy NULL
15 19 yyy NULL
Expected result:
RecNo Budget_ID Item_Code Revision
---------------------------------------
1 16 xxx 2
2 16 xxx 1
3 16 xxx 0
12 19 yyy 3
13 19 yyy 2
14 19 yyy 1
15 19 yyy 0
However with following approach, I ended up with the result set as below.
UPDATE a
SET a.Revision = (SELECT MIN(b.Revision)
FROM [dbo].[foo] b
WHERE b.item_code = a.item_code
AND b.budget_id = a.budget_id
GROUP BY b.item_code ) -1
FROM [dbo].[foo] a
WHERE a.Revision is NULL
Result:
RecNo Budget_ID Item_Code Revision
---------------------------------------
1 16 xxx 2
2 16 xxx 1
3 16 xxx 1
12 19 yyy 3
13 19 yyy 2
14 19 yyy 2
15 19 yyy 2
Can anyone help me to get this right?
Thanks in advance!

Try this:
;with cte as
(select *, row_number() over (partition by budget_id order by rec_no desc) rn from dbo.foo)
update cte
set revision = rn - 1
Basically, since the revision value seems to be decreasing with increase in rec_no, we simply use the row_number() function to get row number of each record within the subset of all records with a particular budget_id, sorted in descending order of rec_no. Since the least possible value of row_number() will be 1, we subtract 1 so that the last record in the partition will have revision set to 0 instead 1.
You may test the code here

I found this example from this link https://stackoverflow.com/a/13629639/1692632
First you select MIN value to some variable and then you can update table by decreasing variable at same time.
DECLARE #table TABLE (ID INT, SomeData VARCHAR(10))
INSERT INTO #table (SomeData, ID) SELECT 'abc', 6 ;
INSERT INTO #table (SomeData) SELECT 'def' ;
INSERT INTO #table (SomeData) SELECT 'ghi' ;
INSERT INTO #table (SomeData) SELECT 'jkl' ;
INSERT INTO #table (SomeData) SELECT 'mno' ;
INSERT INTO #table (SomeData) SELECT 'prs' ;
DECLARE #i INT = (SELECT ISNULL(MIN(ID),0) FROM #table)
UPDATE #table
SET ID = #i, #i = #i - 1
WHERE ID IS NULL
SELECT *
FROM #table

I'm not sure if this will do the trick but you can try with
Update top(1) a
SET a.Revision = (Select MIN(b.Revision)
FROM [dbo].[foo] b where b.item_code = a.item_code and b.budget_id = a.budget_id
group by b.item_code ) -1
FROM [dbo].[foo] a
WHERE a.Revision is NULL
and repeat until there's no changes left

Update Data
set Revision = x.Revision
from
(select RecNo, Budget_ID, Item_Code, case when Revision is null then ROW_NUMBER() over(partition by Budget_ID order by RecNo desc) - 1 else Revision end Revision
from Data
) x
where x.RecNo = data.RecNo
You basically use ROW_NUMBER() to count backwards for each Budget_ID, and use that row number minus 1 where Revision is null. This is basically the same as Shree's answer, just without the CTE.

Related

SQL Server script not working as expected

I have this little script that shall return the first number in a column of type int which is not used yet.
SELECT t1.plu + 1 AS plu
FROM tovary t1
WHERE NOT EXISTS (SELECT 1 FROM tovary t2 WHERE t2.plu = t1.plu + 1)
AND t1.plu > 0;
this returns the unused numbers like
3
11
22
27
...
The problem is, that when I make a simple select like
SELECT plu
FROM tovary
WHERE plu > 0
ORDER BY plu ASC;
the results are
1
2
10
20
...
Why the first script isn't returning some of free numbers like 4, 5, 6 and so on?
Compiling a formal answer from the comments.
Credit to Larnu:
It seems what the OP really needs here is an (inline) Numbers/Tally (table) which they can then use a NOT EXISTS against their table.
Sample data
create table tovary
(
plu int
);
insert into tovary (plu) values
(1),
(2),
(10),
(20);
Solution
Isolating the tally table in a common table expression First1000 to produce the numbers 1 to 1000. The amount of generated numbers can be scaled up as needed.
with First1000(n) as
(
select row_number() over(order by (select null))
from ( values (0),(0),(0),(0),(0),(0),(0),(0),(0),(0) ) a(n) -- 10^1
cross join ( values (0),(0),(0),(0),(0),(0),(0),(0),(0),(0) ) b(n) -- 10^2
cross join ( values (0),(0),(0),(0),(0),(0),(0),(0),(0),(0) ) c(n) -- 10^3
)
select top 20 f.n as Missing
from First1000 f
where not exists ( select 'x'
from tovary
where plu = f.n);
Using top 20 in the query above to limit the output. This gives:
Missing
-------
3
4
5
6
7
8
9
11
12
13
14
15
16
17
18
19
21
22
23
24

Grouping between two datetimes

I have a bunch of production orders and I'm trying to group by within a datetime range, then count the quantity within that range. For example, I want to group from 2230 to 2230 each day.
PT.ActualFinish is datetime (eg. if PT.ActualFinish is 2020-05-25 23:52:30 then it would be counted on the 26th May instead of the 25th)
Currently it's grouped by date (midnight to midnight) as opposed to the desired 2230 to 2230.
GROUP BY CAST(PT.ActualFinish AS DATE)
I've been trying to reconcile some DATEADD with the GROUP without success. Is it possible?
Just add 1.5 hours (90 minutes) and then extract the date:
group by convert(date, dateadd(minute, 90, pt.acctualfinish))
For this kind of thing you can use a function I created called NGroupRangeAB (code below) which can be used to create groups over values with an upper and lower bound.
Note that this:
SELECT f.*
FROM core.NGroupRangeAB(0,1440,12) AS f
ORDER BY f.RN;
Returns:
RN GroupNumber Low High
--- ------------ ------ -------
0 1 0 120
1 2 121 240
2 3 241 360
3 4 361 480
4 5 481 600
5 6 601 720
6 7 721 840
7 8 841 960
8 9 961 1080
9 10 1081 1200
10 11 1201 1320
11 12 1321 1440
This:
SELECT
f.GroupNumber,
L = DATEADD(MINUTE,f.[Low]-SIGN(f.[Low]),CAST('00:00:00.0000000' AS TIME)),
H = DATEADD(MINUTE,f.[High]-1,CAST('00:00:00.0000000' AS TIME))
FROM core.NGroupRangeAB(0,1440,12) AS f
ORDER BY f.RN;
Returns:
GroupNumber L H
------------- ---------------- ----------------
1 00:00:00.0000000 01:59:00.0000000
2 02:00:00.0000000 03:59:00.0000000
3 04:00:00.0000000 05:59:00.0000000
4 06:00:00.0000000 07:59:00.0000000
5 08:00:00.0000000 09:59:00.0000000
6 10:00:00.0000000 11:59:00.0000000
7 12:00:00.0000000 13:59:00.0000000
8 14:00:00.0000000 15:59:00.0000000
9 16:00:00.0000000 17:59:00.0000000
10 18:00:00.0000000 19:59:00.0000000
11 20:00:00.0000000 21:59:00.0000000
12 22:00:00.0000000 23:59:00.0000000
Now for a real-life example that may help you:
-- Sample Date
DECLARE #table TABLE (tm TIME);
INSERT #table VALUES ('00:15'),('11:20'),('21:44'),('09:50'),('02:15'),('02:25'),
('02:31'),('23:31'),('23:54');
-- Solution:
SELECT
GroupNbr = f.GroupNumber,
TimeLow = f2.L,
TimeHigh = f2.H,
Total = COUNT(t.tm)
FROM core.NGroupRangeAB(0,1440,12) AS f
CROSS APPLY (VALUES(
DATEADD(MINUTE,f.[Low]-SIGN(f.[Low]),CAST('00:00:00.0000000' AS TIME)),
DATEADD(MINUTE,f.[High]-1,CAST('00:00:00.0000000' AS TIME)))) AS f2(L,H)
LEFT JOIN #table AS t
ON t.tm BETWEEN f2.L AND f2.H
GROUP BY f.GroupNumber, f2.L, f2.H;
Returns:
GroupNbr TimeLow TimeHigh Total
-------------------- ---------------- ---------------- -----------
1 00:00:00.0000000 01:59:00.0000000 1
2 02:00:00.0000000 03:59:00.0000000 3
3 04:00:00.0000000 05:59:00.0000000 0
4 06:00:00.0000000 07:59:00.0000000 0
5 08:00:00.0000000 09:59:00.0000000 1
6 10:00:00.0000000 11:59:00.0000000 1
7 12:00:00.0000000 13:59:00.0000000 0
8 14:00:00.0000000 15:59:00.0000000 0
9 16:00:00.0000000 17:59:00.0000000 0
10 18:00:00.0000000 19:59:00.0000000 0
11 20:00:00.0000000 21:59:00.0000000 1
12 22:00:00.0000000 23:59:00.0000000 2
Note that an inner join will eliminate the 0-count rows.
CREATE FUNCTION core.NGroupRangeAB
(
#min BIGINT, -- Group Number Lower boundary
#max BIGINT, -- Group Number Upper boundary
#groups BIGINT -- Number of groups required
)
/*****************************************************************************************
[Purpose]:
Creates an auxilliary table that allows for grouping based on a given set of rows (#rows)
and requested number of "row groups" (#groups). core.NGroupRangeAB can be thought of as a
set-based, T-SQL version of Oracle's WIDTH_BUCKET, which:
"...lets you construct equiwidth histograms, in which the histogram range is divided into
intervals that have identical size. (Compare with NTILE, which creates equiheight
histograms.)" https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions214.htm
See usage examples for more details.
[Author]:
Alan Burstein
[Compatibility]:
SQL Server 2008+
[Syntax]:
--===== Autonomous
SELECT ng.*
FROM dbo.NGroupRangeAB(#rows,#groups) AS ng;
[Parameters]:
#rows = BIGINT; the number of rows to be "tiled" (have group number assigned to it)
#groups = BIGINT; requested number of tile groups (same as the parameter passed to NTILE)
[Returns]:
Inline Table Valued Function returns:
GroupNumber = BIGINT; a row number beginning with 1 and ending with #rows
Members = BIGINT; Number of possible distinct members in the group
Low = BIGINT; the lower-bound range
High = BIGINT; the Upper-bound range
[Dependencies]:
core.rangeAB (iTVF)
[Developer Notes]:
1. An inline derived tally table using a CTE or subquery WILL NOT WORK. NTally requires
a correctly indexed tally table named dbo.tally; if you have or choose to use a
permanent tally table with a different name or in a different schema make sure to
change the DDL for this function accordingly. The recomended number of rows is
1,000,000; below is the recomended DDL for dbo.tally. Note the "Beginning" and "End"
of tally code.To learn more about tally tables see:
http://www.sqlservercentral.com/articles/T-SQL/62867/
2. For best results a P.O.C. index should exists on the table that you are "tiling". For
more information about P.O.C. indexes see:
http://sqlmag.com/sql-server-2012/sql-server-2012-how-write-t-sql-window-functions-part-3
3. NGroupRangeAB is deterministic; for more about deterministic and nondeterministic functions
see https://msdn.microsoft.com/en-us/library/ms178091.aspx
[Examples]:
-----------------------------------------------------------------------------------------
--===== 1. Basic illustration of the relationship between core.NGroupRangeAB and NTILE.
-- Consider this query which assigns 3 "tile groups" to 10 rows:
DECLARE #rows BIGINT = 7, #tiles BIGINT = 3;
SELECT t.N, t.TileGroup
FROM ( SELECT r.RN, NTILE(#tiles) OVER (ORDER BY r.RN)
FROM core.rangeAB(1,#rows,1,1) AS r) AS t(N,TileGroup);
Results:
N TileGroup
--- ----------
1 1
2 1
3 1
4 2
5 2
6 3
7 3
To pivot these "equiheight histograms" into "equiwidth histograms" we could do this:
DECLARE #rows BIGINT = 7, #tiles BIGINT = 3;
SELECT TileGroup = t.TileGroup,
[Low] = MIN(t.N),
[High] = MAX(t.N),
Members = COUNT(*)
FROM ( SELECT r.RN, NTILE(#tiles) OVER (ORDER BY r.RN)
FROM core.rangeAB(1,#rows,1,1) AS r) AS t(N,TileGroup);
GROUP BY t.TileGroup;
Results:
TileGroup Low High Members
---------- ---- ----- -----------
1 1 3 3
2 4 5 2
3 6 7 2
This will return the same thing at a tiny fraction of the cost:
SELECT TileGroup = ng.GroupNumber,
[Low] = ng.[Low],
[High] = ng.[High],
Members = ng.Members
FROM core.NGroupRangeAB(1,#rows,#tiles) AS ng;
--===== 2.1. Divide 25 Rows into 3 groups
DECLARE #min BIGINT = 1, #max BIGINT = 25, #groups BIGINT = 4;
SELECT ng.GroupNumber, ng.Members, ng.low, ng.high
FROM core.NGroupRangeAB(#min,#max,#groups) AS ng;
--===== 2.2. Assign group membership to another table
DECLARE #min BIGINT = 1, #max BIGINT = 25, #groups BIGINT = 4;
SELECT
ng.GroupNumber, ng.low, ng.high, s.WidgetId, s.Price
FROM (VALUES('a',$12),('b',$22),('c',$9),('d',$2)) AS s(WidgetId,Price)
JOIN core.NGroupRangeAB(#min,#max,#groups) AS ng
ON s.Price BETWEEN ng.[Low] AND ng.[High]
ORDER BY ng.RN;
Results:
GroupNumber low high WidgetId Price
------------ ---- ----- --------- ---------------------
1 1 7 d 2.00
2 8 13 a 12.00
2 8 13 c 9.00
4 20 25 b 22.00
-----------------------------------------------------------------------------------------
[Revision History]:
Rev 00 - 20190128 - Initial Creation; Final Tuning - Alan Burstein
****************************************************************************************/
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT
RN = r.RN, -- Sort Key
GroupNumber = r.N2, -- Bucket (group) number
Members = g.S-ur.N+1, -- Count of members in this group
[Low] = r.RN*g.S+rc.N+ur.N, -- Lower boundary for the group (inclusive)
[High] = r.N2*g.S+rc.N -- Upper boundary for the group (inclusive)
FROM core.rangeAB(0,#groups-1,1,0) AS r -- Range Function
CROSS APPLY (VALUES((#max-#min)/#groups,(#max-#min)%#groups)) AS g(S,U) -- Size, Underflow
CROSS APPLY (VALUES(SIGN(SIGN(r.RN-g.U)-1)+1)) AS ur(N) -- get Underflow
CROSS APPLY (VALUES(#min+r.RN-(ur.N*(r.RN-g.U)))) AS rc(N); -- Running Count
GO

SQL Server find chains between two columns

I have a table like this:
from | to
-----+-----
23 | 24
24 | 25
25 | 27
27 | 30
45 | 46
46 | 47
50 | 52
53 | 60
I need a SQL Server query that detect chain's and return min (from) and max (to) in each chain (also chain's with one record):
from | to
-----+-----
23 | 30
45 | 47
50 | 52
53 | 60
Here's an approach using a recursive CTE.
CREATE TABLE #chainLinks(linkFrom INTEGER, linkTo INTEGER);
INSERT INTO #chainLinks VALUES (23,24);
INSERT INTO #chainLinks VALUES (24,25);
INSERT INTO #chainLinks VALUES (25,27);
INSERT INTO #chainLinks VALUES (27,30);
INSERT INTO #chainLinks VALUES (45,46);
INSERT INTO #chainLinks VALUES (46,47);
INSERT INTO #chainLinks VALUES (50,52);
INSERT INTO #chainLinks VALUES (53,60);
WITH reccte AS
(
/*Recursive Seed*/
SELECT linkFrom AS chainStart,
linkFrom,
linkTo,
0 as links
FROM #chainLinks as chainLinks
WHERE linkFrom NOT IN (SELECT DISTINCT linkTo FROM #chainLinks)
UNION ALL
/*Recursive Term*/
SELECT
reccte.chainStart,
chainLinks.linkFrom,
chainLinks.linkTo,
links + 1
FROM reccte
INNER JOIN #chainLinks as chainLinks ON reccte.linkTo = chainLinks.linkFrom
)
SELECT chainStart, linkTo AS chainEnd
FROM
(
SELECT chainStart, linkFrom, linkTo, links, ROW_NUMBER() OVER (PARTITION BY chainStart ORDER BY links DESC) AS rn
FROM reccte
)subrn
WHERE rn = 1;
A recursive CTE takes two parts
A recursive seed - This is the part above the UNION where we determine which records from our table begin the recursion. Here we want any linkFrom that isn't also a linkTo
A recusrive term - This is the part below the UNION where we join the cte called reccte back to the original table. This part of the CTE iterates over and over again until that join fails.
In here we are also tracking that links which is just a counter of the number of iterations we have gone through to get to that outputted record. We keep the highest number of links for each starting point chainStart.
Here is the working example: https://rextester.com/JWUW57837
If there are branches within the chains it become a little bit more tricky.
In the sample data below, there's a split on From=12.
So the result shows 2 chains starting from 14.
create table yourtable (
[From] int not null,
[To] int not null,
PRIMARY KEY ([From],[To])
)
GO
✓
insert into yourtable
([From],[To]) values
(2,3),(3,5),(5,4)
,(14,12),(12,15),(15,11),(11,10)
,(12,9)
,(21,23)
GO
9 rows affected
;WITH RCTE_CHAINS AS
(
-- seeding with the start of chains
SELECT [From] AS MinFrom, [From], [To], 0 AS Lvl
, CAST(IIF(EXISTS(
SELECT 1 FROM YourTable n
WHERE n.[From] = t.[To]
),1,0) AS BIT) AS hasNext
FROM YourTable t
WHERE NOT EXISTS
(
SELECT 1
FROM YourTable t2
WHERE t2.[To] = t.[From]
)
UNION ALL
-- looping through the childs
SELECT c.MinFrom, t.[From], t.[To], c.Lvl+1
, CAST(IIF(EXISTS(
SELECT 1 FROM YourTable n
WHERE n.[From] = t.[To]
),1,0) AS BIT) AS hasNext
FROM RCTE_CHAINS c
JOIN YourTable t ON t.[From] = c.[To]
)
SELECT MinFrom AS [From], [To]
FROM RCTE_CHAINS
WHERE hasNext = 0
GO
From | To
---: | -:
21 | 23
14 | 9
14 | 10
2 | 4
db<>fiddle here

Convert hierarchy defined by position to SQL hierarchy id defined

I have a lot of data from an old system which defines the data in a Bill of Materials by the position it exists in a table.
The BoM data table coming from the old system looks like
ID level ItemNumber
1 1 TopItem
2 .2 FirstChildOfTop
3 .2 2ndChildofTop
4 .2 3ChildOfTop
5 ..3 1stChildof3ChildofTop
6 ..3 2ndChildof3ChildofTop
7 .2 4thChildofTop
8 ..3 1stChildof4ChildTop
9 ...4 1stChildof4ChildTop
10 ..3 2ndChildof4ChildofTop
11 .2 5thChildofTop
12 ..3 1stChildof5thChildofTop
13 ...4 1stChildof1stChildof5thChildofTop
14 ..3 2ndChildof5thChildofTop
15 1 2ndTopItem
16 1 3rdTopItem
In my example the ID is consecutive, the real data the ID can be broken but always lowest to highest as that is how the hierarchy is defined.
By using some simple code to replace the level number with tabs we can get visual hierarchy
1 TopItem
2 FirstChildOfTop
3 2ndChildofTop
4 3ChildOfTop
5 1stChildof3ChildofTo
6 2ndChildof3ChildofTo
7 4thChildofTop
8 1stChildof4ChildTop
9 1stChildof4ChildTop
10 2ndChildof4ChildofTo
11 5thChildofTop
12 1stChildof5thChildof
13 1stChildof1stChildof
14 2ndChildof5thChildof
15 2ndTopItem
16 3rdTopItem
As I have about 5,000 of these lists and they are all between 25 and 55 thousand lines long, I need some code to convert this hierarchy to use sql HierarchyID so we can query at any level in the list. At the moment I hope my explanation shows, you have to work from the top to find in the Item is 2nd, 3rd or some other level and if it has any children. The items in the third column exist in a simple Item Master table but its role in a BoM is defined in these tables only.
I'd offer some code but all my attempts and conversion have failed miserably. I'd claim I'm OK a set based queries
The target is Microsoft SQL 2014
The primary aim is to data warehouse the data but enable to people to find sub-assemblies and where used.
Edit:
In answer to Anthony Hancock's very pertinent question I did some work. Please consider the following
ID level ItemNumber sampH lft rgt
1 1 TopItem 1/2 2 28
2 .2 FirstChildOfTop 1/2/3 3 4
3 .2 2ndChildofTop 1/2/3 5 6
4 .2 3ChildOfTop 1/2/3 7 11
5 ..3 1stChildof3ChildofTop 2/3/4 8 9
6 ..3 2ndChildof3ChildofTop 2/3/4 10 11
7 .2 4thChildofTop 1/2/3 13 20
8 ..3 1stChildof4ChildTop 2/3/4 14 17
9 ...4 1stChildof4ChildTop 3/4/5 15 16
10 ..3 2ndChildof4ChildofTop 2/3/4 18 19
11 .2 5thChildofTop 1/2/3/ 20 25
12 ..3 1stChildof5thChildofTop 2/3/4 21 24
13 ...4 1stChildof1stChildof5thChildofTop 3/4/5 22 23
14 ..3 2ndChildof5thChildofTop 2/3/4 26 27
15 1 2ndTopItem 1/2 2 28
16 1 3rdTopItem 1/2 2 28
17 0 verytop 1/ 1 29
Apologies for the awful formatting
1) I have added at line 17 the item we are making - ie this BoM makes the 'verytop' item - so I have renumbered the 'level'
2) I have added in the column 'sampH' column my hand edited PathEnumeratedTree values
3) In the two columns 'lft' and 'rgt' I have added some identifiers of NestedSets data
Please forgive if my hand edited columns aren't correct.
My aim is to get a structure so that someone can query these many deep list to find where an item sits in the tree and what are its children. So I'm open to whatever works.
My testing of the NestedSets - so far - has shown I can do stuff like this:
-- Children of a given parent ItemNumber
Select c.itemnumber, ' is child of 2ndTopItem'
from [dbo].[Sample] as p, [dbo].[Sample] as c
where (c.lft between p.lft and p.rgt)
and (c.lft <> p.lft)
and p.ItemNumber = '2ndTopItem'
But I am completely open to any suggestions how to enumerate the tree structure.
Try the following code:
declare #Source table (
Id int ,
[Level] varchar(20) ,
[Name] varchar(50)
);
declare #Target table (
Id int ,
[Level] int ,
[Name] varchar(50) ,
ParentId int ,
Hid hierarchyid ,
primary key (Id),
unique ([Level], Id),
unique (ParentId, Id)
);
-- 1. The Test Data (Thanks Anthony Hancock for it)
insert into #Source
values
( 1 , '1' , 'TopItem' ),
( 2 , '.2' , 'FirstChildOfTop' ),
( 3 , '.2' , '2ndChildofTop' ),
( 4 , '.2' , '3ChildOfTop' ),
( 5 , '..3' , '1stChildof3ChildofTop' ),
( 6 , '..3' , '2ndChildof3ChildofTop' ),
( 7 , '.2' , '4thChildofTop' ),
( 8 , '..3' , '1stChildof4ChildTop' ),
( 9 , '...4' , '1stChildof4ChildTop' ),
( 10 , '..3' , '2ndChildof4ChildofTop' ),
( 11 , '.2' , '5thChildofTop' ),
( 12 , '..3' , '1stChildof5thChildofTop' ),
( 13 , '...4' , '1stChildof1stChildof5thChildofTop' ),
( 14 , '..3' , '2ndChildof5thChildofTop' ),
( 15 , '1' , '2ndTopItem' ),
( 16 , '1' , '3rdTopItem' );
-- 2. Insert the Test Data to the #Target table
-- with converting of the Level column to int data type
-- to use it as an indexed column in the query # 3
-- (once there are millions of records, that index will be highly useful)
insert into #Target (Id, [Level], [Name])
select
Id,
[Level] = cast(replace([Level],'.','') as int),
[Name]
from
#Source
-- 3. Calculate the ParentId column and update the #Target table
-- to use the ParentId as an indexed column in the query # 4
update t set
ParentId = (
select top 1 Id
from #Target as p
where p.Id < t.Id and p.[Level] < t.[Level]
order by p.Id desc )
from
#Target t;
-- 4. Calculate the Hid column
-- based on the ParentId link and in accordance with the Id order
with Recursion as
(
select
Id ,
ParentId ,
Hid = cast(
concat(
'/',
row_number() over (order by Id),
'/'
)
as varchar(1000)
)
from
#Target
where
ParentId is null
union all
select
Id = t.Id ,
ParentId = t.ParentId ,
Hid = cast(
concat(
r.Hid,
row_number() over (partition by t.ParentId order by t.Id),
'/'
)
as varchar(1000)
)
from
Recursion r
inner join #Target t on t.ParentId = r.Id
)
update t set
Hid = r.Hid
from
#Target t
inner join Recursion r on r.Id = t.Id;
-- 5. See the result ordered by Hid
select
Id ,
[Level] ,
[Name] ,
ParentId ,
Hid ,
HidPath = Hid.ToString()
from
#Target
order by
Hid;
Read more about Combination of Id-ParentId and HierarchyId Approaches to Hierarchical Data
Using your example data to create a test table and then create parent IDs for each row I think this is what you are after? The big caveat is that this is entirely dependent on your table being ordered correctly for the hierarchies but I don't see any other options from the information provided.
DROP TABLE IF EXISTS TEST;
CREATE TABLE TEST
(
ID INT
,[Level] VARCHAR(20)
,ItemNumber VARCHAR(50)
)
;
INSERT INTO TEST
(ID,[Level],ItemNumber)
VALUES
(1,'1','TopItem')
,(2,'.2','FirstChildOfTop')
,(3,'.2','2ndChildofTop')
,(4,'.2','3ChildOfTop')
,(5,'..3','1stChildof3ChildofTop')
,(6,'..3','2ndChildof3ChildofTop')
,(7,'.2','4thChildofTop')
,(8,'..3','1stChildof4ChildTop')
,(9,'...4','1stChildof4ChildTop')
,(10,'..3','2ndChildof4ChildofTop')
,(11,'.2','5thChildofTop')
,(12,'..3','1stChildof5thChildofTop')
,(13,'...4','1stChildof1stChildof5thChildofTop')
,(14,'..3','2ndChildof5thChildofTop')
,(15,'1','2ndTopItem')
,(16,'1','3rdTopItem')
;
SELECT *
,V.ParentID
FROM TEST AS T
OUTER APPLY
(
SELECT TOP 1 ID AS ParentID
FROM TEST AS _T
WHERE _T.ID < T.ID
AND REPLACE(_T.[Level],'.','') < REPLACE(T.[Level],'.','')
ORDER BY _T.ID DESC
) AS V
ORDER BY T.ID
;
DROP TABLE IF EXISTS TEST;

How to select distinct rows, but repeat if it has a different row between the equal ones

Having data like this:
id text bit date
1 row 1 2016-11-24
2 row 1 2016-11-25
3 row 0 2016-11-26
4 row 1 2016-11-27
I want to select the data based on where the text and bit columns are distinct, but based on some order, in this case the id, the data changes between two identical rows, it should duplicate this row on the selection.
So, if I use distinct on SQL, I would get rows 1 and 3, but I want to retreive rows 1, 3 and 4, because even 1 and 4 being identical, row 3 is between then when ordering by id.
With a larger dataset, like:
id text bit date
1 row 1 2016-11-24
2 row 1 2016-11-25
3 row 0 2016-11-26
4 row 1 2016-11-27
5 foo 1 2016-11-28
6 bar 1 2016-11-29
7 row 1 2016-11-30
8 row 0 2016-12-01
9 row 0 2016-12-02
10 row 1 2016-12-03
Again, selecting with distinct on text and bit columns, the query would retrieve rows 1,3,5 and 6, but actually I want rows 1,3,4,5,6,7,8 and 10.
;with tb(id,[text],[bit],[date]) AS (
SELECT 1,'row',1,'2016-11-24' union
SELECT 2,'row',1,'2016-11-25' union
SELECT 3,'row',0,'2016-11-26' union
SELECT 4,'row',1,'2016-11-27' union
SELECT 5,'foo',1,'2016-11-28' union
SELECT 6,'bar',1,'2016-11-29' union
SELECT 7,'row',1,'2016-11-30' union
SELECT 8,'row',0,'2016-12-01' union
SELECT 9,'row',0,'2016-12-02' union
SELECT 10,'row',1,'2016-12-03')
select t1.* from tb as t1
OUTER APPLY (select top 1 [text],[bit] from tb as tt where tt.id<t1.id order by id desc ) as t2
where t1.[text]!=isnull(t2.[text],'') or t1.[bit]!=isnull(t2.[bit],1-t1.[bit])
result set:
1 row 1 2016-11-24
3 row 0 2016-11-26
4 row 1 2016-11-27
5 foo 1 2016-11-28
6 bar 1 2016-11-29
7 row 1 2016-11-30
8 row 0 2016-12-01
10 row 1 2016-12-03
It seems that you need a row-by-row operator. You need to know if the new row is the same as the previous one or not. If it is, neglect it, if not, keep it. Here is my solution:
declare #text varchar(100)=(select [text] from Mytable where id = 1)
declare #bit bit = (select [bit] from Mytable where id = 1)
declare #Newtext varchar(100)
declare #Newbit bit
declare #Mytable table(id int, [text] varchar(100), [bit] bit)
Insert into #Mytable select id,text, bit from Mytable where id = 1
declare #counter int =2
while #counter<=(select COUNT(*) from MyTable)
Begin
select #Newtext=(select [text] from Mytable where id = #counter)
select #Newbit=(select [bit] from Mytable where id = #counter)
IF #Newtext!=#text or #Newbit!=#bit
Begin
Insert into #Mytable
select * from Mytable where id = #counter
End
set #text = #Newtext
set #bit = #Newbit;
set #counter = #counter+1
END
select * from #Mytable

Resources