Является ли хранение разделенного списка в столбце базы данных действительно так плохо?

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

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

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

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

Ответ 1

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

  • Не могу гарантировать, что каждое значение является правильным типом данных: нет способа предотвратить 1,2,3, банан, 5
  • Не могу использовать ограничения внешнего ключа, чтобы связать значения с таблицей поиска; нет способа обеспечить ссылочную целостность.
  • Не могу обеспечить уникальность: нет способа предотвратить 1,2,3,3,3,5
  • Не могу удалить значение из списка, не выбирая весь список.
  • Не удается сохранить список дольше, чем умещается в столбце строки.
  • Трудно найти все объекты с данным значением в списке; Вы должны использовать неэффективное сканирование таблицы. Возможно, придется прибегнуть к регулярным выражениям, например, в MySQL:
    idlist REGEXP '[[:<:]]2[[:>:]]' *
  • Трудно сосчитать элементы в списке или выполнить другие сводные запросы.
  • Трудно соединить значения с таблицей поиска, на которую они ссылаются.
  • Трудно получить список в отсортированном порядке.

Чтобы решить эти проблемы, вы должны написать тонны кода приложения, заново изобретая функциональность, которую СУБД уже обеспечивает гораздо более эффективно.

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

Бывают случаи, когда вам нужно применить денормализацию, но, как @OMG Ponies упоминает, это исключительные случаи. Любая нереляционная "оптимизация" приносит пользу одному типу запроса за счет других видов использования данных, поэтому убедитесь, что вы знаете, какие из ваших запросов необходимо обрабатывать настолько специально, чтобы они заслуживали денормализации.


* MySQL 8.0 больше не поддерживает этот синтаксис выражения границы слова.

Ответ 2

"Одной из причин была лень".

Звучит звонок. Единственная причина, по которой вы должны сделать что-то подобное, - это то, что вы знаете, как это сделать "правильно", но вы пришли к выводу, что есть осязаемая причина не делать этого таким образом.

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

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

Ответ 3

Есть много вопросов о том, как SO спрашивает:

  • как получить счет определенных значений из списка, разделенного запятыми
  • как получить записи, которые имеют только одно и то же значение 2/3/etc из этого списка, разделенного запятыми.

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

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

Ответ 4

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

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

Ответ 5

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

Он разбивает первую нормальную форму.

Вторая критика заключается в том, что помещение исходных исходных данных непосредственно в базу данных без каких-либо валидаций или привязки вообще не дает вам открывать атаки SQL-инъекций.

То, что вы называете лень и отсутствие знаний SQL, - это материал, из которого сделаны неофиты. Я бы рекомендовал потратить время, чтобы сделать это правильно, и рассмотреть его как возможность учиться.

Или оставьте это как есть и узнайте болезненный урок атаки SQL-инъекции.

Ответ 6

Ну, я уже более 4 лет использую раздел с разделителями вкладок key/value в столбце NTEXT в SQL Server и работает. Вы теряете гибкость при создании запросов, но, с другой стороны, если у вас есть библиотека, которая сохраняет/держирует пару ключевых значений, то это не такая плохая идея.

Ответ 7

Мне нужен столбец с несколькими значениями, он может быть реализован как поле xml

Он может быть преобразован в запятую, если необходимо

запрос XML-списка на сервере sql с помощью Xquery.

Будучи полем xml, некоторые проблемы могут быть решены.

С CSV: Невозможно гарантировать, что каждое значение является правильным типом данных: невозможно предотвратить 1,2,3, банан, 5

С XML: значения в теге могут быть принудительно корректными.


С CSV: Нельзя использовать ограничения внешнего ключа для привязки значений к таблице поиска; нет возможности обеспечить ссылочную целостность.

С XML: все еще проблема


С CSV: Невозможно обеспечить уникальность: нет возможности предотвратить 1,2,3,3,3,5

С XML: все еще проблема


С CSV: Не удается удалить значение из списка, не извлекая весь список.

С XML: отдельные элементы могут быть удалены


С CSV: Сложно искать все объекты с заданным значением в списке; вам нужно использовать неэффективное сканирование таблицы.

С XML: поле xml можно индексировать


С CSV: Трудно подсчитать элементы в списке или выполнить другие агрегированные запросы. **

С XML: не особенно сложно


С CSV: Жестко присоединиться к значениям в справочной таблице, которую они ссылаются. **

С XML: не особенно сложно


С CSV: Жесткий выбор списка в отсортированном порядке.

С XML: не особенно сложно


С CSV: Сохранение целых чисел в виде строк занимает в два раза больше места, чем сохранение двоичных целых чисел.

С хранилищем XML: еще хуже, чем csv


С CSV: Плюс много запятых.

С тегами XML: используются вместо запятых


Короче говоря, использование XML обходит некоторые проблемы с ограниченным списком И может быть преобразовано в список с разделителями по мере необходимости

Ответ 8

Да, это так плохо. Я считаю, что если вам не нравится использование реляционных баз данных, ищите альтернативу, которая вам подходит, есть много интересных проектов "NOSQL" с некоторыми действительно расширенными функциями.

Ответ 9

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

Ответ 10

Если у вас есть фиксированное количество логических полей, вы можете использовать INT(1) NOT NULL (или BIT NOT NULL если он существует) или CHAR (0) (с нулевым значением) для каждого. Вы также можете использовать SET (я забыл точный синтаксис).