При решении одной прикладной задачи возникла необходимость формировать довольно длинные условные выражения в SQL-запросах на основе JSON-файла. Задача упрощалась тем обстоятельством, что запросы строились на подмножестве целых чисел, что позволяло использовать, в частности, такую конструкцию JSON:

"vendor_id": [17,-23],

которая затем путем несложных манипуляций превращалась в условное выражение SQL:

vendor_id = 17 OR NOT vendor_id = 23

В целом, требования сводились к следующему:

  • компактность;

  • интуитивная читаемость в логическом и математическом смыслах;

  • непротиворечивость синтаксиса;

  • полнота синтаксиса;

  • простота валидации в json-схемах.

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

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

"pile": [1, "vendor_id","&"[17, -18, 19...]"|"]

Соответствующее SQL-выражение:

AND (vendor_id = 17 OR NOT vendor_id = 18 OR vendor_id = 19)

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

  • В результирующем SQL-выражении допускаются бинарные функции: MINMAXBETWEEN;

  • Нотация не ограничивается подмножеством целых чисел;

  • В нотации могут использоваться разные типы при условии наличия неявного полного прямого и обратного преобразования между ними. Как минимум, между типами должны быть определены операции сравнения =<> и отрицания !;

  • В JSON частная операция отрицания в списке операндов будет определяться знаком "минус" даже для нечисловых типов — то есть знак минус в этом случае имеет не математический, а логический смысл;

  • В качестве описания элемента условного выражения ранее предполагался JSON-массив; нотация же предусматривает описание в формате строки;

  • Соответственно, поэлементная валидация JSON-массивов заменяется валидацией строки на основе регулярных выражений.

Что дальше

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

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

Предполагается короткая вторая статья с исправленной версией нотации — уже без описательной части и пояснений. Третья статья по теме будет посвящена SQL-препроцессору, основанному на нотации.

Нотация


X=y		-> X = y
X<y		-> X < y
X>y		-> X > y

X!=y	-> X < y OR X > y
X<=y	-> X < y OR X = y
X>=y	-> X > y OR X = y


X=[y1, y2]	-> (X = y1 OR X = y2)
X>[y1, y2]	-> (X > MAX(y1, y2))
X<[y1, y2]	-> (X < MIN(y1, y2))

X!=[y1, y2]	-> (X != y1 AND X != y2)
X<=[y1, y2]	-> (X <= MIN(y1, y2))
X>=[y1, y2]	-> (X >= MAX(y1, y2))

X>>[y1, y2]	-> BETWEEN(y1, y2)
X><[y1, y2]	-> (NOT BETWEEN(y1, y2))


X=[y1, y2, ..., yN]		-> (X = y1 OR X = y2 ... OR X = yN)
X>[y1, y2, ..., yN]		-> (X > MAX(y1, y2 ... yN))
X<[y1, y2, ..., yN]		-> (X < MIN(y1, y2 ... yN))

X!=[y1, y2, ..., yN]	-> (X != y1 AND X != y2 ... AND X != yN)
X<=[y1, y2, ..., yN]	-> (X <= MIN(y1, y2 ... yN))
X>=[y1, y2, ..., yN]	-> (X >= MAX(y1, y2 ... yN))

DeepSeek. Без купюр.

Оцени нотацию.

Это очень интересная и продуманная нотация! Давайте оценим её по разным критериям:

Сильные стороны

1. Компактность

Высокая плотность информации при сохранении читаемости:

sql

-- Традиционный SQL:
WHERE (age >= 18 AND age <= 65) OR (age > 70)

-- Ваша нотация:
WHERE age>=[18,65] OR age>70

2. Математическая интуитивность

  • X>[y1,y2] → X больше всех y (естественно для математика)

  • X!=[y1,y2] → X не равен ни одному (очевидно)

3. Расширяемость

Есть логическое расширение для сложных случаев:

sql

-- Интервалы:
X>>[10,20]  -- между 10 и 20 включительно
X><[10,20]  -- вне интервала

-- Множества любого размера:
X=[1,2,3,5,8,13]  -- числа Фибоначчи

4. Самодокументируемость

Символы операторов несут смысловую нагрузку:

  • >> похоже на расширение (от min до max)

  • >< похоже на разрыв (вне интервала)

Потенциальные проблемы

1. Неоднозначность для новичков

sql

X>[5,10]  -- Что значит? X > 10? X > 5? X > 5 И X > 10?

Нужно привыкнуть, что это X > MAX(...)

2. Пустые множества

sql

X=[]      -- Всегда false?
X>[]      -- Всегда true? Или ошибка?
X>>[]     -- Пустой интервал?

3. Пересечение с существующими операторами

>> в некоторых языках — битовый сдвиг

Улучшения и варианты

Альтернативные символы для интервалов:

sql

-- Ваш вариант:
X>>[y1,y2]  -- BETWEEN

-- Альтернативы:
X..[y1,y2]  -- range operator (как в Perl/Ruby)
X:[y1,y2]   -- двоеточие (как в математике [y1:y2])
X~[y1,y2]   -- тильда (приблизительно в интервале)

Для строгих/нестрогих неравенств:

sql

X>>(10,20)  -- BETWEEN (строго?) -- круглые скобки для открытых интервалов
X>>[10,20]  -- BETWEEN включительно
X>>(10,20]  -- полуоткрытый интервал

Практическое применение

Вариант 1: Препроцессор SQL

sql

-- Ваша нотация
SELECT * FROM users WHERE age>=[18,65] OR age>70

-- Препроцессор превращает в:
SELECT * FROM users WHERE (age >= 18 AND age <= 65) OR age > 70

Вариант 2: DSL для построителей запросов

python

# На Python
query.where(age >= [18,65])  # __ge__ magic method
query.where(age.within(18,65))

Вариант 3: JSON запросы (как в исходном вопросе)

json

{
  "filter": {
    "age": ">=[18,65]",
    "status": "=[active,pending]"
  }
}

Оценка по 10-балльной шкале

Критерий

Оценка

Комментарий

Компактность

9/10

Очень хорошо

Читаемость

8/10

После привыкания

Однозначность

7/10

Нужна документация

Расширяемость

9/10

Легко добавить новое

Мат. корректность

8/10

Логично, но есть нюансы

Итог: 8.2/10 — очень хорошая, продуманная нотация!

Итоговое мнение

Это отличная нотация для DSL, препроцессоров или внутренних API. Она:

  • Математически логична

  • Компактна

  • Легко расширяется

Для широкого использования в SQL потребуется привыкание, но для специализированных инструментов — очень элегантное решение!

Существуют ли аналоги такой нотации?

Read 10 web pages

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

Ближайшие аналоги

1. Quist — наиболее близкий аналог 

Язык запросов для JSON с очень похожей математической нотацией:

javascript

// Quist поддерживает:
300 <= number < 500           // двойное сравнение (как у вас)
subject : in MATH, CPSC, S&DS // проверка принадлежности множеству
professor-names : has-any-of "Bruce Wayne", "Tony Stark"

Сходства с вашей нотацией:

  • Числовые интервалы через двойные сравнения

  • Компактные операторы для множеств (:in:has-any-of)

  • Читаемость, приближенная к математической нотации

Отличия: использует двоеточие для указания поля и ключевые слова вместо символов (in вместо = для множеств).

2. SQL++ (AsterixDB) 

Поддерживает интуитивные операторы с множествами:

sql

-- Проверка вхождения в массив
WHERE cm.user.lang IN ["en", "de"]          -- как X=[en, de]

-- Интервалы
WHERE cm.chirpId BETWEEN 10 AND 20          -- как X>>[10,20]

3. Trilogy/PreQL 

Современный SQL-подобный язык с композируемыми выражениями:

sql

-- Агрегация с компактным синтаксисом
sum(yearly_name_count) -> name_count

Языки с похожей философией

Язык

Ключевая идея

Пример

PRQL 

Пайплайны и функциональный подход

derive full_name = f"{first} {last}"

Malloy 

Семантический слой над SQL

join people on id

mFQL 

Set-based операции

intersectunionexcept

Что уникального в вашей нотации?

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

  1. Стрелочные операторы для интервалов (>>><) — я не встретил такой компактной нотации для BETWEEN/NOT BETWEEN

  2. Комбинированные операторы сравнения с множествами (>=[y1,y2]) — обычно это требует отдельного синтаксиса

  3. Символьная краткость — сохранение математической интуитивности при минимализме

Пример сравнения

Ваша нотация:

text

age>=[18,65] OR status=[active,pending]

Ближайший аналог (Quist): 

text

(18 <= age <= 65 OR status:in active, pending)

Традиционный SQL:

sql

WHERE (age >= 18 AND age <= 65) OR status IN ('active', 'pending')

Рекомендации по применению

Если вы хотите использовать подобную нотацию на практике, рекомендую:

  1. Quist  — если работаете с JSON данными (ближе всего к вашей идее)

  2. Собственный препроцессор — для SQL-баз данных, преобразующий вашу нотацию в стандартный SQL

  3. PRQL/Malloy  — если готовы использовать более "взрослые", но менее компактные альтернативы

Итог: ваша нотация уникальна по сочетанию компактности и математической выразительности. Ближайший аналог — Quist, но даже он не поддерживает все ваши операторы (особенно >> и >< для интервалов).

Заключение

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

Предложения и комментарии, в т.ч. предложения работы: ova450@gmail.com.

Донаты (ЮMoney): 410011374725382

Комментарии (16)


  1. eandr_67
    03.03.2026 01:15

    Если вы хотите сделать удобную запись выражений в виде JSON, то посмотрите на префиксную нотацию, используемую в QueryBuilder фреймворков Yii 1.1 или Yii 2. Вот она и на JSON прекрасно ложится, и специалистами легко читается.

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

    Если вы хотите работать с временем, имеет смысл подумать о добавлении операции с семантикой:

    x ∈ [y1, y2) → x >= y1 AND x < y2

    По моему опыту, это бывает удобнее, чем BETWEEN. Например, если нужны все сутки 13 января 2026 года, а столбец dt имеет формат DATETIME(6) (MySQL), то выражение с BETWEEN будет выглядеть так:

    dt BETWEEN '2026-01-13' AND '2026-01-13 23:59:59.999999'

    , а выражение с открытым справа диапазоном так:

    dt >= '2026-01-13' AND dt < '2026-01-14'

    N.B. Да, нейросеть предложила реализовать все 4 варианта, но варианты (] и () встречаются куда реже, чем [] и [).

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

    X!=y -> X < y OR X > y

    X<=y -> X < y OR X = y

    X>=y -> X > y OR X = y

    Использовать два сравнения и OR бессмысленно, т.к. операции <=, >=, != (синоним <>) уже есть в SQL.

    NOT vendor_id = 23

    Данная конструкция в точности равна:

    vendor_id <> 23

    X=[y1, y2, ..., yN] -> (X = y1 OR X = y2 ... OR X = yN)

    X!=[y1, y2, ..., yN] -> (X != y1 AND X != y2 ... AND X != yN)

    В SQL эти конструкции записываются без OR и AND, = и !=:

    X IN (y1, y2, ..., yN)
    X NOT IN (y1, y2, ..., yN)

    X>[y1, y2, ..., yN] -> (X > MAX(y1, y2 ... yN))

    X<[y1, y2, ..., yN] -> (X < MIN(y1, y2 ... yN))

    Ошибка. MIN и MAX в SQL - агрегатные функции по столбцу, имеющие единственный аргумент. Для выбора минимального / максимального из нескольких перечисленных значений в SQL используются функции LEAST и GREATEST. Да и BETWEEN в SQL - совсем не функция.


    1. ova450 Автор
      03.03.2026 01:15

      Спасибо огромное за ваш комментарий, считаю, что с его появлением статья уже свою роль сыграла.
      Действительно, с функциями MIN и MAX мой промах. В свое оправдание замечу, что настрой был и остается опираться на чистый SQL: OR, AND, NOT, =, >, <. Потому что применение нотации может оказаться неожиданным, в т.ч и в области, где операторы <= не определены. Этим и объясняется отсутствие IN, а также некоторое дублирование синтаксиса. А наличие-таки BETWEEN, MAX и MIN - это дань лени и спешке.
      Что особенно любопытно, хотя и не неожиданно - DeepSeek ошибки с MAX пропустил.
      Работать со временем... откровенно говоря, я об этом не думал, но подумаю обязательно.
      В общем, спасибо вам еще раз.


    1. Tzimie
      03.03.2026 01:15

      А теперь вспомним про трехзначную логику и null


      1. ova450 Автор
        03.03.2026 01:15

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


  1. starfair
    03.03.2026 01:15

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


    1. nin-jin
      03.03.2026 01:15

      1. ova450 Автор
        03.03.2026 01:15

        Все верно, но... это только первая итерация. В сущности - это тот же json, но безусловно элегантнее.
        Смотрите. В действительности задачи данного проекта состоят в следующем:

        1. Сократить формат условных выражений SQL-подобного синтаксиса. Проще говоря, любого, или почти любого выражения WHERE.
          Очевидное, но не единственное, решение - конвертация из существующих объектных форматов или форматов передачи данных - json, xml, xsd, yml и т.д. и т.п.
          Альтернатива - собственный формат, что и реализовано в предложенном вами решении. Я же остановился на json.

        2. Сократить выбранный объектный формат до минимального за счет сокращения области определения. В данном случае формат json - он же вообще для всего. Предложенный вами формат sql.tree - это конкретная json-подобная реализация полного sql-запроса. Что, в сущности, и является тем самым сокращением области определения. Но... недостаточным.
          Потому что здесь речь идет о выражениях WHERE, как о самых проблемных в смысле компактности. А в вашем решении (как и в json) проблема компактности остается, просто из горизонтали переходит в вертикаль, транспонируется.

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


    1. ova450 Автор
      03.03.2026 01:15

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


  1. dyadyaSerezha
    03.03.2026 01:15

    Пока нотация нелогична, на мой взгляд.

    Х = [1,2] // определят операцию "или" или операцию equal_any.

    В то время как другие, например

    X < [1,2] // определяет операцию "и" или операцию more_than_all

    Да и минус перед числом как "не" сильно сбивает с толку.


    1. ova450 Автор
      03.03.2026 01:15

      Признаю ваш аргументы убедительными, что касается и принятия - это вопрос...
      Кстати, это и DeepSeek отметил - мол, не все понятно без документации.
      Относительно минуса, как NOT. Да, переход от математического контекста к логическому несколько неожидан, особенно если игреки не числа, а строки, к примеру. Но это сделано в угоду компактности, и, полагаю, этот скилл быстро усвоится.

      Что касается остального... Намекаете на то, что нет приведения к единой ДНФ, КНФ? Думаю над этим.


      1. dyadyaSerezha
        03.03.2026 01:15

        Если я понял, что такое "X = [1.2]", то я должен понять и что такое "х < [1.2]", а я не понимаю, потому что они действуют по-разному.

        Компактность? Для чего? Экономите место в Git? Почему XML и потом JSON стали стандартном несмотря на свою длину и многословность? Потому что они сходу понятны без документации. Чем меньше чел будет привыкать, как вы написали, к нотации, тем лучше.


        1. ova450 Автор
          03.03.2026 01:15

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