Последовательность PostgreSQL на основе другого столбца - PullRequest
36 голосов
/ 26 июля 2011

Допустим, у меня есть такая таблица:

Column   |     Type    |                        Notes
---------+------------ +----------------------------------------------------------
 id      | integer     | An ID that's FK to some other table
 seq     | integer     | Each ID gets its own seq number
 data    | text        | Just some text, totally irrelevant.

id + seq - это комбинированный ключ.

То, что я хотел бы видеть:*

ID  | SEQ   |                        DATA
----+------ +----------------------------------------------
 1  | 1     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 1  | 2     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 1  | 3     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 1  | 4     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 2  | 1     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 3  | 1     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 3  | 2     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 3  | 3     | Quick brown fox, lorem ipsum, lazy dog, etc etc.
 3  | 4     | Quick brown fox, lorem ipsum, lazy dog, etc etc.

Как видите, комбинация id и seq уникальна.

Я не уверен, как настроить свою таблицу (или оператор вставки?), Чтобы сделатьэтот.Я хотел бы вставить id и data, в результате чего seq будет подпоследовательностью, зависящей от id.

Ответы [ 8 ]

20 голосов
/ 13 мая 2015

Нет проблем! Мы собираемся сделать две таблицы, things и stuff. stuff будет таблицей, которую вы описываете в своем вопросе, а things будет той, к которой относится:

CREATE TABLE things (
    id serial primary key,
    name text
);

CREATE TABLE stuff (
    id integer references things,
    seq integer NOT NULL,
    notes text,
    primary key (id, seq)
);

Затем мы установим things с помощью триггера, который будет создавать новую последовательность каждый раз, когда создается строка:

CREATE FUNCTION make_thing_seq() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  execute format('create sequence thing_seq_%s', NEW.id);
  return NEW;
end
$$;

CREATE TRIGGER make_thing_seq AFTER INSERT ON things FOR EACH ROW EXECUTE PROCEDURE make_thing_seq();

Теперь мы получим thing_seq_1, thing_seq_2, и т. Д., И т. Д. *

Теперь еще один триггер на stuff, чтобы он каждый раз использовал правильную последовательность:

CREATE FUNCTION fill_in_stuff_seq() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  NEW.seq := nextval('thing_seq_' || NEW.id);
  RETURN NEW;
end
$$;

CREATE TRIGGER fill_in_stuff_seq BEFORE INSERT ON stuff FOR EACH ROW EXECUTE PROCEDURE fill_in_stuff_seq();

Это обеспечит, что когда строки переходят в stuff, столбец id используется для поиска правильной последовательности для вызова nextval on.

Вот демонстрация:

test=# insert into things (name) values ('Joe');
INSERT 0 1
test=# insert into things (name) values ('Bob');
INSERT 0 1
test=# select * from things;
 id | name
----+------
  1 | Joe
  2 | Bob
(2 rows)

test=# \d
              List of relations
 Schema |     Name      |   Type   |  Owner
--------+---------------+----------+----------
 public | stuff         | table    | jkominek
 public | thing_seq_1   | sequence | jkominek
 public | thing_seq_2   | sequence | jkominek
 public | things        | table    | jkominek
 public | things_id_seq | sequence | jkominek
(5 rows)

test=# insert into stuff (id, notes) values (1, 'Keychain');
INSERT 0 1
test=# insert into stuff (id, notes) values (1, 'Pet goat');
INSERT 0 1
test=# insert into stuff (id, notes) values (2, 'Family photo');
INSERT 0 1
test=# insert into stuff (id, notes) values (1, 'Redundant lawnmower');
INSERT 0 1
test=# select * from stuff;
 id | seq |        notes
----+-----+---------------------
  1 |   1 | Keychain
  1 |   2 | Pet goat
  2 |   1 | Family photo
  1 |   3 | Redundant lawnmower
(4 rows)

test=#
16 голосов
/ 26 июля 2011

Вы можете использовать оконную функцию , чтобы назначить SEQ значения, например:

INSERT INTO YourTable
    (ID, SEQ, DATA)
    SELECT ID, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY DATA), DATA
        FROM YourSource
4 голосов
/ 13 мая 2015

Если seq отражает (или должен отражать) порядок вставки строк, я бы предпочел использовать timestamp, который заполняется автоматически и генерирует порядковый номер на лету при выборе строк с помощью row_number():

create table some_table
( 
  id          integer   not null,
  inserted_at timestamp not null default current_timestamp,
  data text
);

Чтобы получить столбец seq, вы можете сделать:

select id,  
       row_number() over (partition by id order by inserted_at) as seq,
       data
from some_table
order by id, seq;

Однако выбор будет несколько медленнее по сравнению с использованием постоянного seq столбец (особенно с индексом на id, seq).

Если это станет проблемой, вы можете изучить материализованное представление или добавить столбец seq, а затем регулярно обновлять его (я бы не сделал бы это втриггер по соображениям производительности).

Пример SQLFiddle: http://sqlfiddle.com/#!15/db69b/1

0 голосов
/ 10 апреля 2019

У меня была такая же необходимость динамически хранить древовидную структуру, чтобы не добавлять все идентификаторы одновременно.
Я предпочитаю не использовать таблицы последовательности для каждой группы, поскольку их может быть тысячи.
Он работает в интенсивной многопроцессорной среде, поэтому должен быть защищен от состояния гонки.
Здесь функция вставки для 1-го уровня. Другие уровни следуют тому же принципу.

Каждая группа в качестве независимых одноразовых последовательных идентификаторов, функция получает имя группы и имя подгруппы, дает вам существующий идентификатор или создает его и возвращает новый идентификатор.
Я попытался зациклить один выбор, но код такой же длинный и трудный для чтения.

CREATE OR REPLACE FUNCTION getOrInsert(myGroupName TEXT, mySubGroupName TEXT)
  RETURNS INT AS
$BODY$
DECLARE
   myId INT;
BEGIN -- 1st try to get it if it already exists
   SELECT id INTO myId FROM myTable
      WHERE groupName=myGroupName AND subGroupName=mySubGroupName;
   IF NOT FOUND THEN
      -- Only 1 session can get it but others can read
      LOCK TABLE myTable IN SHARE ROW EXCLUSIVE MODE; 
      -- 2nd try in case of race condition
      SELECT id INTO myId FROM myTable
         WHERE groupName=myGroupName AND subGroupName=mySubGroupName;
      IF NOT FOUND THEN -- Doesn't exist. Get next ID for this group.
         SELECT COALESCE(MAX(id), 0)+1 INTO myId FROM myTable
            WHERE groupName=myGroupName;
         INSERT INTO myTable (groupName, id, subGroupName)
            VALUES (myGroupName, myId, mySubGroupName);
      END IF;
   END IF;
   RETURN myId;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE COST 100;

Чтобы попробовать это:

CREATE TABLE myTable (GroupName TEXT, SubGroupName TEXT, id INT);
SELECT getOrInsert('groupA', 'subgroupX'); -- Returns 1
...
SELECT * FROM myTable;
 groupname | subgroupname | id 
-----------+--------------+----
 groupA    | subgroupX    |  1
 groupA    | subgroupY    |  2
 groupA    | subgroupZ    |  3
 groupB    | subgroupY    |  1
0 голосов
/ 14 мая 2015

Вот простой способ использования стандартного SQL:

INSERT INTO mytable (id, seq, data)
SELECT << your desired ID >>,
       COUNT(*) + 1,
       'Quick brown fox, lorem ipsum, lazy dog, etc etc.'
FROM mytable
WHERE id = << your desired ID (same as above) >>;

См. Демонстрация SQL Fiddle .

(Если вы хотите быть немного умнее, вы можете подумать о создании триггера для обновления строки тем же способом сразу после вставки.)

0 голосов
/ 12 мая 2015

Просто предположение.

INSERT INTO TABLE (ID, SEQ, DATA)
VALUES
(
 IDVALUE,
 (SELECT max(SEQ) +1 FROM TABLE WHERE ID = IDVALUU),
 DATAVALUE
);
0 голосов
/ 26 июля 2011

У меня нет опыта работы с postgresql, но можете ли вы использовать подзапрос в своем операторе вставки?Что-то вроде, в Mysqlish,

INSERT INTO MYTABLE SET 
   ID=4, 
   SEQ=(  SELECT MAX(SEQ)+1 FROM MYTABLE WHERE ID=4  ),
   DATA="Quick brown fox, lorem ipsum, lazy dog, etc etc."
0 голосов
/ 26 июля 2011

PostgreSQL поддерживает сгруппированные уникальные столбцы, такие как:

CREATE TABLE example (
    a integer,
    b integer,
    c integer,
    UNIQUE (a, c)
);

См. Документация PostgreSQL - Раздел 5.3.3

Легко: -)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...