[英]SQL - How to find a value in a tree level data structure
I have two SQL Server
tables: 我有两个
SQL Server
表:
invoice
) invoice
) invoice_relation
) invoice_relation
) invoice
table stores all invoice records with a transaction folio. invoice
表存储具有事务作品集的所有发票记录。
invoice_relation
table stores any relation between invoices. invoice_relation
表存储发票之间的任何关系。
This is an example of how invoices can be related between each other: 这是发票如何相互关联的示例:
So the goal is to find the " folio
" under invoice
table given an invoicenumber
and a folio
but the folio
sometimes won't be the folio
that the invoice
has, so I need to do a search on all the tree relation in order to find if any invoice match the invoice number but also the folio
is part of the relation. 因此,目标是在
invoice
表下找到“ folio
”,给出一个invoicenumber
和folio
但folio
有时不会是invoice
的folio
,所以我需要搜索所有树关系才能找到如果任何发票与发票编号匹配,但是folio
是该关系的一部分。
For example I have to find the folio and match invoice number of: 例如,我必须找到对开的发票编号:
In my query I would need to first find by folio because it's my invoice
table primary key. 在我的查询中,我需要首先通过作品集查找,因为它是我的
invoice
表主键。 Then, will try to match A1122
with D1122
that won't match, so then I have to search all the tree structure to find if there is a A1122
. 然后,将尝试匹配
A1122
与不匹配的D1122
,因此我必须搜索所有树结构以查找是否存在A1122
。 The result will be that the invoice A1122
was found in folio 1000
. 结果是在对开页
1000
中找到了发票A1122
。
Any clue on how to do this?
有关如何做到这一点的任何线索?
Here is a script of how to create the above example tables with data: 这是一个如何使用数据创建上述示例表的脚本:
CREATE TABLE [dbo].[invoice](
[folio] [int] NOT NULL,
[invoicenumber] [nvarchar](20) NOT NULL,
[isactive] [bit] NOT NULL,
CONSTRAINT [PK_invoice] PRIMARY KEY CLUSTERED
(
[folio] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[invoice_relation](
[relationid] [int] NOT NULL,
[invoice] [nvarchar](20) NOT NULL,
[parentinvoice] [nvarchar](20) NOT NULL,
CONSTRAINT [PK_invoice_relation_1] PRIMARY KEY CLUSTERED
(
[relationid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
INSERT [dbo].[invoice] ([folio], [invoicenumber], [isactive]) VALUES (1000, N'A1122', 1)
GO
INSERT [dbo].[invoice] ([folio], [invoicenumber], [isactive]) VALUES (1001, N'B1122', 1)
GO
INSERT [dbo].[invoice] ([folio], [invoicenumber], [isactive]) VALUES (1002, N'C1122', 1)
GO
INSERT [dbo].[invoice] ([folio], [invoicenumber], [isactive]) VALUES (1003, N'D1122', 1)
GO
INSERT [dbo].[invoice] ([folio], [invoicenumber], [isactive]) VALUES (1004, N'F1122', 1)
GO
INSERT [dbo].[invoice] ([folio], [invoicenumber], [isactive]) VALUES (1005, N'G1122', 1)
GO
INSERT [dbo].[invoice_relation] ([relationid], [invoice], [parentinvoice]) VALUES (1, N'A1122', N'B1122')
GO
INSERT [dbo].[invoice_relation] ([relationid], [invoice], [parentinvoice]) VALUES (2, N'C1122', N'A1122')
GO
INSERT [dbo].[invoice_relation] ([relationid], [invoice], [parentinvoice]) VALUES (3, N'D1122', N'A1122')
GO
INSERT [dbo].[invoice_relation] ([relationid], [invoice], [parentinvoice]) VALUES (4, N'F1122', N'B1122')
GO
INSERT [dbo].[invoice_relation] ([relationid], [invoice], [parentinvoice]) VALUES (5, N'G1122', N'F1122')
GO
I am still not sure what you really want, I had written something similar to JamieD77 which is to find top parent and then walk back down tree but then you get children and granchildren that are not directly related to A1122..... 我仍然不确定你真正想要什么,我写了类似JamieD77的东西,找到顶级父母,然后走回树下但是你得到的孩子和granchildren与A1122没有直接关系.....
Here is a way to walk up and down the tree and return all children and parents directly related to an invoicenumber 这是一种在树上走来走去的方式,让所有直接与发票人相关的孩子和父母归来
DECLARE @InvoiceNumber NVARCHAR(20) = 'A1122'
DECLARE @Folio INT = 1003
;WITH cteFindParents AS (
SELECT
i.folio
,i.invoicenumber
,CAST(NULL AS NVARCHAR(20)) as ChildInvoiceNumber
,CAST(NULL AS NVARCHAR(20)) as ParentInvoiceNumber
,0 as Level
FROM
dbo.invoice i
WHERE
i.invoicenumber = @InvoiceNumber
UNION ALL
SELECT
i.folio
,i.invoicenumber
,c.invoicenumber as ChildInvoiceNumber
,i.invoicenumber as ParentInvoiceNumber
,c.Level - 1 as Level
FROM
cteFindParents c
INNER JOIN dbo.invoice_relation r
ON c.invoicenumber = r.invoice
INNER JOIN dbo.invoice i
ON r.parentinvoice = i.invoicenumber
)
, cteFindChildren as (
SELECT *
FROM
cteFindParents
UNION ALL
SELECT
i.folio
,i.invoicenumber
,i.invoicenumber AS ChildInvoiceNumber
,c.invoicenumber AS ParentInvoiceNumber
,Level + 1 as Level
FROM
cteFindChildren c
INNER JOIN dbo.invoice_relation r
ON c.invoicenumber = r.parentinvoice
INNER JOIN dbo.invoice i
ON r.invoice = i.invoicenumber
WHERE
c.Level = 0
)
SELECT *
FROM
cteFindChildren
But Depending on what exactly you are looking for you may actually get a couple of cousins that are not desired..... 但根据你究竟在寻找什么,你可能会得到几个不想要的堂兄......
--------------Here was a method to find top parent and get the whole tree --------------这是一个找到顶级父母并获得整棵树的方法
DECLARE @InvoiceNumber NVARCHAR(20) = 'A1122'
DECLARE @Folio INT = 1003
;WITH cteFindParents AS (
SELECT
i.folio
,i.invoicenumber
,CAST(NULL AS NVARCHAR(20)) as ChildInvoiceNumber
,0 as Level
FROM
dbo.invoice i
WHERE
i.invoicenumber = @InvoiceNumber
UNION ALL
SELECT
i.folio
,i.invoicenumber
,c.invoicenumber as ChildInvoiceNumber
,c.Level + 1 as Level
FROM
cteFindParents c
INNER JOIN dbo.invoice_relation r
ON c.invoicenumber = r.invoice
INNER JOIN dbo.invoice i
ON r.parentinvoice = i.invoicenumber
)
, cteGetTopParent AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY LEVEL DESC) as RowNum
FROM
cteFindParents
)
, cteGetWholeTree AS (
SELECT
p.folio
,p.invoicenumber
,p.invoicenumber as TopParent
,p.invoicenumber as Parent
,CAST(p.invoicenumber AS NVARCHAR(1000)) as Hierarchy
,0 as Level
FROM
cteGetTopParent p
WHERE
RowNum = 1
UNION ALL
SELECT
i.folio
,i.invoicenumber
,c.TopParent
,c.invoicenumber AS Parent
,CAST(c.TopParent + '|' + (CASE WHEN Level > 0 THEN c.invoicenumber + '|' ELSE '' END) + i.invoicenumber AS NVARCHAR(1000)) as Hierarchy
,Level + 1 as Level
FROM
cteGetWholeTree c
INNER JOIN dbo.invoice_relation r
ON c.invoicenumber = r.parentinvoice
INNER JOIN dbo.invoice i
ON r.invoice = i.invoicenumber
)
SELECT *
FROM
cteGetWholeTree
Your model is broken to begin with. 你的模型一开始就被打破了。 parentinvoice should be in the invoice table.
parentinvoice应该在发票表中。 It's a recursive database model....so make the table schema recursive.
它是一个递归数据库模型....所以使表模式递归。 Have a nullable foreign key in a column that references it's own table.
在引用它自己的表的列中有一个可以为空的外键。 Any time that field (the parent invoice field) is null indicates that it is the primary invoice.
该字段(父发票字段)为空时,表示它是主发票。 Any row having a parent is a piece of invoice.
任何有父母的行都是一张发票。
When you want to find a value in a tree level structure you wrap your initial sql query into a 'SELECT(.....)' statement (creating your own custom selectable table) that filters out what you want. 如果要在树级结构中查找值,可以将初始sql查询包装到“SELECT(.....)”语句中(创建自己的自定义可选表),过滤掉所需的内容。 Let me know if you have any questions!
如果您有任何疑问,请告诉我!
I was a little unclear as to your actual requirements, so I figured a Table Valued Function may be appropriate here. 我对你的实际要求有点不清楚,所以我认为表值函数可能适合这里。 I added a few optional items, and if unwanted, they are easy enough to remove (ie TITLE, Nesting, TopInvoice, TopFolio).
我添加了一些可选项,如果不需要,它们很容易删除(即TITLE,Nesting,TopInvoice,TopFolio)。 Also, you may notice the Range Keys (R1/R2).
此外,您可能会注意到范围键(R1 / R2)。 These serve many functions: Presentation Sequence, Selection Criteria, Parent/Leaf Indicators, and perhaps most importantly Non-Recursive Aggregation.
它们提供许多功能:表示顺序,选择标准,父/叶指示符,也许最重要的是非递归聚合。
To Return the Entire Hierarchy 返回整个层次结构
Select * from [dbo].[udf_SomeFunction](NULL,NULL)
To Return an Invoice and ALL of its descendants 退回发票及其所有后代
Select * from [dbo].[udf_SomeFunction]('A1122',NULL)
To Return the PATH of a Folio 返回Folio的PATH
Select * from [dbo].[udf_SomeFunction](NULL,'1003')
To Return a Folio Limited to an Invoice 将Folio限制为发票
Select * from [dbo].[udf_SomeFunction]('A1122','1003')
The following code requires SQL 2012+ 以下代码需要SQL 2012+
CREATE FUNCTION [dbo].[udf_SomeFunction](@Invoice nvarchar(25),@Folio nvarchar(25))
Returns Table
As
Return (
with cteBld as (
Select Seq = cast(1000+Row_Number() over (Order By Invoice) as nvarchar(500)),I.Invoice,I.ParentInvoice,Lvl=1,Title = I.Invoice,F.Folio
From (
Select Distinct
Invoice=ParentInvoice
,ParentInvoice=cast(NULL as nvarchar(20))
From [Invoice_Relation]
Where @Invoice is NULL and ParentInvoice Not In (Select Invoice from [Invoice_Relation])
Union All
Select Invoice
,ParentInvoice
From [Invoice_Relation]
Where Invoice=@Invoice
) I
Join Invoice F on I.Invoice=F.InvoiceNumber
Union All
Select Seq = cast(concat(A.Seq,'.',1000+Row_Number() over (Order by I.Invoice)) as nvarchar(500))
,I.Invoice
,I.ParentInvoice
,A.Lvl+1
,I.Invoice,F.folio
From [Invoice_Relation] I
Join cteBld A on I.ParentInvoice = A.Invoice
Join Invoice F on I.Invoice=F.InvoiceNumber )
,cteR1 as (Select Seq,Invoice,Folio,R1=Row_Number() over (Order By Seq) From cteBld)
,cteR2 as (Select A.Seq,A.Invoice,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.Invoice )
Select Top 100 Percent
B.R1
,C.R2
,A.Invoice
,A.ParentInvoice
,A.Lvl
,Title = Replicate('|-----',A.Lvl-1)+A.Title -- Optional: Added for Readability
,A.Folio
,TopInvoice = First_Value(A.Invoice) over (Order By R1)
,TopFolio = First_Value(A.Folio) over (Order By R1)
From cteBld A
Join cteR1 B on A.Invoice=B.Invoice
Join cteR2 C on A.Invoice=C.Invoice
Where (@Folio is NULL)
or (@Folio is Not NULL and (Select R1 from cteR1 Where Folio=@Folio) between R1 and R2)
Order By R1
)
Final Thoughts: 最后的想法:
This certainly may be more than what you were looking, and there is good chance that I COMPLETELY misunderstood your requirements. 这当然可能比你看到的更多,而且很有可能我完全误解了你的要求。 That said, being a TVF you can expand with additional WHERE and/or ORDER clauses or even incorporate into a CROSS APPLY.
也就是说,作为一个TVF,您可以使用其他WHERE和/或ORDER子句进行扩展,甚至可以合并到CROSS APPLY中。
This uses an approach of using a hierarchyid
, first generating hierarchyid for every row, then selecting the row where folio is 1003, then finding all ancestors who have an invoicenumber of 'A1122'. 这使用了使用
hierarchyid
的方法,首先为每一行生成hierarchyid,然后选择folio为1003的行,然后查找具有'A1122'的invoicenumber的所有祖先。 It's not very efficient but may give you some different ideas: 这不是很有效但可能会给你一些不同的想法:
;WITH
Allfolios
AS
(
Select i.folio, i.InvoiceNumber,
hierarchyid::Parse('/' +
CAST(ROW_NUMBER()
OVER (ORDER BY InvoiceNumber) AS VARCHAR(30)
) + '/') AS hierarchy, 1 as level
from invoice i
WHERE NOT EXISTS
(SELECT * FROM invoice_relation ir WHERE ir.invoice = i. invoicenumber)
UNION ALL
SELECT i.folio, i.invoiceNumber,
hierarchyid::Parse(CAST(a.hierarchy as VARCHAR(30)) +
CAST(ROW_NUMBER()
OVER (ORDER BY a.InvoiceNumber)
AS VARCHAR(30)) + '/') AS hierarchy,
level + 1
FROM Allfolios A
INNER JOIN invoice_relation ir
on a.InvoiceNumber = ir.ParentInvoice
INNER JOIN invoice i
on ir.Invoice = i.invoicenumber
),
Ancestors
AS
(
SELECT folio, invoiceNumber, hierarchy, hierarchy.GetAncestor(1) as AncestorId
from Allfolios
WHERE folio = 1003
UNION ALL
SELECT af.folio, af.invoiceNumber, af.hierarchy, af.hierarchy.GetAncestor(1)
FROM Allfolios AF
INNER JOIN
Ancestors a ON Af.hierarchy= a.AncestorId
)
SELECT *
FROM Ancestors
WHERE InvoiceNumber = 'A1122'
Edited for the case highlighted by @jj32 where you wish to find the root element in the hierarchy which folio 1003 is in, then find any descendant of that root which has an invoice number of 'A1122'.
编辑了由@jj32突出显示的案例,您希望在其中找到对开页1003所在的层次结构中的根元素,然后查找该根的任何后代,其发票号为“A1122”。 See below:
见下文:
;WITH
Allfolios -- Convert all rows to a hierarchy
AS
(
Select i.folio, i.InvoiceNumber,
hierarchyid::Parse('/' +
CAST(ROW_NUMBER()
OVER (ORDER BY InvoiceNumber) AS VARCHAR(30)
) + '/') AS hierarchy, 1 as level
from invoice i
WHERE NOT EXISTS
(SELECT * FROM invoice_relation ir WHERE ir.invoice = i. invoicenumber)
UNION ALL
SELECT i.folio, i.invoiceNumber,
hierarchyid::Parse(CAST(a.hierarchy as VARCHAR(30)) +
CAST(ROW_NUMBER()
OVER (ORDER BY a.InvoiceNumber)
AS VARCHAR(30)) + '/') AS hierarchy,
level + 1
FROM Allfolios A
INNER JOIN invoice_relation ir
on a.InvoiceNumber = ir.ParentInvoice
INNER JOIN invoice i
on ir.Invoice = i.invoicenumber
),
Root -- Find Root
AS
(
SELECT *
FROM AllFolios AF
WHERE Level = 1 AND
(SELECT hierarchy.IsDescendantOf(AF.hierarchy) from AllFolios AF2 WHERE folio = 1003) = 1
)
-- Find all descendants of the root element which have an invoicenumber = 'A1122'
SELECT *
FROM ALLFolios
WHERE hierarchy.IsDescendantOf((SELECT TOP 1 hierarchy FROM Root)) = 1 AND
invoicenumber = 'A1122'
this was tricky since you have a separate relation table and the root invoice is not in it. 这很棘手,因为你有一个单独的关系表,根本发票不在其中。
DECLARE @folio INT = 1003,
@invoice NVARCHAR(20) = 'A1122'
-- find highest level of relationship
;WITH cte AS (
SELECT i.folio,
i.invoicenumber,
ir.parentinvoice,
0 AS [level]
FROM invoice i
LEFT JOIN invoice_relation ir ON ir.invoice = i.invoicenumber
WHERE i.folio = @folio
UNION ALL
SELECT i.folio,
i.invoicenumber,
ir.parentinvoice,
[level] + 1
FROM invoice i
JOIN invoice_relation ir ON ir.invoice = i.invoicenumber
JOIN cte r ON r.parentinvoice = i.invoicenumber
),
-- make sure you get the root folio
rootCte AS (
SELECT COALESCE(oa.folio, c.folio) AS rootFolio
FROM (SELECT *,
ROW_NUMBER() OVER (ORDER BY [level] DESC) Rn
FROM cte ) c
OUTER APPLY (SELECT folio FROM invoice i WHERE i.invoicenumber = c.parentinvoice) oa
WHERE c.Rn = 1
),
-- get all children of root folio
fullTree AS (
SELECT i.folio,
i.invoicenumber
FROM rootCte r
JOIN invoice i ON r.rootFolio = i.folio
UNION ALL
SELECT i.folio,
i.invoicenumber
FROM fullTree ft
JOIN invoice_relation ir ON ir.parentinvoice = ft.invoicenumber
JOIN invoice i ON ir.invoice = i.invoicenumber
)
-- search for invoice
SELECT *
FROM fullTree
WHERE invoicenumber = @invoice
Here's an attempt that flattens out the relations first so you can travel in any direction. 这是一种尝试,首先使关系变得平坦,以便你可以向任何方向旅行。 Then it does the recursive CTE to work through the levels:
然后它执行递归CTE来完成各个级别:
WITH invoicerelation AS
(
select relationid, invoice, parentinvoice AS relatedinvoice
from invoice_relation
union
select relationid, parentinvoice AS invoice, invoice AS relatedinvoice
from invoice_relation
),
cteLevels AS
(
select 0 AS relationid, invoice.folio,
invoicenumber AS invoice, invoicenumber AS relatedinvoice,
0 AS Level
from invoice
UNION ALL
select invoicerelation.relationid, invoice.folio,
invoicerelation.invoice, cteLevels.relatedinvoice,
Level + 1 AS Level
from invoice INNER JOIN
invoicerelation ON invoice.invoicenumber = invoicerelation.invoice INNER JOIN
cteLevels ON invoicerelation.relatedinvoice = cteLevels.invoice
and (ctelevels.relationid <> invoicerelation.relationid)
)
SELECT cteLevels.folio, relatedinvoice, invoice.folio AS invoicefolio, cteLevels.level
from cteLevels INNER JOIN
invoice ON cteLevels.relatedinvoice = invoice.invoicenumber
WHERE cteLevels.folio = 1003 AND cteLevels.relatedinvoice = 'a1122'
I agree with SwampDev's comment that the parentinvoice should really be in the invoice table. 我同意SwampDev的评论,即parentinvoice应该在发票表中。 This could also be done without a recursive CTE if you know the maximum number of levels of separation between invoices.
如果您知道发票之间的最大分隔级别,也可以在没有递归CTE的情况下完成此操作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.