如何使用尽可能少的查询来构建层次结构(一个?)

时间:2019-03-24 10:01:08

标签: sql-server common-table-expression hierarchy

我有一个较新的SQLServer,其数据库具有一个客户表和一个具有关系(父/子关系)的表。这些关系可以具有不同的类型(例如,标准的公司层次结构,朋友,竞争等)。

我想从单个客户开始列出完整的公司层次结构(特定的关系类型)。只是一个完整的列表,不必订购,因为它将在外面处理。

我可以使用网络脚本来完成此任务,该脚本将构建可视化部分,但其中可能会有一些巨大的层次结构,这将导致运行数百个单个查询。这可能会变得很耗时。

我尝试查看CTE(公用表表达式),但我认为我不太了解。我似乎能够列出所有我的初始客户所在的地方,但无法列出根本不属于我的地方。

表格:

CREATE TABLE CUSTOMERS
(
 id int not null, 
 name varchar(100) not null
);


CREATE TABLE RELATIONSHIPS
(
 relationid int not null,
 relationtype int not null,
 customerid int not null,
 parentid int not null
);

INSERT INTO CUSTOMERS(id,name) VALUES 
(1040,'Cust A'),
(1041,'Cust B'),
(1042,'Cust C'),
(1043,'Cust D'),
(1044,'Cust E'),
(1045,'Cust F'),
(1046,'Cust G'),
(1047,'Cust H');

INSERT INTO RELATIONSHIPS(relationid,relationtype,customerid,parentid)
VALUES
(1,1,1041,1040),
(2,1,1042,1040),
(3,1,1043,1042),
(4,1,1047,1043),
(5,2,1041,1040);

当前SQL

with cte as (
    select t.parentid,parent.name as parentname,t.customerid,child.name as childname
    from RELATIONSHIPS t
    INNER JOIN CUSTOMERS parent ON t.parentid = parent.id
    INNER JOIN CUSTOMERS child ON t.customerid = child.id
    where relationtype = 1 and (customerid = 1042 or parentid = 1042)

    union all

    select t.parentid,parent.name as parentname,t.customerid,child.name as childname
    from RELATIONSHIPS t
    INNER JOIN CUSTOMERS parent ON t.parentid = parent.id
    INNER JOIN CUSTOMERS child ON t.customerid = child.id
    inner join cte c on (c.customerid=t.parentid )
    where relationtype = 1
)
select distinct t.* from cte t;

提琴:http://sqlfiddle.com/#!18/3e919b/14

基于上面数据中的数据,我希望列出以下内容:

1040,Cust A,1041,Cust B
1040,Cust A,1042,Cust C
1042,Cust C,1041,Cust D
1043,Cust D,1047,Cust H

我上面的查询尝试列出客户1042(客户C)所在的整个层次树。通过我的查询,我似乎得到了全部,除了为孩子1041(Cust B)的那个。不确定如何在查询中包括这些内容,因为它与我的成立公司没有直接关系(除了处于同一层次结构中)。

1 个答案:

答案 0 :(得分:1)

这真丑。不知道这是否是表现最好的。您实际上要说的是,您希望客户的所有子代('Cust C')是客户的所有父代,然后,然后是那些父母的所有子代(不在先前的分支)。我最后使用3个rCTE来完成此操作,这可能是较大数据集上的绝对性能杀手,但是,嘿,它完成了任务:

USE Sandbox;
GO

CREATE TABLE CUSTOMERS
(
 id int not null, 
 name varchar(100) not null
);


CREATE TABLE RELATIONSHIPS
(
 relationid int not null,
 relationtype int not null,
 customerid int not null,
 parentid int not null
);

INSERT INTO CUSTOMERS(id,name) VALUES 
(1040,'Cust A'),
(1041,'Cust B'),
(1042,'Cust C'),
(1043,'Cust D'),
(1044,'Cust E'),
(1045,'Cust F'),
(1046,'Cust G'),
(1047,'Cust H');

INSERT INTO RELATIONSHIPS(relationid,relationtype,customerid,parentid)
VALUES
(1,1,1041,1040),
(2,1,1042,1040),
(3,1,1043,1042),
(4,1,1047,1043),
(5,2,1041,1040);
GO

DECLARE @Customer varchar(100) = 'Cust C';
--Get the children of the Customer
WITH Children AS(
    SELECT Cp.name AS ParentName,
           Cp.Id AS ParentID,
           CC.name AS ChildName,
           Cc.id AS ChildID
    FROM CUSTOMERS Cp
         JOIN RELATIONSHIPS R ON Cp.id = R.parentid
         JOIN CUSTOMERS Cc ON R.customerid = Cc.id
    WHERE Cp.name = @Customer
    UNION ALL
    SELECT C.ChildName AS ParentName,
           C.ChildID AS ParentID,
           Cc.name AS ChildName,
           Cc.Id AS ChildId
    FROM Children C
         JOIN RELATIONSHIPS R ON C.ChildID = r.parentid
         JOIN CUSTOMERS Cc ON R.customerid = Cc.id),
--Get the Parents of the customer
Parents AS(
    SELECT Cp.name AS ParentName,
           Cp.Id AS ParentID,
           CC.name AS ChildName,
           Cc.id AS ChildID
    FROM CUSTOMERS Cc
         JOIN RELATIONSHIPS R ON Cc.id = R.customerid
         JOIN CUSTOMERS Cp ON R.parentid = Cp.id
    WHERE Cc.name = @Customer
    UNION ALL
    SELECT Cp.name AS ParentName,
           Cp.Id AS ParentID,
           P.ParentName AS ChildName,
           P.ParentID AS ChildId
    FROM Parents P
         JOIN RELATIONSHIPS R ON P.ParentID = R.customerid
         JOIN CUSTOMERS Cp ON R.parentid = Cp.id),
--Get the children of the parents. Yuck
ParentChildren AS(
    SELECT DISTINCT
           P.ParentName AS ParentName,
           P.ParentID AS ParentID,
           Cc.name AS ChildName,
           Cc.id AS ChildId
    FROM Parents P
         JOIN RELATIONSHIPS R ON P.ParentID = R.parentid
         JOIN CUSTOMERS Cc ON R.customerid = Cc.id    
    WHERE NOT EXISTS (SELECT 1
                      FROM Parents E
                      WHERE E.ChildID = Cc.id
                        AND E.ParentID = P.ParentID)
    UNION ALL    
    SELECT PC.ChildName AS ParentName,
           PC.ChildId AS ParentID,
           Cc.name AS ChildName,
           Cc.id AS ChildID
    FROM ParentChildren PC
         JOIN RELATIONSHIPS R ON PC.ChildId = R.parentid
         JOIN CUSTOMERS Cc ON R.customerid = Cc.id
    WHERE NOT EXISTS (SELECT 1
                      FROM Parents E
                      WHERE E.ChildID = Cc.id
                        AND E.ParentID = PC.ParentID)
)

SELECT *
FROM Children
UNION ALL
SELECT *
FROM Parents
UNION ALL
SELECT *
FROM ParentChildren
ORDER BY ParentID ASC;

GO

DROP TABLE RELATIONSHIPS;
DROP TABLE CUSTOMERS;