Я создаю приложение управления, чтобы помочь управлять моей мобильной автоматической компанией по детализации (и надо надеяться другие). Я изо всех сил пытаюсь выяснить, как смоделировать некоторые данные.
Этот вопрос связан с предыдущим вопросом, который я отправил, но я воспроизвел релевантную информацию ниже: Проектирование баз данных - механизм приложения Google
В этом приложении существует понятие "Назначений" и "Позиций".
Назначения являются местом и время, где сотрудники, как ожидают, будут в порядке для предоставления услуги.
Позиции являются сервисом, сбором или скидкой и ее связанной информацией. Пример позиций, которые могли бы войти в назначение:
Name: Price: Commission: Time estimate Full Detail, Regular Size: 160 75 3.5 hours $10 Off Full Detail Coupon: -10 0 0 hours Premium Detail: 220 110 4.5 hours Derived totals(not a line item): $370 $185 8.0 hours
В моей предыдущей реализации этого приложения Позиции содержались единственным назначением. Это хорошо работало большую часть времени, но иногда вызывало проблемы. Пример был бы то, если бы назначение было прервано на полпути через из-за дождя, и технический специалист должен был возвратиться на следующий день и закончить. Эта ситуация потребовала двух назначений для той же позиции. В случаях как это я просто уклонился бы от данных немного путем установки "позиции" на втором назначении для чтения, чему-то нравится, "Заканчиваются", и затем стоимость составила бы 0$.
В этой следующей версии я полагаю, что Позиции включения согласованы больше чем одной встрече со структурой таблицы, которая похожа на это:
Appointment
start_time
etc...
Line_Item
appointment_Key_List
name
price
etc...
Общая проблема с этой структурой состоит в том, что она является сложной, и я даже не уверен если ее соответствующее для соответствия одной позиции нескольким назначениям. Если Позиции могут только быть частью одного Назначения, то я могу на самом деле просто поместить список позиций В каждом Назначении, когда я получаю Назначения, я уже получил бы Позиции.
Более определенная проблема состоит в том, что я использую механизм приложения Google и если бы я хочу запросить для ряда назначений и их связанных позиций, я должен был бы сначала запросить для набора назначений и затем сделать второй запрос для позиций с помощью оператора IN, чтобы протестировать, если какой-либо из ключей назначения Line_Item попадает в набор ключей назначения, возвращенный из предыдущего запроса. Второй запрос перестанет работать, если у меня будет больше чем 30 ключей, требующих меня к черепку запрос. Я мог денормализовать данные для предотвращения этого сложного и обширного запроса чтения, и я должен буду, вероятно, денормализовать до некоторой степени так или иначе, но я избежал бы сложности в соответствующих случаях.
Мой вопрос состоит в том, как этот тип ситуации обычно моделируется? Для Позиции даже уместно быть соединенным больше чем с одним назначением, или действительно ли нормально просто разделить позиции на отдельные для каждого назначения, такие как "1-я половина 2-дневного задания" и "2-я половина двухдневного задания". Как подобные успешные приложения делают это? Каковы эмпирические правила в этом типе ситуации? Какие реализации оказались менее проблематичными?
Спасибо!
Предлагаемый вами подход будет работать нормально; вы можете смоделировать строку «assign_Key_list» как свойство списка, и оно будет работать так, как вы ожидаете. Вам не нужно использовать оператор IN - это для сопоставления одного значения в хранилище данных со списком имеющихся у вас ключей (например, "WHERE datastore_column IN ('a', 'b', 'c')), а вы делаете обратное - сопоставляете одно значение со списком в хранилище данных.
Я бы предположил, однако, что обратное могло бы лучше подходить для вашей задачи: пусть каждое мероприятие имеет список ключей позиции. работает примерно так же, но для получения всех данных о встрече вы вместо этого сначала выбираете встречу, а затем выполняете массовый доступ к позициям, используя ключи из сущности Встреча. Если вы знаете ключ встречи, таким образом, вы вообще избегаете необходимости делать какие-либо запросы.
Я пытался объяснить Пиндатджу, почему запрос свойства списка не менее эффективен, чем однозначный, но, по-видимому, требуется более подробное описание Итак, без лишних слов, вот ...
Хотя Python и Java предоставляют различные интерфейсы высокого уровня для хранилища данных, само хранилище данных использует абстракцию нижнего уровня, называемую сущностями. Сущность состоит из следующего:
Первичный ключ - это ключ хранилища данных, с которым вы уже знакомы. Список пар (имя, значение) - это представление App Engine для данных в вашей сущности. Пока все просто. Сущность со следующими значениями:
a_string = "Hello, world"
an_int = 123
будет сериализована во что-то вроде этого:
[('a_string', 'Hello, world'), ('an_int', 123)]
Но как это взаимодействует со списками? Что ж, списки рассматриваются как "многозначные" свойства. То есть список из n элементов сохраняется как n отдельных свойств. Пример, вероятно, проясняет это:
a_string = "Hello, world"
an_int = 123
a_list_of_ints = [42, 314, 9]
будет сериализован как:
[('a_string', 'Hello, world'), ('an_int', 123), ('a_list_of_ints', 42), ('a_list_of_ints', 314), ('a_list_of_ints', 9)]
Как вы можете видеть, список представляет собой серию значений с одним и тем же именем. Когда вы загружаете данные из хранилища данных, SDK видит повторяющееся значение и превращает его в список.
Когда это становится важным, так это при взаимодействии с индексированием. Предположим, у вас есть указатель на «a_string» и «an_int». Когда вы вставляете или изменяете значение, App Engine создает для него набор записей указателя; для указанного выше индекса и указанной выше сущности он генерирует одну строку в индексе, которая выглядит примерно так:
('Hello, world', 123, a_key)
('a_key' здесь является заполнителем для ключа исходной сущности.) Когда вы выполняете запрос, который использует этого индекса, ему просто нужно выполнить поиск по индексу, чтобы найти строки с соответствующим префиксом (например, 'SELECT * FROM Kind WHERE a_string = "Hello, world" ORDER BY an_int').
Однако при индексировании списка App Engine вставляет несколько строк индекса. Индекс по 'an_int' и 'a_list_of_ints' сгенерирует эти строки для указанного выше объекта:
(123, 42, a_key)
(123, 314, a_key)
(123, 9, a_key)
Опять же, запросы работают так же, как и раньше - App Engine просто должен найти строку с правильным префиксом в индексе. Количество записей в списке не влияет на скорость выполнения запроса - только на то, сколько времени потребовалось для создания и записи записей индекса. Фактически, планировщик запросов не знает, что 'a_list_of_ints' является свойством с несколькими значениями - он просто обрабатывает его как любую другую запись индекса.
Итак, вкратце:
Обычным решением для такого рода проблем является нормализация модели, то есть к Первой нормальной форме .
Ваша модель в нормализованной форме будет иметь третью таблицу со ссылками на строки Назначение
и Line_Item
:
Appointment
start_time
...
Line_Item
name
price
...
Appointment_Line_Item
appointment_key
line_item_key
Однако есть проблема! Поскольку вы используете Google App Engine, а их хранилище данных весьма ограничено («GQL не может выполнять SQL-подобное JOIN») и в основном требует денормализации.
Вы предложили использовать поле в виде списка. Это возможно, но очень сложно проиндексировать. Поиск ключа ( assign_key
) в списке для каждой строки в базе данных на самом деле неэффективен. Я предлагаю две возможности:
Дублировать Line_Item
.
Line_Item
assign_key
имя
цена
законченный
...
Line_Item
должен иметь состояние завершено
, когда элемент был закончен или нет сотрудником. Если сотрудник не выполнил все позиции, отметьте их как незавершенные, создайте новую встречу и скопируйте все незавершенные позиции. Вы можете проиндексировать поле assign_key
для всех Line_Items
, что является хорошей вещью. Однако дублированные данные могут быть проблемой.
Динамические поля для Line_Item
:
Line_Item
duplicate_key
assign_key
имя
цена
законченный
...
Создайте новое поле duplicate_key
для Line_Item
, которое указывает на другой Line_Item
или на нуль (зарезервируйте этот ключ!). Нулевое значение означает, что Line_Item
является исходным, любое другое значение означает, что этот Line_Item
является дубликатом Line_Item
, на который указывает поле. Все поля Line_Item
, помеченные как повторяющиеся, наследуют поля исходного Line_Item
, за исключением assign_key
: поэтому он займет меньше места. Также это решение должно иметь индексированный ключ assign_key
, чтобы ускорить поиск. Для этого требуется один дополнительный запрос на каждый дублированный Line_Item
, что может быть проблемой.
Теперь это очевидный выбор: либо лучшая скорость, либо лучшее хранилище. Я бы выбрал первое, так как это снижает сложность вашей модели, а хранение данных никогда не является проблемой для современных систем.Меньшая сложность обычно означает меньше ошибок и меньше затрат на разработку / тестирование, что оправдывает стоимость требований к хранилищу.