在树形结构表中找到共同的祖先

时间:2015-02-06 15:41:40

标签: sql sql-server tree sql-server-2012

我有这样的表结构(实际上有更多级别):

------------------------------------------
|region1|region2|region3|region4|postcode|
|-------|-------|-------|-------|--------|
|a      |x      |i      |       |1       |
|a      |y      |i      |       |2       |
|a      |y      |j      |       |2       |
|a      |z      |k      |       |3       |
|b      |u      |m      |       |4       |
|b      |       |n      |       |4       |
|c      |       |       |       |5       |
|c      |q      |       |       |6       |
------------------------------------------

例如,a => x => ia => y => i是不同的地方,但两者都位于同一地区1 a

我想知道每个邮政编码可以涵盖哪个区域。

例如,代码2涵盖了a => y => ia => y => j区域,因此这些代码的共同祖先是a => y

以下是示例运行查询的所需输出:

------------------------------------------
|postcode|region1|region2|region3|region4|
|--------|-------|-------|-------|-------|
|1       |a      |x      |i      |       |
|2       |a      |y      |       |       |
|3       |a      |z      |k      |       |
|4       |b      |       |       |       |
|5       |c      |       |       |       |
|6       |c      |q      |       |       |
------------------------------------------

我真的不知道如何解决这个问题。我想过通过邮政编码进行分区,但这仍然存在在每个分区中找到共同祖先的问题......

2 个答案:

答案 0 :(得分:2)

这是一个非常混乱的解决方案,但似乎确实给出了正确的答案。毫无疑问,需要相当多的工作来满足您的实际需求,但也许这可以帮助您指明某种方向!

-- Setup a test table

DECLARE @tbl AS TABLE(R1 NVARCHAR(10), R2 NVARCHAR(10), R3 NVARCHAR(10), R4 NVARCHAR(10), PC NVARCHAR(10));

INSERT INTO @tbl(R1,R2,R3,R4,PC) VALUES ('a','x','i',NULL,'1');
INSERT INTO @tbl(R1,R2,R3,R4,PC) VALUES ('a','y','i',NULL,'2');
INSERT INTO @tbl(R1,R2,R3,R4,PC) VALUES ('a','y','j',NULL,'2');
INSERT INTO @tbl(R1,R2,R3,R4,PC) VALUES ('a','z','k',NULL,'3');
INSERT INTO @tbl(R1,R2,R3,R4,PC) VALUES ('b','u','m',NULL,'4');
INSERT INTO @tbl(R1,R2,R3,R4,PC) VALUES ('b',NULL,'n',NULL,'4');
INSERT INTO @tbl(R1,R2,R3,R4,PC) VALUES ('c',NULL,NULL,NULL,'5');
INSERT INTO @tbl(R1,R2,R3,R4,PC) VALUES ('c','q',NULL,NULL,'6');

-- Calculate the result:

SELECT 
    PC,
    CASE WHEN LVL1 = 1 THEN R1 ELSE NULL END AS R1,
    CASE WHEN LVL2 = 1 THEN R2 ELSE NULL END AS R2,
    CASE WHEN LVL3 = 1 THEN R3 ELSE NULL END AS R3,
    CASE WHEN LVL4 = 1 THEN R4 ELSE NULL END AS R4
FROM
(
    SELECT
      PC,
      MAX(R1) AS R1, 
      MAX(R2) AS R2,
      MAX(R3) AS R3, 
      MAX(R4) AS R4,
      COUNT(DISTINCT ISNULL(R1,'.')) AS LVL1, 
      COUNT(DISTINCT ISNULL(R1,'.') + ISNULL(R2,'.')) AS LVL2, 
      COUNT(DISTINCT ISNULL(R1,'.') + ISNULL(R2,'.') + ISNULL(R3,'.')) AS LVL3,
      COUNT(DISTINCT ISNULL(R1,'.') + ISNULL(R2,'.') + ISNULL(R3,'.') + ISNULL(R4,'.')) AS LVL4
    FROM @tbl
    GROUP BY PC
) A

最终结果与问题中的表格匹配。

答案 1 :(得分:1)

这个问题引起了我的兴趣,我提出了一个替代方案,你可能会发现它很有用:

-- Setup test table
DECLARE @InputTable TABLE (region1 varchar(2), region2 varchar(2), region3 varchar(2), region4 varchar(2), postcode varchar(2))

INSERT INTO @InputTable (region1, region2, region3, region4, postcode)
          SELECT 'a','x','i',null,'1' 
UNION ALL SELECT 'a','y','i',NULL,'2'
UNION ALL SELECT 'a','y','j',NULL,'2'
UNION ALL SELECT 'a','z','k',NULL,'3'
UNION ALL SELECT 'b','u','m',NULL,'4'
UNION ALL SELECT 'b',NULL,'n',NULL,'4'
UNION ALL SELECT 'c',NULL,NULL,NULL,'5'
UNION ALL SELECT 'c','q',NULL,NULL,'6'

-- Find the common ancestors
;with totals as (
  select postcode, count(*) as postcodeCount from @InputTable group by postcode
)
, region4group as (
  select postcode, region1, region2, region3, region4 from @InputTable in1 
  group by postcode, region1, region2, region3, region4 having count(*)=(select postCodeCount from totals where totals.postcode=in1.postcode)
)
, region3group as (
  select * from region4group
  union
  select in1.postcode, in1.region1, in1.region2, in1.region3, null from @InputTable in1 
  left outer join region4group on region4group.postcode=in1.postcode
  where region4group.postcode is null
  group by in1.postcode, in1.region1, in1.region2, in1.region3 
  having count(*)=(select postCodeCount from totals where totals.postcode=in1.postcode)
)
, region2group as (
  select * from region3group
  union
  select in1.postcode, in1.region1, in1.region2, null, null from @InputTable in1
  left outer join region3group on region3group.postcode=in1.postcode
  where region3group.postcode is null
  group by in1.postcode, in1.region1, in1.region2 
  having count(*)=(select postCodeCount from totals where totals.postcode=in1.postcode)
)
, commonancestors as (
  select * from region2group
  union
  select in1.postcode, in1.region1, null, null, null from @InputTable in1 
  left outer join region2group on region2group.postcode=in1.postcode
  where region2group.postcode is null
  group by in1.postcode, in1.region1 
  having count(*)=(select postCodeCount from totals where totals.postcode=in1.postcode)
)
select * from commonancestors