在sql中对部分链接列表进行排序

时间:2017-12-04 15:21:58

标签: sql django postgresql

我在SQL中声明了一个任务优先级列表,如下所示:

CREATE TABLE TaskList(
    id int not null primary key,
    execute_before int null,
    SomeData nvarchar(50) NOT NULL)

我现在想要从这个表中选择行,这样行将按id排序,但是任何行都是" execute_before"给定的行将放在这些行之前,再按id进行排序。

因此,如果表包含这样的数据:

id      execute_before
1       null
2       null
3       2
4       3
5       2
6       null

结果应如下:

id      execute_before
1       null
4       3
3       2
5       2
2       null
6       null

我的目标SQL服务器是PostgreSQL,但是我通过Django使用它,所以这些解决方案都可以。

我目前在代码中使用相当低效(粗略的低估)排序来解决问题:

def annotate_exec_sort(queryset):
    from functools import cmp_to_key
    from django.db.models import Case, When

    def cmp(item1, item2):
        rb = item1.run_before
        while rb:
            if rb.id == item2.id:
                return -1
            rb = rb.run_before
        rb = item2.run_before
        while rb:
            if rb.id == item1.id:
                return 1
            rb = rb.run_before

        if item1.id < item2.id:
            return -1
        elif item1.id > item2.id:
            return 1
        return 0

    recs = sorted(queryset.all(), key=cmp_to_key(cmp))
    return queryset.annotate(srt=Case(*[When(id=r[1], then=r[0]) for r in enumerate([r.id for r in recs])],
                                      default=0,
                                      output_field=models.IntegerField()))

1 个答案:

答案 0 :(得分:1)

这是一个使用PostgreSQL WITH RECURSIVE查询来遍历的解决方案 依赖图。设置如下:

begin;

drop table if exists tasklist;

create table tasklist
(
    id int not null primary key,
    execute_before int null
);

\copy tasklist(id, execute_before) from stdin with csv delimiter E'\t' null as 'null'
1   null
2   null
3   2
4   3
5   2
6   null
\.

commit;

然后解决方案如下所示:

with recursive s as
(
    select id, execute_before, 0 as level, '{}'::integer[] as before
      from tasklist t1
     where execute_before is null

 union

    select tasklist.id,
           tasklist.execute_before,
           level + 1 as level,
           case when tasklist.execute_before is not null
                then before || tasklist.id
                else before
            end as before
      from tasklist
           join s
             on tasklist.execute_before = s.id
     where not tasklist.id = any(before)
)
  select id, execute_before, level, before
    from s
order by level desc, id;

给出以下输出:

 id │ execute_before │ level │ before 
════╪════════════════╪═══════╪════════
  4 │              3 │     2 │ {3,4}
  3 │              2 │     1 │ {3}
  5 │              2 │     1 │ {5}
  1 │              ¤ │     0 │ {}
  2 │              ¤ │     0 │ {}
  6 │              ¤ │     0 │ {}
(6 rows)