Чому реляційні бази даних не підтримують повернення інформації у вкладеному форматі?


46

Припустимо, я будую блог, який хочу мати публікації та коментарі. Таким чином я створюю дві таблиці: таблицю 'posts' зі стовпцем 'id', що автоматично збільшується, та таблицю «коментарів», що має зовнішній ключ «post_id».

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

SELECT id, content, (SELECT * FROM comments WHERE post_id = 7) AS comments
FROM posts
WHERE id = 7

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

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

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

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

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


1
не всі органи працюють так. hibernate / nhibernate дозволяє вказувати приєднання, і може нетерпляче завантажувати цілі дерева об'єктів з одного запиту.
nathan gonzalez

1
Крім того, хоч цікавий момент обговорення, я не впевнений, що це справді відповідає, не маючи зустрічі з хлопцями ansi sql
Nathan gonzalez

@nathan: Так, не все. Я використовував Sequel, який дозволяє вам вибрати, який підхід вам надається для даного запиту ( документів ), але вони все ще заохочують підхід із декількома запитами (я вважаю, з міркувань продуктивності).

5
Оскільки RDBMS призначений для зберігання та отримання наборів - він не призначений для повернення даних для відображення. Подумайте про це як MVC - чому б він намагався реалізувати погляд ціною, щоб зробити модель повільнішою чи складнішою у використанні? RDBMS пропонують переваги, що бази даних NoSQL не можуть (і навпаки) - якщо ви використовуєте її, тому що це правильний інструмент для вирішення вашої проблеми, ви не просите її повернути дані, готові до відображення.

1
Вони дійсно бачать xml
Ян

Відповіді:


42

CJ Date детально про це детально описується у главі 7 та додатку B SQL та реляційної теорії . Ви маєте рацію, в реляційній теорії немає нічого, що забороняє типу даних атрибута бути самим відношенням, якщо це однаковий тип відношення у кожному рядку. Ваш приклад може відповідати.

Але Дата каже, що подібні структури "зазвичай - але не незмінно - протипоказані" (тобто погана ідея), оскільки ієрархії відносин асиметричні . Наприклад, перетворення з вкладеної структури у звичну "плоску" структуру не завжди може бути повернене для відтворення гніздування.

Запити, обмеження та оновлення є складнішими, складніше писати і важче підтримувати RDBMS, якщо ви допускаєте атрибути, що оцінюються за відношенням (RVA).

Це також мутить принципи дизайну баз даних, тому що найкраща ієрархія відносин не така чітка. Чи слід розробити відношення Постачальників із вкладеною RVA для деталей, що постачаються даним Постачальником? Або відношення деталей до вкладеної RVA для постачальників, які постачають дану деталь? Або зберігати обидва, щоб спростити запуск різних типів запитів?

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

З того, що я розумію (я не використовував їх), Rel і Dataphor - це проекти RDBMS, які підтримують атрибути, що мають відношення.


Re коментар від @dportas:

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

Вкладені таблиці також підтримуються Oracle, і вони дозволяють створювати кілька кортежів у рядку базової таблиці. Але я не знаю, що це частина стандартного SQL. І майте на увазі висновок одного блогу: "Я ніколи не використовуватиму вкладену таблицю у виписці CREATE TABLE. Ви витрачаєте весь свій час НЕВІДКЛЮЧЕННЯ на них, щоб зробити їх знову корисними!"


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

Набори та таблиці результатів є своєрідними. Дата називає їх відношеннями і відносними відповідно (за аналогією 42 - це ціле число, тоді як змінна xможе мати значення цілого числа 42). Ті ж операції стосуються відносин та відношень, тому їх структура повинна бути сумісною.
Білл Карвін

2
Стандартний SQL підтримує вкладені таблиці. Їх називають "структурованими типами". Oracle - це одна СУБД, яка має цю особливість.
nvogel

2
Чи не абсурдно стверджувати, що щоб уникнути дублювання даних, ви повинні писати запит плоско, дублюючи дані?
Еймон Нербонна

1
@EamonNerbonne, симетрія реляційних операцій. Наприклад, проекція. Якщо я ВИБІРУЮ деякі субатрибути з RVA, як я можу застосувати зворотну операцію проти набору результатів для відтворення вихідної ієрархії? Я знайшов сторінку сторінки 293 книги "Дата" на Google Books, тож ви можете побачити, що він написав: books.google.com/…
Білл Карвін

15

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

Є ще деякі приклади ієрархічних систем баз даних у дикій природі, зокрема реєстр Windows та LDAP.

Широке висвітлення цієї теми доступне в наступній статті


10

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

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

Я не бачу нічого ефективного в надсиланні 2 запитів та отриманні 2 партій результатів за допомогою:

--- Query-1-posts
SELECT id, content 
FROM posts
WHERE id = 7


--- Query-2-comments
SELECT * 
FROM comments 
WHERE post_id = 7

Я б заперечував, що це (майже) найефективніший спосіб (майже, оскільки вам не потрібні справді posts.idне всі стовпці comments.*)

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

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

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

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

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


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

2
@Precious: Для запуску декількох запитів не повинно бути великих накладних витрат. Більшість баз даних дозволяють надсилати кілька запитів в одній партії та отримувати декілька наборів результатів від одного запиту.
Даніель Приден

@PreciousBodilyFluids - фрагмент SQL у відповіді ypercube - це єдиний запит, який буде надіслано в одному виклику бази даних і поверне два набори результатів в одній відповіді.
Carson63000

5

Я не в змозі відповісти правильною аргументованою відповіддю, тому сміливо зволікайте мене у небуття, якщо я помиляюся (але, будь ласка, виправте мене, щоб ми могли дізнатися щось нове). Я думаю, що причина полягає в тому, що реляційні бази даних зосереджені на реляційній моделі, яка, в свою чергу, ґрунтується на чомусь, про що я нічого не знаю, називається "логікою першого порядку". Те, що ви можете запитати, ймовірно, не вкладається концептуально в математичні / логічні рамки, засновані на реляційних базах даних. Більше того, те, що ви запитуєте, як правило, вирішується легко за допомогою баз даних графіків, що дає більше натяків, що саме основна концептуалізація бази даних суперечить тому, що ви хочете досягти.


5

Я знаю, що принаймні SQLServer підтримує вкладені запити при використанні ДЛЯ XML.

SELECT id, content, (SELECT * FROM comments WHERE post_id = posts.id FOR XML PATH('comments'), TYPE) AS comments
FROM posts
WHERE id = 7
FOR XML PATH('posts')

Проблема тут не в недостатній підтримці RDBMS, а в недостатній підтримці вкладених таблиць у таблицях.

Крім того, що заважає тобі скористатися внутрішнім з'єднанням?

SELECT id, content, comments.*
FROM posts inner join comments on comments.post_id = posts.id
WHERE id = 7

Ви можете реально дивитися на внутрішнє з'єднання як вкладену таблицю, лише вміст перших 2 полів повторюється протягом певного часу. Я б не хвилювався за ефективність приєднання, єдина повільна частина запиту на кшталт цього - це io з бази даних для клієнта. Це буде лише тоді, коли вміст містить велику кількість даних. У такому випадку я запропонував би два запити, один із select id, contentта один із внутрішнім з'єднанням та select posts.id, comments.*. Це масштабується навіть із кількома публікаціями, оскільки ви все ще використовуєте лише 2 запити.


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

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

5

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

SELECT id, content, cursor(SELECT * FROM comments WHERE post_id = 7) AS comments
FROM posts
WHERE id = 7

1

Деякі підтримують гніздування (ієрархічне).

Якщо ви хотіли одного запиту, у вас може бути одна таблиця, яка самостійно посилається на себе. Деякі RDMS підтримують цю концепцію. Наприклад, за допомогою SQL Server можна користуватися загальними табличними виразами (CTE) для ієрархічного запиту.

У вашому випадку повідомлення буде на рівні 0, і тоді всі коментарі будуть на рівні 1.

Інші параметри - це 2 запити або приєднання з додатковою інформацією для кожної повернутої записи (яку згадували інші).

Приклад ієрархічного:

https://stackoverflow.com/questions/14274942/sql-server-cte-and-recursion-example

У вищенаведеному посиланні EmpLevel показують рівень введення (або ієрархію).


Я не можу знайти жодної документації про набори підрезультатів у SQL Server. Навіть при використанні CTE. Під набором результатів я маю на увазі рядки даних із достатньо сильно набраними стовпцями. Чи можете ви додати посилання на свою відповідь?
SandRock

@SandRock - База даних відправить назад один набір результатів із запиту SQL. Визначаючи рівні в самому запиті, ви можете створити ієрархічний або вкладений набір результатів, який повинен оброблятися. Я думаю, що в даний час, що найближче, ми гоніг, щоб повернути дані, які вкладені.
Джон Рейнор

0

Вибачте, я не впевнений, що я точно розумію ваше питання.

У MSSQL ви можете просто виконати 2 заяви SQL.

SELECT id, content
FROM posts
WHERE id = 7

SELECT * FROM comments WHERE post_id = 7

І це одночасно поверне ваші 2 набори результатів.


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

Але це буде одна туди-назад. stackoverflow.com/questions/2336362 / ...
Биф MaGriff

0

RDBM засновані на теорії і вони дотримуються теорії. Це дозволяє отримати приємну консистенцію та математично доведену надійність.

Оскільки модель проста і знову заснована на теорії, вона дозволяє людям легко робити оптимізацію та багато реалізацій. Це на відміну від NoSQL, де всі роблять це трохи по-іншому.

У минулому були спроби створити ієрархічні бази даних, але у IIRC (не здається, що це гуглити) виникали проблеми (цикли та рівність приходять у голову).


0

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

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

Єдина аргументація, яку я маю проти вашої пропозиції, - це можливість обробляти цей набір результатів "sql" способом. Було б погано задумати створити результат у базі даних, який не зможе працювати з ним або певною мірою маніпулювати ним. Скажімо, я створив представлення, побудоване так, як ви запропонували, як я його включати в інший оператор вибору? Бази даних люблять брати результати і робити з ними справи. Як би я приєднав його до іншої таблиці? Як би я порівняв ваш набір результатів з іншим?

Тоді перевагою RDMS є гнучкість sql. Синтаксис вибору даних із таблиці досить близький до списку користувачів або інших об’єктів у системі (принаймні, це мета.). Не впевнений, що є сенс робити щось абсолютно різне. Вони навіть не змусили їх дуже ефективно обробляти процедурні коди / курсори або BLOBS з даними.


0

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

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

Особисто я використовую такі рамки, як Doctrine ORM (PHP), які роблять додаток для агрегації та підтримують такі функції, як ледача завантаження для підвищення продуктивності.


0

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


1
це, здається, не пропонує нічого суттєвого щодо питань, викладених та пояснених у попередніх 13 відповідях
gnat

У запитанні конкретно згадується JSON, і ця відповідь є єдиною, яка вказує на те, що JSON може бути повернутий у запитах щонайменше з однієї RDBMS. Я б скоріше прокоментував це запитання, щоб сказати, що він заснований на помилковій передумові, і тому не можу очікувати жодної остаточної відповіді. Однак StackExchange не дозволить мені це зробити.
Джонатан Роджерс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.