Мнения по сенсору / чтению / дизайну базы данных оповещений - PullRequest
6 голосов
/ 02 декабря 2010

В последнее время я задал несколько вопросов относительно дизайна базы данных, возможно, слишком много ;-) Однако я полагаю, что я постепенно разбираюсь в сути дела с моим дизайном и постепенно его разрушаю. Я все еще борюсь с парой решений относительно того, как «предупреждения» хранятся в базе данных.

В этой системе оповещение - это объект, который должен быть подтвержден, обработан и т. Д.

Изначально я связывал показания с такими предупреждениями (очень урезано): -

[Location]
LocationId

[Sensor]
SensorId
LocationId
UpperLimitValue
LowerLimitValue

[SensorReading]
SensorReadingId
Value
Status
Timestamp

[SensorAlert]
SensorAlertId

[SensorAlertReading]
SensorAlertId
SensorReadingId

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

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

Я думал, чтобы упростить вещи, зачем вообще возиться с таблицей SensorAlertReading? Вместо этого я мог бы сделать это:

[Location]
LocationId

[Sensor]
SensorId
LocationId

[SensorReading]
SensorReadingId
SensorId
Value
Status
Timestamp

[SensorAlert]
SensorAlertId
SensorId
Timestamp

[SensorAlertEnd]
SensorAlertId
Timestamp

По сути, сейчас я не связываю показания с предупреждением - вместо этого я просто знаю, что оповещение было активным между временем начала и окончания для определенного датчика, и если я хочу просмотреть показания для этого предупреждения, я могу сделать ,

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

Теперь, когда вы выглядите разработчиком / администратором базы данных со стороны, это заставляет вас хотеть заболеть или это кажется разумным?

Может быть, есть еще один способ сделать это, что я могу пропустить?

Спасибо.

EDIT: Вот еще одна идея - это работает по-другому. Он хранит каждое изменение состояния датчика, переходя от нормального состояния к предупреждению в таблице, а затем показания просто связываются с определенным состоянием. Кажется, это решает все проблемы - что ты думаешь? (единственное, в чем я не уверен, так это в вызове таблицы «SensorState», не могу не подумать, что есть более подходящее имя (возможно, SensorReadingGroup?): -

[Location]
LocationId

[Sensor]
SensorId
LocationId

[SensorState]
SensorStateId
SensorId
Timestamp
Status
IsInAlert

[SensorReading]
SensorReadingId
SensorStateId
Value
Timestamp

Должно быть элегантное решение для этого!

Ответы [ 3 ]

8 голосов
/ 04 декабря 2010

Редакция 01 января 21:50 UTC

Модель данных

Я думаю, что ваша модель данных должна выглядеть следующим образом: ▶ Модель данных датчика ◀ . (Страница 2 относится к вашему другому вопросу об истории).

Читатели, которые не знакомы со стандартом реляционного моделирования, могут найти ▶ Запись IDEF1X ◀ полезной.

Бизнес (правила, разработанные в комментариях)

Я определил некоторые ранние бизнес-правила, которые сейчас устарели, поэтому я удалил их

Их можно «прочитать» в отношениях (читать рядом с моделью данных). Бизнес-правила и вся подразумеваемая ссылочная целостность и целостность данных могут быть реализованы и, следовательно, гарантированы с помощью RULES, CHECK Constraints, в любой базе данных ISO SQL. Это демонстрация IDEF1X при разработке как реляционных ключей, так и сущностей и отношений. Обратите внимание, что глагольные фразы - это не просто процветание.

Помимо трех справочных таблиц, единственными статическими идентифицирующими объектами являются Location, NetworkSlave и User. Датчик занимает центральное место в системе, поэтому я дал ему собственный заголовок.

Местоположение

  • A Location содержит один ко многим Sensors
  • A Location может иметь один регистратор

NetworkSlave

  • NetworkSlave собирает показания для однозначных сетевых сенсоров

Пользователь

  • User может поддерживать ноль-ко-многим Locations
  • User может поддерживать ноль-ко-многим Sensors
  • User может поддерживать ноль-ко-многим NetworkSlaves
  • User может выполнять ноль-ко-многим Downloads
  • User может составлять ноль-ко-многим Acknowledgements, каждый на один Alert
  • User может принимать ноль-ко-многим Actions, каждый из которых один ActionType

Датчик

  • A SensorType устанавливается как ноль-ко-многим Sensors

  • A Logger (дома и) собирает Readings за одного LoggerSensor

  • A Sensor равен либо один NetworkSensor или один LoggerSensor

    • A NetworkSensor записей Readings, собранных одним NetworkSlave
      ,
  • A Logger периодически Downloaded один-ко-много раз
    • A LoggerSensor записей Readings, собранных одним Logger
      ,
  • A Reading может считаться Alert, одним AlertType
    • AlertType может произойти на нуле ко многим Readings
      ,
  • Alert может быть одним Acknowledgement одним пользователем .
  • Acknowledgement может быть закрыт одним Action, одним ActionType, одним User
    • ActionType может быть взято для ноля ко многим Actions

Ответы на комментарии

  1. Наклейка столбцов Id на все, что движется, мешает определению Идентификаторов, естественных Реляционных ключей, которые дают вашей базе данных реляционную "силу". Они являются суррогатными ключами, что означает дополнительный ключ и индекс, и это препятствует той силе отношений; что приводит к большему количеству соединений, чем необходимо. Поэтому я использую их только тогда, когда ключ реляции становится слишком громоздким для переноса в дочерние таблицы (и принимаю наложенное дополнительное соединение).

  2. Обнуляемые ключи являются классическим признаком ненормализованной базы данных. Нули в базе данных - плохая новость для производительности; но Null в FK означает, что каждая таблица делает слишком много вещей, имеет слишком много значений, а результаты - очень плохой код. Хорошо для людей, которые любят "рефакторинг" своих баз данных; совершенно не требуется для реляционной базы данных.

  3. Разрешено: Alert может быть Acknowledged; Acknowledgement может быть Actioned.

  4. Столбцы над строкой являются первичным ключом (см. Документ обозначения).SensorNo - порядковый номер в LocationId;см. Бизнес-правила, это бессмысленно вне Location;две колонки вместе образуют ПК.Когда вы готовы ВСТАВИТЬ датчик (после того, как вы проверили, что попытка действительна и т. Д.), Она получается следующим образом.Это исключает LoggerSensors, которые равны нулю:

    <code>INSERT Sensor VALUES (
        @LocationId,
        SensorNo = ( SELECT ISNULL(MAX(SensorNo), 0) + 1
            FROM Sensor
            WHERE LocationId = @LocationId
            )
        @SensorCode
        )
  5. Для точности или улучшенного значения я изменил NetworkSlave monitors NetworkSensor на NetworkSlave collects Readings from NetworkSensor.

  6. Проверьте ограничения.NetworkSensor и LoggerSensor являются исключительными подтипами Sensor, и их целостность может быть установлена ​​с помощью ограничений CHECK.Alerts, Acknowledgements и Actions не являются подтипами, но их целостность задается одним и тем же методом, поэтому я перечислю их вместе.

    • Каждая связь в модели данных реализована какCONSTRAINT в потомке (или подтипе) как FOREIGN KEY (child_FK_columns). ССЫЛКИ Родитель (PK_columns)

    • Дискриминатор необходим для определения того, какой подтип является Sensor.Это SensorNo = 0 для LoggerSensors;и ненулевое значение для NetworkSensors.

    • Существование NetworkSensors и LoggerSensors ограничено ограничениями FK до NetworkSlave и Logger соответственно;а также для датчика.
    • В NetworkSensor, включите ограничение CHECK, чтобы гарантировать, что SensorNo ненулевой
    • В LoggerSensor, включите ограничение CHECK, чтобы убедиться, чтоSensorNo равно нулю

    • Существование Acknowledgements и Actions ограничено указанными ФУНКЦИЯМИ FK (Acknowledgement не может существовать без Alert; Action не может существовать без Acknowledgement).И наоборот, Alert без Acknowledgement находится в неподтвержденном состоянии;Alert с и Acknowledgement, но без Action находится в подтвержденном, но бездействующем состоянии.,

  7. Оповещения.Концепция такого приложения (мониторинг в режиме реального времени и оповещение) заключается в создании множества небольших программ, работающих независимо;все используют базу данных как единственную версию правды.Некоторые программы вставляют строки (Readings, Alerts);другие программы опрашивают БД на наличие таких строк (и отправляют SMS-сообщения и т. д .; или портативные устройства получают оповещения, относящиеся только к устройству).В этом смысле БД может быть описан как окно сообщения (одна программа помещает строки, в которые другая программа читает и выполняет действия).

    Предполагается, что Readings для Sensors записываются"live" с помощью NetworkSlave, и каждую минуту или около того добавляется новый набор Readings.Фоновый процесс выполняется периодически (каждую минуту или что-то в этом роде), это основная программа-монитор, в цикле которой будет много функций.Одной из таких функций будет мониторинг Readings и получение Alerts, которые произошли с момента последней итерации (цикла программы).

    Следующий сегмент кода будет выполняться внутри цикла, по одному для каждого типа AlertType.Это классическая проекция: <pre> -- Assume @LoopDateTime contains the DateTime of the last iteration INSERT Alert SELECT LocationId, SensorNo, ReadingDtm, "L" -- AlertType "Low" FROM Sensor s, Reading r WHERE s.LocationId = r.LocationId AND s.SensorNo = r.SensorNo AND r.ReadingDtm > @LoopDtm AND r.Value < s.LowerLimit INSERT Alert SELECT LocationId, SensorNo, ReadingDtm, "H" -- AlertType "High" FROM Sensor s, Reading r WHERE s.LocationId = r.LocationId AND s.SensorNo = r.SensorNo AND r.ReadingDtm > @LoopDtm AND r.Value > s.UpperLimit Так что Alert - это определенно факт, который существует в виде строки в базе данных.Впоследствии это может быть Acknowledged с помощью User (другой ряд / факт) и Actioned с ActionType с помощью User.

    Другое, что это (создание посредством проекции)т.е.в общем и неизменном случае я бы назвал Alert только строкой в ​​Alert;статический объект после создания.

  8. Касается изменения Users.Об этом уже позаботились, как следует.В верхней части моего (исправленного вчера) Ответа я заявляю, что основными Идентификационными элементами являются статические .Я пересмотрел бизнес-правила, чтобы улучшить ясность.

    • По указанным вами причинам User.Name не является хорошим ПК для User, хотя он остается альтернативным ключом (уникальным) и тем, который используется для взаимодействия с человеком.

    • User.Name не может быть продублировано, их не может быть больше одного Fred;может быть с точки зрения FirstName-LastName;два Fred Bloggs, но не с точки зрения User.Name.Нашему второму Фреду нужно выбрать еще User.Name.Обратите внимание на идентифицированные индексы.

    • UserId - это постоянная запись, и это уже PK.Никогда не удаляйте User, это имеет историческое значение.Фактически ограничения FK остановят вас (никогда не используйте CASCADE в реальной базе данных, это просто безумие).Нет необходимости в коде или триггерах и т. Д.

    • В качестве альтернативы (для удаления Users, который никогда ничего не делал, и, следовательно, освобождения User.Name для использования), разрешите Удалить до тех пор, пока нетНарушения FK (т.е. UserId - это , а не , на которые ссылается Download, Acknowledgement, Action).

    Чтобы гарантировать, что только Users, которые являются текущими, выполняют Actionsдобавить логическое значение IsObsolete в пользовательском (DM Обновлено) и проверить этот столбец, когда эта таблица запрашивается для любой функции (кроме отчетов). Вы можете реализовать View UserCurrent, который возвращает только те Users.

    То же самое касается Location и NetworkSlave.Если вам нужно провести различие между текущими и историческими, дайте мне знать, я добавлю к ним также IsObsolete.

    Не знаю: вы можете очистить базу данных древних ИсторическихПериодически удаляйте данные, которые (например) старше 10 лет.Это должно быть сделано сначала снизу (таблицы), работая над связями.

Не стесняйтесь задавать вопросы.

Обратите внимание, что документ нотации IDEF1 расширен.

2 голосов
/ 02 декабря 2010

Вот мои два цента по этой проблеме.

alt text

Таблица AlertType содержит все возможные типы предупреждений. AlertName может быть что-то вроде высокой температуры, низкого давления, низкого уровня воды и т. Д.

Таблица AlertSetup позволяет настроить пороги оповещения от датчика для определенного типа оповещения. Например, TresholdLevel = 100 и TresholdType = 'HI' должны вызывать предупреждение для показаний свыше 100.

Чтение * Таблица 1018 * содержит показания датчиков по мере их поступления на сервер (приложение).

Таблица

Alert содержит все предупреждения. Он сохраняет ссылки на первое чтение, которое вызвало предупреждение, и последнее, которое закончило его (FirstReadingId, LastReadingId). IsActive имеет значение true, если имеется активное предупреждение для комбинации (SensorId, AlertTypeId). IsActive может быть установлено в false только при чтении ниже порога предупреждения. IsAcknowledged означает, что оператор подтвердил предупреждение.

  1. Прикладной уровень вставляет новое чтение в таблицу Reading , захватывает ReadingId.

  2. Затем приложение проверяет показания по настройкам оповещений для каждой (SensorId, AlertTypeId) комбинации. На этом этапе создается коллекция объектов {SensorId, AlertTypeId, ReadingId, IsAlert}, и для каждого объекта устанавливается флаг IsAlert.

  3. Затем проверяется таблица Alert на наличие активных предупреждений для каждого объекта {SensorId, AlertTypeId, ReadingId, IsAlert} из коллекции.

    • Если IsAlert имеет значение ИСТИНА и нет активных предупреждений для комбинации (SensorId, AlertTypeId), новая строка добавляется в таблицу Предупреждение с помощью FirstReadingID указывает на текущий ReadingId. IsActive установлен в ИСТИНА, IsAcknowledged в ЛОЖЬ.

    • Если IsAlert имеет значение ИСТИНА и имеется активное предупреждение для комбинации (SensorId, AlertTypeId), эта строка обновляется путем установки LastReadingID, указывающего на текущий ReadingId.

    • Если IsAlert равен FALSE и имеется активное предупреждение для комбинации (SensorId, AlertTypeId), эта строка обновляется установкой IsActive FALSE.

    • Если IsAlert равен FALSE и нет активных предупреждений для комбинации (SensorId, AlertTypeId), таблица Alert не изменяется.

0 голосов
/ 02 декабря 2010

Основным «треугольником», с которым вам приходится иметь дело здесь, является Датчик, Чтение [Сенсора] и Предупреждение. Предполагая, что вы должны отслеживать активность по мере ее появления (в отличие от схемы «загрузите все сразу»), ваше третье решение похоже на то, что мы делали недавно. Несколько настроек, и это будет выглядеть так:

[Location] 
LocationId 

[Sensor] 
SensorId 
LocationId 
CurrentSensorState  --  Denormalized data!

[SensorReading] 
SensorReadingId 
SensorState
Value 
Timestamp 

[SensorStateLog] 
SensorId 
Timestamp 
SensorState
Status   --  Does what?
IsInAlert 
(Primary key is {SensorId, Timestamp})

«SensorState» может быть SensorStateId со связанным списком справочной таблицы (и ограничивающим) всех возможных состояний.

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

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

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

...