【问题标题】:How keep data don't sort?如何保持数据不排序?
【发布时间】:2013-07-20 21:34:23
【问题描述】:

我有一张桌子 A:

Col1 Col2
12    a
12    c
12    b

如果我编码:Select * from A; 结果是:

Col1 Col2
12    a
12    b
12    d

我要获取的数据是:

Col1 Col2
12    a
12    c
12    b

如何获取数据不排序?

【问题讨论】:

  • 从没有order by 的选择返回的行不排序。您看到的任何订单纯属巧合。您看到的顺序可以并且将会根据系统中的其他查询和表的更新而改变。
  • @a_horse_with_no_name 发表您的评论作为答案。我会投票给它
  • 行索引如何插入?
  • 您的表中是否有 auto_increment 列或时间戳列?

标签: sql postgresql


【解决方案1】:

要理解的关键是 SQL 表没有顺序。当您SELECT 没有ORDER BY 时,您看到的行顺序仅保持不变,因为数据库按该顺序获取它们比其他顺序更快。当您对表进行顺序扫描时,PostgreSQL 只会按此顺序返回行;如果它可以使用索引进行查询,那么您通常会以其他顺序获取行。

您可能会发现 this answer I wrote earlier 信息丰富。

在 PostgreSQL 中,UPDATEs 到行可以将它们移动到表中的不同位置,从而更改它们返回的顺序。后台 autovacuum 进程和各种其他操作(如 VACUUMCLUSTER)也可以.

因此,您必须从不依赖“默认”排序来处理任何事情。如果你想给行某种顺序,它们必须有一个你可以对它们进行排序的键。

如果您创建了一个没有键的表,但现在意识到它应该有一个键,您可以使用ctid 系统列从这种情况中恢复过来。 不要将此用于生产用途,它是系统内部的列,用户仅可出于紧急恢复和诊断目的而看到它。首先,看看物理上的磁盘顺序是否真的是你想要的顺序:

SELECT row_number() OVER () AS mytable_id, *
FROM mytable
ORDER BY ctid;

如果是,您可以添加一个新的键列,该列已预先设置为按磁盘行顺序应用的自动递增键。有两种方法可以做到这一点。最安全的是:

BEGIN;
LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE;
ALTER TABLE mytable RENAME TO mytable_old;

CREATE TABLE mytable (id SERIAL PRIMARY KEY, LIKE mytable_old INCLUDING ALL);

INSERT INTO mytable
SELECT row_number() OVER () AS id, *
FROM mytable_old
ORDER BY ctid;

SELECT setval('mytable_id_seq', (SELECT max(id)+1 FROM mytable));

COMMIT;

然后,一旦您确定对结果感到满意,DROP TABLE mytable_old;。看这个演示:http://sqlfiddle.com/#!12/2cb99/2

一种快速简单但不太安全的方法是只创建列并依赖 PostgreSQL 从头到尾重写表:

ALTER TABLE mytable ADD COLUMN mytable_id SERIAL PRIMARY KEY;

绝对不能保证 PostgreSQL 会按顺序分配 ID,尽管实际上它会这样做。见this SQLFiddle demo

请注意,当您使用 SEQUENCE(这是 SERIAL 列创建的内容)时,可能会出现一些您意想不到的行为。当您一次插入多行时,这些行可能不一定按照您期望的确切顺序获得分配的 ID,并且它们可能以与分配 ID 和插入的顺序不同的顺序“出现”(变得可见)此外,如果事务回滚,生成的 ID 将被永远丢弃,因此您会在编号中出现空白。如果您希望数据库速度很快,这非常好,但如果您想要无间隙编号,这并不理想。如果这是您需要的,请搜索“postgresql gapless sequence”。

【讨论】:

    【解决方案2】:

    要补充 Craig Ringer 给出的非常全面的答案,您可能需要考虑重新构建问题:为什么您希望行以特定顺序显示?显然,这个顺序有一些特殊的含义,“它们碰巧按那个顺序插入”是让数据库的技术主导应用程序,而不是相反。

    例如,这些行可能代表某种事件,而您希望按照事件发生的顺序检索它们。在这种情况下,适当的排序列将是时间戳;就像一个自增序列,你可以给它一个插入行的默认值(但可以通过显式插入列来覆盖它,或者稍后更新值,如有必要):

    ALTER TABLE some_table ADD COLUMN event_date TIMESTAMP NOT NULL DEFAULT ( NOW() );
    

    或者,也许它实际上是基于将在 UI 上显示的显示标签,在这种情况下,您只需 ORDER BY 显示标签,并确保数据库使用正确的 collation

    最后,也许它是一个完全任意的显示顺序,可以对其进行调整以将常见或重要的项目置于菜单顶部。在这种情况下,显示顺序本身就是被建模项目的属性,因此表中应该有自己的列。由于order是SQL关键字,所以我经常把这样的列称为order_hint,这样我就可以说ORDER BY order_hint,而不必担心专门引用列名。

    【讨论】:

    • 说得好,解释得好。
    【解决方案3】:

    a_horse_with_no_name 在评论中给出了正确答案。这是为了解决如何添加自增列。

    创建表时,请使用以下内容:

    create table A (
        Aid int not null auto_increment primary key,
        col1 int,
        col2 varchar(255)
    )
    

    当您插入其中时,显式列出列:

    insert into A(col1, col2)
        select 12, 'A';
    

    现在您有了所需格式的数据,Aid 列提供“插入订单”。然后你可以这样做:

    select col1, col2
    from A
    order by id;
    

    这会以“插入顺序”返回数据。请注意,由于更新和删除,数据在页面上的实际排列顺序可能与输入顺序不同。不过,order by 明确地重新排序数据。

    能够看到插入顺序是我总是在所有表中使用自动递增主键的原因之一。

    【讨论】:

    • 但是我的表没有id,只有索引
    • 实际上,ORDER BY id 不一定是更复杂环境中的插入顺序,例如生成和缓存 ID 块的环境、在逻辑复制中使用分布式序列的环境等。即使在简单的环境中设置插入顺序不一定与提交顺序相同,因此当“稍后”行已插入且可见时,可能会插入一行但尚不可见。出于这个原因,我认为使用生成的 ID 进行订购是不明智的。
    • @CraigRinger 确实,有一些论点认为永远不要使用自动增量列,而是依赖 GUID 或类似的,这样就不会有将含义读入任意 ID 的诱惑。请参阅下面我的回答,建议应该重新提出问题。
    • @IMsOp 我有时认为 PostgreSQL 应该默认通过a non-repeating pseudo-randomization function like a Feistel network 传递生成的序列值(当然是在 C 中,而不是 PL/PgSQL)。它永远不会发生,因为这会减慢速度,但打破人们对有序序列的期望是很好的。
    猜你喜欢
    • 1970-01-01
    • 2011-12-12
    • 2013-12-19
    • 2019-08-09
    • 1970-01-01
    • 2021-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多