All streams
Search
Write a publication
Pull to refresh

Comments 170

bit. в базах данных обычно в качестве boolean. mssql

а тут можно вообще связанную таблицу запилить. "статусы", а в юзера поле с _id "статуса" и извращаться дальше.

Ага, SQL запрос только в начале, как он выглядит по "правильному" видимо только ORM известно)))

А вы видели старые Легаси проекты где этого не было и вам пришлось сделать "не очень хорошую" решению чтобы ваша работа закончилась?

да.

название статьи какое? про boolean? или про реализацию статусов через перечисление?

Спасибо было интересно

Я так понял это не boolean так плох а тот кто его задумал использовать там где водможно понадобиться более чем два состояния

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

Причем тут булевой формат данных, если сама структура данных не продумана? 🤔 всё равно что винить создателей алфавита, потому как некоторые не умеют из букв складывать слова

А вы видели старые Легаси проекты где этого не было и вам пришлось сделать "не очень хорошую" решению чтобы ваша работа закончилась?

Можно подумать что структура не хороша но есть такие проекты где в структуре вообще не предусмотрели такие вещи.

Так зачем ломать существующую, когда её можно внедрить в новую? Легаси это не приговор - его можно оборачивать в новое.

Алфавит не продуман ибо для некоторых звуков букв нет - приходится извращаться

Бывает и наоборот, и приходится страдать.

Поле "Имя пользователя" не сможет сохранить дату рождения пользователя, если это потребуется, поэтому предлагаю имя пользователя хранить в json структуре в БД. На всякий случай

А ведь кто-последует)

Логично же. Имя может изменяться во времени + отдельно инициалы + поддержка падежей + написание другими алфавитами + хранение старых значений с метками времени. Ну и журналы аудита изменений удобно прямо здесь же хранить.

В первой записи хранить списком список последующих идентификаторов, в следующих указывать сначала название поля и следующий идентификатор, потом тип поля и следующий идентификатор, потом значение поля. Связный список-дерево. Примерно так:

1 "2, 5, 8"

2 name, 3

3 varchar(50), 4

4 Иван

5 second_name, 6

6 varchar(50), 7

7 Иванович

8 isDeleted, 9

9 bool, 10

10 0

Так можно вместо сложной струкруты БД иметь всё в одной таблице.

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

Но если и эти записи собрать в список, можно хранить в одной ячейке, зачем множить строки

А еще его надо хранить на разных языках в разных написаниях (кириллица, латиница, арабское письмо, иероглифы разные)

Для данных сложнее номеров и UUID обычно используется поле безразмерной строки вроде TEXT в PostgreSQL, которые хоть и не совсем JSON, но тоже хранятся обычно отдельно от основных данных.

Думал сейчас узнаю, сакральную тайну про булиан в БД.

В итоге рассказали про кривые руки... Еще и уровень сложности статьи "Средний".

Кривые руки сложности средний. Таким что Легаси что новое

Уровень сложности статьи: True

В приличном обществе за магические строки могут и в морду дать.

- Я правильно написал = Inactiv, а не работает, почему?
- Потому что архитектор очень не любит коллег и работающий код

P.S. А если вы хотите прикрутить логику установки полей, так сделайте хранимую процедуру, триггер, view и там пилите любые извращения.

Enum же, а не магические строки

Это может выглядеть как enum, но надо чтобы уважаемый компьютер думал также. Чтобы выражение "where state=Anactive" валилось с ошибкой про неверное перечисление, а не возвращало False.

Проблема то не новая с тех пор, как пол не булево :)

Не знаю, как в греп с синтаксисом сиквела, а в постгресе есть enum. И указание неверного значения для такого типа как раз валит запрос.

Всё правильно изложено.

Для тех, кто считает "неужели нельзя продумать?" - а вот нельзя: жизненный цикл ПО может превышать "собрали и запустили", могут возникать новые вводные, которые продумать заранее просто невозможно.

Чем компактнее и "изящнее" вы сделали сейчас - тем сложнее будет это поддерживать потом.

Прямое оспаривание KISS, YAGNI и логики? Ну-ну.

Нет, это просто некоторые отрицают реальность в пользу моделей.

Да вот хотя бы учебный пример:
- компания торгует товарами, ведет учет продаж. Закупка - у оптовика. Программа по сути - книга продаж и покупок плюс склад, в электронном виде
Задачка для студента на вечер. Что может пойти не так?

Что угодно:
- компания растет, и переходит с УСН на ОСН (переделка финансового блока, учет и выделение НДС, округление копеек и т.д.)
- на часть товаров вводят НДС 20, на часть - 18, на часть - 0 (разделение документов по видам НДС, снова переделки)
- вводятся возрастные ограничения (переделка блока работы с клиентами), иные ограничения (проверка лицензий, их учет) и т.д.
- компания растет, начинает закупать товар у производителя за рубежом (ввод валютных закупочных цен, ведение курсов валют)
- что-то происходит, этот производитель и валюты больше неактуальны, ищутся альтернативы и другие валюты, а также вводятся посредники со своим процентом

То есть, либо надо было N лет назад заложить в систему все будущие возможные риски (вплоть до прохождения разрешений Цензурного комитета, по закону от 2036 года), либо вносить изменения и усложнять по мере необходимости.

И вот тут-то ваше очередное простое и изящное решение в виде какого-то булева флага (то же наличие НДС, например) становится костью в заднице, потому что теперь вам не только надо переделать bool в int или float, но и переписать кучу функций в самых разных местах.
Хотя бы часть работы можно было бы сделать сразу, только вводя это самое НДС, в виде явного указания %%.

Это я еще не разбираю типичные проблемы складского учета, когда нельзя просто так взять и написать в базу "Пепяка заморская, 3 штуки", а нужно учесть товарную партию, принять ее по накладной, зарезервировать за заказом и списать по оплате, иначе через время N у вас никто не разберется, почему на учете стоят "Метлибер, 50 шт.", откуда они взялись, куда они делись и что это вообще такое.

KISS - говорили они...

А не надо проектировать так, что НДС рассчитывается в 20 разных местах. "Хороший дизайн" не равно "дизайн с предвидением будущего на 50 лет вперёд". Это две большие разницы.

А вот что понравилось, это ваше "что-то происходит".

Понятно, значит вы не сталкивались на практике.
Штош...

Разводятся две трети пар. Значит ли это, что уже до свадьбы надо собрать и держать наготове чемоданчик со своими вещами? Врядли.

Наконец, покупаете ли ребёнку одежду на вырост на 5-10 лет? Просто невозможно - он не сможет в ней ходить, будет слишком неудобно прежде всего, а засмеют, это уже менее важно. Но если потом нет денег на замену одежды для подросшего ребёнка, значит, плохо экономили или мало зарабатывали и прочее, включая всякие "что-то происходит", но это никак не значит, что надо было покупать на вырост на 5-10 лет вперёд.

Нет ресурсов на рефакторинг? Плохой менеджмент, плохой дизайн, мало инвесторов и прочее, и только в самом конце "не сделали на вырост". Кроме "на очевидный вырост".

Значит ли это, что уже до свадьбы надо собрать и держать наготове чемоданчик со своими вещами? Врядли.

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

На самом деле иметь под рукой такой чемоданчик на случай экстренной ситуации лишним не будет.
...

Всякое в жизни бывает.

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

Дадада. "что-то происходит", "а что случилось" и все такое 😎

Только это уже совсем далеко от темы.

почему на учете стоят "Метлибер, 50 шт.", откуда они взялись

«Тисочки генитальные с розовым мехом». Правда, там очень прозаичная история их учёта.

Но тем не менее в вашем примере постоянно "компания растёт", будем считать это заслугой KISS =)

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

Или, вовсе наоборот, — потому что она продуманная и не лезущая под руку.

Подобные вводные абсолютно всегда продумываются до начала разработки.

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

И, внезапно, никакой сложности в том, чтобы "спроектировать добавление служебной колонки" нет. В конце концов, это не имеет прямого отношения к предметной области (или к большинству) и для этого даже аналитик не нужен.

Речь не об этом.

Первое: всё продумать заранее нельзя. Пример: вы торгуете онлайн, напитками, квас, кола, лимонад, энергетики. Несколько лет назад это были равнозначные категории, сейчас по энергетикам ограничение 18+, завтра по энергетикам планируют биометрию.

Вы могли предвидеть заходы законодательной мысли, скажем, в 2019 году? Заранее? Не могли.

Значит, при поступлении вводных нужна доработка, так? Так (или переделка с нуля, но там другие вопросы будут)

И вот тут второе: теперь, когда вам надо вынести энергетики в отдельную категорию 18+, не надо просто добавлять признак isForAdult, простое булево значение!

Потому что теперь у вас есть пример превращения обычного товара в необычный, а значит теперь вы можете предположить, что завтра, скажем, кола попадет в категорию 12+. Почему бы и нет?

Поэтому надо вводить признак, допускающий и 18+, и 12+, сейчас, заранее, пока все равно дорабатываете, чтобы это не дорабатывать потом опять.

Вот о чем речь.

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

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

И потерять к примеру историю продаж за прошлые периоды - а это ценные данные. Либо вместе с переделкой с нуля - городить ещё систему миграции данных из старой базы в новую...

Т.е. можно ожидать такую же статью на каждый тип данных. Даже на BLOB, видимо.

описание первой задачи -"хранить состояние об включенном аккаунте и об удаленном аккаунте"
решение сразу же применено вредное - заводим флаги is_active и is_deleted.
что будет записано в is_active когда аккаунт удален is_deleted=true - в is_active может быть записано любое значение (зачем оно нужно тогда ?), может быть is_deleted=true и is_active=true (а что это значит) ? или же возможен единственный вариант is_deleted=true и is_active=false ?
одновременно звучит бизнес требование - "обеспечить жизненный цикл аккаунта". а у него состояния - активный, удален, выключен.
тогда действительно да - надо заводить словарь с константами. реализация словаря зависит от ваших технологических условий.
автор тут дельно предложил использовать enum - как один из вариантов возможно.

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

Давайте будем честными, проблема не столько в Boolean, сколько в попытке булями заменить бинарные категории, при том, что, как тут верно подмечено, когда вводится больше категорий, то и для анализа расширяется поле работы. Були изначально нужны для логических конструкций и ответа на вопросы да/нет, соответствует/не соответствует, а не для категорирования данных. Использовать були для категорирования это какая то костыльная упячка

А если у данных всего две категории? Упячка или не упячка - две.

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

Это плохой принцип - делать каждый элемент программы "на вырост". Это надо делать только в очевидных случаях.

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

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

Каждый вырост не продумаешь и техдолг будет всегда. А вот выйти на рынок быстрее конкурентов, это часто решающий фактор.

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

Да тут действительно не о чем спорить, нужно чувствовать исходя из собственного опыта. Каждый "вырост", естественно, не продумаешь, но когда хоть что-то заранее предугадал/предусмотрел гораздо менее попабольно делать доработку

нужно чувствовать исходя из собственного опыта

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

А вы точно будете уверены, что ваша логика соответствует реальности, а реальность будет соответствовать здравому смыслу? )

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

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

А если у данных всего две категории?

Главное потом никогда не путать, кто из них True а кто нет.

Путать или не путать - две. А кто из них кто, по ходу разберёмся.

И кстати, True и нет - не двоичный выбор, квадратичный - True, False, да, нет. ;)

В non-nullable столбце... Вот это поворот.

Потому что поле надо было называть не sex, а is_male.

и всех небинарных называть женщинами? Феминистки будут недовольны )

is_men Nullable

И все довольны

В мире, где местами уже есть 6 гендеров и планируется добавление новых?

Даже в мире где есть только два пола, пол может быть "не указан" и/или "неизвестен". Булев флаг для пола в любом случае так себе идея.

Не указан = null

Вот если нужно отличать "не указан" от "неизвестен" или ещё чего-то такого - да, нулябельного boolean уже маловато.

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

Конкретно в статье доказывается нужность булина. Флаг is_deleted очень сильный. И вероятно не может быть перезаписан без специальных процедур. При этом другие статусы могут переходить туда-сюда без особых проблем. И их логично и правильно разделять.

Я согласен. Например, удаленные записи не должны показываться в UI. А активный/не активный же показывается в карточке пользователя, в строке статуса. Тут скорее 2 флага нужно, или 2 связанные таблицы:

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

  • is_active может быть временным параметром с таймаутом, или постоянным, и можно будет удалить как активную так и неактивную учётку, и сервис изначально получает не-удаленные учётные записи, поэтому ему не нужно будет заботиться о проверке является ли account.status == status_enum.DELETED

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

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

Boolean обычно отвечает за 2 абсолютно разделимых состояния одной категории и включён/выключен и доступен/удалён выглядит как 2 разных категории, которые лучше не объединять.

Я вам даже больше скажу. Удаление часто сопровождается также удалением данных, например, удалением/маскированием e-mail и других персональных данных, которые нам больше не нужны (привет GDPR). Т.е. мы можем оставить сам объект в базе для каких-то внутренних целей, но он будет настолько "искорежен" с точки зрения логики системы, перевести его из этого состояния в любое другое совершенно невозможно. id_deleted это терминальный статус. А вопрос автора поста: может ли войти в систему пользователь, помеченный как is_deleted..... Если он возникает, то, видимо, в системе что-то не так :-)

В приведенном вами примере нужно использовать bit. Битовые поля видимо не для вас придумали. Вы предлагаете хранить строки там где это не нужно.

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

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

Один флаг isDeleted не отвечает на эти вопросы и не позволяет различать разные жизненные состояния пользователя.

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

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

Во-вторых, чтобы потом код не пестрел десятками if-ов при обработке этих статусов, надо опять же не нарушать 3 НФ, и делать это не через енумы, а через связанные таблицы. В последствии к статусам можно будет легко привязать алгоритмы их обработки в разных ситуациях.

На больших данных Enum выигрывает у Join.

Мы все видели и использовали поля типа boolean в базах данных ...

Не надо обобщать. Если о чем-то пишите, то исследуйте область, о которой пишите. В статье укажите, к какой области она применима. Например, в Oracle никто и никогда не мог видеть колонки таблицы типа boolean.

По ссылке ничего не видно. Процитируйте суть того что там написано.

Статья написана на уровне, хватит использовать тип number для id, ведь есть вероятность что вы будете использовать uuid и придется везде переписывать тип на string, поэтому number плох. А еще, вы не поверите, вилка очень хороша, но как только решите попробовать поесть суп из нее, то как выяснится она не подойдет, поэтому выбрасываем вилки и используем ложки

зануда mode: есть супы которые удобно есть вилкой :)

Нет уж, давайте разберёмся. Удобно? А может практично? Или быстро? Или вкуснее? Тогда надо ввести enum: удобно | практично | быстро | вкуснее | ...

Да, и надо добавить две даты: дату последней еды и дату осознания данного факта. Для полноты - ещё дату покупки ложки.

Итак: есть супы, которые { mode: удобно, дата_еды: 02.09.2025, дата_осзн: 01.01.0033, дата_пкпк_лжк: 02.01.0033 } есть ложкой.

Остаются ещё вопросы к первому "есть". Некто, за скобками, что делает? Ест! Без мягкого знака. Значит, "ест супы, которые..." и далее по тексту.

А еще можно утюгом гвозди забивать

Сорри, забыл сразу дописать, сделаю здесь: ...и не удобно есть ложкой.

Во-первых, правильно писать WHERE is_deleted. Поле само по себе булево, его не надо ни с чем сравнивать, чтобы получить булевый контекст, как, например, инты.

Во-вторых, ну появится у тебя второе булево поле, и с ним — ограничение на одновременное взведение двух флагов разом. Эка невидаль, CONSTRAINT к таблице добавить.

А всё потому, что опять фронтендеров к базе данных допустили.

А вот этого CONSTRAINT не должно быть. В развитие ПО может появиться и другие флаги.

По этому лучше вообще отказаться от простых флагов и использовать рекомендованные структуры который подходить и сейчас и в будущем их использовать не будет проблемой

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

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

is_deleted и is_active/status/etc - это, очевидно, флаги разного уровня.

is_deleted - просто показывает, что запись удалена, и не важно в каком статусе она была на момент удаления. Вот в каком состоянии она была, в таком состоянии ее и удалили, и состояние это было сохранено.

Например, вы случайно удалили запись. В случае отдельного флага is_deleted вы просто снимете галочку у этого флага, и всё.

А в случае флага status в какое состояние вы будете переводить запись, отменяя удаление? Вы потеряли информацию о состоянии объекта, которое было на момент удаления.

А если вы случайно удалили миллион записей? Как вы будете восстанавливать исходные статусы?

1-ое правило нормализации БД:
"Запрещается хранить разные по смыслу значения в одной колоноке" !

status: "active" | "inactive" | "deleted"
Все три значения разные по смыслу,
это то же самое что дату рождения и дату смерти хранить в одной колонке...

Все три значения имеют один смысл "Статус". :-) И тут получается дискуссия уже не про флаги, но про смысл жизни, как его понимать...

Все три значения имеют один смысл "Статус". :-)

Только статусы эти относятся к разным сущностям. "active" | "inactive" например относятся к абоненту, а "deleted" - к записи в БД, но не к абоненту.

Абонент тоже может быть удалён (из сервиса). Поэтому тут уже понимание смыслов.

Абонент тоже может быть удалён (из сервиса).

... может быть удален с возможностью восстановления... - это важное уточнение.

А восстановить запись нужно иметь возможность полностью, включая все статусы.

Кстати, да. Вариант вполне рабочий.

Абонент тоже может быть удалён (из сервиса).

Абонент может быть eliminated, а запись о нем deleted. Тип данных criminal.

"Женат", "иностранец", "несудим" тоже статусы. Однако вы не будете хранить их все в одной колонке.

Вспомнилось прям
Вспомнилось прям

Не касаясь рассуждений на тему архитектуры скажу именно про були. Давно уже пришёл к мысли, что гораздо удобнее использовать банальный char с значениями 0/1. Ещё и запись сократится, т.к вместо =false будет ='0' или вообще =0. Да и запас на вырост есть.

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

И в каждом месте где читаешь проверять что не пришло ‘B’ и как-то обрабатывать ситуацию если пришло.

Спасибо, лучше не надо.

С чего вдруг должно прийти какое-то 'В'?

И не надо ничего обрабатывать. В базовом случае вообще достаточно одного сравнения, допустим с 0, который будет обозначать false, а все остальное считать true. При необходимости такой код легко доработать и расширить функционал. Сколько использую такой подход ни разу проблем не было.

P.S.: Вот за это и "люблю" публику Хабра, на ровном месте минусов навтыкать. Конструктивно. Демократично. Интеллектуально.

P.P.S.: Например, в PostgreSQL логический тип данных boolean (BOOLEAN) хранится в виде одного байта. В SQL-запросах могут представляться ключевыми словами SQL: TRUE, FALSE и NULL. Также можно использовать строковые представления: для TRUE - «true», «yes», «on», «1», для FALSE - «false», «no», «off», «0». Также принимаются уникальные префиксы этих строк, например «t» или «n».

И чем это отличается от того что использую сам? Всего лишь упростил и сократил до простого 0/1, что понятно всем и гораздо удобнее, чем все эти true/false. А, например, в Python они еще и регистрозависимые. Вообще "прекрасно" :)

Обрабатывать надо потому что оно может там придти. Это же чар. И вы никогда не знаете что это 'B' означает на самом деле. Может не true, а что-то другое. Среди 256 возможных значений всякое бывает.

Типы вообще не просто так придуманы. И ограничения возможных значений типов тоже не просто так придуманы в 70тые. Это важно и это помогает писать программы которые работают однозначно.

Если там может прийти что угодно, то это бардак в разработке. При нормальном подходе ничего лишнего прийти не может. Плюс есть описание механизма, где все расписано. Если же кто-то пихает куда попало что попало, то гнать в шею таких разрабов. Таким никакая типизация не поможет. Ведь никто не гарантирует что они правильное значение true/false отправляют :) Зачем такие случаи рассматривать вообще...

То есть не может? Если поле чар, значит может. Это не бардак, это типизация.

Типизация как раз строго гарантирует что ничего другого не придет. В отличии от всего остального.

Пальцы тоже при желании можно сунуть в розетку, только вот зачем?

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

В MySQL это TinyBit, который по сути байт, где 0 - false, а все остальное true. При этом запихать можно что угодно в диапазоне -128/127. Чем хуже вариант с чаром, где '0' - false, а все остальное true? Т.е. ничто не мешает убрать слой абстракции и работать со значением напрямую.

В MS SQL Server буль вроде вообще отсутствует, а вместо него используется как раз тип Bit, который принимает значения 0, 1 и NULL.

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

Но я, конечно, ни на чем не настаиваю. То что выбрал для себя это мой выбор, который никому не навязываю. Мне так удобно. И ничего лишнего передано там не будет никогда, потому что сам этого не сделаю, т.к. это лишено смысла. А если с этим кодом будет работать другой разработчик, который не умеет читать описание к используемой функции, то придется его научить читать :)

Как оно там храниться в 99.9 процентов случае не имеет значения. Как оно называется в конкретной БД тоже не имеет значения. Массовые БД достаточно хорошо оптимизированы и достаточно одинаковы чтобы не думать об этом.

Пальцы тоже при желании можно сунуть в розетку, только вот зачем?

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

Типизация нужна чтобы компу не приходилось пытаться преобразовать теплое в мягкое потому что кодер балбес

Нет. Типы для человека, а не для компьютера. У вас абстракции текут.

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

Нет нельзя. Ни то ни другое нельзя. Для разумного по размерам списка нужен enum, для неразумного табличка рядом.

то проще взять целое и превратить его в набор битовых тумблеров

Счастливой отладки ситуации, когда, например, постгрес игнорирует индекс на поле с битовой маской. Например, казалось бы, a&b > 0 семантически то же самое, что a&b != 0, но не для постгреса (впрочем, у него беззнаковых типов нет, но я ещё ни разу не добирался до старшего бита). Сбрасывать один флаг среди множества других — тоже нужно уметь (для сишников никаких проблем, впрочем).

Впрочем, когда флагов много, я как раз тоже завожу flags integer. Но и работаю с ними очень аккуратно.

Что в данном случае нужно отлаживать и в чем проблема? Работоспособность или скорость?
При чем тут беззнаковость и прочее? В любом случае цифирь это набор бит. То что старший бит учитывается как знак это не более чем условность. Тот же буль хранится как чар, а можно хранить как первый бит или еще как.
Сбрасывать один бит надо "уметь"? Серьезно? Меня пугают такие заявления. Это же детский сад. И работать с битами просто. Один раз написал пару функций и пользуешься.
В постгресе не приходилось битовую маску держать, но в текущей СУБД (Firebird) с индексами по этой части нет никаких проблем. Специально проверил. Индекс цепляется.

Коротко: Заместо булевы (bool) используйте перечислитель (enum)

Ну, я бы ещё явно указывал тип перечислителя, например тот же byte, и по памяти будет как bool (bool занимает 8 битов из-за некоторых особенностей), и 256 состояний думаю будет предостаточно

Boolean использовать в полях базы данных можно и нужно, и вот почему:

Встроенные примитивные типы данных хорошо индексируются, причем, сами индексы занимают не так много места. Обеспечивается высокая скорость доступа к данным. Это может быть не очень заметно поначалу, когда пользователей немного. Но как будет работать SELECT * FROM users WHERE... с вашей вложенной структурой, если посльзоватетелей 10 миллионов? А если таких запросов 10 в секунду? Конечно, можно проиндексировать и вложенную структуру, но тут уже хранение индексов будет дороже, и скорость upd/ins/del существенно ниже при большей нагрузке на сервер.

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

Это и есть одна из инженерных концепций релятивистских баз данных.

И is_deleted ни к чему. Удалил, и всё.

В некоторых сервисах юзеры имеют привычку трезветь на утро и просить восстановить аккаунт. Не всегда надо сразу удалять.

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

И на самом деле проще не удалять. Места это не экономит.

И связи поломаются

Это правильно, уволился менеджер продаж, удалил запись из БД и всё, отчеты поплыли, красота!

И is_deleted ни к чему. Удалил, и всё.

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

В таких проектах вполне может применяться сочетание "мягкого" удаления с "жестким". Когда что-то надо удалить сейчас - выставляется флаг is_deleted, а собственно DELETE выполняется фоновом режиме либо периодически и небольшими порциями (чтобы не создавать повышенную нагрузку в моменте), либо в регулярно, в назначенное время, когда нагрузка на БД минимальна (например, ночью).

Потому что удалить целую строку, и поменять лишь одно поле в ней - это совершенно разные по стоимости задачи.

(Под стоимостью я понимаю нагрузку, если что)

Хех, умник, всезнающий и всё понимающий.

Когда это нагрузка на бд с сотнями млн строк будет минимальна? 🤣 В каком часовом поясе должна быть в это время ночь?

Если хочешь удалить - удаляй. А если не хочешь, то и не ври, что is_deleted, хамло ахуевшее.

 В каком часовом поясе должна быть в это время ночь?

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

Когда это нагрузка на бд с сотнями млн строк будет минимальна?

Когда (почти) никто не работает с системой, либо запущено минимальное количество задач. Редко когда бывает, что некая система нагружена 24/7 под завязку, зачастую какие-никакие окна простоя да найдутся.

Кстати, @moderator, а можно мне тоже так выражаться, как выражается автор этого комментария?)

релятивистских баз данных

Реляционные базы данных наблюдают эффект замедления времени от высказанных мыслей в данной статье.

Простите, не удержался, понимаю что просто опечатка)

Да, смешно получилось) К ночи бывает. Встречались и "триногометрия", и "чем больше градусов, тем меньше косинусов")

Конечно же, " реляционных баз данных")

Чушь какая-то... Давайте теперь у выключателя светильника сделаем кучу выводов: пользователь посмотрел на выключатель, пользователь подошёл к выключателю, пользователь дотронулся до выключателя и и.д...

А в базе 0-1-2-3 и потом через год ищи что там у тебя за херня и где ее описание

Афтор иди и доучивайся в школе.

BYTE 0-255.

Минимален. Меньше кушает чем STRING

Прост.

А главное задел на будущее. О котором ты не подумал. Тут для твоей фантазии 256 вариантов

0 выключен

1- включен

2 уволен

3 умер . Ты об этом не подумал а для CRM это важно. Помнить о сотруднике

4 на больничном.

5 улетел на Бали с любовницей шефа.

6 улетел в Адлер с твоей любовницей

И тд.

Думай на перед и проще.

Можно также управлять по другому

00 - 0

FF - 255

Если конечно байты знаешь

0--выключен

1- включен

Пример

01- выключен уволен

02-выключен первеведен

0F- выключен умер

10- включен но неактивен

11-вклбчен активен

1A- включен на удаленке

1B включен в декрете

0B выключен в декрете

FF - не выключать никогда. Админский доступ

Думаю смысл понятен.

Так красивке и проще

А если ты Эксель табличку 0-F, 0-F сделаешь.

То у тебя перед глазами вообще красота будет.

Сразу поймешь как красиво в ячейки распихать статусы

База почему то вернула код 0хЕЕ, в нашей табличке такого нет. Упсь?

Народ, вы серьезно? Делается соседняя табличка со статусами, биндится значение статуса = ид статуса, в табличке лежит имя статуса, описание, все что угодно. Вытягиваете запросом и ложите в какойнибудь конфиг/di-конфиг и не паритесь.

P.S. Или меня затролили?

А, действительно. Join он же бесплатно нам достался. Нуок

На сколько такой JOIN затормозит вам запрос можете посмотреть EXPLAIN'ом. Но если дочитаете мой комментарий, то там видно что я предлагаю загрузить в конфиг. Можно и джойном

один такой джойн — действительно ненамного

но в жизни встречваются проекты с одновременным онлайном в десятки тыщ

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

В 1С у документа есть два булева поля - Проведен и ПометкаУдаления. Так вот казалось бы имеем 4 состояния объекта, однако состояние Проведен=Истина и ПометкаУдаления=Истина невозможен в рамках платформы. Впрочем, я встречал таки запросы, в которых все равно пишут условия:

ГДЕ
  Проведен и Не ПометкаУдаления

Кто обжегся на молоке, тот и на водку дует.

Перестраховка чистой воды.

У проведённого документа нельзя пометку удаления установить.

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

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

так-то все логично... "и не" много времени не займет и много ресурсов не скушает

зато в этой выборке будут гарантировано "проведенные" и "не помеченные для удаления" документы

бо никто не знает что за вася пупкин работал здесь до тебя и куда вставил логику в которой Проведен=Истина и ПометкаУдаления=Истина таки возможно

Она невозможна на уровне платформы. Даже для Васи Пупкина

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

Историчность (в расширенном варианте с updatedAt) — мы понимаем, когда именно изменилось состояние.

Ну, такая себе историчность... Для истории надо хранить не одно, а все моменты изменений.

если цель проекта в слежении за действиями юзерей — дарадибога. и кто поставил новый статус, когда поставил... да хоть "reason TEXT NOT NULL" да еще и с допустимыми вариантами причины установки

"любой каприз за ваши деньги"©

Логи в любом проекте рано или поздно становятся необходимы. Знать кто когда и что поменял бесценно. Но делать их надо действительно отдельно от данных.

я правильно понял вас что "делать надо, хранить не надо"?

Хранить надо. Но отдельно от данных. Прямо совсем отдельно. Желательно даже в другой БД.

как по мне — слегка оверинжиниринг

но это уже вкусовщина

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

Им есть разница из какой БД будет этот кусочек лога?

Если в той БД есть метаданные когда менялись/добавлялись данные то есть. Возьмем вырожденный случай. Аудит лог складывается файликами в s3. И тогда у нас есть запись что юзер с id=42 15.08.2025 удалил объект и метаданные от s3 что этот файлик был записан 16.08.2025 и с тех пор не трогался. Такое всем приятнее чем просто строчка в БД которая неизвестно когда менялась.

Да и архитектурно так правильнее.

А если в ЭТОЙ бд есть все то же самое?

Тогда норм и в этой. Другая бд это не обязательное условие, это из разряда обычно так лучше но бывают варианты.

Обязательное это другая таблица. Чтобы аудит дог не смешивался с данными.

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

Вы слово желательно пропустили? Или так чисто поговорить?

Boolean в принципе редко нужный тип для хранения изменяемых данных. Тут вы правы.
Вместо is_deleted / is_inactive лучше использовать deleted_at / inactivated_at и хранить там дату либо NULL. Это полезно для отладки, а boolean / bit СУБД ещё и не факт, что сожмёт нормально.

Тем не менее, древовидный структуры на пустом месте не нужны. Тут вы немного не правы.

Скорее всего в будущем нам понадобится не только знать, удалён ли пользователь, но и...

Скорее всего автор не в курсе что такое миграция данных и поэтому галлюцинирует на тему что там может поменяться когда-нибудь в будущем "скорее всего". Я с тем же успехом предполагаю, что за хранение удалённых юзеров в основной таблице автору по рукам надают на ревью и отправят делать таблицу с историческими данными, потому что мусор в основной таблице ломает constraint на уникальность номера телефона.

Кроме этого, бинарные флаги простые как мычание, условие выборки по ним пишется буквально спинным мозгом, а в сложных статусах - нихрена же непонятно что там имел в виду автор и трое его последователей которые переписывали за ним эти статусы по кругу. Вот, к примеру, в новой версии появился новый статус - ShadowBanned и это сразу ломает ВЕСЬ имеющийся код. Будь это отдельный новый флаг - можно не спеша начать добавлять проверку этого нового конкретного флага в тех местах где это нужно, а с зоопарком статусов поди разберись теперь как имеющийся код поменять чтобы починить всё что сломалось.

ломает constraint на уникальность номера телефона.

Это что за констрейнт такой? У двух людей не может быть одного контактного номера телефона?

В 2025 году нет.

ломает constraint на уникальность номера телефона

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

Для справки: если в констраинт добавить is_actual, который имеет значение true для актуальных записей и null для архивных, то все продолжит работать...

Ух как лихо статья "Никогда не используйте Boolean вместо этого используйте Enum" залетела в тренды.

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

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

Троллинг он и есть троллинг, даже если не преднамеренный.

Если вам мало одного бита для хранения состояния, и у вас 3 статуса - используйте триты! Можете для этого использовать компьютеры Сетунь.

В случае с признаком удаления, я приверженец использования нуллового DateTime поля DeletedAt. Если null - объект не удалён. Если не null - знаем когда был удалён.

А если удален, а потом восстановлен?

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

Лучше делать явный признак, чем использовать некую "договоренность". Видел неоднократно, когда изменившаяся логика ломала такую логику и приходилось переделывать на ходу. К тому же нулл надо явно писать в запросах типа is not null, мелочь вроде бы, но все равно неудобно.

Если нужно хранить несколько состояний, можно завести несколько полей bool или хранить одно поле, например int и работать с флагами 0x1,0x2,0x4 и т.д. Оба подхода имеют преимущества и недостатки. К чему эти наезды на bool? Личная неприязнь?

Скорее всего = «You aren’t gonna need it» (YAGNI)

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

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

export interface User {
  id: string;
  email: string;
  status: {
    state: "active" | "inactive" | "deleted";
    updatedAt: Date;
  }
}
  • Простая аналитика — легко посчитать, сколько пользователей сейчас «inactive», как часто они переходят в «locked» и т.д.

1)что мешало вам пересчитать неактивных пользователей, пока inactive было отдельным флагом?
2)что такое locked? Оно нигде не упоминалось в статье раньше. Осуждаю генерацию нейросетью
3)у вас в записи всего одно поле статуса с одним полем даты. Как вы собрались по нему вычислить частоту? Я уже молчу о том, что вы зачем-то это поле сделали безымянной вложенной структурой, хотя ее лучше сделать отдельной сущностью

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

export interface User {
  id: string;
  email: string;
  status: {
    state: "active" | "inactive" | "deleted";
    updatedAt: Date;
  }[],
  is_deleted: boolean;
  is_active: boolean;
}

SCD2 + Data Vault 2

Из пушки по воробьям, но зато вся история есть

  • Историчность (в расширенном варианте с updatedAt) — мы понимаем, когда именно изменилось состояние.

А ничего, что состояние может меняться множество раз? Пользователя удалили, через какое-то время восстановили, потом через какое-то время снова решили удалить. Что будет храниться в updatedAt ? Правильно, дата и время последнего изменения статуса.

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

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

"Историчность" достигается не сменой boolean на enum, как предлагает автор. Она достигается созданием таблиц для хранения истории изменений. Когда строка в основной таблице изменяется - то изменения пишутся и в таблицу с историей (с фиксацией даты и времени изменения, само собой, и, возможно, ещё какими-либо дополнительными мета-данными). И тут будет уже совершенно не важно, в каком типе данных вы храните статус удаления пользователя - в boolean, в enum или как-то ещё.

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

Скорее всего в будущем нам понадобится не только знать, удалён ли пользователь, но и...

status: "active" | "inactive" | "deleted";

Автор предлагает вместо булевого флага использовать enum, на случай, если мы при расширении системы захотим ввести новые статусы. Это решение само по себе спорное, потому что для добавления нового статуса, нам каждый раз придётся делать ALTER TABLE, то есть менять структуру БД.

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

Что это даст? Можно будет добавлять и менять статусы простыми INSERT/UPDATE, при этом структура БД будет оставаться без изменений. Можно будет легко менять названия самих статусов, если потребуется. Наконец, это более универсальное решение: enum доступен лишь в нескольких СУБД. Тогда как вариант с отдельным справочником статусов и числовым полем STATUS_ID будет работать в любой реляционной СУБД.

Sign up to leave a comment.

Articles