Несколько данных хранятся в поле для лучшего управления?

У меня есть таблица MySQL следующим образом:

id, user, culprit, reason, status, ts_register, ts_update

Я думал об использовании причины в качестве поля int и хранении только идентификатора причины, который может быть выбран пользователем, а сама причина может быть увеличена администратором.

Что я имел в виду под увеличением, так это то, что администратор может зарегистрировать новую причину, например, в настоящее время у нас есть:

Flood, Racism, Hacks, Other

Но администратор может добавить новую причину, например:

Refund

Теперь моя проблема в том, что я хотел бы разрешить своим пользователям выбирать несколько причин, например:

В отчете 01 есть причины Flood и Hack.

Как мне сохранить поле причины, чтобы я мог выбрать несколько причин, сохраняя при этом хороший формат таблицы?

Должен ли я просто сохранить его в виде строки и использовать как INT, когда я ищу его, или есть лучшие формы для его хранения?

ОБНОВЛЕНИЕ На основе ответа Джонатана:

SELECT mt.*, group_concat(r.reason separator ', ') AS reason
  FROM MainTable AS mt
  JOIN MainReason AS mr ON mt.id = mr.maintable_ID
  JOIN Reasons AS r ON mr.reason = r.reason_id
  GROUP BY mt.id

person Prix    schedule 15.05.2011    source источник
comment
GROUP_CONCAT доступен в MySQL; Я знаю, что это можно смоделировать в некоторых других СУБД (Informix, PostgreSQL), и я не знаю о других СУБД. Это полезный агрегат.   -  person Jonathan Leffler    schedule 15.05.2011
comment
@Jonathan, спасибо за ответ, я немного беспокоился о том, что использование group_concat для моих нужд не будет правильным подходом.   -  person Prix    schedule 15.05.2011


Ответы (2)


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

CREATE TABLE MainReasons
(
    MainTable_ID INTEGER NOT NULL REFERENCES MainTable(ID),
    Reason       INTEGER NOT NULL REFERENCES Reasons(ID),
    PRIMARY KEY(MainTable_ID, Reason)
);

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


Из комментария:

[Не могли бы] вы [так] любезны показать мне пример выбора чего-либо для получения причины сообщения? Я имею в виду, что если я просто выберу его SELECT * FROM MainTABLE, я никогда не получу никаких причин, так как MainTable не знает этого, верно? Поскольку он связан только с таблицей MainReasons и Reasons, поэтому мне нужно будет сделать что-то вроде SELECT * FROM MainTable LEFT JOIN MainReasons USING (MainTable_ID) или что-то подобное, но как мне получить все причины, если их несколько?

SELECT mt.*, r.reason
  FROM MainTable AS mt
  JOIN MainReason AS mr ON mt.id = mr.maintable_ID
  JOIN Reasons AS r ON mr.reason = r.reason_id

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

Вы можете добавить критерии к запросу, добавив термины в предложение WHERE. Если вы хотите увидеть отчеты, в которых указана конкретная причина:

SELECT mt.*
  FROM MainTable AS mt
  JOIN MainReason AS mr ON mt.id = mr.maintable_ID
  JOIN Reasons AS r ON mr.reason = r.reason_id
 WHERE r.reason = 'Flood'

(Вам не нужна причина в результатах - вы знаете, что это такое.)


Если вы хотите увидеть отчеты, в которых в качестве причин были указаны «Наводнения» и «Взломы», вы можете написать:

SELECT mt.*
  FROM MainTable AS mt
  JOIN (SELECT f.MainTable_ID
          FROM (SELECT MainTable_ID
                  FROM MainReason AS mr1
                  JOIN Reasons    AS r1  ON mr1.reason = r1.reason_ID
                 WHERE r1.reason = 'Floods'
               ) AS f ON f.MainTable_ID = mt.MainTable_ID
  JOIN (SELECT f.MainTable_ID
          FROM (SELECT MainTable_ID
                  FROM MainReason AS mr2
                  JOIN Reasons    AS r2  ON mr2.reason = r2.reason_ID
                 WHERE r1.reason = 'Hacks'
               ) AS h ON h.MainTable_ID = mt.MainTable_ID
person Jonathan Leffler    schedule 15.05.2011
comment
Итак, в основном у меня была бы 3-я таблица, чтобы связать причины с их отчетом, и как в этом случае будет работать выбор, просто интересно, оставил бы я соединение, чтобы собрать имя причин? - person Prix; 15.05.2011
comment
@Prix: да, три таблицы (MainTable, Reasons, MainReasons — таблица отношений M:N, иногда называемая связующей таблицей, между MainTable и Reasons). Нет необходимости в левом соединении - обычно. Но да, присоединяется. Базы данных хороши для соединений, особенно когда одна таблица крошечная (например, таблица Reasons). - person Jonathan Leffler; 15.05.2011
comment
Возможно, я прошу слишком многого, но не могли бы вы показать мне пример выбора чего-либо для получения причины отчета? Я имею в виду, что если я просто выберу его SELECT * FROM MainTABLE, я никогда не получу никаких причин, так как MainTable не знает этого правильно? Поскольку он связан только с таблицей MainReasons и Reasons, поэтому мне нужно будет сделать что-то вроде SELECT * FROM MainTable LEFT JOIN MainReasons USING (MainTable_ID) или что-то подобное, но как мне получить все причины, если их несколько? не уверен, что правильно сказал. - person Prix; 15.05.2011
comment
это очень помогает, но я все еще застрял, мой php извлекает данные, но, как вы сказали, если у отчета есть несколько причин, он появляется в новой строке (так что в основном он будет повторять запись), в то время как я хотел бы вытащить каждую запись со всеми причинами только один раз. Есть ли запрос, который позволил бы мне получить всю причину отчета без необходимости открывать второй запрос внутри цикла, чтобы получить причину, или лучший способ - иметь второй запрос внутри, чтобы проверить его? - person Prix; 15.05.2011
comment
Я придумал запрос, который может подойти для моих нужд, но я не совсем уверен, что это будет приемлемый запрос, не могли бы вы его проверить? Это внизу моего вопроса. - person Prix; 15.05.2011

Чтобы создать отношение «один ко многим», я бы выделил причину в свою собственную таблицу, например так:

id, parent_id, reason

parent_id будет ссылаться на идентификатор вашей текущей таблицы.

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

person dpmattingly    schedule 15.05.2011
comment
Какая польза от столбца «id»? - person Jonathan Leffler; 15.05.2011
comment
@dpmattingly Я не думаю, что понял вашу идею, не могли бы вы объяснить ее немного подробнее? Вы можете объединить все причины, поэтому вы предлагаете мне иметь 3-ю таблицу, где будут зарегистрированы все причины для отчета? Это кажется более сложным, если я правильно вас понял - person Prix; 15.05.2011
comment
По привычке я помещаю во все свои таблицы отдельное поле первичного ключа. Технически вы, вероятно, могли бы обойтись без этого, поскольку (parent_id, Reason) должны быть уникальными. - person dpmattingly; 15.05.2011
comment
Как и в моей таблице, где я объявляю первичный ключ составным из двух столбцов — parent_id и Reason в вашей записи. - person Jonathan Leffler; 15.05.2011
comment
@Prix Причина башни из слоновой кости в том, что она ведет к нормализованной базе данных, что, как мне сказали, хорошо. Прагматическая причина заключается в том, что я не хочу прибегать к манипуляциям со строками каждый раз, когда хочу добавить или удалить причину. - person dpmattingly; 15.05.2011
comment
@Le Dorfier: в таблице ссылок может быть много строк для данного идентификатора в основной таблице; каждая строка в связывающей таблице отличается тем, что имеет один и тот же идентификатор, но другой код причины. - person Jonathan Leffler; 15.05.2011
comment
Да, связывание таблиц — это то, как многие ко многим. Этот ответ описывает (цитата) один ко многим (и, насколько я могу понять, на что он и идентификатор ссылаются), это то, что они описывают. Ваш ответ OTOH правильный, особенно. после добавления объяснения. Хорошая работа. - person dkretz; 15.05.2011