Графовые базы данных SQL Server
Кардинальным отличием графовых баз данных в SQL Server от графовых баз данных направления NoSQL является то, что для моделирования графа в SQL Server используются таблицы. Это таблицы двух специальных видов. Один вид таблиц используется для создания узлов, и другой – для создания ребер (связей). Отметим, что табличное представление узлов и ребер дает возможность писать запросы к этим таблицам на языке SQL.
Таблица типа узла описывает некую сущность. Экземпляры этой сущности представлены строками таблицы и характеризуются одинаковым набором свойств (столбцами таблицы). Таблица типа ребра определяет характер и направление связи между узлами. Скажем, таблица ToBeFriends (дружить) могла бы описывать дружественную связь между экземплярами одной или разных сущностей.
Вероятно, пора переходить к примерам. Предлагаю взять учебную базу «Окраска», и отобразить реляционную структуру этой базы на структуру графа без потери информации. Это позволит нам писать запросы и сравнивать результаты на двух моделях, представляющих в разной форме одну и ту же информацию.
Итак, у нас есть два типа узлов, представляющих сущности квадрата и баллона, и одна связь между ними, которую можно выразить так: баллон окрашивает квадрат. Направление связи – от баллона к квадрату.
Создадим таблицы узлов:
--Квадраты
CREATE TABLE utqG (
q_id INT PRIMARY KEY,
q_name VARCHAR(35) NOT NULL,
) AS NODE;
--Баллончики
CREATE TABLE utvG (
v_id INT PRIMARY KEY,
v_name VARCHAR(35) NOT NULL,
v_color char(1) not null
) AS NODE;
Как видите, таблицы создаются аналогично обычным реляционным за исключением указания типа – AS NODE (т.е. узел). Теперь посмотрим на структуру созданных таблиц:
select table_name, column_name, data_type
from information_schema.columns
where table_name='utqG';
table_name | column_name | data_type |
---|---|---|
utqG | graph_id_AA21DCF7CB44457BB308B21482806B87 | bigint |
utqG | $node_id_EEFDE0FB86F243E4A6667A5CE470F4F6 | nvarchar |
utqG | q_id | int |
utqG | q_name | varchar |
При создании таблицы узлов, помимо пользовательских, автоматически создаются еще два псевдостолбца – graph_id и $node_id. Давайте наполним таблицу данными и посмотрим, что находится в этих столбцах.
insert into utqG(q_id, q_name)
select * from [sql-ex]..utq;
Здесь мы просто берем готовые данные, которые находятся в учебной базе данных с именем sql-ex.
select top 2 * from utqG;
$node_id_EEFDE0FB86F243E4A6667A5CE470F4F6 | q_id | q_name |
---|---|---|
{“type”:“node”,“schema”:“dbo”,“table”:“utqG”,“id”:0} | 1 | Square # 01 |
{“type”:“node”,“schema”:“dbo”,“table”:“utqG”,“id”:1} | 2 | Square # 02 |
Столбец graph_id отсутствует в выборке. Причиной является то, что этот столбец используется ядром СУБД и недоступен пользователю напрямую. Действительно, если выполнить запрос
select graph_id from utqG;
то мы получим сообщение об ошибке:
Столбец $node_id является уникальным идентификатором узла, представленным в формате JSON. Шестнадцатеричный суффикс в имени столбца делает имя столбца глобально уникальным, однако для доступа к столбцу он не используется. Например,
select top 2 $node_id, q_id from utqG;
$node_id_EEFDE0FB86F243E4A6667A5CE470F4F6 | q_id |
---|---|
{“type”:“node”,“schema”:“dbo”,“table”:“utqG”,“id”:0} | 1 |
{“type”:“node”,“schema”:“dbo”,“table”:“utqG”,“id”:1} | 2 |
Таблица utvG выглядит аналогично таблице utqG, и поэтому мы не будем представлять тут её содержимое.
Теперь создадим таблицу ребра.
CREATE TABLE utbG (
b_datetime datetime not null,
b_vol tinyint NOT NULL,
) AS EDGE;
Эта таблица содержит столбцы свойств – время окраски (B_datetime) и количество нанесенной из баллончика краски (b_vol). Она отличается от таблиц узлов типом – теперь это EDGE, а не NODE. Рассмотрим структуру этой таблицы:
select column_name, data_type
from information_schema.columns where table_name='utbG';
column_name | data_type |
---|---|
graph_id_9D6121CFD14948A5B03BBD6A4BDB4774 | bigint |
$edge_id_EAB5B85BC07649ED89435D6F2A2ACE83 | nvarchar |
from_obj_id_D0D5B23329B5409895672D5C32283E51 | int |
from_id_526FECFDEBB84E86B2F7440FF625BCF1 | bigint |
$from_id_BF1E69FB306E4225A58F4DAFAA8FBE94 | nvarchar |
to_obj_id_14C309807A224F7BA7BD8F88425A15AB | int |
to_id_295F2F2BD42A425CAAD6ACC9F2174BC7 | bigint |
$to_id_79504454FC184B7F849B1AB28BCCC670 | nvarchar |
b_datetime | datetime |
b_vol | tinyint |
Как и для узлов, автоматически были созданы несколько псевдостолбцов, среди которых доступными пользователю являются:
- $edge_id – идентификатор ребра, формируется автоматически;
- $from_id – идентификатор узла, откуда исходит ребро;
- $to_id – идентификатор узла, куда входит ребро.
Заполним и эту таблицу данными. Если мы вспомним структуру исходной таблицы utb, то для каждой строки в столбец $from_id мы должны поместить идентификатор того узла из таблицы utvG, для которого v_id равен b_v_id таблицы utb. Тогда столбец $to_id должен содержать идентификатор того узла из таблицы utqG, для которого q_id равен, соответственно, b_q_id из той же строки таблицы utb. Читателю, наверное, будет проще понять оператор, который вставит описанные данные:
insert into utbG(b_datetime, b_vol, $from_id, $to_id)
select b_datetime, b_vol,
(select $node_id from utvG where v_id = orig.b_v_id),
(select $node_id from utqG where q_id = orig.b_q_id)
from [sql-ex]..utb orig
Теперь мы можем посмотреть на данные в таблице ребра, которая устанавливает связь между двумя узлами – баллончиком с краской и окрашиваемым квадратом.
select top 1 * from utbG;
Поскольку ширина результирующей таблицы значительно превышает ширину страницы, представим результат в формате «ключ: значение»
$edge_id_EAB5B85BC07649ED89435D6F2A2ACE83: {“type”:“edge”,“schema”:“dbo”,“table”:“utbG”,“id”:0} |
$from_id_BF1E69FB306E4225A58F4DAFAA8FBE94: {“type”:“node”,“schema”:“dbo”,“table”:“utvG”,“id”:49} |
$to_id_79504454FC184B7F849B1AB28BCCC670: {“type”:“node”,“schema”:“dbo”,“table”:“utqG”,“id”:21} |
b_datetime: “2000-01-01 01:13:36.000” |
b_vol: 50 |