3 поля составной первичный ключ (уникальный элемент) в Dynamodb
Я пытаюсь создать таблицу для хранения позиций счетов-фактур в DynamoDB. Допустим, элемент определяется CompanyCode
, InvoiceNumber
и LineItemId
, количеством и другими деталями позиции.
Уникальный элемент определяется комбинацией первых 3 атрибутов. Любые 2 из этих атрибутов могут быть одинаковыми для разных предметов. Что я должен выбрать в качестве атрибута хеширования и атрибута диапазона?
Ответы
Ответ 1
Я считаю, что первый вариант, предлагаемый @georgeaf99, не будет работать, потому что если вы сделаете это таким образом, то CompanyCode
должен быть уникальным в таблице. Поэтому в каждой компании будет разрешен только один товар. Я думаю, что второе решение - единственный реальный способ сделать это.
Вы можете использовать CompanyCode
в качестве хэш-ключа, а затем все другие поля, которые объединяются, чтобы сделать элемент уникальным (в этом случае InvoiceNumber
и LineItemId
), должны быть каким-то образом объединены в одно значение (например, конкатенация с разделителем полей), которое будет ваш ключ диапазона. К сожалению, это некрасиво, но это характер базы данных NoSQL, такой как DynamoDB. Тем не менее, это позволит вам успешно хранить записи с правильной уникальностью. При обратном чтении записей, если вы не хотите разбирать объединенное поле обратно на отдельные его части, вам придется добавить дополнительные отдельные поля для InvoiceNumber
и LineItemID
.
Если у вас нет большого количества счетов на одну компанию, вы можете делать запросы только по хеш-ключу и выполнять фильтрацию на стороне клиента. Если у вас есть большое количество счетов на одну компанию и вам нужно иметь возможность запрашивать только элементы для одного счета, то я бы создал дополнительный индекс для CompanyCode и InvoiceNumber.
Ответ 2
Некоторое вступление
Для эффективности я бы предложил совершенно другой дизайн. С базами данных NoSQL (и DynamoDB не отличаются) нам всегда нужно сначала рассмотреть шаблоны доступа. Также, если это возможно, мы должны стараться вписать все наши данные в одну таблицу и несколько индексов. Исходя из того, что мы получили из OP и его комментариев, это две модели доступа:
- Для компании X получите полный счет Y (включая все товары или ассортимент товаров) [на основе этого комментария ]
- Получить все счета для компании 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
- Это позволяет делать запросы только по индексу, но структура таблицы довольно некрасива