При разработке баз данных, каков предпочтительный способ хранения нескольких значений true / false?

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

10 ответов


на 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 тоже (с точки зрения хранения), но логически это не неявно конвертируется в любой другой тип.


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

интересно отметить, что MySQL information_schema использует VARCHAR (3) Для " да " или "нет".

пример:

information_schema.USER_PRIVILEGES (
  ...
  IS_GRANTABLE VARCHAR(3) NOT NULL DEFAULT ''
) 

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

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

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

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

Table "Permissions":       (permission_id, permission_text)

Table "Users_Permissions": (user_id, permission_id)

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

С вышеуказанной моделью вы затем укажете истинное значение, назначив user_id С permission_id in the Users_Permissions таблица. В противном случае по умолчанию будет FALSE.

например:

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 таблицы.) Вы можете легко хранить больше информации о каждом факте.

недостатки

  • запросы: для простых запросов потребуются соединения.
  • значение False: чтобы установить значение false, вам нужно будет удалить строку (из Users_Permissions таблица.) В противном случае вы можете использовать флаг "deleted" в Users_Permissions стол, который позволит вам хранить информацию для аудита, таких, как когда разрешение было изменено и кем. Если вы удалите строку, вы не сможете сохранить эту информацию.

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


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

  • определите столбцы как char (1)
  • добавить проверочное ограничение, что можно значения ограничены значением " in ('Y', 'N')
  • Если соответствует бизнес-правилам, сделайте их не nullable - это может избежать проблем, когда вы неявно предположим, что все, что не является "Y" имеет значение 'N' в вашем SQL

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

I определенно не будет упаковывать флаги в одну переменную с битовыми операциями. Да, это сэкономит некоторое дисковое пространство, но цена намного больше сложности и возможностей для ошибок. Если ваша СУБД не поддерживает битовые операции - а у меня никогда не было желания делать такие вещи, я не знаю с головы до ног, что, если таковые имеются, - тогда вам будет очень сложно выбрать или отсортировать на основе такого флага. Конечно, вы можете получить все записи, соответствующие другим критериям, а затем вызывающий код отсеивает те, у которых есть правильное значение флага. Но что, если только небольшой процент записей имеет желаемое значение флага, и у вас есть запрос, который соединяет многие другие записи? Например, " выберите сотрудника.имя, сумма (pay.суммы) от работника присоединиться оплатить используя (ид_сотрудника), где сотрудник.executive=true и pay.бонус=правда". С предложением where вы, вероятно, получите очень скромное количество записей. Без него вы получите всю базу данных.

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

Edit: позвольте мне подробно рассказать о написании класса для преобразования вашего "SQL boolean"в" Java boolean". То же самое относится к любому языку, но я буду использовать 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 ("флаг", флаг.toString ());"

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


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

Предположим, у вас есть таблица с именем 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
)

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

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

вы увидите, что эта стратегия очень переносима через RDBMSs:

  • поиск клиентов с возможностью поиска использует 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' */

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


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


"выберите * От mytable Где col1

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

который идет только, чтобы показать, что смешной и смехотворный мерзость Oracle и SQL server на самом деле.

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

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


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

затем у нас есть таблицы:

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

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

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

является ли атрибут проекта true или false зависит от того, есть ли для него строка в 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'];
}

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

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

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

надеюсь, что это помогает.