При проектировании баз данных, каков предпочтительный способ хранения нескольких значений true / false? - PullRequest
13 голосов
/ 04 февраля 2010

Как указано в заголовке, при проектировании баз данных, каков предпочтительный способ обработки таблиц, которые имеют несколько столбцов, которые просто хранят значения true / false как просто одно или значение (например, «Y / N: или» 0 / 1 ")? Также существуют ли проблемы, которые могут возникнуть между различными базами данных (например, Oracle и SQL Server), которые могут повлиять на обработку столбцов?

Ответы [ 10 ]

14 голосов
/ 04 февраля 2010

В SQL Server имеется тип данных BIT. Там можно сохранить 0 или 1, сравнить значения, но не запускать MIN или MAX.

В Oracle вы просто используете NUMBER или CHAR(1).

В MySQL и PostgreSQL любой тип данных неявно преобразуется в BOOLEAN.

Обе системы поддерживают тип данных BOOLEAN, который можно использовать как есть, без операторов, в предложениях WHERE или ON:

SELECT  *
FROM    mytable
WHERE   col1

, что невозможно в SQL Server и Oracle (вам нужно иметь какой-то вид или предикат).

В MySQL, BOOLEAN является синонимом TINYINT(1).

В PostgreSQL тоже (с точки зрения хранения), но по логике он неявно не конвертируется в любой другой тип.

5 голосов
/ 04 февраля 2010

Исходя из собственного опыта, я предпочитаю char (1) для 'Y' или 'N'.Использование 0 и 1 может быть немного запутанным в зависимости от того, сколько пива я уже выпил, а функция C ++ main () возвращает 0 в случае успеха.Типы ENUM и BIT доставляют больше хлопот, чем стоят.

Интересно отметить, что MySQL information_schema использует VARCHAR (3) для «YES» или «NO».

Пример:

information_schema.USER_PRIVILEGES (
  ...
  IS_GRANTABLE VARCHAR(3) NOT NULL DEFAULT ''
) 
4 голосов
/ 04 февраля 2010

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

  • когда у вас будет много столбцов да / нет.
  • когда вам, вероятно, потребуется добавить больше столбцов да / нет в будущем.
  • когда значения да / нет меняются не очень часто.

Определение пользовательских разрешений может быть типичным примером выше. Рассмотрим следующие таблицы:

Table "Users":             (user_id, name, surname, country)

Table "Permissions":       (permission_id, permission_text)

Table "Users_Permissions": (user_id, permission_id)

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

В приведенной выше модели вы могли бы указать ИСТИННОЕ значение, присвоив user_id с permission_id в таблице Users_Permissions. В противном случае это будет ЛОЖЬ по умолчанию.

Например:

Table "Permissions"

permission_id   text
-----------------------------------
1               "Read Questions"
2               "Answer Questions"
3               "Edit Questions"
4               "Close Questions"


Table "Users_Permissions"

user_id         permission_id
-----------------------------------
1               1
1               2
1               3
2               1
2               3

Преимущества

  • Индексирование : вы можете легко использовать индекс для запроса конкретных фактов.
  • Пробел : соглашение по умолчанию экономит место, когда у вас много ложных значений.
  • Нормализовано : Факты определены в их собственных таблицах (в таблицах Permissions и Users_Permissions.) Вы можете легко хранить больше информации по каждому факту.

Недостатки

  • Запросы : Простые запросы потребуют JOINs.
  • Установка в Ложь : Чтобы установить значение в Ложь, вам нужно будет удалить строку (из таблицы Users_Permissions). В противном случае вы можете использовать «удаленный» флаг в Users_Permissions таблица, которая также позволит вам хранить информацию для контрольных журналов, например, когда и кем было изменено разрешение. Если вы удалите строку, вы не сможете сохранить эту информацию.
2 голосов
/ 04 февраля 2010

Я думаю, что значения «Y / N» более значимы, чем «1/0». С Oracle я бы сделал следующее, чтобы данные были максимально проверены ядром базы данных:

  • определить столбцы как char (1)
  • добавить проверочное ограничение, которое возможно значения ограничены "в ('Y', 'N')
  • если соответствует бизнес-правилам, сделать их не обнуляемыми - это может предотвратить проблемы, когда вы неявно Предположим, что все, что не 'Y' имеет значение 'N' в вашем SQL
2 голосов
/ 04 февраля 2010

Если ваша СУБД поддерживает логический тип данных, такой как MySQL, используйте его. Если это не так, как в Oracle, я обычно использую char (1) со значениями Y или N. В последнем случае хорошей идеей будет написать пару функций для преобразования вашего Java или C ++ или любого другого логического типа в и от Y / N, чтобы избежать лишнего кода для этого. Это довольно тривиальная функция, но она должна иметь дело с такими случаями, как нули или значения, отличные от Y или N, и вы хотите делать это последовательно.

Я бы определенно НЕ упаковывал флаги в одну переменную с битовыми операциями. Да, это сэкономит немного дискового пространства, но цена намного сложнее и возможности для ошибок. Если ваша СУБД не поддерживает битовые операции - и, поскольку у меня никогда не было желания делать такие вещи, я не знаю, насколько это возможно, - тогда у вас будет очень сложный выбор или сортировка на основе такого флага. Конечно, вы можете извлечь все записи, отвечающие другим критериям, а затем сделать так, чтобы вызывающий код отсеивал записи с правильным значением флага. Но что, если только небольшой процент записей имеет желаемое значение флага, и у вас есть запрос, который объединяет многие другие записи? Например, «выберите employee.name, sum (pay.amount) из заработной платы сотрудника, используя (employee_id), где employee.executive = true и pay.bonus = true». С предложением where вы, вероятно, получите очень скромное количество записей. Без этого вы получаете всю базу данных.

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

Редактировать: Позвольте мне подробно описать написание класса для преобразования вашего "логического значения SQL" в "логическое значение Java". То же относится и к любому языку, но я буду использовать Java в качестве примера.

Если ваша СУБД имеет встроенный логический тип, то с помощью Java вы можете прочитать это непосредственно в логическую переменную с помощью ResultSet.getBoolean ().

Но если вам нужно сохранить его как, скажем, символ "Y" или "N", то вы должны прочитать его в строку. Поэтому для меня имеет смысл объявить такой класс:

class MyBoolean
{
  boolean value;
  final static MyBoolean TRUE=new MyBoolean(true), FALSE=new MyBoolean(false);
  public MyBoolean(boolean b)
  {
    value=b;
  }
  public MyBoolean(String s)
  {
    if (s==null)
      return null;
    else if (s.equals("Y"))
      return MyBoolean.TRUE;
    else
      return MyBoolean.FALSE;
  }
  public static String toString(MyBoolean b)
  {
    if (b==null)
      return null;
    else if (b.value)
      return "Y";
    else
      reutrn "N";
  }
  public String toString()
  {
    return toString(this);
  }
}

Тогда вы можете легко получить логические значения из базы данных с помощью «MyBoolean flag = new MyBoolean (rs.getString (" flag »));" и записать в базу данных с помощью «rs.setString (« flag », flag.toString ());»

И, конечно, затем вы можете добавить любую другую логику, которая вам нужна, к классу, если у вас есть другие логические вещи, которые вам нужно сделать. Если для некоторых целей вы хотите отобразить логическое значение как T / F или Да / Нет, Вкл. / Выкл. И т. Д., Вы можете просто добавить альтернативные варианты toString - toTFString или toString (значение, truetext, falsetext) или как угодно - вместо записи похожий код снова и снова.

2 голосов
/ 04 февраля 2010

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

1 голос
/ 04 февраля 2010

Вместо добавления столбца я предлагаю вам создать еще одну таблицу . Услышь меня ...

Предположим, у вас есть таблица с именем Customer:

CREATE TABLE Customer
(
  CustomerID NUMBER,
  Name       VARCHAR(100)
)

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

CREATE TABLE Customer
(
  CustomerID NUMBER,
  Name       VARCHAR(100),
  Searchable BOOLEAN /* or CHAR(1) or BIT... */
)

Ваш поисковый запрос будет выглядеть примерно так:

SELECT CustomerID, Name
  FROM Customer
 WHERE Name LIKE '%TheCustomerNameIAmLookingFor%'
   AND Searchable = TRUE /* or 'Y' or 0... */

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

Альтернатива: создание отдельной таблицы

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

CREATE TABLE Customer
(
  CustomerID NUMBER,
  Name       VARCHAR(100)
)

CREATE TABLE SearchableCustomer
(
  CustomerID NUMBER
)

В этом случае клиент считается доступным для поиска, если его CustomerID существует в таблице SearchableCustomer. Запрос для поиска клиентов теперь становится:

SELECT CustomerID, Name
  FROM Customer
 WHERE Name LIKE '%TheCustomerNameIAmLookingFor%'
   AND CustomerID IN (SELECT CustomerID FROM SearchableCustomer)

Вы увидите, что эта стратегия очень переносима в СУРБД:

  • При поиске по запросу клиентов используется предложение IN или JOIN
  • Для поиска клиента используется оператор INSERT
  • Чтобы сделать клиента недоступным для поиска, используйте оператор DELETE

Сюрприз

Вы можете сделать определение клиента с возможностью поиска настолько сложным, насколько вы хотите, если вы сделаете SearchableCustomer представление вместо таблицы :

CREATE VIEW SearchableCustomer AS
SELECT CustomerID
  FROM Customer
 WHERE Name LIKE 'S%' /* For some reason, management only cares about customers whose name starts with 'S' */

Ваш поисковый запрос не меняется вообще! : D По моему опыту, это привело к огромной гибкости.

0 голосов
/ 05 февраля 2010

"ВЫБРАТЬ * ИЗ mytable ГДЕ col1

, что невозможно в SQL Server и Oracle (вам нужно иметь какой-то вид или предикат). "

Что показывает только то, что на самом деле смешная и смешная мерзость Oracle и SQL-сервер.

Если объявлено, что col1 имеет тип BOOLEAN, то выражение "col1" IS является предикатом.

Если семантика предложения WHERE требует, чтобы его выражение просто оценивалось как значение истины, и если объявлен некоторый столбец типа «значение истины», то «WHERE that-column» должен быть разрешен и поддерживаться , Период. Любая система, которая не просто выставляет своих авторов за некомпетентные посредственные кряки, которыми они являются.

0 голосов
/ 04 февраля 2010

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

Тогда у нас есть таблицы:

Project
 - Project_ID (INT),
 - Name (VARCHAR)

Attribute
 - Attribute_ID (INT),
 - Name (VARCHAR)

ProjectAttribute_Rel
 - Project_ID (INT),
 - Attribute_ID (INT)

Является ли атрибут проекта истинным или ложным, зависит от того, есть ли строка для него в ProjectAttribute_Rel.

Как правило, вы имеете дело с Attribute_IDs в своем коде, поэтому, когда вы читаете атрибуты проекта (где у вас, вероятно, есть Project_ID), вы просто делаете (PHP произвольно используется в качестве примера):

$arrAttributes = array();
$oQuery = mysql_query('
    SELECT Attribute_ID
    FROM ProjectAttribute_Rel
    WHERE Project_ID = '.addslashes($iProjectId).'
');
while ($rowAttribute = mysql_fetch_assoc($oQuery)) {
    $arrAttributes[] = $rowAttribute['Attribute_ID'];
}

На данный момент вы можете проверить, является ли атрибут проекта истинным, проверив, существует ли он вообще в $ arrAttributes. В PHP это будет:

if (in_array($arrAttributes, $iAttributeId)) {
    // Project attribute is true!
}

Этот подход также позволяет вам выполнять всевозможные трюки, чтобы избежать перечисления множества атрибутов при обновлении, снова при выборе (поскольку SELECT * плох в коде), при вставке и так далее. Это связано с тем, что вы всегда можете пройтись по таблице атрибутов таблицы, чтобы найти доступные атрибуты, поэтому, если вы добавляете один из них и делаете все так, добавление / редактирование / удаление атрибутов тривиально. Шансы на то, что ваш SQL даже не нужно менять, потому что сами атрибуты определены в базе данных, а не в коде.

Надеюсь, это поможет.

0 голосов
/ 04 февраля 2010

Битовые столбцы обычно используются для представления значений типа T / F или Y / N, по крайней мере, в SQL Server. Хотя пурист базы данных может сказать вам, что битовые столбцы не имеют места в базах данных, потому что они «слишком близки к аппаратному обеспечению» - Джо Селко.

...