SQL 中用户定义的排序

Phi*_*lᵀᴹ 7 order-by

我刚刚阅读了一篇关于用户定义结果集排序的有趣博客文章

某些应用程序,例如待办事项列表,需要维护用户定义的项目顺序。挑战在于顺序是任意的,并且可以在用户重新排列项目时改变

一个示例表可能是这样的:

create table items
(
  itemid int primary key,
  itemdata varchar(200),
  ...
  ...
  userorder ??? 
);
Run Code Online (Sandbox Code Playgroud)

使用可能的检索查询:

select * from items order by userorder asc;
Run Code Online (Sandbox Code Playgroud)

考虑到我希望将更新保持在最低限度、更新原子性和检索查询尽可能简单和“高效”,我是否应该考虑任何方法?

我最初的想法是使用一个额外的TIMESTAMP列,并且只更新单行userordertimestamp值(使用ORDERing by检索查询userorder asc, usertimestamp desc),但是当您需要在具有相同值的其他两行之间移动一行时失败.

没有考虑特定的数据库,因为我自己使用的范围很广。

Jac*_*las 5

考虑到我希望将更新保持在最低限度、更新原子性和检索查询尽可能简单和“高效”,我是否应该考虑任何方法?

我认为这是该博客文章中“真实分数”方法的深奥变体——优点是不需要引入用户定义的类型(至少如果您使用 Postgres)。

这可以使用自然排序属性varbit并将它们的序列映射到二叉树上,以便始终可以varbit在任何两个现有相邻值之间生成另一个值:

                          _____ 1 ____
                   ______/            \______
              _ 01 _                          11
            _/      \_                    _/      \_
        001            011            101            111
       /   \          /   \          /   \          /   \
    0001   0011    0101   0111    1001   1011    1101    1111
Run Code Online (Sandbox Code Playgroud)
create table foo(id serial primary key, userorder varbit unique);

create function adj(orig varbit, b varbit) returns varbit language sql as $$
  select substring(orig for length(orig)-1)
       ||b
       ||substring(orig from length(orig));
$$;

create function f(l varbit, h varbit) returns varbit language plpgsql as $$

begin
  if l is null and h is null then return B'1'; end if;
  if l is null then return adj(h,B'0'); end if;
  if h is null then return adj(l,B'1'); end if;
  if length(l)>length(h) then return adj(l,B'1'); end if;
  return adj(h,B'0');
end;
$$;
Run Code Online (Sandbox Code Playgroud)
insert into foo(userorder) values(f(null,null));
select * from foo order by userorder;
Run Code Online (Sandbox Code Playgroud)
身份证 | 用户订单
-: | :--------
 1 | 1        
insert into foo(userorder) values(f(null,B'1'));
select * from foo order by userorder;
Run Code Online (Sandbox Code Playgroud)
身份证 | 用户订单
-: | :--------
 2 | 01       
 1 | 1        
insert into foo(userorder) values(f(B'01',B'1'));
select * from foo order by userorder;
Run Code Online (Sandbox Code Playgroud)
身份证 | 用户订单
-: | :--------
 2 | 01       
 3 | 011      
 1 | 1        
insert into foo(userorder)
values(f(B'01',B'011')),(f(B'011',B'1')),(f(B'1',null));

select * from foo order by userorder;
Run Code Online (Sandbox Code Playgroud)
身份证 | 用户订单
-: | :--------
 2 | 01       
 4 | 0101     
 3 | 011      
 5 | 0111     
 1 | 1        
 6 | 11       

dbfiddle在这里

如果您最初加载大量有序行,您可能需要使用其他一些算法来生成初始userorder值,因为您将遇到空间使用的最坏情况(每行将比userorder之前的行多使用一位)。对于足够多的位(例如,对于 8 个值:B'0001', B'0011', B'0101', B'0111', B'1001', B'1011', B'1101', B'1111'),您可以逐步遍历相同长度的值。


Mic*_*utz 2

介绍

一些非常古老的 BASIC 语言要求每行代码都有一个行号。

编程时,行号通常以 10 的倍数间隔。这允许您以后在行之间添加更多代码。

您的items表格应该遵循相同的概念。

基本算法

  1. 起始值userorder是 10 的倍数,每个值从 10 开始userid
  2. userorder根据需要更新应用程序
  3. userorder您使用 Analytics重新编号该用户
  4. “userorder”的值保持为 10 的倍数。
  5. commit数据发生变化。

重新枚举代码

这基本上是我用过的:

merge into items a
using (
  select i.itemid
    ,10 * row_number() over (partition by i.userid order by i.userorder)
      as new_userorder
  from items i
  where i.userid=?
) b
on (a.itemid = b.itemid)
when matched then update
  set a.userorder=b.new_userorder
;
Run Code Online (Sandbox Code Playgroud)

我假设这个表保存了每个人的 TODO 列表,并且每个用户都由 标识userid

本示例使用ROW_NUMBER()来自 Oracle 的。您必须用它来替换您正在使用的任何 RDBMS。