Дизайн базы данных: заменить логический столбец столбцом метки времени? - PullRequest
8 голосов
/ 12 октября 2010

Ранее я создавал таблицы следующим образом:

create table workflow (
    id number primary key,
    name varchar2(100 char) not null,
    is_finished number(1) default 0 not null,
    date_finished date
);

Столбец is_finished указывает, завершен ли рабочий процесс или нет.Столбец date_finished - это когда рабочий процесс был завершен.

Тогда у меня возникла идея: «Мне не нужен is_finished, поскольку я могу просто сказать: где data_finished не равен NULL», и я разработал без столбца is_finished:

create table workflow (
    id number primary key,
    name varchar2(100 char) not null,
    date_finished date
);

(мы используем Oracle 10)

Это хорошая или плохая идея?Я слышал, что у вас не может быть индекса для столбца со значениями NULL, поэтому where data_finished is not null будет очень медленным для больших таблиц.

Ответы [ 10 ]

15 голосов
/ 12 октября 2010

Это хорошая или плохая идея?

Хорошая идея.

Вы удалили пространство, занятое избыточным столбцом;столбец DATE выполняет двойную функцию - вы знаете, что работа была завершена, и , когда .

Я слышал, что у вас не может быть индекса для столбца с NULLЗначения, поэтому «где data_finished не равен NULL» будет очень медленным для больших таблиц.

Это неверно.Индексы Oracle игнорируют значения NULL.

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

11 голосов
/ 27 октября 2010

Существует правильный способ индексирования нулевых значений, и он не использует ФБР. Oracle будет индексировать нулевые значения, но НЕ будет индексировать нулевые значения LEAF в дереве. Таким образом, вы могли бы исключить столбец is_finished и создать индекс, подобный этому.

CREATE INDEX ON workflow (date_finished, 1);

Тогда, если вы проверите план объяснения по этому запросу:

SELECT count(*) FROM workflow WHERE date_finished is null;

Возможно, вы увидите, что индекс используется (если оптимизатор доволен).

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

  1. Запись закончена. is_finished
  2. Запись закончена к определенной дате. date_finished

Может быть, вам нужно держать это отдельно, а может и нет. Когда я думаю об исключении столбца is_finished, это беспокоит меня. В будущем может возникнуть ситуация, когда запись закончится, но вы точно не знаете, когда. Возможно, вам придется импортировать данные из другого источника, а дата неизвестна. Конечно, сейчас это не соответствует требованиям бизнеса, но все меняется. Что вы делаете тогда? Что ж, вы должны поместить фиктивное значение в столбец date_finished, и теперь вы немного скомпрометировали данные. Не ужасно, но есть загвоздка. Тихий голос в моей голове кричит Вы делаете НЕПРАВИЛЬНО , когда я делаю такие вещи.

Мой совет, держите его отдельно. Вы говорите о крошечной колонке и очень узком указателе. Хранение не должно быть проблемой здесь.

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

-Эрик С. Раймонд

5 голосов
/ 27 октября 2010

Это хорошая или плохая идея? Я слышал, что у вас не может быть индекса для столбца со значениями NULL, поэтому «где data_finished не равен NULL» будет очень медленным для больших таблиц.

Oracle индексирует пустые поля , но не индексирует NULL значения

Это означает, что вы можете создать индекс для поля, помеченного NULL, но записи, содержащие NULL в этом поле, не попадут в индекс.

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

Таким образом, запросы, включающие в себя поиск по диапазону на date_finished, на самом деле будут работать лучше.

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

Вы можете обойти это, создав два индекса:

CREATE INDEX ON mytable (date_finished)
CREATE INDEX ON mytable (DECODE(date_finished, NULL, 1))

и используйте этот запрос, чтобы найти незаконченную работу:

SELECT  *
FROM    mytable
WHERE   DECODE(date_finished, NULL, 1) = 1

Это будет вести себя как секционированный индекс: все работы будут проиндексированы по первому индексу; неполные будут проиндексированы вторым.

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

4 голосов
/ 12 октября 2010

Для всех тех, кто сказал, что колонна - пустая трата пространства:

Double Duty не очень хорошая вещь в базе данных. Ваша основная цель должна быть ясность. Многие системы, инструменты, люди будут использовать ваши данные. Если вы замаскируете значения, спрятав значение в других столбцах, вы НАЧИНАЕТЕ, что другая система или пользователь ошибаются.

И тот, кто считает, что это экономит место, совершенно неправ.

Вам понадобятся два индекса в этом столбце даты ... один будет на основе функций, как предлагает OMG. Это будет выглядеть так:

NVL (Date_finished, TO_DATE ('01 -JAN-9999 '))

Таким образом, чтобы найти незавершенные задания, вам нужно обязательно правильно написать предложение where

Это будет выглядеть так:

WHERE NVL (Date_finished, TO_DATE ('01 -JAN-9999 ')) = TO_DATE ('01 -JAN-9999')

Да. Это так ясно. Это совершенно лучше, чем

WHERE IS_Unfinished = 'ДА'

Причина, по которой вам нужно иметь второй индекс в том же столбце, заключается в КАЖДОМ ДРУГОМ запросе на эту дату ... вы не захотите использовать этот индекс для поиска работы по дате.

Итак, давайте посмотрим, чего вы добились с предложением OMG и др.

Вы использовали больше места, вы запутали смысл данных, вы допустили ошибки с большей вероятностью ... ПОБЕДИТЕЛЬ!

Иногда кажется, что программисты все еще живут в 70-х годах, когда МБ на жестком диске был первоначальным взносом на дом.

Вы можете сэкономить место, не теряя при этом ясности. Сделайте Is_unfinished либо Y, либо NULL ... ЕСЛИ , вы будете использовать только этот столбец, чтобы найти «работу». Это сохранит этот индекс компактным. Он будет только размером с незавершенные строки (таким образом вы будете использовать неиндексированные нули вместо того, чтобы ими завинчивать). Вы помещаете немного места в свой стол, но в целом это меньше, чем ФБР. Вам нужен 1 байт для столбца, и вы будете индексировать только незаконченные строки, так что «небольшая часть работы и, вероятно, останется довольно постоянной. ФБР понадобится 7 байтов для КАЖДОГО РЯДА, пытаетесь ли вы найти их или нет. Этот индекс будет соответствовать размеру таблицы, а не только размеру незавершенных заданий.

Ответ на комментарий OMG

В своем комментарии он / она заявляет, что для поиска незавершенных работ вы просто используете

WHERE date_finished IS NULL

Но в своем ответе он говорит

Вы можете создать индекс на основе функции, чтобы обойти значения NULL, не индексируемые

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

3 голосов
/ 12 октября 2010

Что касается дизайна таблицы, я думаю, что хорошо, что вы удалили столбец is_finished, так как сказали, что в этом нет необходимости (это избыточно). Нет необходимости хранить дополнительные данные, если в этом нет необходимости, они просто тратят пространство. С точки зрения производительности, я не вижу в этом проблемы для значений NULL. Их следует игнорировать.

2 голосов
/ 12 октября 2010

Я бы использовал значения NULL для работы индексов, как уже упоминалось в других ответах, для всех запросов, кроме "WHERE date_finished IS NULL" (поэтому зависит от того, нужно ли вам использовать этот запрос).Я определенно не буду использовать выбросы, такие как год 9999, как следует из ответа:

вы также можете использовать «фиктивное» значение (например, 31 декабря 9999) в качестве значения date_finished для незавершенных рабочих процессов

Выбросы, такие как год 9999, влияют на производительность, поскольку (из http://richardfoote.wordpress.com/2007/12/13/outlier-values-an-enemy-of-the-index/):

Селективность сканирования диапазона в основном рассчитывается CBO как число значенийв диапазоне интересов , деленном на полный диапазон возможных значений (т.е. максимальное значение минус минимальное значение)

Если вы используете значение, например 9999, тоБД будет думать, что диапазон значений, хранящихся в поле, например, 2008-9999, а не фактический 2008-2010, поэтому любой запрос диапазона (например, «между 2008 и 2009») будет охватывать крошечный% диапазонавозможных значений по сравнению с фактически охватывающим около половины диапазона. Он использует эту статистику, чтобы сказать, что, если% из этих возможных значений является высоким, вероятно, будет совпадать множество строк, а затемполное сканирование таблицы будет быстрее, чем сканирование индекса.Это не будет сделано правильно, если в данных есть выбросы.

1 голос
/ 17 марта 2011

Чтобы разрешить индексированные / неиндексированные столбцы, не будет ли проще просто объединить две таблицы, например так:

-- PostgreSQL
CREATE TABLE workflow(
    id SERIAL PRIMARY KEY
  , name VARCHAR(100) NOT NULL
);

CREATE TABLE workflow_finished(
    id INT NOT NULL PRIMARY KEY REFERENCES workflow
  , date_finished date NOT NULL
);

Таким образом, если запись существует в workflow_finished, этот рабочий процесс завершен, в противном случае это не так. Мне кажется, это довольно просто.

При запросе незавершенных рабочих процессов запрос становится:

-- Only unfinished workflow items
SELECT workflow.id
FROM workflow
WHERE NOT EXISTS(
  SELECT 1
  FROM workflow_finished
  WHERE workflow_finished.id = workflow.id);

Может быть, вы хотите оригинальный запрос? С флагом и датой? Запрос вот так:

-- All items, with the flag and date
SELECT
    workflow.id
  , CASE
    WHEN workflow_finished.id IS NULL THEN 'f'
    ELSE                                   't'
    END AS is_finished
  , workflow_finished.date_finished
FROM
            workflow
  LEFT JOIN workflow_finished USING(id);

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

1 голос
/ 27 октября 2010

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

Еще одна мысль состоит в том, что, удалив столбец, вы избежите парадоксальных условий, которые вам нужно будет кодировать, например, что происходит, когда is_finished = No иодед_дата = вчера ... и т.д.

0 голосов
/ 12 октября 2010

Я предпочитаю одноколоночное решение.

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

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

0 голосов
/ 12 октября 2010

В качестве альтернативы индексу на основе функций вы также можете использовать фиктивное значение (например, 31 декабря 9999 года или, альтернативно, за один день до самого раннего ожидаемого значения date_finished) в качестве значения date_finished для незавершенных рабочих процессов.*

РЕДАКТИРОВАТЬ: альтернативное значение фиктивной даты, следующие комментарии.

...