使用SQL对具有但具有预定替代值的列表进行排序

时间:2018-08-14 15:04:57

标签: sql postgresql sorting

业务问题有点晦涩难懂,所以我不再赘述。

我必须为一组键提供一个排序索引,但是其中一些键在索引中具有预定位置,必须予以重视。其余的键必须按正常顺序排列,但要“预定”在“周围”。

一个简单的示例是对字母A到E进行排序,不同的是A必须位于位置3,D必须位于位置1。我要实现的结果是:

A: 3 B: 2 C: 4 D: 1 E: 5

DDL设置示例:

CREATE TABLE test.element (element_key TEXT, override_sort_idx INTEGER);

insert into test.element VALUES ('A', 3), ('B', Null), ('C', NULL), ('D', 1), ('E', NULL);

我能想到的最好的解决方案是这个,但是尽管它似乎适用于这个简单的示例,但是在一般情况下它还是有问题的-如果添加更多的预定义值,它就会崩溃[编辑-它不会在本例中甚至都不起作用,因为A以4表示歉意]:

WITH inner_sort AS (SELECT element_key, override_sort_idx, row_number()
OVER (ORDER BY element_key) AS natural_sort_idx
              FROM test.element)
SELECT element_key, row_number()
    OVER
     (ORDER BY
      CASE
      WHEN override_sort_idx IS NULL
      THEN natural_sort_idx
      ELSE override_sort_idx END) AS hybrid_sort
FROM inner_sort;

对适用于一般情况的解决方案有何想法?

2 个答案:

答案 0 :(得分:2)

事实证明,这更是我最初预期的挑战。

但是此SQL返回预期结果:

WITH OPENNUMBERS AS
(
  select row_number() over () as num
  from test.element 
  except
  select override_sort_idx
  from test.element 
  where override_sort_idx is not null
)
, OPENNUMBERS2 AS
(
  select num, row_number() over (order by num) as rn
  from OPENNUMBERS
)
,NORMALS AS
(
    select element_key, row_number() over (order by element_key) as rn
    from test.element
    where override_sort_idx is null
)
select n.element_key, o.num as hybrid_sort_idx
from OPENNUMBERS2 o
join NORMALS n ON n.rn = o.rn 
union all
select element_key, override_sort_idx
from test.element  
where override_sort_idx is not null
order by hybrid_sort_idx;

您可以here在SQL Fiddle上对其进行测试。

使用的trick俩?
删除覆盖后,获取仍然可用的索引编号列表。 (使用EXCEPT)
然后获取这些数字以及未覆盖的row_number。 加入行号中的那些。
然后将覆盖的内容粘贴到上面。

答案 1 :(得分:1)

将其发布,因为它可以正常工作,我认为这是我所能做的最好的事情,但这非常可怕。

WITH grouped AS (
   SELECT element_key, override_sort_idx, 
          row_number() OVER (
                             PARTITION BY override_sort_idx IS NULL
                             ORDER BY override_sort_idx, element_key) 
                                                        AS group_idx,
          row_number() OVER (ORDER BY element_key)      AS natural_sort_idx
    FROM test.element),
 remaining_idx AS (
    SELECT row_number() OVER () AS remain_idx FROM test.element
    EXCEPT
    SELECT override_sort_idx FROM test.element),
 indexed_remaining AS (
    SELECT row_number() OVER (ORDER BY remain_idx) AS r_sort_idx, 
           remain_idx 
    FROM remaining_idx)
SELECT g.element_key, 
       coalesce(g.override_sort_idx, r.remain_idx) AS hybrid_index
FROM grouped g
LEFT JOIN indexed_remaining r ON 
     (CASE WHEN g.override_sort_idx IS NULL 
           THEN g.group_idx END = r.r_sort_idx)
ORDER BY hybrid_index

这涉及首先创建“剩余的”索引值,作为简单的row_number()与预定索引值之间的差,然后将其加入没有预定索引值的键排序列表中。

考虑到合并的顺序,JOIN中的CASE语句在功能上是不必要的,但似乎是“更纯粹”的方法。

我觉得比我更聪明,能够正确理解窗口功能的人可以使用带有过滤器的窗口功能或操纵窗口功能的范围来编写此代码,而无需疯狂地嵌套子查询/ CTE和联接。