Почему SQL не является более подходящим? [закрыто]

39

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

Введите SQL. Да, способ мышления кода SQL отличается от процедурного мышления кода, но этот принцип кажется вполне применимым.

Допустим, у меня есть запрос, который принимает форму:

select * from subQuery1 inner join subQuerry2 left join subquerry3 left join join subQuery4 

Использование некоторых идентификаторов или дат и т. Д.

Эти подзапросы сами по себе сложны и могут содержать собственные подзапросы. Ни в каком другом контексте программирования я бы не подумал, что логика для сложных подзапросов 1-4 принадлежит моему родительскому запросу, который объединяет их все. Это кажется настолько простым, что эти подзапросы должны быть определены как представления, точно так же, как они были бы функциями, если бы я писал процедурный код.

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

Я подумал о трех возможных ответах:

  1. Это уже распространено, и я работаю с неопытными людьми

  2. Опытные программисты не пишут сложный SQL, потому что предпочитают решать сложные проблемы обработки данных с помощью процедурного кода

  3. Что-то другое

ebrts
источник
12
Существуют организации, которые позволяют запрашивать базу данных только через представления и изменять ее с помощью хранимых процедур.
Питер Б
3
SQL стал намного приятнее для меня, когда я наконец понял, что он никогда не будет таким СУХИМЫМ, как мой обычный процедурный код.
Грэм
1
4. SQL действительно старый и существенно не обновлялся десятилетиями. Для очень сложных вещей, многие команды выбирают хранимые процедуры. Вы можете добавить различные пункты для этого. Иногда вам просто нужно выполнить задания, чтобы поместить данные во временную таблицу, а затем присоединиться к ней. Вот как разные декларативные и процедурные языки.
Берин Лорич
8
Также одной из причин является то, что существует ужасная проблема производительности, называемая «треугольным соединением», которая может возникнуть при использовании представлений (конечно, совершенно случайно). Если ваш запрос объединяет View A и View B, но View A также в своей реализации повторно использует View B, вы начинаете видеть эту проблему. Поэтому люди часто начинают с того, что пишут один монолитный запрос, чтобы увидеть, что на самом деле будет лучше всего с точки зрения рефакторинга к представлениям, а затем к их срокам исполнения, и монолит отправляется в производство. Вроде как 98% всех разработчиков ПО, правда :) :)
Стивен Бирн
3
«Представьте, что другим типам программистов приходится отправлять запрос каждый раз, когда они создают функцию» ... ммм. Вы не делаете обзоры кода?
свидген

Ответы:

25

Я думаю, что основная проблема заключается в том, что не все базы данных поддерживают Common Table Expressions.

Мой работодатель использует DB / 2 для многих вещей. Последние его версии поддерживают CTE, так что я могу делать такие вещи, как:

with custs as (
    select acct# as accountNumber, cfname as firstName, clname as lastName,
    from wrdCsts
    where -- various criteria
)
, accounts as (
    select acct# as accountNumber, crBal as currentBalance
    from crzyAcctTbl
)
select firstName, lastName, currentBalance
from custs
inner join accounts on custs.accountNumber = accounts.accountNumber

В результате у нас могут быть сильно сокращенные имена таблиц / полей, и я по сути создаю временные представления с более разборчивыми именами, которые затем я могу использовать. Конечно, запрос становится длиннее. Но в результате я могу написать что-то, что довольно четко отделено (используя CTE так, как вы бы использовали функции для получения DRY), и в итоге получится довольно разборчивый код. И поскольку я могу выделять свои подзапросы и ссылаться на один из подзапросов, это не все «встроенные» операции. Иногда я писал один CTE, затем четыре других CTE ссылались на него, а затем объединял основной запрос с результатами этих последних четырех.

Это можно сделать с помощью:

  • DB / 2
  • PostGreSQL
  • оракул
  • MS SQL Server
  • MySQL (последняя версия; еще немного новая)
  • возможно другие

Но это делает ДЛИННЫЙ способ сделать код чище, более разборчивым, более СУХИМЫМ.

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

Со временем может иметь смысл превратить некоторые из них в представления, чтобы эта «стандартная библиотека» была доступна без необходимости копирования / вставки. Но мои CTE заканчиваются тем, что они слегка подправлены для различных нужд, так что я не смог привыкнуть к одному CTE ТАК ДЕЙСТВИТЕЛЬНО, без модов, что, возможно, стоит создать представление.

Казалось бы, часть твоей жалобы - «почему я не знаю о CTE?» или "почему моя БД не поддерживает CTE?"

Что касается обновлений ... да, вы можете использовать CTE, но, по моему опыту, вы должны использовать их внутри предложения set AND в предложении where. Было бы неплохо, если бы вы могли определить один или несколько элементов перед оператором обновления, а затем просто иметь части «основного запроса» в предложениях set / where, но это не работает таким образом. И нельзя избежать неясных имен таблиц / полей в обновляемой таблице.

Вы можете использовать CTE для удаления. Для определения значений PK / FK для записей, которые вы хотите удалить из этой таблицы, может потребоваться несколько CTE. Опять же, вы не можете избежать неясных имен таблиц / полей в таблице, которую вы изменяете.

Поскольку вы можете делать выборку для вставки, вы можете использовать CTE для вставок. Как всегда, вы можете иметь дело с неясными именами таблиц / полей в таблице, которую вы изменяете.

SQL НЕ позволяет вам создавать эквивалент объекта домена, оборачивая таблицу, с помощью getters / setters. Для этого вам нужно будет использовать какой-либо ORM, наряду с более процедурным / OO языком программирования. Я написал вещи такого рода в Java / Hibernate.

Meower68
источник
4
У нас был мистер Большой CTE, человек, который писал худший SQL. Проблема заключалась в том, что CTE были плохим выбором абстракции, и оптимизатор не может отменить каждый заложенный вами алгоритм.
Джошуа
3
Кроме того, ORM может делать довольно отвратительные вещи и с точки зрения производительности, особенно когда вы просто используете методы получения и установки для получения данных. Hibernate известен тем, что использует сотни отдельных запросов вместо одного большого объединенного запроса, что является проблемой, когда накладные расходы на каждый запрос.
user3067860
2
@ Джошуа Вы можете написать плохой код на любом языке. Включая SQL. Но рефакторинг для CTE, сделанный должным образом, может создать проекты снизу вверх, которые легче анализировать людям. Я склонен рассматривать это как желательную черту, независимо от того, с каким языком я имею дело :-)
Meower68
2
Другие ответы великолепны, но это то, что я лично искал. «Почему я не знаю о CTE» было большинство моей проблемы.
ebrts
2
@ Meower68 Существует ли риск того, что широкое использование CTE не позволит людям правильно изучить объединения и узнать о хорошем дизайне баз данных? Я поддерживаю ценность CTE, но это также облегчает работу с подзапросами, где вы не должны этого делать.
Питер Б
36

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

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

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

Именно с абстракциями код становится проще для рефакторинга, потому что абстракция скрывает детали реализации от потребителя этой абстракции. Прямой SQL не обеспечивает такого разделения, хотя процедурные расширения для SQL, такие как PL / SQL для Oracle или Transact-SQL для SQL Server, начинают немного размыть границы.

Грег Бургхардт
источник
«SQL имеет дело только с конкретными структурами данных, а не с абстрактным поведением (или абстракцией в любом смысле этого слова)». Это странное утверждение, поскольку, с моей точки зрения, SQL имеет дело исключительно с абстрактным поведением, а не с конкретным программированием в любом смысле этого слова! Просто рассмотрите все огромные степени сложности, которые абстрагируются в простом слове «ПРИСОЕДИНЯЙТЕСЬ»: вы говорите, что хотите получить объединенный результат, извлеченный из двух разных наборов данных, и предоставляете СУБД возможность определять конкретные применяемые методы, разбираться с ними. индексация, обработка различий между таблицами и подзапросами и т. д.
Мейсон Уилер
5
@MasonWheeler: Думаю, я больше думал о SQL с точки зрения данных, над которыми он работает, а не о реализации возможностей языка. Таблицы в базе данных не кажутся абстракцией. Они являются конкретными, так как в таблице под названием "phone_numbers" содержатся номера телефонов. Номер телефона не является абстрактным понятием.
Грег Бургардт
12

Я думаю, что в вашем вопросе / точке зрения вы можете упустить то, что SQL выполняет операции над множествами (используя операции над множествами и т. Д.).

Когда вы работаете на этом уровне, вы, естественно, отказываетесь от определенного контроля над двигателем. Вы все еще можете форсировать некоторый код процедурного стиля с помощью курсоров, но, как показывает опыт 99/100 раз, вам не следует этого делать.

Рефакторинг SQL возможен, но он не использует те же принципы рефакторинга кода, которые мы использовали в коде прикладного уровня. Вместо этого вы оптимизируете то, как вы используете сам движок SQL.

Это можно сделать разными способами. Если вы используете Microsoft SQL Server, вы можете использовать SSMS, чтобы предоставить вам примерный план выполнения, и вы можете использовать его, чтобы увидеть, какие шаги вы можете предпринять для настройки своего кода.

В случае разбиения кода на более мелкие модули, как упоминалось @ greg-burghardt, SQL, как правило, представляет собой специально созданный фрагмент кода и, как следствие,. Это делает то, что вам нужно, и больше ничего. Он придерживается S в SOLID, у него есть только одна причина для изменения / воздействия, и именно тогда вам нужен этот запрос, чтобы сделать что-то еще. Остальная аббревиатура (OLID) здесь не применяется (AFAIK, в SQL нет внедрения зависимостей, интерфейсов или зависимостей как таковых), в зависимости от разновидности используемого вами SQL, вы можете расширить некоторые запросы, заключив их в оболочку в хранимой процедуре / табличной функции или использовании их в качестве подзапросов, поэтому, я бы сказал, принцип открытого-закрытого все-таки будет применяться, в некотором роде. Но я отвлекся.

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

При этом существуют способы, с помощью которых вы можете сделать свой код более привлекательным, если удобочитаемость является одним из главных приоритетов в организации. Сохранение битов часто используемых блоков SQL (общих наборов данных, которые вы используете) в хранимых процедурах / функциях табличных значений, а затем запрос и сохранение их во временных таблицах / табличных переменных с последующим их использованием для объединения частей в одну массивную транзакцию. то, что вы в противном случае написали бы, вариант. ИМХО, не стоит делать что-то подобное с SQL.

Как язык, он разработан, чтобы быть легко читаемым и понятным любому, даже не программисту. Таким образом, если вы не делаете что-то очень умное, нет необходимости реорганизовывать код SQL в куски меньшего размера. Лично я написал массивные запросы SQL, работая над решением ETL / Reporting хранилища данных, и все было очень ясно с точки зрения происходящего. Все, что могло бы показаться кому-то немного странным, получило бы краткий набор комментариев, чтобы дать краткое объяснение.

Надеюсь, это поможет.

Тони Костелак
источник
6

Я собираюсь сосредоточиться на «подзапросах» в вашем примере.

Почему они используются так часто? Потому что они используют естественный способ мышления человека: у меня есть этот набор данных, и я хочу выполнить действие над его подмножеством и объединить его с подмножеством других данных. 9 из 10 раз, что я вижу подзапрос, он используется неправильно. Моя шутка о подзапросах такова: люди, которые боятся присоединений, используют подзапросы.

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

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

Рефакторинг в SQL часто преследует другую цель: повысить производительность, сократить время запросов, «избежать сканирования таблиц». Это может даже сделать код менее читабельным, но очень ценным.

Так почему же вы видите так много огромных монолитных нерефакторизованных запросов?

  • SQL во многих отношениях не является языком программирования.
  • Плохой дизайн базы данных.
  • Люди не очень хорошо владеют SQL.
  • Нет власти над базой данных (например, не разрешено использовать представления)
  • Разные цели с рефакторингом.

(для меня, чем больше у меня опыта работы с SQL, тем меньше у меня запросов, у SQL есть способы, позволяющие людям с любым уровнем квалификации выполнять свою работу, несмотря ни на что).

Питер Б
источник
6
«Подзапросы» с такой же вероятностью будут представлять собой некоторую агрегацию правильно нормализованного БД, так же как и произвольную нормализацию ненормализованного БД
Калет
@ Калет, это так правда.
Питер Б
5
Даже в хорошо нормализованных базах данных все еще часто необходимо объединяться с подзапросами, а не соединяться непосредственно с таблицами. Например, если вам нужно объединить сгруппированные данные.
Бармар
1
@ Бармар определенно, поэтому мой комментарий 9 из 10. Подзапросы имеют свое место, но я вижу, что они используются неопытными людьми.
Питер Б
Мне нравится ваша метрика «количество подзапросов» как показатель нормализации базы данных (или ее отсутствия).
Джейсон
2

Разделение обязанностей

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

Под созданием нового представления в базе данных понимается, что он служит долговременным целям и должен использоваться сообществом пользователей. В представлении DBA это допустимо только в том случае, если представление оправдано структурой данных. Каждое изменение представления тогда связано с рисками для всех его текущих пользователей, даже тех, кто не использует приложение, но кто обнаружил представление. Наконец, создание новых объектов требует управления полномочиями и, в случае просмотра, в соответствии с полномочиями базовых таблиц.

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

SQL дизайн

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

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

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

Собственные расширения

Можно надеяться на некоторый рефакторинг, передавая некоторые обязанности процедурным расширениям SQL, таким как PL / SQL или T-SQL. Однако они зависят от поставщика и создают технологическую зависимость. Кроме того, эти расширения выполняются на сервере базы данных, создавая большую вычислительную нагрузку на ресурс, который гораздо сложнее масштабировать, чем на сервере приложений.

Но в чем проблема в конце?

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

Итак, чтобы добиться успешного рефакторинга:

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

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

  • улучшить понимание команды : убедитесь, что ваше приложение не выполняет задачи, которые могут быть выполнены более эффективно с помощью СУБД. Как вы правильно заметили, процедурный подход и подход, ориентированный на данные, не одинаково освоены разными членами команды. Это зависит от их фона. Но чтобы оптимизировать систему в целом, ваша команда должна понимать ее в целом. Так что создавайте осведомленность, чтобы быть уверенным, что менее опытные игроки не изобретают велосипед и не делятся своими мыслями с более опытными участниками.

Christophe
источник
+1 Несколько замечательных моментов здесь. Учитывая, насколько плохим является какой-либо SQL-код, скрытность администраторов баз данных для разрешения представлений часто полностью понятна. Кроме того, SQL может определенно выиграть от экспертной оценки, если он требует много ресурсов и / или будет часто запускаться.
Робби Ди
1

В отношении пунктов 1 и 3: представления - не единственный способ. Существуют также временные таблицы, витрины, переменные таблиц, агрегированные столбцы, CTE, функции, хранимые процедуры и, возможно, другие конструкции в зависимости от РСУБД.

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

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

Существует также тенденция к выполнению запросов на стороне клиента с помощью таких технологий, как LINQ, которые вы затронули в пункте 2.

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

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

Робби Ди
источник
1
Я никогда не слышал о конструкции "витрина". Что это?
епископ
1
Марты - это всего лишь подмножество хранилища (основная база данных). Если существуют конкретные сложные запросы, которые необходимо выполнить, можно создать специальную базу данных специально для обслуживания этих запросов. Очень распространенный пример - рынок отчетности.
Робби Ди
1
Смущенный, почему это было понижено. Непосредственно не отвечает на вопрос, но дает довольно четкий неявный ответ «вариант 3: есть много способов справиться с этим, которые широко используются».
Деви Морган
TIL о витринах данных. +1!
епископ