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

363

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

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

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

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

Злой ученый
источник
21
в таком случае, зачем беспокоиться о базе данных? Сохранение в файл подойдет.
13
6
Согласился с @thavan. Зачем даже сохранять данные для подтверждения концепции? После завершения проверки добавьте базу данных правильно. Вы прекрасно справляетесь с задачами в качестве доказательства концепции, просто не делайте вещи, которые вам придется позже разобрать.
Джефф Дэвис
1
В Postgres столбец массива должен быть предпочтительнее списка, разделенного запятыми. Это, по крайней мере, обеспечивает правильный тип данных, не имеет проблем с разграничением разделителя от фактических данных и может быть эффективно проиндексировано.
a_horse_with_no_name

Ответы:

568

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

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

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

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

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


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

Билл Карвин
источник
8
ARRAY (любого типа данных) может исправить исключение, просто проверьте PostgreSQL: postgresql.org/docs/current/static/arrays.html (@Bill: отличная книга, которую должен прочитать любой разработчик или dba)
Фрэнк Хайкенс,
4
+1 Билл Карвин Отличный ответ! Прекрасные краткие пункты пули. Это тоже похоже на отличную книгу. Люблю обложку тоже +1 NullUserException. Я нахожусь в процессе разработки схемы для базы данных MySQL, чтобы заменить систему, основанную на текстовом файле. До сих пор я столкнулся с несколькими дилеммами. Так что эту книгу стоит купить.
therobyouknow
2
Сайт pragprog.com тоже выглядит хорошо: приятный стиль, верстка, удобство и чистота. Это должно быть довольно новым, я не мог купить их книги в прошлом. PS. Я не работаю, у них нет никакой связи с авторами. Мне нравится отмечать хорошие продукты, услуги и помощь, когда я вижу это.
therobyouknow
2
Что касается серьезной стороны, я бы добавил к вашему списку: сложно искать. Скажем, вы хотите все записи, которые включают «2». Конечно, вы не можете просто выполнить поиск foobar = '2', потому что это пропустит его, если будут другие значения. Вы не можете искать foobar как "% 2%", потому что это приведет к ложным попаданиям для 12 и 28 и так далее. Вы не можете искать foobar как "%, 2,%", потому что 2 может быть первым или последним элементом списка и поэтому иметь только одну из этих запятых.
Джей
2
Я знаю, что это не рекомендуется, но игра дьяволов защищает: большинство из них можно снять, если есть пользовательский интерфейс, который обрабатывает уникальность и типы данных (в противном случае это ошибка или неправильное поведение), пользовательский интерфейс удаляется и создает его в любом случае, есть таблица драйверов, где значения берутся, чтобы сделать их уникальными, можно использовать такие поля, как «% P%», значениями являются P, R, S, T, счет не имеет значения, а сортировка не имеет значения. В зависимости от пользовательского интерфейса значения могут быть разделены [], например, чтобы установить флажки в списке из таблицы драйверов в наименьшем общем сценарии без необходимости переходить к другой таблице, чтобы получить их.
jmcclure
44

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

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

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

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

Hammerite
источник
Я всегда слышу, как некоторые люди говорят, что «мой дизайн более гибок, чем ваш», когда я сталкиваюсь с ними о таких вещах, как не установка ограничений внешнего ключа или хранение списков в одном поле. Для меня гибкость (в таких случаях) == нет дисциплины == лень.
foresightyj
41

Есть много вопросов о том, как задать вопрос:

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

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

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

OMG пони
источник
19

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

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

bobbymcr
источник
10

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

Это ломает первую нормальную форму.

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

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

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

duffymo
источник
19
В этом вопросе я не вижу ничего, что бы указывало на то, что он уязвим для SQL-инъекций. Внедрение SQL и нормализация базы данных являются ортогональными темами, и ваше отступление от внедрения не имеет отношения к вопросу.
Hammerite
5
@Paul: И, возможно, такое же отношение приведет к тому, что он будет сбит автобусом, когда он не сможет посмотреть в обе стороны, прежде чем перейти улицу, но вы не предупредили его об этом. Изменить: я думал, что вы были плакатом этого ответа, моя ошибка.
Hammerite
1
@Hammerite - твоя экстраполяция на автобусы смешна.
Duffymo
4
Да, это должно было быть смешным. Эта нелепость иллюстрирует то, что я подчеркиваю: нет смысла предупреждать его о том, о чем у вас нет оснований думать, что его нужно предупреждать.
Hammerite
1
Да я вижу. Я думаю, что у меня было гораздо больше причин, чем ваше предупреждение об автобусах.
duffymo
7

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

Радж
источник
13
Нет, это ужасная идея. Вам удалось сойти с рук, но стоимость ваших нескольких минут разработки стоила вам паршивой производительности запросов, гибкости и удобства сопровождения вашего кода.
Пол Томблин
5
Пол, я согласен. Но, как я уже сказал, я использовал его для определенной цели, и это для операции ввода данных, когда у вас есть много видов форм. Сейчас я пересматриваю дизайн, изучив NHibernate, но тогда мне понадобилась гибкость для разработки формы в ASP.NET и использования идентификаторов текстового поля в качестве ключа в паре ключ / значение.
Радж
28
+1 только для противодействия понижающим голосам. Говорить кому-то, кто поддерживает приложение в течение 4 лет о проблемах обслуживания, немного самонадеянно. В разработке очень мало «ужасных» идей - в основном это просто идеи с очень ограниченной применимостью. Разумно предупредить людей об ограничениях, но наказание тех, кто сделал это и пережил это, производит на меня впечатление более святого, чем ты, без которого я могу обойтись.
Марк Брэкетт
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 позволяет обойти некоторые проблемы со списком с разделителями и при необходимости может быть преобразовано в список с разделителями.

Джеймс Мохлер
источник
6

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

Робин
источник
0

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

Джерри Гроб
источник
Форма содержит еще несколько полей, это только одна часть формы (которую я не очень хорошо объяснил в вопросе).
Безумный Ученый
0

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

Соломон Уцко
источник
1
INT(1)занимает 4 байта; (1)не имеет смысла.
Рик Джеймс