Назначения и позиции - PullRequest
5 голосов
/ 26 июня 2010

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

Этот вопрос относится к предыдущему вопросу, который я опубликовал, но я воспроизвел соответствующую информацию ниже: Дизайн базы данных - Google App Engine

В этом приложении используются понятия «Назначения» и «Позиции».

Назначения - это место и время, когда сотрудники должны быть в состоянии оказать услугу.

Позиции - это услуга, плата или скидка и связанная с ней информация. Пример позиций, которые могут попасть на встречу:

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 App Engine, и если я хочу запросить набор встреч и связанных с ними позиций, мне нужно сначала запросить набор встреч, а затем выполнить второй запрос. для позиций, использующих оператор IN, чтобы проверить, попадает ли какой-либо из ключей назначения Line_Item в набор ключей назначения, которые были возвращены из предыдущего запроса. Второй запрос потерпит неудачу, если у меня будет более 30 ключей, требующих, чтобы я осветил запрос Я мог бы денормализовать данные, чтобы избежать этого сложного и обширного запроса на чтение, и мне, вероятно, все равно придется в какой-то степени денормализовать, но я бы предпочел избежать сложности, где это уместно.

Мой вопрос: как обычно моделируется ситуация такого типа? Является ли целесообразным, чтобы позиция была сопряжена с более чем одной встречей, или это нормально - просто разделять позиции на отдельные для каждой встречи, например, «1-я половина двухдневной работы» и «2-я половина двухдневной работы» «. Как подобные успешные приложения делают это? Каковы практические правила в такой ситуации? Какие реализации оказались менее проблематичными?

Спасибо!

Ответы [ 2 ]

2 голосов
/ 30 июня 2010

Подход, который вы предлагаете, будет работать нормально;Вы можете смоделировать 'position_Key_list' позиции элемента как свойство списка, и оно будет работать так, как вы ожидаете.Вам не нужно использовать оператор IN - это для сопоставления одного значения в хранилище данных со списком имеющихся у вас ключей (например, «WHERE datastore_column IN ('a', 'b', 'c')), в то время каквы делаете обратное - сопоставление одного значения со списком в хранилище данных.

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

Я пытался объяснить Пиндатюху, почему запрос свойства списка не менее эффективен, чем однозначный, но, по-видимому,требуется более подробное описание, поэтому без лишних слов, вот ...

Краткое руководство по индексации хранилища данных App Engine

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

  1. Уникальный первичный ключ
  2. Список пар (имя, значение)

Первичный ключ - хранилище данныхключ, с которым вы уже знакомы.Список пар (имя, значение) представляет собой представление 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 является многозначным свойством - он просто обрабатывает его как любую другую запись индекса.

Итак, в двух словах:

  1. Естьнет практической разницы между списком с одним элементом и отдельным свойством в терминах индексирования и запроса
  2. Размер индексированного списка влияет на время и пространство, необходимые для индексации, но не для запросов.
  3. Вы можете выполнить запрос, который сопоставляет любую сущность с данным значением в списке, используя простой фильтр равенства.
1 голос
/ 26 июня 2010

Обычным решением для такого рода проблем является нормализация модели, т.е. Первая нормальная форма .

Ваша модель в нормализованной форме будет иметь третью таблицу со ссылкамив строки Appointment и Line_Item:

Appointment
 start_time
 ...

Line_Item
 name
 price
 ...

Appointment_Line_Item
 appointment_key
 line_item_key

Однако существует проблема!Поскольку вы используете Google App Engine, и их хранилище данных весьма ограничено («GQL не может выполнить SQL-подобное JOIN») и в большинстве случаев требует денормализации.

Вы предложили использовать список-подобныйполе.Можно использовать это, но это очень трудно проиндексировать.Поиск ключа (appointment_key) в списке на строку в базе данных на самом деле не выполняется.Я предлагаю две возможности:

  1. Дублировать Line_Item.

    Line_Item
     appointment_key
     name
     price
     finished
     ...
    

    A Line_Item должен иметь состояние finished, когда элемент был закончен или нетсотрудником.Если сотрудник не завершил все позиции, отметьте их как незавершенные, создайте новую встречу и скопируйте все незавершенные позиции.Вы можете индексировать в поле appointment_key все Line_Items, что является хорошей вещью.Однако дублированные данные могут быть проблемой.

  2. Динамические поля для Line_Item:

    Line_Item
     duplicate_key
     appointment_key
     name
     price
     finished
     ...
    

    Создать новое поле, duplicate_key, для Line_Item, который указывает на другой Line_Item или на ноль (зарезервируйте этот ключ!).Нуль означает, что Line_Item является оригинальным, любое другое значение означает, что этот Line_Item является дубликатом Line_Item, на который указывает поле.Все поля Line_Item, помеченные как дубликаты, наследуют поля исходного Line_Item, кроме appointment_key: поэтому он займет меньше места.Также это решение должно иметь индексированный appointment_key, чтобы ускорить поиск.Для этого требуется один дополнительный запрос на дублируемый Line_Item, что может быть проблемой.

Теперь перед нами ясный выбор: либо лучшая скорость, либо лучшее хранилище.Я бы пошел на первое, так как это уменьшает сложность вашей модели, а хранение никогда не является проблемой современных систем.Меньшая сложность обычно означает меньше ошибок и меньше затрат на разработку / тестирование, что оправдывает затраты на хранение.

...