PostgreSQL:ORDER BY和LIMIT/OFFSET的奇怪碰撞

Pau*_*aul 4 sql postgresql sql-order-by case-insensitive slice

我试图在PostgreSQL 9.1中这样做:

SELECT m.id, vm.id, vm.value
FROM m
LEFT JOIN vm ON vm.m_id = m.id and vm.variation_id = 1
ORDER BY lower(trim(vm.value)) COLLATE "C" ASC LIMIT 10 OFFSET 120
Run Code Online (Sandbox Code Playgroud)

结果是:

 id |  id | value
----+-----+---------------
504 | 511 | "andr-223322"
506 | 513 | "andr-322223"
824 | 831 | "angHybrid"
866 | 873 | "Another thing"
493 | 500 | "App update required!"
837 | 844 | "App update required!"
471 | 478 | "April"
905 | 912 | "Are you sure you want to delete this thing?"
 25 |  29 | "Assignment"
196 | 201 | "AT ADDRESS"
Run Code Online (Sandbox Code Playgroud)

好的,让我们执行相同的查询OFFSET 130:

 id |  id | value
----+-----+---------------
196 | 201 | "AT ADDRESS"
256 | 261 | "Att Angle"
190 | 195 | "Att Angle"
273 | 278 | "Att Angle:"
830 | 837 | "attAngle"
475 | 482 | "August"
710 | 717 | "Averages"
411 | 416 | "AVG"
692 | 699 | "AVG SHAPE"
410 | 415 | "AVGs"
Run Code Online (Sandbox Code Playgroud)

我们AT ADDRESS再次看到我们的项目,但在开始!

事实是该vm表包含以下两项:

 id | m_id | value
----+------+---------------
201 |  196 | "AT ADDRESS"
599 |  592 | "At Address"
Run Code Online (Sandbox Code Playgroud)

我通过解决方法解决了这种情况:

(lower(trim(vm.value)) || vm.id)
Run Code Online (Sandbox Code Playgroud)

但是什么地狱??? !!! 为什么我必须使用解决方法?

Erw*_*ter 9

发誓不会改变定义此行为的SQL标准.除非在中指定,否则
行的顺序是未定义的ORDER BY.每个文件:

如果未选择排序,则将以未指定的顺序返回行.在这种情况下,实际的顺序将取决于扫描和连接计划类型以及磁盘上的顺序,但不能依赖它.只有明确选择排序步骤,才能保证特定的输出顺序.

由于您没有为这两个对等方定义订单(按您的排序顺序):

 id | m_id | value
----+------+---------------
201 |  196 | "AT ADDRESS"
599 |  592 | "At Address"
Run Code Online (Sandbox Code Playgroud)

..你得到任意的订单 - 对Postgres来说很方便.查询LIMIT通常使用不同的查询计划,可以解释不同的结果.

固定:

ORDER BY lower(trim(vm.value)) COLLATE "C", vm.id;
Run Code Online (Sandbox Code Playgroud)

或者(可能更有意义 - 可能还调整到现有索引):

ORDER BY lower(trim(vm.value)) COLLATE "C", vm.value, vm.id;
Run Code Online (Sandbox Code Playgroud)

(这与使用COLLATE "C"此处无关,顺便说一下.)
不要为此目的连接,这样会更昂贵,并且可能使得无法使用索引(除非您对该精确表达式有索引).添加另一个表达式,当ORDER BY列表中的先前表达式出现歧义时,该表达式将启动.

此外,由于你有一个,LEFT JOIN所以m没有匹配的行vm对所有当前ORDER BY表达式都有空值.它们是最后的,并且是任意排序的.如果你想要一个稳定的排序顺序,你也需要处理它.喜欢:

ORDER BY lower(trim(vm.value)) COLLATE "C", vm.id, m.id;
Run Code Online (Sandbox Code Playgroud)

旁白

为什么要存储双引号?似乎是昂贵的噪音.没有它们你可能会更好.如果需要,您始终可以在输出中添加引号.

许多客户端无法在一个结果中多次处理相同的列名称.您需要至少一个列的列别名id:SELECT m.id AS m_id, vm.id AS vm_id ....去表明为什么列的"id"是一个反模式开始.

  • @Paul:它通常也不在Postgres中.但你永远不能依赖它.如果未指定,则顺序将根据实现的具体情况以及实际保存单个行的位置生成. (2认同)