【问题标题】:Incrementing multi-column sequence in PostgreSQL在 PostgreSQL 中递增多列序列
【发布时间】:2012-12-05 15:22:46
【问题描述】:

是否有任何内置方式(我的意思是,不需要触发器和/或函数)为每个多列增加索引?

所以在表演之后:

INSERT INTO "table"
    ("month", "desc")
    VALUES
    (1, 'One thing')
,   (1, 'Another thing')
,   (1, 'Last task of the month')
,   (2, 'Last task of the month')
,   (2, 'Last task of the month')
,   (3, 'First of third month')

我的表格最终会是这样(注意“任务”列):

month    task    desc
1        1       One thing
1        2       Another thing
1        3       Last task of the month
2        1       First of second month
2        2       Second and last of second month
3        1       First of third month

【问题讨论】:

    标签: postgresql sequences multiple-columns


    【解决方案1】:

    您可以将 simlpe SERIAL 列添加到您的表中(它会为您提供事物的顺序),然​​后使用类似:

    SELECT *, row_number() OVER (PARTITION BY month ORDER BY serial_column)
    FROM table
    

    这会给你想要的结果。

    如果您不需要对行进行排序,您可以尝试:

    SELECT *, row_number() OVER (PARTITION BY month)
    FROM table
    

    详情在这里:row_number() OVER(...)

    UPD 工作原理:

    SERIAL 类型的列本质上是一个“自动增量”字段。它会自动从序列中获取一个值。当您向表中插入行时,它们将如下所示:

    | MONTH | SERIAL_COLUMN |                     DESCRIPTION |
    -----------------------------------------------------------
    |     1 |             1 |                       One thing |
    |     1 |             2 |                   Another thing |
    |     1 |             3 |          Last task of the month |
    |     2 |             4 |           First of second month |
    |     2 |             5 | Second and last of second month |
    |     3 |             6 |            First of third month |
    

    关键 - 每一个下一个添加行的值都比前面的所有行都为SERIAL_COLUMN 大于

    接下来。 row_number() OVER (PARTITION BY month ORDER BY serial_column) 确实:

    1) 将所有行划分为具有相等month (PARTITION BY month) 的组

    2) 按serial_column (ORDER BY serial_column) 的值对它们进行排序

    3) 在每个组中使用第 2 步中的顺序分配一个行号 (`row_number() OVER)

    输出是:

    | MONTH | SERIAL_COLUMN |                     DESCRIPTION | ROW_NUMBER |
    ------------------------------------------------------------------------
    |     1 |             1 |                       One thing |          1 |
    |     1 |             2 |                   Another thing |          2 |
    |     1 |             3 |          Last task of the month |          3 |
    |     2 |             4 |           First of second month |          1 |
    |     2 |             5 | Second and last of second month |          2 |
    |     3 |             6 |            First of third month |          1 |
    

    要更改row_number() 的输出,您需要更改SERIAL_COLUMN 中的值。例如,将Second and last of second month 放在First of second month 之前会改变SERIAL_COLUMN 的值,如下所示:

    UPDATE Table1
    SET serial_column = 5
    WHERE description = 'First of second month';
    
    UPDATE Table1
    SET serial_column = 4
    WHERE description = 'Second and last of second month';
    

    它将改变查询的输出:

    | MONTH | SERIAL_COLUMN |                     DESCRIPTION | ROW_NUMBER |
    ------------------------------------------------------------------------
    |     1 |             1 |                       One thing |          1 |
    |     1 |             2 |                   Another thing |          2 |
    |     1 |             3 |          Last task of the month |          3 |
    |     2 |             4 | Second and last of second month |          1 |
    |     2 |             5 |           First of second month |          2 |
    |     3 |             6 |            First of third month |          1 |
    

    SERIAL_COLUMN 中的确切值无关紧要。 他们只对一个月内的任务进行排序。

    我的 SQLFiddle 示例是 here

    【讨论】:

    • 这就是这样做的方法。在多用户环境中,在不创建竞争条件的情况下实施无缝编号是非常困难。您将不得不大量使用表锁,这确实会降低性能。我建议你使用row_number() 而不是rank()。在没有同行的情况下结果是一样的(就像在这种情况下),但row_number() 是更便宜和更合适的功能。
    • @ErwinBrandstetter 谢谢row_number()。我编辑了查询。
    • 我以后可以在一个月内更改任务的顺序吗?
    • @Alec 是的。您需要更改serial_column 中的值。
    • 我有点迷茫,哪个是serial_column?月份还是任务?
    【解决方案2】:

    如果您愿意在每次插入时将 INSERT 语句拆分为一行数据,那么您可以使用 PostgreSQL rules。下面的示例有点令人费解,因为规则似乎不允许您将写入重定向到关系本身。这通常是通过触发器来完成的。但我们正在看看这是否可能在没有触发器的情况下实现,所以这里是:

    --drop table table_data cascade;
    CREATE TABLE table_data (
      month integer not null,
      task integer not null,
      "desc" text
    );
    ALTER TABLE table_data add primary key (month, task);
    
    CREATE VIEW "table" as 
     select month, task, "desc" from table_data;
    
    CREATE OR REPLACE RULE calculate_task AS ON INSERT TO "table"
      DO INSTEAD
      INSERT into table_data (month, task, "desc") 
      VALUES (
      NEW.month, 
      (select coalesce(max(task),0) + 1 from table_data where month = NEW.month), 
      NEW."desc");
    
    BEGIN;
    INSERT INTO "table" ("month", "desc") VALUES (1, 'One thing');
    INSERT INTO "table" ("month", "desc") VALUES (1, 'Another thing');
    INSERT INTO "table" ("month", "desc") VALUES (1, 'Last task of the month');
    INSERT INTO "table" ("month", "desc") VALUES (2, 'Last task of the month');
    INSERT INTO "table" ("month", "desc") VALUES (2, 'Last task of the month');
    INSERT INTO "table" ("month", "desc") VALUES (3, 'First of third month');
    COMMIT;
    
    select * from "table";
    

    注意事项

    • 如果您需要在“表”上支持 DELETE/UPDATE,那么您也可以为每个操作添加规则。
    • 上面的BEGINCOMMIT 块用于表明即使在同一个事务中,只要将每一行都分成自己的INSERT,此方法就可以工作。
    • 您使用了一些reserved words,例如tabledesc。一定要像你做的那样双引号,你不会有任何问题。

    Here是上面sqlfiddle中的代码

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-02
      • 2017-03-22
      • 2021-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多