Skip to content

MSSQL Graph DB

MSSQL GraphDB

Table is nothing, relation does matter

Disclaimer

Post language: Ukrainian

Трохи теорії

Компанія Microsoft зробила ще один крок для об’єднання різних технологій і методологій роботи з даними під одним дахом. Додавання обробки JSON дало можливість працювати з даними в стилі NOSQL Document DB. Добавання роботи з графовою моделлю даних дасть можливість працювати в стилі NOSQL Graph DB. Звичайно, що під капотом залишився той самий движок реляційних баз даних, на який навішали новий функціонал.

Світ не розділений на реляційні моделі, графові моделі, документи – все це існує інтегроване одне з другим. Зазвичай в реальна модель даних виглядає по різному з різних кутів зору. Відповно зручно мати універсальний комбайн, який дозволить працювати з такою «мішаною» моделлю. Звичайно, вузькоспеціалізовані моделі даних задачі будуть вимагати вузькоспеціалізованих інструментів. Але для того, що називається «золотою серединою» універсальний комбайн стане в пригоді. Прикладом може бути той самий PostgreSQL з підтримкою JSON – реляційні дані в таблицях, те що не має строгої моделі даних – в JSON.

Той No SQL, який є Graph DB ніколи не був на хвилі хайпу і не старався витіснити реляційні бази даних з насиджених місць. Реляційні і графові бази даних мають одну спільну рису – і та і та системи побудовані на основі математичних теорій – теорії множин і теорії графів. У решті аспектів роботи з даними вони різні. Реляційні бази даних працюють з сутностями, їхніми властивостями. Вони добре підходять для управління відностно статичними наборами даних з одноріднми зв’язками між ними. Графові бази даних оперують зв’язками між сутностями. Вони взяли на себе вирішення задач що стосувалися складності структури (моделі) даних, стали альтернативою, коли звязки між об’єктами є такими самими важливими як і самі об’єкти. Сьогодні бізнес для досягнення успіху більше орієнтується на зв’язки між даними, а не тільки на самі дані. А це територія графових баз. Відповідно графова і реляційна модель доповнюють одна іншу, дозволяють аналізувати дані в різних ракурсах.

Структура графових баз даних проста. Є два типи об’єктів – вершини (або ноди), які представляють різні об’єкти, та ребра які репрезентують зв’язки між цими вершинами. Графова база даних надає можливості аналізувати зв’язки. Зв’язки між сутностями містять значний контекст, який втрачається при нормалізації даних в реляційних базах. З іншої сторони графові бази даних мають більш інтуїтивну модель даних. В реляційні моделі ми оперуєм множинами записів і менший акцент робиться на зв’язки між записами в межах множини. В класичному “NOSQL” модель спрощена по максимуму (ключ або документ) для досягнення кращого масштабування і швидкодії. Фактично графові бази даних добре працюють з складними моделями, дозволяють отримувати знання з зв’язків між даними. Сама теорія графів має практичні застосування в медицині, фізиці, соціології і, звичайно, в комп’ютерних науках. Типи задач, де використовуються графові бази даних: виявлення шахрайства, GDPR та інші типи регулювання, ІТ – управління мережевими даними і звичайно соціальні мережі та системи рекомендацій.

В архітектурі графових баз даних виділяються два основних підходи. Один з них - це RDF (Resource Description Framework) розроблений в кінці 90-х років для аналізу web ресурсів і зв’язків між ними. RDF формально оперує об’єктами, котрі концептуально є трійками “сутність: атрибут: значення”. Для навігації використовується декларативна мова (SQL подібна) SPARQL, розроблена консорціумом W3C. Інший підхід – labeled property graph (проблема перекладу українською). Його розробила група шведських інженерів для потреб власного проекту. Тут вершини і ребра можуть мати додаткові властивості. Прикладом реалізації цього підходу є графова база даних Neo4J. Мовою навігації є декларативна мова Cypher (CQL).

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

Підтримка графових баз даних була добавлена у версії MS SQL Server 2017 року, нові команди добавлені в 2019. Для роботи з графовими даними в MS SQL Server реалізовано підхід labeled property graph. Що було додано: нові типи таблиць (NODE, EDGE (+ обмеження)), ключові слова MATCH (для підтримки пошуку за шаблонами та навігації по графу), FOR PATH, функція SHORTEST PATH, розширення агрегатних функцій WITHING GROUP (GRAPH PATH) Було внесено зміни в системні каталоги, додані нові системні функції. Документація є на офіційному сайті компанії Майкрософт.

Приклад

Розглянемо варіант соціальної мережі

Соціальна мережа Соціальна мережа

Вершини – випускники, університети, міста. Ребра: Випускники дружать, колись вчилися, живуть у містах. Університети знаходяться у містіах. Схематично запити до соціальної мережі прості – показати коло друзів (безпосередніх, друзів друзів (2 рівень)...) і знайти найкоротший шлях між друзями (якщо він існує). Повний скрипт для створення структури і заливки даних знаходиться в додатку. Тут проаналізуємо окремі команди і нові ключові слова.

Створення таблиці вершин:

CREATE TABLE sn.alumnus
( id         INTEGER PRIMARY KEY
, name       VARCHAR(100)
)
AS NODE;

Нове ключове слово – AS NODE визначає тип таблиці. Коли таблиця створена, то крім явно заданих колонок вона містить і набір системних. Дивимося структуру таблиці і дані.

SELECT object_name(c.object_id) AS table_name
     , c.name                   AS column_name
     , c.graph_type_desc        AS graph_type_desc
  FROM sys.columns              c
 WHERE c.object_id              = OBJECT_ID('[sn].[alumnus]')

-- table_name   column_name                                 graph_type_desc
-- ----------   -----------------------------------------   ---------------
-- alumnus      graph_id_21C6D6D17C4648039ED00A332D88D152   GRAPH_ID
-- alumnus      $node_id_4BE90463CC054FAD9926854B90F3D5CB   GRAPH_ID_COMPUTED
-- alumnus      id                                          NULL
-- alumnus      name                                        NULL
SELECT $node_id, id, name FROM [sn].[alumnus]

-- $node_id_4BE90463CC054FAD9926854B90F3D5CB                id   name
-- ------------------------------------------------------   --   ----
-- {"type":"node","schema":"sn","table":"alumnus","id":0}   1    Vera
-- {"type":"node","schema":"sn","table":"alumnus","id":1}   2    Patrizio
-- {"type":"node","schema":"sn","table":"alumnus","id":2}   3    Sergio
-- {"type":"node","schema":"sn","table":"alumnus","id":3}   4    Caprice
-- {"type":"node","schema":"sn","table":"alumnus","id":4}   5    Bellissa

Як ми бачимо, є дві службових колонки, обидві автогенеровані, одна з них доступна на читання $node_id. Структура колонки $node_id – JSON. Що цікаво, ідентифікатри в таблиці стартують з 1, а в зненерованому JSON-I – з 0. Колонка Graph_id недоступна, але у кожній таблиці по ній створюється унікальний некластерний індекс.

Створення таблиці ребер. Якщо ребро має властивості (так як «вчилися у», то табличка буде створюватися з колонками, якщо немає – то у ній будуть присутні тільки службові колонки)

CREATE TABLE sn.graduatedfrom
( rating        INTEGER
, graduatedyear INTEGER
)
AS EDGE;

CREATE TABLE sn.friendof AS EDGE;

Ключове слово AS EDGE визначає тип таблиці – ребро. Маємо нові типи графових колонок. Працювати будем з колонками $edge_id, $from_id, $to_id. На відміну від традиційних таблиць є можливість створити табличку без колонок. Структура таблиці (ребро містить користувацькі властивості – рейтинг, рік закінчення):

table_name      column_name                                    graph_type_desc
-------------   --------------------------------------------   ---------------
graduatedfrom   graph_id_3CDE2433553947C9979FB537014C37DD      GRAPH_ID
graduatedfrom   $edge_id_3199B59CF4264209B3C33C2A9A6D3993      GRAPH_ID_COMPUTED
graduatedfrom   from_obj_id_377B54A257834F19AB3D376E6EC38C41   GRAPH_FROM_OBJ_ID
graduatedfrom   from_id_A4EC331A614D4B87810AA535509CDB30       GRAPH_FROM_ID
graduatedfrom   $from_id_96F31C58E41848349732356ABBBFA1C4      GRAPH_FROM_ID_COMPUTED
graduatedfrom   to_obj_id_0E6E76FE72CC4194A4C7A41A7F538B6E     GRAPH_TO_OBJ_ID
graduatedfrom   to_id_13C58060E6DF416CBE08E05A9652FACE         GRAPH_TO_ID
graduatedfrom   $to_id_48D2A88E1AA94EFABD3B68B08C10612A        GRAPH_TO_ID_COMPUTED
graduatedfrom   rating                                         NULL
graduatedfrom   graduatedyear                                  NULL

Зразок даних.

-- $edge_id_3199B59CF4264209B3C33C2A9A6D3993                      $from_id_96F31C58E41848349732356ABBBFA1C4                $to_id_48D2A88E1AA94EFABD3B68B08C10612A                     rating  graduatedyear
-- {"type":"edge","schema":"sn","table":"graduatedfrom","id":0}   {"type":"node","schema":"sn","table":"alumnus","id":0}   {"type":"node","schema":"sn", table":"university","id":0}   9       2010
-- {"type":"edge","schema":"sn","table":"graduatedfrom","id":1}   {"type":"node","schema":"sn","table":"alumnus","id":1}   {"type":"node","schema":"sn","table":"university","id":0}   8       2010
-- {"type":"edge","schema":"sn","table":"graduatedfrom","id":2}   {"type":"node","schema":"sn","table":"alumnus","id":2}   {"type":"node","schema":"sn","table":"university","id":1}   8       2011

Структура таблиці у випадку, коли немає додаткових властивостей.

table_name   column_name                                    graph_type_desc
----------   --------------------------------------------   ---------------
friendof     graph_id_2DD2FDDB742A43419F03B53351B0EEA1      GRAPH_ID
friendof     $edge_id_BA3A385B57654E1B845ACD1BFCB3DD1D      GRAPH_ID_COMPUTED
friendof     from_obj_id_E0CB7FFB93124606AEFED8296507FA47   GRAPH_FROM_OBJ_ID
friendof     from_id_38010D8B5F854BD5BAF35B1DBC451878       GRAPH_FROM_ID
friendof     $from_id_719A9DBB01774C16BCB1C55E665AAADF      GRAPH_FROM_ID_COMPUTED
friendof     to_obj_id_4D67821CE5D349638B6DC911A0C68894     GRAPH_TO_OBJ_ID
friendof     to_id_F48CDB2969B24A8FA0522BEECFDEA073         GRAPH_TO_ID
friendof     $to_id_73917B0BD5F84EB2B810BE72714B4731        GRAPH_TO_ID_COMPUTED

Зразок даних.

-- $edge_id_BA3A385B57654E1B845ACD1BFCB3DD1D                 $from_id_719A9DBB01774C16BCB1C55E665AAADF                $to_id_73917B0BD5F84EB2B810BE72714B4731
-- -------------------------------------------------------   ------------------------------------------------------   ------------------------------------------------------
-- {"type":"edge","schema":"sn","table":"friendof","id":0}   {"type":"node","schema":"sn","table":"alumnus","id":0}   {"type":"node","schema":"sn","table":"alumnus","id":1}
-- {"type":"edge","schema":"sn","table":"friendof","id":1}   {"type":"node","schema":"sn","table":"alumnus","id":1}   {"type":"node","schema":"sn","table":"alumnus","id":2}
-- {"type":"edge","schema":"sn","table":"friendof","id":2}   {"type":"node","schema":"sn","table":"alumnus","id":2}   {"type":"node","schema":"sn","table":"alumnus","id":0}

Для таблиць ребер є можливість створювати додаткові обмеження – аналог зовнішніх ключів.

ALTER TABLE sn.graduatedfrom ADD CONSTRAINT ec_graduatedfrom CONNECTION (sn.alumnus TO sn.university);
ALTER TABLE sn.friendof ADD CONSTRAINT ec_friendof CONNECTION (sn.alumnus TO sn.alumnus);

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

INSERT
  INTO sn.friendof
     ( $from_id
     , $to_id
     )
VALUES
     ( (SELECT $node_id FROM sn.alumnus WHERE id = 2)
     , (SELECT $node_id FROM sn.alumnus WHERE id = 3)
     )
;

Обмеження на таблицях вершин не дозволять вставити туди невалідні дані. Спробуєм виконати такий запит:

INSERT INTO sn.friendof ($from_id, $to_id) VALUES ('bla-bla','bla-bla-bla');

Результатом буде помилка

Msg 515, Level 16, State 2, Line 26
Cannot insert the value NULL into column 'from_obj_id_E0CB7FFB93124606AEFED8296507FA47', table 'GraphDB_demo.sn.friendof'; column does not allow nulls. INSERT fails.
The statement has been terminated.

Таблички створені, дані залиті, можна пробувати писати запити. Починаєм з найпростіших.

Запит 1: Знайти Університет, який закінчила Vera:

SELECT university.name
  FROM sn.alumnus       AS alumnus
     , sn.graduatedfrom AS graduatedfrom
     , sn.university    AS university
 WHERE MATCH (alumnus - (graduatedfrom) -> university)
   AND alumnus.name = 'Vera'
;
-- name
-- University of Bologna
Що бачимо – в FROM є список вершин і ребер, котрі їх з’єднують, в WHERE – умова фільтрування і шлях, по якому будемо проходити по графу - MATCH. Тут спосіб думання має перемкнутися від оперування множинами до траверсу (переходу між вершинами) графа. Зазвичай в запитах такого типу є задана одна або кілька вершин, ребра і напрямок проходження.

Запит 2: знайти університети які закінчили друзі Donatello:

SELECT university.name  AS university_name
     , alumnus2.name    AS friends_name
  FROM sn.alumnus       alumnus1
     , sn.alumnus       alumnus2
     , sn.graduatedfrom AS graduatedfrom
     , sn.friendof      AS friendof
     , sn.university    AS university
WHERE MATCH( alumnus1 - (friendof) -> alumnus2 - (graduatedfrom) -> university)
  AND alumnus1.name = 'Donatello'
;
-- university_name       friends_name
-- ---------------       ------------
-- University of Milan   Emilia
-- University of Padua   Massimo

Якщо нам потрібно знайти університети котрі закінчили друзі друзів Donatello, то ми додаєм ще одну вершину і ще одне ребро (послідовність таблиць в FROM не має значення). І відкоригувати шлях (опція MATCH)

SELECT university.name    AS university_name
       , alumnus3.name    AS friends_name
  FROM sn.alumnus         alumnus1
     , sn.alumnus         alumnus2
     , sn.alumnus         alumnus3
     , sn.graduatedfrom   AS graduatedfrom
     , sn.friendof        AS friendof
     , sn.friendof        AS friendof1
     , sn.university      AS university
WHERE MATCH( alumnus1 - (friendof) -> alumnus2 -  (friendof1) -> alumnus3 - (graduatedfrom) -> university)
  AND alumnus1.name = 'Donatello'
;
-- university_name       friends_name
-- ---------------       ------------
-- University of Naples  Gian
-- University of Padua   Carina

Тепер спробуєм додати декілька шляхів переходів в опцію MATCH.

Запит 3: знайти всіх випускників, котрі вчилися в тому самому місті де живуть.

SELECT alumnus.name       AS person
     , city.name          AS city_name
  FROM sn.alumnus         alumnus
     , sn.graduatedfrom   AS graduatedfrom
     , sn.university      AS university
     , sn.livesin         AS livesin
     , sn.locatedin       AS locatedin
     , sn.city            AS city
 WHERE MATCH ( alumnus - (graduatedfrom) -> university - (locatedin) -> city
               AND alumnus - (livesin) -> city
             )
;
-- person       city_name
-- ------       ---------
-- Vera         Bologna
-- Sistine      Perugia
-- Aleksandra   Perugia

Тут ми шукаємо пари вершин персона – місто використовуючи перехід по різних шляхах. Перший : персона –> живе в -> місто; другий : персона -> закінчила –> університет –> розташований -> місто. І вибираємо ті пари, які співпадають.

Виконаємо два запити, що шукають друзів 3 і 5 кола для Donatello. З цих запитів видно, що FROM працює аналогічно INNER JOIN-у. Друзі третього кола не потраплять у список друзів 5 кола. А список друзів 6 кола поверне порожній результат (через те, що чиїхось друзів не внесли в табличку).

Запит 4 і 5: знайти всіх друзів 3 і 5 кола для Donatello

SELECT alumnus1.name  AS name1
     , alumnus2.name  AS name2
     , alumnus3.name  AS name3
     , alumnus4.name  AS name4
  FROM sn.alumnus     alumnus1
     , sn.friendof    fo
     , sn.friendof    fo2
     , sn.friendof    fo3
     , sn.alumnus     alumnus2
     , sn.alumnus     alumnus3
     , sn.alumnus     alumnus4
 WHERE MATCH (alumnus1-(fo)->alumnus2-(fo2)-> alumnus3-(fo3) -> alumnus4)
   AND alumnus1.name = 'Donatello'
;
-- name1       name2     name3    name4
-- -----       -----     -----    -----
-- Donatello   Emilia    Gian     Stefano
-- Donatello   Massimo   Carina   Sistine
-- Donatello   Massimo   Carina   Stefano

SELECT alumnus1.name  AS name1
     , alumnus2.name  AS name2
     , alumnus3.name  AS name3
     , alumnus4.name  AS name4
     , alumnus5.name  AS name5
     , alumnus6.name  AS name6
  FROM sn.alumnus     alumnus1
     , sn.friendof    fo
     , sn.friendof    fo2
     , sn.friendof    fo3
     , sn.friendof    fo4
     , sn.friendof    fo5
     , sn.alumnus     alumnus2
     , sn.alumnus     alumnus3
     , sn.alumnus     alumnus4
     , sn.alumnus     alumnus5
     , sn.alumnus     alumnus6
 WHERE MATCH (alumnus1-(fo)->alumnus2-(fo2)-> alumnus3-(fo3) -> alumnus4 -(fo4) -> alumnus5 -(fo5) -> alumnus6)
   AND alumnus1.name = 'Donatello'
;
-- name1       name2     name3    name4     name5        name6
-- -----       -----     -----    -----     -----        -----
-- Donatello   Massimo   Carina   Sistine   Aleksandra   Stefano

Ще раз щодо наступного кола друзів – в якийсь момент ми отримаєм зациклення. Vera дружить з Patrizio, котрий дружить з Sergio, а той дружить з Vera. Коло замикається і у цьому випадку ми отримаєм друзів будь-якого кола (до запиту можна докидати нові пари вершина – ребро і він буде повертати результат).

Запит 6: приклад зациклення

SELECT alumnus1.name  as name1
     , alumnus2.name  as name2
     , alumnus3.name  as name3
     , alumnus4.name  as name4
     , alumnus5.name  as name5
     , alumnus6.name  as name6
     , alumnus7.name  as name7
  FROM sn.alumnus     alumnus1
     , sn.friendof    fo
     , sn.friendof    fo2
     , sn.friendof    fo3
     , sn.friendof    fo4
     , sn.friendof    fo5
     , sn.friendof    fo6
     , sn.alumnus     alumnus2
     , sn.alumnus     alumnus3
     , sn.alumnus     alumnus4
     , sn.alumnus     alumnus5
     , sn.alumnus     alumnus6
     , sn.alumnus     alumnus7
 WHERE MATCH (alumnus1-(fo)->alumnus2-(fo2)-> alumnus3-(fo3) -> alumnus4 -(fo4) -> alumnus5 -(fo5) -> alumnus6 -(fo6) -> alumnus7)
   AND alumnus1.name IN('Vera', 'Patrizio', 'Sergio')
;
-- name1      name2      name3      name4      name5      name6      name7
-- -----      -----      -----      -----      -----      -----      -----
-- Patrizio   Sergio     Vera       Patrizio   Sergio     Vera       Patrizio
-- Sergio     Vera       Patrizio   Sergio     Vera       Patrizio   Sergio
-- Vera       Patrizio   Sergio     Vera       Patrizio   Sergio     Vera

І останній тип запитів – знайти найкоротший шлях між двома вершинами. Для прикладу шукаємо найкоротший шлях між Donatello i Stefano. З попередніх запитів ми бачимо що Stefano є як в 3 колі друзів Donatello, так і в 5. Шукаєм найкоротший шлях.

Запит 7: знайти найкоротший шлях між Donatello i Stefano.

SELECT q.personname
     , q.friends
     , q.levels
  FROM
     (
       SELECT alumnus1.name                                              AS personname
            , STRING_AGG(alumnus2.name, '->') WITHIN GROUP (GRAPH PATH)  AS friends
            , LAST_VALUE(alumnus2.name)       WITHIN GROUP (GRAPH PATH)  AS lastnode
            , COUNT(alumnus2.name)            WITHIN GROUP (GRAPH PATH)  AS levels
         FROM sn.alumnus                   alumnus1
            , sn.friendof  FOR PATH        fo
            , sn.alumnus   FOR PATH        alumnus2
         WHERE MATCH(SHORTEST_PATH(alumnus1(-(fo)->alumnus2)+))
           AND alumnus1.name = 'Donatello'
     )                                 q
 WHERE q.lastnode                      = 'Stefano'
;
-- personname   friends                 levels
-- ----------   -------                 ------
-- Donatello    Emilia->Gian->Stefano   3

Результат збігається з очікуванням. В запиті ми бачимо підтримку агрегатних функцій: опції GRAPH PATH/FOR PATH і функцію SHORTEST_PATH, котра використовується тільки всередині MATCH. «+» показує, чи ми проходимо весь граф, чи зупиняємся на якомусь рівні. Внутрішній запит знаходить найкоротший шлях від вказаної вершини до всіх решта. Зовнішній вже відфільтровує результат. З прикладами все.

Під капотом використовується той самий движок баз даних без додаткових компонент в плані запиту. Компанія Microsoft відома тим, що покращення на рівні движка бази даних вносяться в наступних версіях після виходу нового функціоналу. Так як було з віконними функціями і пакетною обробкою записів для даних, що зберігаються по рядках (batch processing for row store). Існують певні обмеження для нових типів таблиць – вони не можуть бути тимчасовими, темпоральними таблицями, табличними змінними чи типами. Неможливо в одному запиті звертатися до графових об’єктів в різних базах даних (виконувати cross-database query). Плюсом для роботи є те, що використовується той самий SQL. Підхід компанії до розвитку свого флагманського продукту – інтеграція під одним дахом різних структур даних і підходів до обробки цих даних. Такий аналог кухонного комбайну. З таким підходом і SQL збагачується новими можливостями, які з часом з категорії розширень можуть перейти в стандарт.

Додаток

SQL Script для створення таблиць і заливки даних. База graphdb_demo повинна бути створена пеерд запуском скріпта. Викноувати на 2019 версії MS SQL Server (моя робоча версія - Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) Sep 24 2019 13:48:23 Copyright (C) 2019 Microsoft Corporation Developer Edition (64-bit) on Windows 10 Enterprise 10.0 (Build 17763: ))

USE [graphdb_demo]
GO

if not exists (select * from sys.schemas where name = 'sn')
   execute('create schema sn;');
go

-- Create NODE tables
CREATE TABLE sn.alumnus
( id         INTEGER PRIMARY KEY
, name       VARCHAR(100)
)
AS NODE;

CREATE TABLE sn.university
( id         INTEGER PRIMARY KEY
, name       VARCHAR(100) UNIQUE
)
AS NODE;

CREATE TABLE sn.city
( id         INTEGER PRIMARY KEY
, name       VARCHAR(100)
, country    VARCHAR(100)
)
AS NODE;

-- Create EDGE tables.
CREATE TABLE sn.graduatedfrom
( rating        INTEGER
, graduatedyear INTEGER
)
AS EDGE;

ALTER TABLE sn.graduatedfrom ADD CONSTRAINT ec_graduatedfrom
 CONNECTION (sn.alumnus TO sn.university);

CREATE TABLE sn.friendof AS EDGE;
 ALTER TABLE sn.friendof ADD CONSTRAINT ec_friendof
 CONNECTION (sn.alumnus TO sn.alumnus);

CREATE TABLE sn.livesin AS EDGE;
 ALTER TABLE sn.livesin ADD CONSTRAINT ec_livesin
 CONNECTION (sn.alumnus TO sn.city);

CREATE TABLE sn.locatedin AS EDGE;
 ALTER TABLE sn.locatedin ADD CONSTRAINT ec_locatedin
 CONNECTION (sn.university TO sn.city);
GO


INSERT INTO sn.alumnus
(id, name)
VALUES
 (1,'Vera')
,(2,'Patrizio' )
,(3,'Sergio')
,(4,'Caprice')
,(5,'Bellissa')
,(6,'Donatello')
,(7,'Emilia')
,(8,'Gian')
,(9,'Stefano')
,(10,'Massimo')
,(11,'Carina')
,(12,'Sistine')
,(13,'Aleksandra')
;
GO

INSERT INTO sn.university
(id, name)
VALUES
 (1,'University of Bologna')
,(2,'University of Florence')
,(3,'University of Milan')
,(4,'University of Naples')
,(5,'University of Padua')
,(6,'University of Perugia')
GO

INSERT INTO sn.city
(id, name, country)
VALUES
 (1,'Bologna','Italy')
,(2,'Florence','Italy')
,(3,'Milan','Italy')
,(4,'Naples','Italy')
,(5,'Padua','Italy')
,(6,'Perugia','Italy')
,(7,'Rome','Italy')
,(8,'Salerno','Italy')
,(9,'Siena','Italy')
,(10,'Turin','Italy')
,(11,'Catania','Italy')
,(12,'Modena','Italy')
,(13,'Cagliari','Italy')
GO

-- Insert data into the friendof edge.
INSERT INTO sn.friendof
VALUES
( (SELECT $node_id FROM sn.alumnus WHERE id = 1)
, (SELECT $node_id FROM sn.alumnus WHERE id = 2)
),
( (SELECT $node_id FROM sn.alumnus WHERE id = 2)
, (SELECT $node_id FROM sn.alumnus WHERE id = 3)
),
( (SELECT $node_id FROM sn.alumnus WHERE id = 3)
, (SELECT $node_id FROM sn.alumnus WHERE id = 1)
),
( (SELECT $node_id FROM sn.alumnus WHERE id = 4)
, (SELECT $node_id FROM sn.alumnus WHERE id = 2)
),
( (SELECT $node_id FROM sn.alumnus WHERE id = 5)
, (SELECT $node_id FROM sn.alumnus WHERE id = 4)
),
( (SELECT $node_id FROM sn.alumnus WHERE id = 6)
, (SELECT $node_id FROM sn.alumnus WHERE id = 7)
),
( (SELECT $node_id FROM sn.alumnus WHERE id = 7)
, (SELECT $node_id FROM sn.alumnus WHERE id = 8)
),
( (SELECT $node_id FROM sn.alumnus WHERE id = 8)
, (SELECT $node_id FROM sn.alumnus WHERE id = 9)
),
( (SELECT $node_id FROM sn.alumnus WHERE id = 6)
, (SELECT $node_id FROM sn.alumnus WHERE id = 10)
),
( (SELECT $node_id FROM sn.alumnus WHERE id = 10)
, (SELECT $node_id FROM sn.alumnus WHERE id = 11)
),
( (SELECT $node_id FROM sn.alumnus WHERE id = 11)
, (SELECT $node_id FROM sn.alumnus WHERE id = 12)
),
( (SELECT $node_id FROM sn.alumnus WHERE id = 12)
, (SELECT $node_id FROM sn.alumnus WHERE id = 13)
 ),
( (SELECT $node_id FROM sn.alumnus WHERE id = 13)
, (SELECT $node_id FROM sn.alumnus WHERE id = 9)
),
( (SELECT $node_id FROM sn.alumnus WHERE id = 11)
, (SELECT $node_id FROM sn.alumnus WHERE id = 9)
);
GO

INSERT INTO sn.graduatedfrom
($from_id, $to_id, rating, graduatedyear )
VALUES
( (SELECT $node_id FROM sn.alumnus    WHERE id = 1)
, (SELECT $node_id FROM sn.university WHERE id = 1)
, 9
, 2010
),
( (SELECT $node_id FROM sn.alumnus    WHERE id = 2)
, (SELECT $node_id FROM sn.university WHERE id = 1)
, 8
, 2010
),
( (SELECT $node_id FROM sn.alumnus    WHERE id = 3)
, (SELECT $node_id FROM sn.university WHERE id = 2)
, 8
, 2011
),
( (SELECT $node_id FROM sn.alumnus    WHERE id = 4)
, (SELECT $node_id FROM sn.university WHERE id = 2)
, 8
, 2009
),
( (SELECT $node_id FROM sn.alumnus    WHERE id = 5)
, (SELECT $node_id FROM sn.university WHERE id = 3)
, 8
, 2006
),
( (SELECT $node_id FROM sn.alumnus    WHERE id = 6)
, (SELECT $node_id FROM sn.university WHERE id = 3)
, 7
, 2009
),
( (SELECT $node_id FROM sn.alumnus    WHERE id = 7)
, (SELECT $node_id FROM sn.university WHERE id = 3)
, 8
, 2013
),
( (SELECT $node_id FROM sn.alumnus    WHERE id = 8)
, (SELECT $node_id FROM sn.university WHERE id = 4)
, 9
, 2012
),
( (SELECT $node_id FROM sn.alumnus    WHERE id = 9)
, (SELECT $node_id FROM sn.university WHERE id = 5)
, 8
, 2005
),
( (SELECT $node_id FROM sn.alumnus    WHERE id = 10)
, (SELECT $node_id FROM sn.university WHERE id = 5)
, 7
, 2014
),
( (SELECT $node_id FROM sn.alumnus    WHERE id = 11)
, (SELECT $node_id FROM sn.university WHERE id = 5)
, 8
, 2003
),
( (SELECT $node_id FROM sn.alumnus    WHERE id = 12)
, (SELECT $node_id FROM sn.university WHERE id = 6)
, 8
, 2003
),
( (SELECT $node_id FROM sn.alumnus    WHERE id = 13)
, (SELECT $node_id FROM sn.university WHERE id = 6)
, 7
, 2018
);
GO

INSERT INTO sn.locatedin
($from_id, $to_id)
VALUES
((SELECT $node_id FROM sn.university WHERE id = 1)
,(SELECT $node_id FROM sn.city WHERE id = 1)
),
((SELECT $node_id FROM sn.university WHERE id = 2)
,(SELECT $node_id FROM sn.city WHERE id = 2)
),
((SELECT $node_id FROM sn.university WHERE id = 3)
,(SELECT $node_id FROM sn.city WHERE id = 3)
),
((SELECT $node_id FROM sn.university WHERE id = 4)
,(SELECT $node_id FROM sn.city WHERE id = 4)
),
((SELECT $node_id FROM sn.university WHERE id = 5)
,(SELECT $node_id FROM sn.city WHERE id = 5)
),
((SELECT $node_id FROM sn.university WHERE id = 6)
,(SELECT $node_id FROM sn.city WHERE id = 6)
);
GO

INSERT INTO sn.livesin
($from_id, $to_id)
VALUES
((SELECT $node_id FROM sn.alumnus WHERE id = 1)
,(SELECT $node_id FROM sn.city WHERE id = 1)
),
((SELECT $node_id FROM sn.alumnus WHERE id = 2)
,(SELECT $node_id FROM sn.city WHERE id = 2)
),
((SELECT $node_id FROM sn.alumnus WHERE id = 3)
,(SELECT $node_id FROM sn.city WHERE id = 3)
),
((SELECT $node_id FROM sn.alumnus WHERE id = 4)
,(SELECT $node_id FROM sn.city WHERE id = 4)
),
((SELECT $node_id FROM sn.alumnus WHERE id = 5)
,(SELECT $node_id FROM sn.city WHERE id = 5)
),
((SELECT $node_id FROM sn.alumnus WHERE id = 6)
,(SELECT $node_id FROM sn.city WHERE id = 6)
),
((SELECT $node_id FROM sn.alumnus WHERE id = 7)
,(SELECT $node_id FROM sn.city WHERE id = 7)
),
((SELECT $node_id FROM sn.alumnus WHERE id = 8)
,(SELECT $node_id FROM sn.city WHERE id = 8)
),
((SELECT $node_id FROM sn.alumnus WHERE id = 9)
,(SELECT $node_id FROM sn.city WHERE id = 9)
),
((SELECT $node_id FROM sn.alumnus WHERE id = 10)
,(SELECT $node_id FROM sn.city WHERE id = 10)
),
((SELECT $node_id FROM sn.alumnus WHERE id = 13)
,(SELECT $node_id FROM sn.city WHERE id = 6)
),
((SELECT $node_id FROM sn.alumnus WHERE id = 11)
,(SELECT $node_id FROM sn.city WHERE id = 11)
),
((SELECT $node_id FROM sn.alumnus WHERE id = 12)
,(SELECT $node_id FROM sn.city WHERE id = 6)
);
GO

Comments