簡體   English   中英

SQL - 如何在樹級數據結構中查找值

[英]SQL - How to find a value in a tree level data structure

我有兩個SQL Server表:

  • 發票( invoice
  • 發票關系( invoice_relation

invoice表存儲具有事務作品集的所有發票記錄。

在此輸入圖像描述

invoice_relation表存儲發票之間的任何關系。

在此輸入圖像描述

這是發票如何相互關聯的示例:

在此輸入圖像描述

因此,目標是在invoice表下找到“ folio ”,給出一個invoicenumberfoliofolio有時不會是invoicefolio ,所以我需要搜索所有樹關系才能找到如果任何發票與發票編號匹配,但是folio是該關系的一部分。

例如,我必須找到對開的發票編號:

  • 對開:1003
  • 發票編號:A1122

在我的查詢中,我需要首先通過作品集查找,因為它是我的invoice表主鍵。 然后,將嘗試匹配A1122與不匹配的D1122 ,因此我必須搜索所有樹結構以查找是否存在A1122 結果是在對開頁1000中找到了發票A1122

有關如何做到這一點的任何線索?

這是一個如何使用數據創建上述示例表的腳本:

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

我仍然不確定你真正想要什么,我寫了類似JamieD77的東西,找到頂級父母,然后走回樹下但是你得到的孩子和granchildren與A1122沒有直接關系.....

這是一種在樹上走來走去的方式,讓所有直接與發票人相關的孩子和父母歸來

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

但根據你究竟在尋找什么,你可能會得到幾個不想要的堂兄......

--------------這是一個找到頂級父母並獲得整棵樹的方法

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

你的模型一開始就被打破了。 parentinvoice應該在發票表中。 它是一個遞歸數據庫模型....所以使表模式遞歸。 在引用它自己的表的列中有一個可以為空的外鍵。 該字段(父發票字段)為空時,表示它是主發票。 任何有父母的行都是一張發票。

如果要在樹級結構中查找值,可以將初始sql查詢包裝到“SELECT(.....)”語句中(創建自己的自定義可選表),過濾掉所需的內容。 如果您有任何疑問,請告訴我!

我對你的實際要求有點不清楚,所以我認為表值函數可能適合這里。 我添加了一些可選項,如果不需要,它們很容易刪除(即TITLE,Nesting,TopInvoice,TopFolio)。 此外,您可能會注意到范圍鍵(R1 / R2)。 它們提供許多功能:表示順序,選擇標准,父/葉指示符,也許最重要的是非遞歸聚合。

返回整個層次結構

Select * from [dbo].[udf_SomeFunction](NULL,NULL)     

在此輸入圖像描述


退回發票及其所有后代

Select * from [dbo].[udf_SomeFunction]('A1122',NULL) 

在此輸入圖像描述


返回Folio的PATH

Select * from [dbo].[udf_SomeFunction](NULL,'1003') 

在此輸入圖像描述


將Folio限制為發票

Select * from [dbo].[udf_SomeFunction]('A1122','1003')

在此輸入圖像描述

以下代碼需要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
)

最后的想法:

這當然可能比你看到的更多,而且很有可能我完全誤解了你的要求。 也就是說,作為一個TVF,您可以使用其他WHERE和/或ORDER子句進行擴展,甚至可以合並到CROSS APPLY中。

這使用了使用hierarchyid的方法,首先為每一行生成hierarchyid,然后選擇folio為1003的行,然后查找具有'A1122'的invoicenumber的所有祖先。 這不是很有效但可能會給你一些不同的想法:

;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'

編輯了由@jj32突出顯示的案例,您希望在其中找到對開頁1003所在的層次結構中的根元素,然后查找該根的任何后代,其發票號為“A1122”。 見下文:

;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'

這很棘手,因為你有一個單獨的關系表,根本發票不在其中。

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

這是一種嘗試,首先使關系變得平坦,以便你可以向任何方向旅行。 然后它執行遞歸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'

我同意SwampDev的評論,即parentinvoice應該在發票表中。 如果您知道發票之間的最大分隔級別,也可以在沒有遞歸CTE的情況下完成此操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM