Плохие привычки: злоупотребление столбцами типа bit
Пересказ статьи Aaron Bertrand. Bad Habits: Abusing bit columns
Я просматриваю много кода DDL от различных команд, и часто встречаю такое:
ALTER TABLE dbo.Widgets ADD IsNotCertified bit NULL;
Когда я вижу битовый столбец, допускающий NULL-значения, то всегда спрашиваю:
Вы намеренно хотите использовать здесь трехзначную логику?
Означает ли NULL что-то отличное от 0, или весь будущий код будет использовать COALESCE, чтобы сравнивать NULL с нулем?
В некоторых случаях NULL и 0 могут иметь различный смысл. Например, битовый столбец может означать включение или выключение установки, а NULL - что установка еще не сконфигурирована, поэтому мы не можем включить её, или же она не допустима для этой сущности. Все это может быть важными фактами, которые нужно различать в нашей бизнес-логике, хотя я, вероятно, предпочел бы столбец типа tinyint для ActiveState, который содержит "включено" (1), "выключено" (0) и еще значения для других возможных состояний.
Хотя чаще да, чем нет, оказывается, что разработчик побоялся добавить столбец с NOT NULL в существующую таблицу, поскольку для древних версий SQL Server это всегда могло вызвать блокировку всей таблицы. (Возможно, это относится к вашей версии/редакции, но тогда я бы ожидал увидеть код, включающий пакетное обновление для установки значения в 0, а затем добавления ограничения NOT NULL.)
И в этом случае при определении битового столбца я также спрашиваю:
Потому что со столбцом IsNot<что-то> неизбежно происходит что-то более странное. Недавно я проверял многооператорную табличнозначную функцию с ужасной производительностью, которая содержала такой код:
Конечно, сначала я подумал, что DPA вставил какой-то артефакт во время сборки или вывода, или это была опечатка. Но затем я увидел таблицу, определенную выше и понял, что это было сделано намеренно:
Здесь столбец имеет противоположный смысл, чем столбец в таблице, так что мы должны обратить бит (а также учитывать тот факт, что значения NULL возможны, но не означают ничего другого). Эта комбинация провела к использованию побитового NOT, что, я уверен, автор посчитал умным, но совершенно не подумал о будущих читателях, которые, смотря на этот код, будут выглядеть как персонаж Эдварда Мунка:
Но, по крайней мере, это было не так уж плохо:
Что касается меня, то я бы постарался не делать код столь загадочным ради экономии нескольких символов - особенно сокращения за счет использования побитовых операторов, на которые я уже жаловался раньше. Следующее выглядит многословней, однако более самодокументированным в смысле намерения, и проверяет столбец, который не может иметь двух различных значений для выражения одного и того же смысла, поэтому мы не должны беспокоиться о их слиянии:
Где это возможно, я либо давал бы столбцу в возвращаемой таблице имя, соответствующее исходному, либо вернулся к проектированию таблицы и заменил его на IsCertified NOT NULL DEFAULT 0 - чтобы 1 означала 1, 0 означал 0, и это все, что нужно сделать.
Хотя чаще да, чем нет, оказывается, что разработчик побоялся добавить столбец с NOT NULL в существующую таблицу, поскольку для древних версий SQL Server это всегда могло вызвать блокировку всей таблицы. (Возможно, это относится к вашей версии/редакции, но тогда я бы ожидал увидеть код, включающий пакетное обновление для установки значения в 0, а затем добавления ограничения NOT NULL.)
И в этом случае при определении битового столбца я также спрашиваю:
Вы на самом деле хотите выразить то, что 1 означает "нет", а 0 - "да"?
Потому что со столбцом IsNot<что-то> неизбежно происходит что-то более странное. Недавно я проверял многооператорную табличнозначную функцию с ужасной производительностью, которая содержала такой код:
/* добавлено M.C. Escher */
INSERT @Awful
SELECT WidgetID,
~COALESCE(IsNotCertified, 0)
FROM dbo.Widgets
WHERE ...
Конечно, сначала я подумал, что DPA вставил какой-то артефакт во время сборки или вывода, или это была опечатка. Но затем я увидел таблицу, определенную выше и понял, что это было сделано намеренно:
RETURNS @Awful table
(
WidgetID int,
IsCertified bit,
Здесь столбец имеет противоположный смысл, чем столбец в таблице, так что мы должны обратить бит (а также учитывать тот факт, что значения NULL возможны, но не означают ничего другого). Эта комбинация провела к использованию побитового NOT, что, я уверен, автор посчитал умным, но совершенно не подумал о будущих читателях, которые, смотря на этот код, будут выглядеть как персонаж Эдварда Мунка:
Но, по крайней мере, это было не так уж плохо:
WHERE NOT (~COALESCE(IsNotCertified, 0) <> -1)
Что касается меня, то я бы постарался не делать код столь загадочным ради экономии нескольких символов - особенно сокращения за счет использования побитовых операторов, на которые я уже жаловался раньше. Следующее выглядит многословней, однако более самодокументированным в смысле намерения, и проверяет столбец, который не может иметь двух различных значений для выражения одного и того же смысла, поэтому мы не должны беспокоиться о их слиянии:
INSERT @Awful(WidgetID, IsCertified) /* включение списка столбцов помогает */
SELECT WidgetID,
IsCertified = CASE WHEN IsNotCertified = 1 THEN 0 ELSE 1 END
/* здесь не повредит алиас, согласующийся с именем столбца */
/* комментарий, объясняющий, почему мы преобразуем, тоже не повредит */
FROM dbo.Widgets
WHERE ...
Где это возможно, я либо давал бы столбцу в возвращаемой таблице имя, соответствующее исходному, либо вернулся к проектированию таблицы и заменил его на IsCertified NOT NULL DEFAULT 0 - чтобы 1 означала 1, 0 означал 0, и это все, что нужно сделать.
Обратные ссылки
Автор не разрешил комментировать эту запись
Комментарии
Показывать комментарии Как список | Древовидной структурой