[SQL] Об использование NULL

Corrector

Есть таблица продаж Sales. Часть продаж проходит при использовании скидочных карт. В этом случае необходимо записывать в БД некоторые поля, которые относятся к продаже с использованием скидки (например, процент фактической скидки и др.).
Поскольку не все продажи идут со скидочными картами, нет необходимости записывать все эти поля в таблицу Sales. Для этого можно создать новую таблицу DiscountSales, и в таблице Sales сделать ссылку по ключевому полю DiscountSaleID. Если продажа происходит без использовании скидочных карт, то Sales.DiscountSaleID=NULL.
Получается, что факт продажи со скидочной картой или без нее определяется по значению поля Sales.DiscountSaleID (NOT NULL/NULL).
Коллеги на работе делают недовольное лицо, когда я показываю такую структуру таблиц.
Предполагается, что по таблице Sales будут делаться хитрожопые отчеты и, соответственно, запросы к БД. Однако, никаких препятствий при использовании NULL в качестве специального значения я не вижу.
Итак, можно ли так делать?

pitrik2

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

mbolik1

Можно.
Хотя я тоже бы скривился: NULL обычно обозначает неизвестно, а не нет.
На вскидку возникает несколько вопросов:
1. Насколько часто происходят продажи без скидочной карты? Т.е. так ли оправдан вынос в отдельную таблицу.
2. Как в такой системе вы собираетесь обеспечивать целостность? Даже с банальными внешними ключами придётся изгаляться: либо сначала вносить данные о размере скидки (что маразм либо отключать их проверку до commit.
3. Не окажется так что на одну покупку будут накладываться несколько скидок: скажем сезонная распродажа + карта клиента + бонус первым ста покупателям и вам захочется получить детали как получилась та скидка, которая получилась.

Corrector

1. Примерно 90% продаж без скидочной карты
2. Информацию о скидке предоставляет сторонняя система типа "черный ящик". У нее один вход для карты, а на выходе - информация о скидке (сроке действия и т.д.). В моей базе данных списка карт нет.
3. Нет

Corrector

ну это не разговор
что конкретно они говорят? что им не нравится?
Ничего конретно сказать не могут.

mbolik1

2. Информацию о скидке предоставляет сторонняя система типа "черный ящик". У нее один вход для карты, а на выходе - информация о скидке (сроке действия и т.д.). В моей базе данных списка карт нет.
Мы о чём-то разном. Мне лень разбираться, но если ты уверен что не поимеешь проблем с внесением данных в базу, то, наверно, так оно и есть.
По основному вопросу: если в вышей системе не возможен вариант когда будет неизвестно была покупка со скидкой или нет, то ничего криминального в твоём дизайне нет.
Если твоим коллегам кажется что запросы по такой схеме писать сложнее сделай им представление с дополнительным столбцом
case 
when DiscountSaleID is null then 0
else 1
end isDiscount

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

mbolik1

Ещё вопрос вдогонку: у тебя что за система? OLTP? DWH?

pitrik2

я бы скорее в таком случае делал связь 1 .. 1
ну тойсть в табличку продаж ничего не добавлять
а в табличке скидок айдишник держать такой-же как у продажи
так как скидок мало (90% без них) то запросы вида

SELECT * FROM sales s
WHERE
NOT EXIST (SELECT * FROM discount_sales ds WHERE ds.id = s.id)

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

mbolik1

я бы скорее в таком случае делал связь 1 .. 1
То что ты описал это не связь один-к-одному а супертип-подтип.
А один к одному реализуется либо одной, либо двумя ссылками.

pitrik2

То что ты описал это не связь один-к-одному а супертип-подтип.
ну извините
я в терминологии не силен

kruzer25

Чем плох вариант не из Sales ссылаться на DiscountSale, а наоборот?
В твоём случае, как уже сказали, Sales зависят от DiscountSales, твоя схема является говном уже хотя бы по этой причине.

Bayur19

1) null-ы не индексируются, так что если у тебя в столбце будет хотя бы один не NULL,то запрос
select * from table where id is null
ВСЕГДА будет делать full table scan (в оракле, по крайней мере). Полный просмотр таблицы на больших объемах будет тормозить. Да и вообще, full table scan - не есть гуд.
2) если используешь null-ы, то готовься обходить подводные камни трехзначной логики, когда ответ на вопрос 'а равно б' может иметь три варианта ответа 1) да, 2) нет, 3) null.
Вылезают всякие приколы со сравнениями и операциями над множествами (подзапрос и аналогичное по смыслу выражение с консрукцией exist даст разные результаты, если в подзапрос или exist попадают NULL-ы)
3) заведи специальное значение (и вообще, сделай столбец обязательным для заполнения. не хрен место в базе экономить с помощью NULL-ов - много не сэкономишь, а геммора от них можно поиметь.

Dasar

ВСЕГДА будет делать full table scan
неверное утверждение, особенно без указания какая база и какая версия подразумевается в утверждении.

mbolik1

1) null-ы не индексируются, так что если у тебя в столбце будет хотя бы один не NULL,то запрос
code:select * from table where id is null
ВСЕГДА будет делать full table scan
Во-первых, в поставленной задаче этот запрос должен возвращать 90% таблицы, так что full scan — это правильно.
Во-вторых, индексы бывают разные, некоторые — индексируют.
2) если используешь null-ы, то готовься обходить подводные камни трехзначной логики, когда ответ на вопрос 'а равно б' может иметь три варианта ответа 1) да, 2) нет, 3) null.
Вылезают всякие приколы со сравнениями и операциями над множествами (подзапрос и аналогичное по смыслу выражение с консрукцией exist даст разные результаты, если в подзапрос или exist попадают NULL-ы)
И если не используешь null-ы всё равно нужно учитывать трёхзначную логику, т.к. то что ты её не используешь ещё не значит что СУБД её не использует.
3) заведи специальное значение (и вообще, сделай столбец обязательным для заполнения. не хрен место в базе экономить с помощью NULL-ов - много не сэкономишь, а геммора от них можно поиметь.
Решение в целом ничем не лучше, решает только одну проблему: программистам вместо is null нужно будет писать != 'Спец значение'.

kruzer25

заведи специальное значение (и вообще, сделай столбец обязательным для заполнения. не хрен место в базе экономить с помощью NULL-ов
Мсье не слышал о такой вещи, как foreign key?
Топикстартер предложил говнорешение; ты же предлагаешь в это говно ещё и костылей напихать. Может быть, лучше сразу сделать нормально?

mbolik1

Мсье не слышал о такой вещи, как foreign key?
Ну это обходится: для фиктивного значения заводится запись с этим специальным значением в целевой таблице. Но это конечно очередной костыль.

kruzer25

Но это конечно очередной костыль.
Вот и я про это.
Кроме того, тогда тебе ещё придётся городить очередной костыль вместо стандартного UNIQUE - ты же не хочешь, чтобы у двух разных Sale был один и тот же DiscountSale, если это не фиктивное значение?
Оставить комментарий
Имя или ник:
Комментарий: