3 поля составной первичный ключ (уникальный элемент) в Dynamodb

Я пытаюсь создать таблицу для хранения позиций счетов-фактур в DynamoDB. Допустим, элемент определяется CompanyCode, InvoiceNumber и LineItemId, количеством и другими деталями позиции.

Уникальный элемент определяется комбинацией первых 3 атрибутов. Любые 2 из этих атрибутов могут быть одинаковыми для разных предметов. Что я должен выбрать в качестве атрибута хеширования и атрибута диапазона?

Ответ 1

Я считаю, что первый вариант, предлагаемый @georgeaf99, не будет работать, потому что если вы сделаете это таким образом, то CompanyCode должен быть уникальным в таблице. Поэтому в каждой компании будет разрешен только один товар. Я думаю, что второе решение - единственный реальный способ сделать это.

Вы можете использовать CompanyCode в качестве хэш-ключа, а затем все другие поля, которые объединяются, чтобы сделать элемент уникальным (в этом случае InvoiceNumber и LineItemId), должны быть каким-то образом объединены в одно значение (например, конкатенация с разделителем полей), которое будет ваш ключ диапазона. К сожалению, это некрасиво, но это характер базы данных NoSQL, такой как DynamoDB. Тем не менее, это позволит вам успешно хранить записи с правильной уникальностью. При обратном чтении записей, если вы не хотите разбирать объединенное поле обратно на отдельные его части, вам придется добавить дополнительные отдельные поля для InvoiceNumber и LineItemID.

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

Ответ 2

Некоторое вступление

Для эффективности я бы предложил совершенно другой дизайн. С базами данных NoSQL (и DynamoDB не отличаются) нам всегда нужно сначала рассмотреть шаблоны доступа. Также, если это возможно, мы должны стараться вписать все наши данные в одну таблицу и несколько индексов. Исходя из того, что мы получили из OP и его комментариев, это две модели доступа:

  1. Для компании X получите полный счет Y (включая все товары или ассортимент товаров) [на основе этого комментария ]
  2. Получить все счета для компании X [на основе этого комментария ]

Теперь нам интересно, что такое хороший первичный ключ? Переводит на вопрос, что такое хороший ключ разделения (PK) и что такое хороший ключ сортировки (SK) и какие вторичные индексы нам нужно создать и какого типа (локальные или глобальные)? Некоторые напоминания:

  • Первичный ключ может быть на одном столбце или составном
  • Составной первичный ключ состоит из ключа раздела и ключа сортировки
  • Ключ разделения используется в качестве входных данных для функции хеширования, которая будет определять раздел элементов
  • Ключ сортировки также может быть составным, что позволяет нам моделировать отношения "один ко многим" в DynamoDB, как указано в одной из ссылок на комментарии: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-sort-keys.html
  • При создании запроса к таблице или индексу вам всегда нужно использовать оператор '=' для ключа раздела
  • При запросе диапазонов по ключу сортировки у вас есть опция для KeyConditionExpression, которая предоставляет вам набор операторов для сортировки и всего, что между ними (один из них является функцией begins_with (a, substr))
  • Вам также разрешено использовать FilterExpression, если вам нужно дополнительно уточнить результаты запроса (отфильтровать по проецируемым атрибутам)
  • Локальные вторичные индексы (LSI) имеют тот же ключ раздела, но другой ключ сортировки, что и исходная таблица, и дают вам другое представление о ваших данных, организованных в соответствии с альтернативным ключом сортировки
  • Глобальные вторичные индексы (GSI) имеют другой ключ раздела и другой ключ сортировки, чем исходная таблица, и дают вам совершенно другое представление о данных
  • Все элементы с одним и тем же ключом разделения хранятся вместе, а для составных первичных ключей упорядочиваются по значению ключа сортировки. DynamoDB разделяет разделы по ключу сортировки, если размер коллекции превышает 10 ГБ.

Вернуться к моделированию

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

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

  • Отношения между компанией и счетами всегда 1: много.
  • Соотношение между Счетом-фактурой и Предметами всегда 1: много.

Я предлагаю дизайн как на изображении ниже: Proposed design in DynamoDB

  • Если PK - это CompanyCode, а SK - InvoiceNumber, то можно хранить все атрибуты этого счета для этой компании.
  • Ничто не мешает мне также добавить запись, где SK - Customer, что позволяет мне хранить все атрибуты о компании.
  • С помощью GSI1 мы создадим обратный поиск, где GSI1PK - это мои таблицы SK (InvoiceNumber), а мой GSI1SK - это мои таблицы PK (CompanyCode).
  • Я использую ту же таблицу для хранения позиций, в которых PK - LineItemId, а SK - CompanyCode (все еще уникально)
  • Для элементов сущности Item мой GSI1PK по-прежнему InvoiceNumber, а мой GSI1SK - LineItemId, который представляет собой таблицы PK, так же как и для элементов сущности Invoice.

Теперь шаблоны доступа поддерживаются этим:

  • Если я хочу получить счет Y для компании X и всех товаров (шаблон доступа 1): запросите таблицу, где CompanyCode=X, и используйте KeyConditionExpression с оператором = на ключе сортировки InvoiceNumber. Если я хочу привязать все элементы к этому счету, я спроецирую атрибут Items, используя ProjectionExpression.
  • Получив все элементы с помощью предыдущего запроса для компании X и счета Y, я теперь могу запустить вызов API BatchGetItem (используя мой уникальный составной ключ LineItemId+CompanyCode) для таблицы, чтобы получить все элементы, принадлежащие этому конкретному счету этого конкретного клиента. (это связано с некоторыми ограничениями BatchGetItem API)
  • Чтобы поддержать шаблон доступа 2, я сделаю запрос с CompanyCode=X на ПК и использую KeyConditionExpression на СК с функцией/оператором begins_with (a, substr), чтобы получить только счета для компании X, а не метаданные об этой компании. Это даст мне все счета для данной компании/клиента.
  • Кроме того, с указанным выше GSI1 для любого заданного InvoiceNumber я могу легко выбрать все позиции, которые относятся к этому конкретному счету. ПОМНИТЕ: Значения ключей в глобальном вторичном индексе не обязательно должны быть уникальными - поэтому в моем GSI1 я мог легко получить invoice_1 → (item_1, item_2), а затем еще один invoice_1 → ( item_1, item_2) но разница между двумя элементами в GSI будет в SK (это будет связано с разными CompanyCode (но для демонстрационных целей я использовал invoice_1 и invoice_2).

Ответ 3

Я уверен, что вы поняли, что у вас не может быть больше двух атрибутов, образующих ваш первичный ключ (хэш + диапазон). Таким образом, в зависимости от типа запросов, которые вы будете выполнять, и размера ваших данных, вы можете структурировать таблицу различными способами.

(Оптимизировано для указанного выше типа запроса: только CompanyCode и все 3)

Лучшее решение для небольших/средних наборов данных:

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

Оптимальное решение для больших массивов данных:

  • Ключ CompanyCode: CompanyCode
  • Ключ диапазона: InvoiceNumber + LineItemId
  • Это позволяет делать запросы только по индексу, но структура таблицы довольно некрасива