Чому 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. Щось ще


12
Є організації, які дозволяють запитувати базу даних лише через представлення даних та змінювати її через збережені процедури.
Пітер Б

3
SQL отримав для мене набагато приємніше, коли я нарешті визнав, що це ніколи не буде настільки сухим, як мій звичайний процедурний код.
Грем

1
4. SQL дійсно старий і істотно не оновлювався десятиліттями. Для суперскладних матеріалів багато команд вибирають збережені процедури. Для цього можна додати різні пропозиції. Іноді просто потрібно запустити завдання, щоб розмістити дані в темп-таблиці, а потім приєднатися до цього. Ознайомтеся, чим відрізняються декларативні та процедурні мови.
Берін Лорич

8
Також однією з причин є те, що існує жахлива проблема продуктивності, яка називається "трикутне з'єднання", яка може статися при використанні поглядів (звичайно, випадково). Якщо ваш запит приєднується до View A і View B, але View A також у своїй реалізації повторно використовує View B, ви починаєте бачити цю проблему. Тож люди часто починають із написання єдиного монолітного запиту, щоб мати змогу побачити, що насправді найкраще працює в рефакторингу поглядів, і тоді їхній термін закінчується, і моноліт йде до виробництва. Начебто 98% всього розробника програмного забезпечення, дійсно :) :)
Стівен Бірн

3
Msgstr "Уявіть, якби інші типи програмістів повинні були подавати запит щоразу, коли вони створювали функцію" ... umm. Ви не робите перевірки коду?
svidgen

Відповіді:


25

Я думаю, що головна проблема полягає в тому, що не всі бази даних підтримують загальні табличні вирази.

Мій роботодавець використовує 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, на які всі посилалися, потім було основне об'єднання запитів про результати останніх чотирьох.

Це можна зробити за допомогою:

  • БД / 2
  • PostGreSQL
  • Oracle
  • MS SQL Server
  • MySQL (остання версія; все ще якась нова)
  • напевно, інші

Але це триває довгий шлях до того, щоб зробити код чистішим, розбірливішим, більш СУМИМ.

Я розробив "стандартну бібліотеку" CTE, на яку я можу підключатися до різних запитів, що дозволяє мені перейти до мого нового запиту. Деякі з них починають сприймати й інші розробники моєї організації.

З часом може виникнути сенс перетворити деякі з них на види, щоб ця "стандартна бібліотека" була доступна без копіювання / вставки. Але мої CTE в кінцевому підсумку перетворюються настільки незначно для різних потреб, що я не зміг використати жодного CTE так широко, без мод, що, можливо, варто створити перегляд.

Здавалося б, ваша частина захвату - це "чому я не знаю про CTE?" або "чому мій БД не підтримує CTE?"

Що стосується оновлень ... так, ви можете використовувати CTE, але, на мій досвід, ви повинні використовувати їх всередині встановленого пункту AND у пункті where. Було б добре, якби ви могли визначити одну або декілька випереджувачів усієї операції оновлення, а потім просто мати "головний запит" частини в наборі / де застереження, але це не працює таким чином. І не можна уникати незрозумілих імен таблиці / полів у таблиці, яку ви оновлюєте.

Ви можете використовувати CTE для видалення. Може знадобитися кілька CTE, щоб визначити значення PK / FK для записів, які потрібно видалити з цієї таблиці. Знову ж таки, ви не можете уникнути незрозумілих імен таблиці / полів у таблиці, яку ви змінюєте.

Оскільки ви можете зробити вибір для вставки, ви можете використовувати CTE для вставок. Як завжди, ви можете мати справу з неясними іменами таблиці / полів у таблиці, яку ви змінюєте.

SQL НЕ дозволяє створювати еквівалент доменного об'єкта, загортаючи таблицю, за допомогою getters / setters. Для цього вам потрібно буде використовувати якийсь ORM разом із більш процедурною / OO мовою програмування. Я писав речі такого характеру в Java / Hibernate.


4
У нас був містер Big CTE, людина, що пише найгірший SQL. Проблема полягала в тому, що CTE були поганим вибором абстракції, і оптимізатор не може скасувати кожен алгоритм, який ви поклали на голову.
Джошуа

3
Крім того, ORM може також робити деякі досить грізні речі, особливо для роботи ... особливо коли ви просто використовуєте геттерів та сетерів, щоб отримати купу даних. Hibernate відомий тим, що використовує сотні індивідуальних запитів замість одного великого об'єднаного запиту, що є проблемою, коли на кожен запит є накладні витрати.
користувач3067860

2
@Joshua Ви можете написати неправильний код будь-якою мовою. У тому числі SQL. Але рефакторинг на CTE, виконаний належним чином, може створити конструкції знизу вгору, які людині простіше розібратися. Я схильний вважати це бажаною рисою, незалежно від того, якою мовою я маю справу :-)
Meower68,

2
Інші відповіді чудові, але саме це я особисто шукав. "Чому я не знаю про CTEs" - це була більшість моїх проблем.
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. Через заданий характер він не може забезпечити багато можливостей мов рівня додатків (generics тощо). SQL ніколи не був розроблений таким чином, це мова для запитів наборів даних, і кожен набір по-своєму унікальний.

Незважаючи на це, існують способи, за допомогою яких ви можете зробити свій код красивішим, якщо читаність є важливим пріоритетом в організації. Збереження бітів часто використовуваних блоків SQL (загальних наборів даних, які ви використовуєте) у збережені процедури / функції значень таблиці, а потім запит і зберігання їх у тимчасових таблицях / змінних таблиць з подальшим використанням цих об'єднань частин разом в одну велику транзакцію що ви б інакше написали - це варіант. IMHO не варто робити щось подібне з SQL.

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

Я сподіваюся, що це допомагає.


6

Я збираюся зосередитись на "підзапитах" у вашому прикладі.

Чому їх використовують так часто? Тому що вони використовують природний спосіб мислення людини: я маю цей набір даних, і хочу зробити дію над його підмножиною та з'єднати його з підмножиною інших даних. 9 із 10 разів, коли я бачу підзапит, він використовується неправильно. Мій жарт про підзапити: люди, які бояться приєднання, використовують підзапити.

Якщо ви бачите такі підзапити, це також часто є ознакою неоптимального дизайну бази даних.

Чим більше нормалізованої вашої бази даних, тим більше приєднань ви отримуєте, тим більше ваша база даних схожа на великий аркуш видатних програм, тим більше підборів ви отримуєте.

Рефакторинг в SQL часто має іншу мету: отримати більшу продуктивність, кращі часи запитів, "уникнути сканування таблиць". Вони навіть можуть зробити код менш читабельним, але вони дуже цінні.

То чому ви бачите стільки величезних монолітних нереставрованих запитів?

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

(для мене, чим досвідченіший досвід роботи з SQL, тим менш великі мої запити, SQL пропонує людям усіх рівнів кваліфікації роботу, незважаючи ні на що.)


6
"Підзапроси" настільки ж ймовірні, що вони будуть агрегацією належним чином нормалізованого ДБ, оскільки вони повинні бути тимчасовою нормалізацією ненормованого
ДБ

@Caleth це так правда.
Пітер Б

5
Навіть у добре нормалізованих базах даних все ще часто доводиться з'єднуватися з підзапросами, а не з'єднуватися безпосередньо з таблицями. Наприклад, якщо вам потрібно приєднатися до згрупованих даних.
Вармар

1
@Barmar, безумовно, звідси моє 9 з 10 коментарів. Підзапити мають своє місце, але я бачу їх надмірним використанням недосвідчених людей.
Пітер Б

Мені подобається ваша метрика "кількості підзапитів" як вказівка ​​на нормалізацію бази даних (або її відсутність).
Джейсон

2

Розподіл обов'язків

У дусі SQL база даних - це спільний актив, який містить дані компанії, і їх захист має надзвичайно важливе значення. Входить до складу DBA як опікун храму.

Створення нового перегляду в базі даних повинно служити довготривалою ціллю і ділитися спільнотою користувачів. На думку DBA, це прийнятно, лише якщо погляд обґрунтований структурою даних. Кожна зміна погляду потім пов'язана з ризиками для всіх його нинішніх користувачів, навіть тих, хто не використовує додаток, але виявили його. Нарешті, для створення нових об'єктів потрібно керувати авторизаціями та, у випадку перегляду, послідовно з авторизаціями базових таблиць.

Все це пояснює, чому DBA не люблять додавати представлення даних, які призначені лише для коду окремої програми.

Дизайн SQL

Якщо ви розкладете один із своїх приємних складних запитів, ви можете виявити, що підзапроси часто знадобляться параметром, який залежить від іншого запиту.

Тож трансформація підзапитів у зорі не обов'язково така проста, як зазначено. Ви повинні ізолювати змінні параметри та спроектувати свій погляд, щоб параметри могли бути додані як критерії вибору у подання.

На жаль, роблячи це, ви іноді нав'язуєте доступ до більшої кількості даних та менш ефективно, ніж у спеціальному запиті.

Власні розширення

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

Але в чому проблема в підсумку?

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

Отже, щоб досягти успішного рефакторингу:

  • розглянути можливість кращого спілкування . Постарайтеся зрозуміти обмеження вашої DBA. Якщо ви докажете DBA, що новий погляд виправданий структурами даних, що він не є викидним шляхом і що він не має впливу на безпеку, він / вона, безумовно, погодиться дозволити його створити. Бо тоді це був би спільний інтерес.

  • спочатку очистіть власний будинок : ніщо не змушує вас генерувати багато SQL у багатьох місцях. Перефактуруйте код програми, щоб виділити доступ до SQL та створити класи або функції, щоб забезпечити повторні запити для багаторазового використання, якщо вони часто використовуються.

  • покращити поінформованість команди : переконайтеся, що ваша програма не виконує завдань, які могли б ефективніше виконувати механізм СУБД. Як ви справедливо зазначали, процедурний підхід та орієнтований на дані підхід не однаково опановують різні члени команди. Це залежить від їхнього тла. Але для оптимізації системи в цілому, вашій команді потрібно зрозуміти її в цілому. Тож створюйте обізнаність, щоб бути впевненим, що менш досвідчені гравці не винаходять колесо та діляться своїми думками БД з більш досвідченими членами.


+1 Деякі чудові моменти тут. Зважаючи на те, наскільки поганий деякий SQL, схильність DBA до дозволу перегляду часто цілком зрозуміла. Крім того, SQL, безумовно, може отримати користь від експертної оцінки, якщо він голодний ресурсів і / або він буде працювати часто.
Роббі Ді

1

Повторні пункти 1 та 3: Перегляд - не єдиний спосіб. Існують також тимчасові таблиці, схеми, змінні таблиці, агреговані стовпці, CTE, функції, збережені процедури та, можливо, інші конструкції залежно від RDBMS.

DBA (і я кажу як людина, яка була і DBA, і розробником), як правило, розглядає світ досить бінарним способом, тому часто проти таких речей, як погляди та функції через сприйняття покарання за ефективність.

Останнім часом потреба у складних з'єднаннях зменшилась із визнанням того, що денормалізовані таблиці, незважаючи на те, що вони є оптимальними з точки зору NF , є високоефективними.

Існує також тенденція робити запити клієнта на стороні таких технологій, як LINQ, які ви піднімаєте в точці 2.

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

Я думаю, це дійсно залежить від того, наскільки ваші керівники баз даних / архітекторів / технологій готові поступитися у цьому плані. Якщо вони відмовляться дозволити що-небудь, крім ванільного SQL з великою кількістю приєднань, можуть виникнути величезні запити. Якщо ви зациклювались на цьому, не стукайте головою об цегляну стіну, ескаліруйте її. Зазвичай існують кращі способи ведення справ за допомогою трохи компромісу - особливо якщо ви можете довести переваги.


1
Я ніколи не чув про конструкцію "марта". Що це?
єпископ

1
Марти - це лише підмножина сховища (головна база даних). Якщо є певні складні запити, які потрібно запустити, спеціальна база даних може бути створена спеціально для обслуговування цих запитів. Дуже поширений приклад - звітуючий март.
Роббі Ді

1
Плутати, чому це було знято. Не відповідає безпосередньо на питання, але дає досить чітку неявну відповідь "варіант 3: Є багато способів вирішення цього питання, які широко використовуються".
Деві Морган

TIL про місця даних. Майте +1!
єпископ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.