Как стать автором
Обновить

Комментарии 40

Лайкнул статью, но покритикую.

Мне всегда казалось (и думаю это обосновано), что SQL значительно проще тех ORM, которые вокруг него приделывают, пытаясь "абстрагироваться от SQL".

Единственное что мне из надстроек над SQL нравилось - шаблонизация. Нечто вроде

SELECT
  *
FROM
  table
WHERE
  type = 'users'
  <% if filter1 %>
      AND field1 = <%= filter1 %>
  <% end %>
  <% if filter2 %>
      AND field2 = <%= filter2 %>
  <% end %>

Движок шаблонизации может отслеживать и гарантировать отсутствие инъекций в данных и такая надстройка позволяет писать многовариативные SQL, располагая их в одном файле.

Всё это круто, отчасти пересекается с идеями статьи.

Когда SQL максимально приближен к "условному" фронтенду, то он максимально эффективен и даёт максимальный профит.

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

Есть законы эм... назовём их законами бытия.

  1. Чтобы сделать выборку из огромного объёма данных нужно перебрать этот объём.

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

В базах данных пункт 2 решается при помощи индексов.

Так вот, SQL абстрагирует пользователя от понимания этих пунктов. Ему кажется, что он может безболезненно добавить условие в секцию WHERE и у него всё зафильтруется (как в моём примере). Но это работает только на маленьких объёмах данных и небольших нагрузках. А при больших нагрузках ему придётся думать над каждым запросом. Придётся по 10 раз их переписывать, чтобы попадать под влияние того или иного индекса. Чтобы даже при индексной выборке исключать большие списки в плане запроса в секции Filtered.

И вот в этом месте мы приходим к базису:

  • Для чего (условный) Вы хотите приблизить SQL к фронту?

  • Чтобы упростить цикл разработки: запрос фронта -> запрос к БД

Перефразируем

  • Зачем это нужно?

  • Чтобы снизить требования к разработке (квалификации разработчиков) системы

Снижая требования, неизбежно приходим к НЕРЕШЁННОМУ пункту 2 и факапам по нагрузке на стороне БД.

И это ещё мы о JOIN'ах не говорили.

Как-то так.

WHERE …
  AND ((@P1 IS NULL) OR ( FIELD1 = @P1))
  AND ((@P2 IS NULL) OR ( FIELD2 = @P2)) …

в сортированном виде

СУБД ведёт статистику.

Темплейтный язык позволяет и такое

SELECT
  t1.*
FROM
  t1
  <% if condition %>
    JOIN t2 ON t2.id = t1.foo_id
  <% end %>
WHERE
   type = 'user'
   <% if condition %>
      AND t2.foo = 'bar'
   <% end %>
ORDER BY
   foobar

  1. Чтобы сделать выборку из огромного объёма данных нужно перебрать этот объём.

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

В базах данных пункт 2 решается при помощи индексов.

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

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

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

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

tree, rtree, gin, gist, и так далее

понятное дело, что какие-то индексы не дают на выходе прямо плоско-сортированный список (например кубический rtree и не может отсортировать плоско), но в целом любое дерево около плоского списка изоморфно виду сортировки, поскольку упорядочивает выдачу по какому-то критерию (расстоянию для knn, вхождениям для gist и так далее)

вот gin-индекс (глоссарий) это же тоже сортировка по сути

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

Уточняю. Сортировка НЕ ОБЯЗАНА совпадать. Даже если она совершенно НЕ совпадает - всё равно использование индекса может настолько снизить объём просматриваемых данных, что это приведёт к ускорению выполнения запроса. Индекс сам по себе в большинстве случаев компактнее самОй таблицы.

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

в filtered плана попадает как раз это несоответствие

это, кстати, распространенная ошибка новичков, читают план запроса: индекс такой-то, поиск такой-то, filtered столько-то

и на filtered внимание не обращают, а потом размышляют: почему же БД висит?

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

Как разработчик (потребитель) вы пишете то, что вам нужно, какие выбрать данные и по каким условиям. А то насколько эффективна будет эта выборка - это вопрос к тем, кто ответственен к проектирование бд (aka dba), ну и к работе бд в целом (оптимизатор, кеши, диски).

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

К слову о filtered, cpu на самом деле не такой затратный как чтение блоков данных с диска или contention с буферным кешем. Потому все "зависит от".

dba - такой же член команды разработки, как фронтендер, бакендер или devx

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

Но также имеют место быть решения in memory data grid с поддержкой sql, а также распределенные кластеры с архивацией данных на диске, которые могут просканировать весь объем за быстрое время. Такие решения как раз используются, когда данные анализируются на лету, без построения индексов.

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

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

Притом формат хранения часто отличается от доменной модели.

совершенно необязательно разделять эти форматы

запись БД — это сериализованная модель, а следовательно им нет смысла (сильно) отличаться

Вы ошибаетесь. Например в event sourcing - поток событий и там никакой сериализации доменных объектов нет.

там и SQL нет смысла применять, очереди и noSQL именно для таких приложений и задуманы

но мы говорим о том, что хранится в БД: пользователи, товары, услуги, витрины, тексты итп

Так спор и возник, из-за предположения, что доменная модель хранится в бд. Но это не так - доменная модель отдельно, хранилище и его структура отдельно. И есть трансляция из одного в другое - и это не обязательно sql. Поэтому во всех книжках, на всех графиках это разделяют. То что иногда оно одинаково в каких-то местах - это бонус но не правило.

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

То что иногда оно одинаково в каких-то местах - это бонус но не правило.

чаще всего это одинаково, но иногда это не так

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

Вы же сами писали, что изобретать свой язык запросов — это зло. Но абзацем ниже предложили именно это - свой язык, основанный на SQL.

Еще один камень преткновения — это то, что вы посылаете запрос как строчку. Строка — это хорошо для человека, но у вас же пользователь не будет писать запрос. Вы его кодом соберёте. А на стороне сервера кодом разберёте. А зачем? Ведь проще послать уже структурированный запрос, как у вас в начале JSON. И ошибок будет меньше, и код проще.

Но абзацем ниже предложили именно это - свой язык, основанный на SQL.

Подмножество существующего языка.

Есть же odata.

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

И graphql в ту же область. Но odata ближе к sql

Использую похожий подход, но через прослойку под названием DataSource.
DataSource - таблица в базе, которая хранит разные SQL запросы с параметрами доступными снаружи через API.
Безусловно, запрос безопасный для базы, с проверкой параметров на SQL Injection и permission check.

Что-то типа:

За 6 лет полет норм.

Чем это лучше updatable views? Не критикую, просто интересно.

Чем это лучше updatable views?

Вообще разные звери.
-- updatable views - для редактируемости
-- datasources - для безопасной начитки данных с доступом через API и определением datasources и параметризированными фильтрами через gui.

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

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

Собственно GraphQL должен помочь решать ту проблему, которую автор пытается решить с помощью SQL

Скорее "обрастет костылями до преотвратности" - так-то никто не мешает в том же postgresql напилить пачку FDW и дергать их в свое удовольствие из одной базы - но то прям такоЭ будет.

КМК тут скорее о "границах применимости" речь. Если "за масштабирование", "развитие", "интернет", то as for me - скорее "нет" - а вот если "по быстрому написать API'шку над базой для интеграции" - то почему бы и не "да"?

Вариант с JSON - лучше. Потому что с ним понятно как работать, скажем - генерировать из заданных на UI фильтров. Даже если надо будет руками хардкодать запросы в большом количестве - то правильнее будет сделать eDSL, а не впихивать SQL строками в коде.

Честно говоря, непонятно почему сами БД не принимают какой-то машино-читаемый формат, а-ля AST. Потому что в большинстве случаев, запросы к ним тоже генерируют - те же ORM, всякие репортинговые системы а-ля Power BI. И непонятно зачем двум программам разговаривать на языке, придуманном даже не для программистов.

Быстрее бы уже api к чат gpt666, просыпаешься такой с утра (или нет) и думаешь где кнопка - получить счастье, и только подумал, тебе её (старше 18 лет) приводят, с кофе и яичнецой с помидорами.

Страшный сон для DBA.

Но если данный подход под микросервис, под которым БД на 3 таблицы в сотню строк - да пофиг, там и sql нафиг. Но "когда у тебя в руках молоток, весь мир - гвозди".

Для промышленного применения, когда у тебя БД нагрузкой, каждый запрос умножается на число его вызовов. Исправить ошибки проектирования сложнее чем ошибку в днк проектировщика. На заре появления реляционных БД инженеры думали головой. Например, static(embedded) sql и никаких инъекций, и сервер не занимается компиляциями однотипных запросов, стоимость которых на порядок выше цены их исполнения, и никаких вопросов "кто написал этот скуль?! Сервер лег!", и безопам счастье, никаких юзеров, одни спузы.

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

Если бы еще инженеры в далеком 2000 не сдались с SQL SP, то не было бы антипаттерна про бизнес-логику в БД... :)

Так _в принципе_ не все в этом мире "highload" - овердохрена регулярно используемых на ежедневной основе вещей никогда не вылезут за границы применимости условного sqlite - но тут именно что "молоток-и-гвозди", обжегшись на молоке (Эффект "лягушки-в-кипятке", ага) - начинаем дуть на воду и тащить в проектирование таких "карманных" сервисов методологию из HA-задач.

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

Эта статья мэйнкампф от мира программирования. Критическое мышление и переосмысление это хорошо. Но автор явно зашёл слишком далеко, зачем обсуждать решение, в котором заведомо нет плюсов и вагон минусов. Вокруг sql и так трётся много извращенцев, спасибо jsonb за это.

Вы говорите, что json заставляет нас изобретать свой язык запросов

Вот здесь уже каждый должен унюхать, что этот код попахивает.

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

Но это не так. Язык уже изобретен (json). Все, что нам нужно, это прикрутить структуру, на подобие вашей для SQL.

К тому же всякие там OpenAPI умеют работать с json и xml, но не умеют с sql. А значит, у вас будет проблема с документированием.

Да и фронты не скажут вам спасибо за SQL в запросах. У них объекты ЯП и есть json. Но придется парсить в SQL. К тому же у них будет чувство тревоги из-за того, что они будут ждать когда и ответ им начнет приходить соответствующий :)

Язык уже изобретен (json)

Это не язык, это соглашение о формате данных. Язык запросов — это, например, graphql.

Это не язык, это соглашение о формате данных. Язык запросов — это, например, graphql.

Я не так выразился. Я к тому, что чего его изобретать то? Фактически это будет структура на том ЯП, который используется на бэке. То есть не важно, что придет, json или sql, оно будет парситься в эту структуру. А дальше уже эта структура будет преобразовываться в запрос к хранилищу.

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

Ах да. Еще я бы отнес к минусам отсутствие поддержки маппинга contentType: application\sql многими современными фреймворками. А значит я не смогу в методе контроллера указать тип данных аргумента. Более того, если я это сделаю, я либо буду навсегда привязан к sql, либо придется изготавливать функционал, подобный graphql, который позволит связать мои методы принимающие sql запросы с MVC фреймворком.

А плюсы от этого всего весьма туманны.

Я разрабатывал свою систему запросов на подобие graphql, она была лишена некоторых недостатков оного (в том числе связанных с производительностью), но была связана с EntityFramework (dotnet). Я так и не смог отделить ее, оставив лишь привязку IQueryable, чтобы можно было работать с любым ORM, но зато не пришлось мастерить оснастку для интеграции с MVC.

Фактически это будет структура на том ЯП, который используется на бэке.

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

Не обязательно продираться. Есть OpenApi который позволяет вести документацию в отрыве от кода. Более того, есть генераторы для DTO, которые позволяют сгенерировать их на основе OpenApi документации (с комментариями). Далее техлид или один из разработчиков ядра должен разработать связку структуры запроса с механизмами запросов. И все. Обычному разработчику не потребуется даже вникать в эти структуры и документацию. Вы просто при создании круда передаете запрос в нужный сервис и он работает.

Чем же подход SQL в API так ужасен, если фактически Microsoft в своем Microsoft REST API Guidelines это и рекомендует делать? И язык запросов соответствующий она там уже изобрела.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий