Каков типичный метод предотвращения дублирования в таблице PostgreSQL?

Рассмотрим следующую таблицу recipe в базе данных PostgreSQL.

Столбец Тип Сортировка Обнуляемый По умолчанию имя текст не ноль дата дата не ноль ингредиенты текст[] не ноль калории целое число не ноль

Предположим, пользователь хочет добавить в таблицу новый рецепт. Пользователь не хочет дублирования в таблице.

Как лучше всего вставлять новую строку в эту таблицу без дубликатов?

Решение 1

Прежде чем добавлять какие-либо рецепты, введите схему идентификации. Можно сказать, что любые два рецепта с одинаковыми name и ingredients являются дубликатами. Цель состоит в том, чтобы предотвратить возникновение дубликатов, поэтому генерируется уникальный идентификатор на основе названия рецепта и ингредиентов (каким-то образом), а столбец добавляется в базу данных.

Столбец Тип Сортировка Обнуляемый По умолчанию идентификатор текст не ноль имя текст не ноль дата дата не ноль ингредиенты текст[] не ноль калории целое число не ноль

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

Один (плохой) возможный способ создания этого уникального идентификатора — объединить name и ingredients любого нового рецепта, хэшировать его и получить из него уникальную 256-битную строку.

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

Решение 2

Создайте составной первичный ключ, состоящий из столбцов name и ingredients.

Следующий пример довольно плох, но потерпите меня.

Если я запущу поиск рецептов в Интернете и добавлю рецепты в свою таблицу, старые рецепты, которые были обновлены для большей точности calories, будут обновлены.

Решение 3

При каждой добавлении в таблицу сверяйте новый рецепт с name и ingredients всех существующих рецептов. Если есть коллизия, обновите столбец calories рецепта в таблице.

Если мой пример recipe масштабируется до тысяч или миллионов строк, решения 1 и 3 не работают.

Риски и ограничения

Решение 1 не работает, потому что коллизии хеширования становятся реальной возможностью в больших масштабах.

Решение 3 «ломается», сравнивая каждую новую запись с каждой строкой в ​​очень большой таблице было бы ужасно неэффективно.

1) Не делайте этого ingredients text[], разнесите ингредиенты в отдельную таблицу. 2) Определить дубликаты будет сложно, потому что все, что вам нужно, это опечатки или различия в написании (например, цвет/цвет, серый/серый), чтобы это не сработало.

Adrian Klaver 13.04.2024 00:23

Просто создайте уникальный указатель по названию и ингредиентам.

yuri kilochek 13.04.2024 00:29

{"stuff", "pasta"} тот же рецепт, что и '{"pasta", "stuff"}'?

Schwern 13.04.2024 00:46

@AdrianKlaver спасибо за ответ. Хотя вы абсолютно правы, этот пример в лучшем случае груб. Суть вопроса заключается в однозначной идентификации строк данных без произвольного столбца идентификатора.

adam 13.04.2024 03:37

@yurikilochek это было бы решение №2. Спасибо за ваш совет.

adam 13.04.2024 03:39

@Schwern предполагает, что список ингредиентов отсортирован в алфавитном порядке и все строчные буквы без опечаток. Т.е. два списка ингредиентов с одинаковыми ингредиентами всегда будут идентичными.

adam 13.04.2024 03:40
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
80
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Какие поля составляют «уникальную идентификацию» — это бизнес-требование, а не техническое. Например, разумным и общепринятым было бы указать, что название рецепта должно быть уникальным, и все, не нужно усложнять больше, и нет смысла говорить, что два рецепта различны только потому, что я написал 815 калорий, а вы написали 810, остальное то же самое. Затем, чтобы создать вариант/немного другую версию уже существующего рецепта, просто дайте ему новое, уникальное имя, которого будет достаточно, чтобы различать, что есть что, когда пользователь просматривает список названий рецептов в каталоге... Или, например. для объекта «Клиенты» в системе бухгалтерского учета, возможно, потребуется НДС, если он уникален на национальном уровне (они есть не во всех странах, и необходим некоторый дополнительный элемент); в то время как для сайта электронной коммерции обычно достаточно электронной почты, поскольку этого достаточно для индивидуальной юридической ответственности в случае возникновения дела, в то время как человек с несколькими учетными записями (под разными адресами электронной почты) сам по себе не является проблемой. И так далее, это всего лишь примеры рассуждений.

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

Наконец, что касается производительности: при правильном использовании СУБД таких проблем не возникает, ведь СУБД якобы реализует наиболее оптимизированный функционал в этом смысле. В частности, ограничение уникальности основано на базовом уникальном индексе (аналогично для ПК), и для обеспечения соблюдения этих ограничений не требуется полное сканирование строк таблицы (возможно, с некоторыми оговорками, которые зависят от конкретной СУБД). .

Хулио, спасибо за ответ. Вы развеяли мое замешательство.

adam 13.04.2024 03:46

Предполагая, что вам не нужно сохранять уникальное имя рецепта, а скорее определить, совпадают ли имя рецепта и ингредиенты, у вас есть два варианта.

  1. Если список ингредиентов не слишком длинный

Создайте таблицу рецептов и индекс для таблиц названий и ингредиентов.

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

  1. Если у вас список ингредиентов больше

Создайте таблицу рецептов, таблицу ингредиентов и таблицу рецепта_ингредиентов для отношений «многие ко многим».

Таблица рецепта_ингредиентов может быть создана с использованием внешних ключей или обычных индексов, на ваше усмотрение.

Если вы правильно используете индексы, поиск в базе данных перед вставкой новой строки не будет проблемой.

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

Самое приятное в вашем ответе — это создание структуры 1-Many. ПК для одной стороны будет названием рецепта, а ПК для ингредиентов будет названием ингредиента. Для дочернего элемента можно создать уникальный индекс, чтобы комбинация IngridiantName+RecipeName была уникальной. Обратите внимание, что это не имеет никакого отношения к тому, маленькая или большая таблица.

NoChance 14.04.2024 22:55

Создайте уникальный указатель названий и ингредиентов.

create unique index recipe_uniq_idx on recipe(name, ingredients);

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

Это также действует как индекс по имени. ингредиенты должны иметь свой индекс джина.

В качестве первичного ключа используйте обычный целочисленный первичный ключ. Почему? Подумайте, использовали ли вы (name, ingredients) в качестве первичного ключа. Все, что хочет ссылаться на строку, должно копировать имя и все ингредиенты; это неудобно и медленно. То же самое, если вы хотите сослаться на рецепт в URL-адресе. Простое целое число делает ссылки простыми и маленькими.


Однако, хотя массивы и заманчивы, обычно лучше хранить списки в виде объединяющей таблицы. Большинство баз данных не поддерживают массивы, так что это тупик. Они делают запросы нестандартными. К ним нельзя прикрепить больше данных, разве ингредиенты в рецепте не нужны порциями?

create table recipes (
  id serial primary key,
  name text not null,
  calories int not null,

  -- Don't name a column "date", that's a keyword and it doesn't explain
  -- what the date is for. Use timestamp for the extra accuracy, you can
  -- always convert it to a date.
  created_at timestamp not null default now()
);

create table recipe_ingredients (
  recipe_id integer not null references recipes,
  name text not null,
  unit text not null,
  portion numeric(6,2) not null
);

select r.name, array_agg(ri.name)
from recipes r
join recipe_ingredients ri on r.id = ri.recipe_id
group by r.id

Это затрудняет обнаружение дубликатов. Вы можете проверить перед вставкой вот так:

select r.id, r.name, array_agg(ri.name) as ingredients
from recipes r
join recipe_ingredients ri on r.id = ri.recipe_id
group by r.id
having array_agg(ri.name) @> $1 and array_agg(ri.name) <@ $1

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

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

Модель EF Core не может выполнить миграцию из-за того, что PrimaryKey ссылается на ForeignKey
Проблема первичного ключа таблицы, импортированной из CSV, с использованием MySQL Workbench (Linux Mint 21 Vanessa)
Как определения первичного ключа Snowflake влияют на производительность запросов?
Как правильно установить последовательность GENERATED BY DEFAULT AS IDENTITY после вставки ручных идентификаторов?
Entity Framework пытается добавить нулевой первичный ключ с автоматическим увеличением
Добавление первичного ключа в секционированную таблицу PostgreSQL 12 без длительной блокировки
Конфликт первичного ключа в рамках одной транзакции
Ошибка Oracle ORA-02270: нет соответствующего уникального или первичного ключа для этого списка столбцов
У вас есть ошибка в синтаксисе SQL; проверьте руководство, соответствующее версии вашего сервера MySQL, для правильного синтаксиса для использования рядом с «PRIMERY KEY»
Каковы эмпирические правила выбора правильного ключа раздела в Cassandra?