按同一分区的排序顺序对连接的结果集进行分组

Posted

技术标签:

【中文标题】按同一分区的排序顺序对连接的结果集进行分组【英文标题】:Grouping a joined result set by sort order for the same partition 【发布时间】:2017-11-10 07:23:20 【问题描述】:

我有 3 个代表关系/层次结构的表:

A.ID  
    B.AID
        C.BID

DDL 和sqlfiddle:(在真实的DB 中有更多的列)

CREATE TABLE A
    ([ID] int, [Title] varchar(50), [Sort] int)
;    
INSERT INTO A
    ([ID], [Title], [Sort])
VALUES
    (5, 'a5', 1),
    (4, 'a4', 2),
    (7, 'a7', 3)
;

CREATE TABLE B
    ([ID] int, [AID] int, [Title] varchar(50), [Sort] int)
;    
INSERT INTO B
    ([ID], [AID], [Title], [Sort])
VALUES
    (2, 5, 'b2', 1), -- a5
    (3, 5, 'b3', 2),
    (8, 4, 'b8', 1), -- a4
    (4, 7, 'b4', 1), -- a7
    (6, 7, 'b6', 2),
    (5, 7, 'b5', 3)
;

CREATE TABLE C
    ([ID] int, [BID] int, [Title] varchar(50), [Sort] int)
;    
INSERT INTO C
    ([ID], [BID], [Title], [Sort])
VALUES
    (1, 2, 'c1', 1), -- b2
    (8, 2, 'c8', 2),
    (2, 3, 'c2', 1), -- b3
    (3, 8, 'c3', 1), -- b8
    (7, 4, 'c7', 1), -- b4
    (4, 6, 'c4', 1), -- b6
    (6, 5, 'c6', 1), -- b5
    (5, 5, 'c5', 2)
;

我需要得到一个按表关系分组的结果集,但顺序由每个组(分区?)的Sort 列确定。

我用过:

SELECT * FROM 
A INNER JOIN B ON A.ID = B.AID
INNER JOIN C ON B.ID = C.BID
ORDER BY A.Sort, B.Sort, C.Sort;

并得到想要的结果:

ID    Title  Sort  ID    AID   Title  Sort  ID    BID   Title  Sort
5     a5     1     2     5     b2     1     1     2     c1     1
5     a5     1     2     5     b2     1     8     2     c8     2
5     a5     1     3     5     b3     2     2     3     c2     1
4     a4     2     8     4     b8     1     3     8     c3     1
7     a7     3     4     7     b4     1     7     4     c7     1
7     a7     3     6     7     b6     2     4     6     c4     1
7     a7     3     5     7     b5     3     6     5     c6     1
7     a7     3     5     7     b5     3     5     5     c5     2

或带有Sort 列的层次结构视图:

a5 (1)
|__
    b2 (1)
    |__
        c1 (1)
        c8 (2)    
    b3 (2)
    |__
        c2 (1)
a4 (2)
|__
    b8 (1)
    |__
        c3 (1)

a7 (3)
|__
    b4 (1)
    |__
        c7 (1)
    b6 (2)
    |__
        c4 (1)
    b5 (3)
    |__
        c6 (1)
        c5 (2)

Sort 值未“标准化”时,问题就开始了。 例如a5a7 具有Sort==1(相同的值)时。或者当所有 A.Sort 值都设置为 0 时(表 BC 也是如此)。

注意:“未标准化”是指Sort 值可以是每个表中的任何数字。例如

UPDATE A SET Sort = 0;

看到分组中断 (a7) 我得到随机结果:

ID    Title  Sort  ID    AID   Title  Sort  ID    BID   Title  Sort
4     a4     0     8     4     b8     1     3     8     c3     1
7     a7     0     4     7     b4     1     7     4     c7     1 <-- a7
5     a5     0     2     5     b2     1     1     2     c1     1
5     a5     0     2     5     b2     1     8     2     c8     2
5     a5     0     3     5     b3     2     2     3     c2     1
7     a7     0     6     7     b6     2     4     6     c4     1 <-- a7
7     a7     0     5     7     b5     3     6     5     c6     1
7     a7     0     5     7     b5     3     5     5     c5     2

我想尽最大努力让Sort 订单正确,但保持分组/层次结构正确。如何做到这一点?

我也试过了:

ORDER BY 
  A.Sort, A.ID,
  B.Sort, B.ID,
  C.Sort, C.ID;

它似乎有效。但不知何故,这对我来说感觉不对。 我几乎可以肯定解决方案是类似于

ROW_NUMBER() OVER(PARTITION BY A.ID, B.ID, C.ID ORDER BY A.Sort, B.Sort, C.Sort) 

但我无法得到正确的结果。


编辑#1:我认为这应该可行,但我仍需要测试,因为我不确定:

SELECT 
  ROW_NUMBER() OVER(ORDER BY A.Sort) s1,
  ROW_NUMBER() OVER(PARTITION BY A.ID ORDER BY B.Sort) s2,
  ROW_NUMBER() OVER(PARTITION BY B.ID ORDER BY C.Sort) s3,
  * 
FROM 
  A INNER JOIN B ON A.ID = B.AID
  INNER JOIN C ON B.ID = C.BID  
  ORDER by s1, s2, s3

EDIT #2:EDIT #1 在测试后没有按预期工作。 似乎唯一起作用的事情很简单:

ORDER BY A.Sort, A.ID, 
         B.Sort, B.ID, 
         C.Sort, C.ID; -- actually the C.ID is not needed

【问题讨论】:

【参考方案1】:

试试这个

SELECT * FROM 
A INNER JOIN B ON A.ID = B.AID
INNER JOIN C ON B.ID = C.BID
ORDER BY CASE WHEN A.Sort = 0 THEN A.ID ELSE A.Sort END,
         CASE WHEN B.Sort = 0 THEN B.ID ELSE B.Sort END,
         CASE WHEN C.Sort = 0 THEN C.ID ELSE C.Sort END

编辑随机数 - 试试下面

SELECT *,DENSE_RANK() OVER (ORDER BY A.ID,A.Sort) AS RNK1 
        ,DENSE_RANK() OVER (ORDER BY B.ID,B.Sort) AS RNK2
        ,DENSE_RANK() OVER (ORDER BY C.ID,C.Sort) AS RNK3
FROM         
A INNER JOIN B ON A.ID = B.AID
INNER JOIN C ON B.ID = C.BID
ORDER BY RNK1,RNK2,RNK3

【讨论】:

是的,但这“无法”为规范化表获取正确的结果集。对于每个 ID 分区,Order 应该是第一个。 实际上,如果我将 3 个表的 ORDER BY 更改为 ORDER BY A.Sort,A.ID 似乎会产生正确的结果。仍在尝试了解它是如何工作的。【参考方案2】:

如果它只是您想要保留的“分组”,您可以使用 id 或 title 的条件排序来执行此操作,如下所示:

SELECT * 
FROM A INNER JOIN B ON A.ID = B.AID
       INNER JOIN C ON B.ID = C.BID
ORDER BY CASE WHEN A.Sort = 0 THEN A.ID ELSE A.Sort END, 
         CASE WHEN B.Sort = 0 THEN B.ID ELSE B.Sort END, 
         CASE WHEN C.Sort = 0 THEN C.ID ELSE C.Sort END;

这样,如果排序值为零,它将仅按 id 排序。 您还可以通过子查询获取 MAX 和 MIN 排序值并检查它们是否相等。

更新:MAX/MIN 聚合示例:

SELECT * 
FROM A INNER JOIN B ON A.ID = B.AID
       INNER JOIN C ON B.ID = C.BID
       LEFT JOIN (SELECT MAX(Sort) amax, MIN(Sort) amin FROM A) aggA ON 1 = 1
       LEFT JOIN (SELECT MAX(Sort) bmax, MIN(Sort) bmin FROM A) aggB ON 1 = 1
       LEFT JOIN (SELECT MAX(Sort) cmax, MIN(Sort) cmin FROM A) aggC ON 1 = 1
ORDER BY CASE WHEN amax = amin THEN A.ID ELSE A.Sort END, 
         CASE WHEN bmax = bmin THEN B.ID ELSE B.Sort END, 
         CASE WHEN cmax = cmin THEN C.ID ELSE C.Sort END;

更新2: 此尝试计算唯一的排序值和 ID,并检查它们是否相同。如果它们不同,则意味着两个或多个 id 具有相同的排序值,因此按 id 排序。 也许这更接近您需要的解决方案

SELECT * 
FROM A INNER JOIN B ON A.ID = B.AID
       INNER JOIN C ON B.ID = C.BID
       LEFT JOIN (SELECT COUNT(DISTINCT Sort) sorts, COUNT(ID) ids FROM A) aggA ON 1=1
       ...
ORDER BY CASE WHEN aggA.sorts <> aggA.ids THEN A.ID ELSE A.Sort END,
         ...
         ;

【讨论】:

0 是一个例子。它可以是任何数字。 就像我说的,您也可以使用最大/最小检查或类似方法。我已经用这样的例子更新了答案。如果你想保持正确的顺序,如果排序列完全搞砸了,我认为这是不可能的。但我喜欢被证明是错误的。 我正在尝试理解代码...也许应该是CASE WHEN amax = amin THEN A.ID, A.Sort...?或者您假设组中的每个Sort 都相同? 当amax = amin条件满足时,表示A表中的所有排序值都是相同的,所以在order by子句中添加A.Sort没有意义跨度> 我认为ORDER BY A.Sort, A.ID, B.Sort, B.ID, C.Sort, C.ID; 确实有效...让我检查您的第二个代码。

以上是关于按同一分区的排序顺序对连接的结果集进行分组的主要内容,如果未能解决你的问题,请参考以下文章

mysql排序数据

数据库概念

SQL 操作结果集 -并集差集交集结果集排序

SQL 操作结果集 -并集差集交集结果集排序

SQL关联查询 直接join 和子查询的区别

SQL 操作结果集 -并集差集交集结果集排序