Первичный ключ - составной или суррогатный?Источник: ln
Этот выпуск посвящен "вечной" теме выбора столбцов для первичного ключа. По мотивам случайно обнаруженного замечательного ответа Тома Кайта на вопросы, заданные в 2001-2003 годах Первичный ключ - составной или суррогатный?Том, У меня есть таблица из 3 полей, комбинация значений которых уникальна для каждой записи. Вот эти поля: Object_ID CHAR(4) Ticket_Number NUMBER Start_DateTime DATE Ни одно из этих полей никогда не будет пустым, и ни один объект в один и тот же момент времени не будет иметь такое же значение Ticket_Number. Поэтому мне кажется, что, вместо добавления нового поля, единственным назначением которого будет уникально идентифицировать строку в таблице, я могу использовать комбинацию этих трех полей. Но в руководстве PL/SQL Developer's Guide рекомендуется не использовать составные первичные ключи. Что ты думаешь по этому поводу? Надо учитывать также следующее: Мне придется вставлять эти же значения для составных внешних ключей в несколько других таблиц. Не будет ли снижена производительность при использовании составных ключей по сравнению с простым ключом, с последовательными номерами записей? Может ли быть ее причиной необходимость сравнивать также строки и даты? С другой стороны, если лучше добавить новое числовое поле для идентификации записей, как проще всего увеличивать значение этого поля при каждой вставке? Есть ли в Oracle подобие типа данных autonumber MS Access? Ответ Тома КайтаЕсли требуется, чтобы "эти три поля уникально идентифицировали запись в любом случае", придется задавать по ним ограничение уникальности (UNIQUE CONSTRAINT) в любом случае. Если дублирование object_id,ticket_number,start_datetime - ошибка, ограничение уникальности НЕОБХОДИМО. Можно добавить еще одно поле в таблицу в качестве "первичного ключа", но это не снимает необходимости добавления ограничения уникальности по данным трем полям. Если на первичный ключ придется ссылаться во внешних ключах многих таблиц, имеет смысл использовать суррогатный ключ. Если внешних ключей немного, я бы просто использовал составной первичный ключ. Чтобы получить тип "auto increment" в Oracle, необходимо выполнить: create sequence my_seq; create trigger my_trigger before insert on T for each row begin select my_seq.nextval into :new.ID from dual; end; / Иногда высказывают опасение, что при генерации последовательных номеров таким образом возможны пропуски (связанные с откатом транзакции, например - В.К. )... Да, использование последовательности не гарантирует отсутствие пропусков при нумерации - в любой не однопользовательской системе они точно будут. Единственный способ нумеровать без пропусков - обрабатывать транзакции по одной. Последовательности предназначались не для этого, и любая система с таким требованием не будет масштабироваться (для компьютерных систем оно вообще смешное - при обработке документов вручную еще куда ни шло, но в компьтерной системе - оно просто бессмысленно). Последовательности - хорошо масштабируемый способ генерации суррогатных ключей. Я считаю, что составные ключи прекрасно работают и могут использоваться при наличии внешних ключей, но:
Эти соображения необходимо учитывать. Как я уже писал, если составной первичный ключ не используется в качестве внешнего во многих таблицах - используйте его. В противном случае серьезно задумайтесь над использованием суррогатного ключа на базе последовательности (а про "пропуски" значений не думайте вовсе - важно, что получается уникальный идентфикатор). Составной ключ в одном столбцеМне интересно твое мнение о том, чтро делать, если клиенты настаивают на использовании "магических кодов" - составных ключей, впихнутых в один столбец. Простой пример - следующий идентификатор события: 03-40123 Формат: FY-XNNNN где: FY = 2 цифры финансового года X = 1 цифра, задающая тип события NNNN = 4 цифры, задающие порядковый номер события данного типа в этом финансовом году Лично я пыаюсь избегать такого рода идентификаторов (как бы они не генерировались) как чумы, по следующим, как мне кажется, очевидным, причинам:
Я периодически сталкиваюсь с этой проблемой, обычно - при обновлении старых систем и/или систем "бумажного" документооборота, пользователи которых не хотят изменять систему нумерации. Часто мне удается уговорить клиента перейти на простые последовательности, но не всегда. Я знаю, что ты не сторонник простых правил, но оправдывает ли природа данных или необходимость "запоминающегося" ключа создание такого типа ключей? Когда ты считаешь обоснованным использование такого составного ключевого столбца для идентфикации данных? Добавишь ли ты собственный суррогатный ключ и позволишь пользователям хранить свои магические коды где угодно, или будешь настаивать на использовании простой последовательности? Ответ Тома КайтаТакого рода поля могут (и должны) быть ПРОИЗВОДНЫМИ от других данных. Клиенту нет необходимости знать, как в физической схеме фактически реализован первичный ключ - это деталь реализации. Так что, я бы сделал так: create table t ( id int number primary key, /* заполняется значениями последовательности */ fy date, incident_type varchar2(1), goofy_number number, ... create view v as select t.*, to_char(fy,'yy')//'-'//incident_type//to_char(goofy_number,'fm0000') their_field from t; Можно даже создать индекс по функции (function-based index) по полю their_field, если они собираются искать по его значениям. Комментарий читателя от 23 ноября 2002 годаКак обычно, твое решение предельно ясно. Мне особенно понравилась идея про индекс по функции! Однако это не рашает мою проблему "изменения адреса". Как я попытался объяснить в первом пункте, как только значения fy, incident_type, и goofy_number определены и строка вставлена, значение their_number тоже неявно определено. С этого момента значение their_number может выдаваться в отчетах, сообщаться заинтересованным сторонам и т.д. Если оказывается, что, например, значение incident_type перовначально оказалось ошибочным, и оно изменяется, значения their_number в базе данных и в отчетах, у заинтересованных сторон и т.д. перестают совпадать. Можно строить their_number по столбцам, которые не меняются, но это не всегда соответствует требованиям клиента. Можно строить значение their_number при вставке, помещать в отдельный столбец (с ограничением уникальности, а не первичного ключа), и больше никогда его не изменять при изменении базовых значений его компонентов. В конечном итоге, меня интересует следующее: Как "эксперт", нанятый для создания солидных моделей данных, не выхожу ли я за пределы моих полномочий (и не трачу ли зря время), часами пытаясь убедить клиентов не использовать their_number, а заменить его простым значением последовательности? Ответ Тома КайтаЕсли вы представили им все факты, как в вопросе, продемонстрировав, что это может привести к ошибкам в интерпретации данных, и они все равно настаивают на своем - вы сделали все, что могли. Можете включить СВОЙ первичный ключ в отчеты, чтобы при возникновении проблемы можно было получить соотвествующее значение. Вы не выходите за пределы своих полномочий. Я неоднократно повторял, что наша работа как раз и состоит в том, чтобы обращать на подобные вещи мнимание тех, кто не является профессиональным программистом. Последний раз пободная проблема возникла, когда меня спросили на сайте, как выбрать N случайных строк из таблицы. Я написал, как это сделать, но проблема все усложнялась, пока не выяснилось, что нужна случайная выборка 4 строк из сложного запроса со множеством соединений и т.п. Причем, выборка эта должна была делаться сотни/тысячи раз в день. Для этого требовалось множество ресурсов. А зачем все это понадобилось? Чтобы на портале "вывесить" фотографии 4 случайно выбранных сотрудников. Я ответил: "Сообщите клиентам, что 90% ресурсов машины теперь будет уходить на выдачу этих 4 фотографий, - захотят ли они за это платить". Мнения разделились - надо ли "знать свое место" и тупо, как бараны, делать то, что требуют, или доказывать, что практически бесполезная возможность дается дорогой ценой, и не нужна. Я бы продолжал настаивать на своем - ваши аргументы на 100% верны. Если они решат не прислушиваться к советам, попытайтесь, по возможности, защитить их от проблем (с помощью суррогатного ключа). Не хотел бы я работать там, где за год происходит только 9999 событий... Маловато перспектив для роста... А первого января придется этот смешной счетчик снова в 0 сбрасывать... Изменение составного первичного ключаУ нас есть две таблицы следующего вида: create table t1(c1 number, c2 varchar2(50), constraint t1p primary key(c1, c2)); create table t2(c1 number, c2 varchar2(50), c3 number, constraint t2p primary key(c1, c2, c3), constraint t2f foreign key (c1, c2) references t1(c1, c2)); insert into t1(c1, c2) values(1, 'c2'); insert into t2(c1, c2, c3) values(1, 'c2', 3); Необходимо изменить значение c2 в таблице t1. Нет ли способа изменить этот первичный ключ, не создавая суррогатного? Ответ Тома КайтаРаз так, c1,c2 не является первичным ключом - первичный ключ не должен меняться. Если хотите, используйте ограничения с отложенной проверкой (deferrable constraints). Можете использовать этот пакет, но если такое действие считается "нормальным", и изменения будут происходить постоянно - выбирайте другой первичный ключ. Изменение первичного ключа. Комментарий от 15 января 2003 годаПакет работает прекрасно. Вопросы:
Ответ Тома КайтаНе знаю, как относиться к утверждения, что "Пакет работает прекрасно". Сомнительный комплимент, как по мне...
Изменение первичного ключа. Комментарий от 16 января 2003 годаСразу после установки пакета и создания таблиц t1, t2, t3, для изменения первичного ключа мне пришлось сначала выполнить команду: exec update_cascade.on_table('t2') Но если открыть новый сеанс после этого, изменить первичный ключ мне удалось и без команды 'exec update_cascade.on_table('t2')'. Но я не хочу, чтобы пользователи постоянно меняли этот первичный ключ. Вот пример: SQL> connect UCDEMO/UCDEMO@e2rs Connected. SQL> update t2 2 set b=900 3 where b = 6; -- первичный ключ изменен без выполнения 'exec update_cascade.on_table('t2')' 1 row updated. Ответ Тома КайтаКоманда exec update_cascade.on_table('t2') создала (как и описано на указанной странице) триггеры и пакеты для поддержки каскадного изменения. Если его больше не нужно поддерживать, УДАЛИТЕ их. Как насчет использования sys_guid() вместо последовательности для генерации значений первичного ключа?Какие преимущества и недостатки связаны с использованием sys_guid() (подозреваю, что используется больше места на диске?) sys_guid() можно указать в качестве стандартного значения (и не понадобиться триггер). Понятно, что "пропуски" значений - не проблема :) Ответ Тома КайтаДа, RAW(16) - больше по размеру, чем большинство числовых полей. Его не так удобно записывать, как число. Во многих случаях данные типа raw не обрабатываются - они неявно преобразуются в 32-байтовую строку типа varchar2. Не уверен, что использовал бы этот подход без веских причин. Первичный ключ: sys_guid или последовательностьМы используем sys_guid вместо последовательности (есть требование глобальной уникальности первичных ключей) - в любом случае, имеет смысл сделать тест и поделиться результатами. Результаты показали следующее:
Ответ Тома Кайта
Комментарий от 4 августа 2003 годаВ документации Oracle 9.0.1.1.1 сказано, что: "Составной первичный ключ может содержать не более 32 столбцов". Но я попробовал задать 33 столбца, и все получилось. Почему? Вот как я это делал. create table Test(x1 number(2) ,x2 number(2) ,x3 number(2) ,x4 number(2) ,x5 number(2) ,x6 number(2) ,x7 number(2) ,x8 number(2) ,x9 number(2) ,x10 number(2),x11 number(2),x12 number(2),x13 number(2),x14 number(2),x15 number(2),x16 number(2),x17 number(2),x18 number(2),x19 number(2),x20 number(2),x21 number(2),x22 number(2),x23 number(2),x24 number(2),x25 number(2),x26 number(2),x27 number(2),x28 number(2),x29 number(2),x30 number(2),x31 number(2),x32 number(2),x33 number(2),x34 number(2),x35 number(2),x36 number(2),x37 number(2),x38 number(2),x39 number(2),x40 number(2), primary key( x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x2 3,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33)) / Ответ Тома КайтаПохоже, предел - 33 столбца. Меня это не беспокоит, поскольку "1" - наиболее типичное количество, а 5 или 6 - разумный максимум... 32 будет "слегка перебор", а 33 - еще хуже. Первичный ключ: sys_guid или последовательность - комментарий от 18 августа 2003 годаМы используем sys_guid, поскольку хотим избежать конфликтов при переносе данных из одной базы в другую. Точная причина мне не известна, но при экспортировании данных из базы db1 и импортировании в другую базу данных, db2, при использовании последовательностей возможны конфликты (поскольку одинаковые последовательности создавались в обеих схемах). Сталкивались ли вы с такой ситуацией? Нет ли более элегантного решения проблемы, кроме использования sys_guid вместо последовательностей. Мы не знаем точного количества баз и не можем просто начинать последовательности с разных значений. Ответ Тома КайтаПусть имеется N баз, которые потенциально придется поддерживать. Если не уверены, увеличьте количество в 100 раз. Потом в перовй базе выполняем: create sequence S start with 1 increment by n*100; Во второй: create sequence S start with 2 increase by n*100; Пусть n = 10, тогда в первой базе будут генерироваться числа: 1, 1001, 2001, 3001, ... Во второй: 2, 1002, 2002, 3002, ... Получили не перекрывающиеся последовательности для 1000 баз данных. Не перекрывающиеся последовательности!"не перекрывающиеся последовательности для 1000 баз данных." Я находил это решение на сайте. Оно очень элегантно, но надо заранее знать количество баз. В нашем случае требовалось, чтобы можно было наполнять данными любую локальную схему, а затем путем экспорта/импорта добавлять накопленные данные в центральную базу. В идеале один и тот же сценарий установки должен работать всегда, не создавая конфликтов в любой базе. Да, кроме использования централизованной таблицы с начальными значениями для последовательностей, к которой будут обращаться последовательно, - другой достойной альтернативы не видно... |