我有两个SQL Server
表:
invoice
)invoice_relation
) invoice
表存储带有交易记录的所有发票记录。
invoice_relation
表存储发票之间的任何关系。
这是发票之间如何相互关联的一个例子:
所以我们的目标是在folio
和invoice
的{{1}}表下找到“invoicenumber
”,但folio
有时不会folio
所拥有的folio
,因此我需要对所有树关系进行搜索,以查找是否有任何发票与发票号匹配,但invoice
是否属于关系。
例如,我必须找到作品集并匹配发票编号:
在我的查询中,我需要首先找到作品集,因为它是我的folio
表主键。然后,将尝试将invoice
与不匹配的A1122
匹配,因此我必须搜索所有树结构以查找是否存在D1122
。结果是在作品集A1122
中找到了发票A1122
。
关于如何做到这一点的任何线索?
以下是如何使用数据创建上述示例表的脚本:
1000
答案 0 :(得分:3)
我仍然不确定你真正想要什么,我写了类似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
答案 1 :(得分:3)
您的模型一开始就被打破了。 parentinvoice应该在发票表中。它是一个递归数据库模型....所以使表模式递归。在引用它自己的表的列中有一个可以为空的外键。该字段(父发票字段)为空时,表示它是主发票。任何拥有父母的行都是一张发票。
如果要在树级结构中查找值,可以将初始sql查询包装到“SELECT(.....)”中。声明(创建您自己的自定义可选表),过滤掉您想要的内容。如果您有任何问题,请告诉我们!
答案 2 :(得分:1)
我对你的实际要求有点不清楚,所以我认为表值函数可能适合这里。我添加了一些可选项,如果不需要,它们很容易删除(即TITLE,Nesting,TopInvoice,TopFolio)。此外,您可能会注意到范围键(R1 / R2)。它们提供许多功能:表示顺序,选择标准,父/叶指示符,也许最重要的是非递归聚合。
要返回整个层次结构
Select * from [dbo].[udf_SomeFunction](NULL,NULL)
要退回发票及其所有后代
Select * from [dbo].[udf_SomeFunction]('A1122',NULL)
返回作品集的路径
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中。
答案 3 :(得分:1)
这使用了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'
答案 4 :(得分:0)
这很棘手,因为你有一个单独的关系表,根本发票不在其中。
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
答案 5 :(得分:-1)
这是尝试首先使关系变得平坦,以便你可以向任何方向旅行。然后它执行递归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的评论,即父母的声音确实应该在发票表中。如果您知道发票之间的最大分隔级别,也可以在没有递归CTE的情况下完成。