Графови бази
Накратко: Графовата база, като Neo4j, пази възли и връзки вместо таблици, а заявките на Cypher търсят шаблони и пътища през тях.
Проблем
Купилите кафе купиха още
Под всеки продукт магазинът иска ред „Купилите това купиха още“. За кафето на зърна заявката към SQL базата е тази:
Тя минава по шест таблици: от продукта до редовете на поръчките, до поръчките, до клиента, до другите му поръчки, до техните редове и до продуктите. orders и order_items участват по два пъти. Следващото желание е още по-тежко: „продуктите на хората, чиито препоръки следваш, и на хората, които те следват“. Всяка стъпка е още съединения, а „до три стъпки“ иска WITH RECURSIVE. Въпросът е за връзките между нещата, а в таблиците връзките са разпилени във външни ключове.
Идеята
Възли и връзки
Графовата база пази данните като граф: възли (нещата, например клиент и продукт) и връзки между тях. Всяка връзка има тип (BOUGHT) и посока (от клиента към продукта). Възлите имат етикети (:Customer), а възлите и връзките имат свойства (name, price, quantity). Връзката е записана в базата, а не се търси с JOIN при всяка заявка: от възела базата стига направо до съседите му.
Най-известната графова база е Neo4j, а езикът ѝ е Cypher. Шаблоните в него са рисунка с букви: (c:Customer) е възел, а -[:BOUGHT]-> е стрелка.
Заявката завършва с ;, затова може да е на няколко реда. CREATE прави възли и връзки. MATCH намира всичко, което пасва на шаблона, а RETURN казва какво да се покаже. Под изхода симулаторът рисува намерените възли и връзки.
По-точно, за любопитните
Симулаторът знае част от Cypher: MATCH, WHERE, CREATE, MERGE, SET, DELETE, DETACH DELETE, RETURN, ORDER BY, SKIP, LIMIT и няколко функции. Няма WITH, OPTIONAL MATCH, UNWIND, индекси и ограничения. Изходът е като в конзолата cypher-shell, но без реда с времето на заявката, а съобщенията за грешки са по-кратки. Симулаторът показва подканата пред всеки ред на заявката. Neo4j Browser не иска ; в края и рисува резултата сам. Без ORDER BY симулаторът връща редовете в реда, в който са направени възлите, а Neo4j не обещава никакъв ред.
Пробвай
Георги и хлябът
Базата е празна. Запиши, че клиентът Георги Петров е купил бял хляб: възел с етикет Customer и свойство name, възел с етикет Product и свойство name и връзка BOUGHT от клиента към продукта.
Решение
CREATE (g:Customer {name: 'Георги Петров'})-[:BOUGHT]->(b:Product {name: 'Бял хляб'});
Една заявка: CREATE (g:Customer {name: 'Георги Петров'})-[:BOUGHT]->(b:Product {name: 'Бял хляб'});. Посоката има значение: стрелка от продукта към клиента казва „хлябът купи Георги“, и проверката от клиента към продукта не намира нищо. Имената g и b живеят само до ;. Ако направиш възлите в три отделни заявки, третата не знае кои са g и b и прави два нови възела без етикети и без свойства.
Идеята
Шаблоните
От тук нататък базата е магазинът: 20 клиенти, 25 продукта в 6 категории и 109 връзки BOUGHT, по една за всеки клиент и продукт от поръчките, които не са отменени. Те са взети от SQL базата на курса. Продуктът е свързан с категорията си с връзка IN.
Шаблонът може да е дълъг колкото трябва: от клиента през продукта до категорията, без нито едно съединение.
Посоката в шаблона трябва да съвпада с посоката на връзката:
От продукта към клиента няма стрелки BOUGHT, затова първата заявка не намира никого. <-[:BOUGHT]- чете същата връзка от другия ѝ край. -[:BOUGHT]- без стрелка намира връзката в която и да е посока.
По-точно, за любопитните
Графът не пази отделните поръчки: връзката BOUGHT казва само, че клиентът е купувал продукта. Ако трябват и поръчките, те стават възли: (:Customer)-[:PLACED]->(:Order)-[:CONTAINS {quantity: 2}]->(:Product). Кое е възел и кое е връзка се решава по въпросите, които ще задаваш, както при схемата на SQL базата.
Виж го
Същият въпрос, един шаблон
Ето „купилите кафе купиха още“ на Cypher:
Отговорът е същият като на SQL заявката от началото, ред по ред. Шаблонът се чете отляво надясно: кафето, купено от клиент, който е купил и друг продукт. Кафето не се брои към себе си: в един MATCH една и съща връзка не участва два пъти, а връзката към кафето вече е заета. В SQL това беше отделно условие (p2.id <> p1.id). Кафе са купили само трима души, затова петте продукта са с по двама купувачи и се подреждат по име.
Разликата не е само в дължината. SQL базата търси всяко съединение в индекс или в цялата таблица. Графовата тръгва от кафето и минава само по неговите връзки: работата зависи от това колко връзки има около кафето, а не колко поръчки има магазинът.
Идеята
Пътища с променлива дължина
Да кажем, че магазинът има профили и всеки клиент може да следва препоръките на други клиенти: (:Customer)-[:FOLLOWS]->(:Customer). Тези връзки са измислени за урока, в SQL базата ги няма.
-[:FOLLOWS*1..2]-> е път от една до две стъпки по FOLLOWS. p = … дава име на целия път, а length(p) е броят на стъпките му:
В първия резултат Виктория е два пъти: Елена следва Мария и Димитър, а двамата следват Виктория. MATCH връща всеки път, не всеки човек. Във втората заявка WHERE f <> me маха самата Елена: по три стъпки пътят се връща при нея (Елена, Мария, Виктория, Елена).
Кои са хората по пътя, се вижда с nodes(p):
В SQL същото е WITH RECURSIVE върху таблица follows: начало, UNION ALL и стъпка, която съединява follows с предните редове, плюс брояч на стъпките. Тук е *1..3.
Виж го
Път без граница
Шаблон без тип и без посока, „всичко, свързано с Елена“, с все по-дълъг път:
98 маршрута, после 688, после 4 370. Всяка стъпка ги умножава между шест и седем пъти, защото продукт като бял хляб е свързан с много клиенти, а всеки от тях с много продукти. * без горна граница пробва всички маршрути. Симулаторът спира след 100 000 стъпки, а Neo4j не спира сама: заявката върви, докато свърши времето или паметта. Затова пиши горна граница винаги, а когато можеш, и типа и посоката на връзката.
Упражнение
Препоръки
1. Под сиренето също трябва ред „Купилите това купиха още“.
Кои три продукта са купували най-много от хората, купили сирене? Покажи името и броя купувачи (buyers), от най-много надолу, а при равен брой по име.
Решение
MATCH (:Product {name: 'Сирене'})<-[:BOUGHT]-(c:Customer)-[:BOUGHT]->(p:Product)
RETURN p.name, count(c) AS buyers
ORDER BY buyers DESC, p.name
LIMIT 3;
Шаблонът е същият като при кафето: (:Product {name: 'Сирене'})<-[:BOUGHT]-(c:Customer)-[:BOUGHT]->(p:Product), после count(c) AS buyers, ORDER BY buyers DESC, p.name и LIMIT 3. Без DESC излизат най-рядко купуваните. Ако стрелката тръгне от сиренето към клиента, шаблонът не намира нищо.
2. Мария Иванова следва няколко души, а те следват други.
Покажи клиентите, до които Мария Иванова стига по FOLLOWS в една или две стъпки, всеки по веднъж, подредени по име.
Решение
MATCH (:Customer {name: 'Мария Иванова'})-[:FOLLOWS*1..2]->(f:Customer)
RETURN DISTINCT f.name
ORDER BY f.name;
-[:FOLLOWS*1..2]-> и RETURN DISTINCT f.name. До Виктория се стига направо и през Георги, затова без DISTINCT тя е два пъти. С *2 липсват хората, които Мария следва направо.
3. Сега истинската препоръка.
Намери продуктите, които са купили хората, следвани от Елена Димитрова в една или две стъпки, но тя още не ги е купила. Покажи трите, купени от най-много различни хора (people), а при равен брой по име.
Решение
MATCH (me:Customer {name: 'Елена Димитрова'})-[:FOLLOWS*1..2]->(f:Customer)-[:BOUGHT]->(p:Product)
WHERE NOT (me)-[:BOUGHT]->(p)
RETURN p.name, count(DISTINCT f) AS people
ORDER BY people DESC, p.name
LIMIT 3;
WHERE NOT (me)-[:BOUGHT]->(p) маха продуктите, които Елена вече има: шаблонът в WHERE само проверява дали такава връзка съществува. count(DISTINCT f) брои хората, а не пътищата. Без DISTINCT Виктория, до която водят два пътя, се брои два пъти.
Идеята
Кога графова база, кога не
Графовата база е добра, когато въпросът е за връзки, често на няколко стъпки: препоръки („купилите това купиха още“), социални мрежи, откриване на измами (сметки, които делят телефон или адрес), маршрути и зависимости, права за достъп през групи. Тя не е добра за отчети, които събират числа от всички записи, например оборот по месеци: такава заявка не обхожда връзки, а чете всичко подред. Това е работа за SQL с GROUP BY или за база за отчети (OLAP). Затова графът обикновено стои до SQL базата: поръчките и парите са в SQL, а копие на връзките отива в графа за препоръките.
По-точно, за любопитните
Освен Neo4j, Cypher разбират Memgraph и Amazon Neptune (там езикът се казва openCypher; Neptune разбира и Gremlin и SPARQL), а разширението Apache AGE добавя Cypher в PostgreSQL. През 2024 г. излезе международният стандарт GQL (ISO/IEC 39075), до голяма степен по идеите на Cypher. Друг вид графова база са RDF хранилищата с езика SPARQL, в които всичко е тройка „субект, предикат, обект“.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Дървета и редици: WITH RECURSIVE · Видове връзки: 1:1, 1:N, M:N · Индекси · OLTP и OLAP
- Продължава в: Как се избира база
- Задачи по темата: Видове бази данни