Как создать автоматически увеличивающийся номер редакции, уникальный для ключа в PGSQL?

StackOverflow https://stackoverflow.com/questions/912057

Вопрос

Предполагая, что у меня есть следующие таблицы.

PARENT: PARENT_ID serial, DESCRIPTION character varying(50)

CHILD: PARENT_ID integer, CHILD_ID integer, DESCRIPTION character varying(50)

Что я хотел бы видеть, так это то, что каждая строка в CHILD имеет CHILD_ID, который начинается с 1 и увеличивается на 1, уникальный для PARENT_ID.Это было бы похоже на номер редакции.Например..

PARENT_ID 1, CHILD_ID 1
PARENT_ID 1, CHILD_ID 2
PARENT_ID 1, CHILD_ID 3
PARENT_ID 2, CHILD_ID 1
PARENT_ID 3, CHILD_ID 1
PARENT_ID 3, CHILD_ID 2

Есть ли какой-либо способ автоматически присвоить значение CHILD_ID, например последовательность или ограничение, только с возможностью повторного использования CHILD_ID, который был удален?Единственный способ, которым я могу выяснить, - это что-то похожее на этот SQL.

INSERT INTO child SELECT parent_id, MAX(child_id)+1, 'description' FROM child WHERE parent_id = :PARENT_ID GROUP BY parent_id

Хотя это немного халтурно.Я понимаю, что нормализация базы данных предполагает, что у вас не должно быть одного ключа, связанного с другим, но у меня нет такой возможности по некоторым другим причинам.Есть какие-нибудь идеи?

Редактировать: Название уродливое.Если кто-нибудь из вас, набравших высокие баллы, может придумать более точный вариант, пожалуйста, не стесняйтесь изменить его.

Это было полезно?

Решение

Я бы предложил использовать:

CHILD: PARENT_ID integer, CHILD_ID serial, DESCRIPTION character varying(50)

Когда вам нужно получить желаемый результат:

  • Вы можете подсчитывать строки на стороне клиента.

  • При выборе строк, где PARENT_ID=?вы можете использовать временную последовательность.

  • В скоро выходящем Postgresql 8.4 вы можете использовать оконные функции, подобные этой:

    $ create table child (parent_id integer, child_id serial);
    NOTICE:  CREATE TABLE will create implicit sequence "child_child_id_seq" for serial column "child.child_id"
    CREATE TABLE
    
    $ insert into child (parent_id) values (1), (1), (1), (2), (3), (3);
    
    $ select * from child;
     parent_id | child_id 
    -----------+----------
             1 |        1
             1 |        2
             1 |        3
             2 |        4
             3 |        5
             3 |        6
    (6 rows)
    
    $ select parent_id, row_number() over (partition by parent_id order by child_id) from child;
     parent_id | row_number 
    -----------+------
             1 |          1
             1 |          2
             1 |          3
             2 |          1
             3 |          1
             3 |          2
    (6 rows)
    

Это очень быстро, легко реализовать и будет очень хорошо масштабироваться, так как не будет никаких проблем с параллелизмом, о которых стоило бы беспокоиться.

Другие советы

Однако эта вставка - это еще не вся история.Вам также нужно будет обработать удаления, чтобы закрыть созданный пробел, если вы действительно хотите, чтобы числа были смежными.

Мое предложение состояло бы в том, чтобы вывести это значение по мере необходимости.Что определяет порядок следования чисел?Если это дата, введенная в систему, то добавьте эту дату в свою таблицу и поместите свой PK поверх parent_id и этой даты, тогда вы можете довольно легко получить номер либо через SQL, либо во внешнем интерфейсе, если вам это нужно.

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

BEGIN
-- Get and hold onto parent_id and version values.
SELECT PARENT_ID, VERSION FROM PARENT WHERE PARENT_ID = :PARENT_ID;
-- Use the values to insert into the child table
INSERT INTO CHILD (PARENT_ID, CHILD_ID) VALUES (:PARENT_ID, :VERSION);
-- Update the version using an optimistic lock.
UPDATE PARENT SET VERSION = VERSION + 1 WHERE PARENT_ID = :PARENT_ID AND 
                                              VERSION = :VERSION_ID
-- If no rows are updated rollback the transaction and try again.
END

Это гарантирует, что дочерние идентификаторы будут строго возрастать, но не будет повторного использования значений идентификаторов после удаления.Если вы сможете избежать ограничения повторного использования старых идентификаторов, это упростит ваше решение (и решение будет более эффективным).Если вы должны повторно используйте идентификаторы, тогда у вас есть 2 варианта, во-первых, решение, указанное вами выше, но при удалении перенумеровывается все значения, которые встречаются после того, которое вы удалили.Другой вариант - иметь какую-то функцию, которая сканирует дочерние идентификаторы по порядку и сравнивает их с набором последовательных номеров и возвращает значение, когда первый не найден.Оба эти решения являются более сложными и будут медленными, поскольку вам нужно будет снять блокировку строк, чтобы предотвратить одновременные обновления, и либо вставки, либо как вставки, так и удаления, повлекут за собой штраф O (n).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top