DBMS функсионалӣ

Дар ҷаҳони пойгоҳи додаҳо муддати тӯлонӣ DBMS-ҳои релятсионӣ, ки забони SQL-ро истифода мебаранд, бартарӣ доранд. То ҳадде, ки вариантҳои пайдошаванда NoSQL номида мешаванд. Ба онҳо муяссар шуд, ки дар ин бозор барои худ ҷои муайяне ҷудо кунанд, аммо DBMS-ҳои релятсионӣ намемиранд ва барои мақсадҳои худ фаъолона истифода мешаванд.

Дар ин мақола ман мехоҳам консепсияи базаи функсионалии маълумотро тавсиф кунам. Барои фаҳмиши беҳтар, ман инро бо муқоисаи он бо модели реляционии классикӣ иҷро мекунам. Мушкилот аз санҷишҳои гуногуни SQL, ки дар Интернет пайдо шудаанд, ҳамчун мисол истифода мешаванд.

Муқаддима

Пойгоҳи додаҳои релятсионӣ дар ҷадвалҳо ва майдонҳо амал мекунанд. Дар пойгоҳи додаҳои функсионалӣ мутаносибан ба ҷои синфҳо ва функсияҳо истифода мешаванд. Майдон дар ҷадвал бо N калидҳо ҳамчун функсияи N параметрҳо муаррифӣ карда мешавад. Ба ҷои муносибатҳои байни ҷадвалҳо, функсияҳое истифода мешаванд, ки объектҳои синферо, ки ба онҳо пайваст карда шудааст, бармегардонанд. Ба ҷои JOIN таркиби функсия истифода мешавад.

Пеш аз он ки бевосита ба вазифаҳо гузаред, ман вазифаи мантиқи доменро тавсиф мекунам. Барои DDL ман синтаксиси PostgreSQL-ро истифода хоҳам кард. Барои функсионалӣ он синтаксиси худро дорад.

Ҷадвалҳо ва майдонҳо

Объекти оддии Sku бо майдонҳои ном ва нарх:

Муносибатӣ

CREATE TABLE Sku
(
    id bigint NOT NULL,
    name character varying(100),
    price numeric(10,5),
    CONSTRAINT id_pkey PRIMARY KEY (id)
)

Функсионалӣ

CLASS Sku;
name = DATA STRING[100] (Sku);
price = DATA NUMERIC[10,5] (Sku);

Мо ду нафарро эълон мекунем функсияҳои, ки як параметри Sku-ро ҳамчун вуруд мегиранд ва навъи ибтидоиро бармегардонанд.

Тахмин меравад, ки дар МБМ-и функсионалӣ ҳар як объект дорои коди дохилие мебошад, ки ба таври худкор тавлид мешавад ва дар ҳолати зарурӣ дастрас шудан мумкин аст.

Биёед нархи маҳсулот/мағоза/таъминкунандаро муқаррар кунем. Он метавонад бо мурури замон тағир ёбад, биёед майдони вақтро ба ҷадвал илова кунем. Ман эълон кардани ҷадвалҳоро барои директорияҳо дар пойгоҳи додаҳои релятсионӣ мегузарам, то кодро кӯтоҳ кунам:

Муносибатӣ

CREATE TABLE prices
(
    skuId bigint NOT NULL,
    storeId bigint NOT NULL,
    supplierId bigint NOT NULL,
    dateTime timestamp without time zone,
    price numeric(10,5),
    CONSTRAINT prices_pkey PRIMARY KEY (skuId, storeId, supplierId)
)

Функсионалӣ

CLASS Sku;
CLASS Store;
CLASS Supplier;
dateTime = DATA DATETIME (Sku, Store, Supplier);
price = DATA NUMERIC[10,5] (Sku, Store, Supplier);

Нишондиҳандаҳо

Барои мисоли охирин, мо дар ҳама калидҳо ва сана индекс месозем, то ки мо нархро барои вақти муайян зуд пайдо кунем.

Муносибатӣ

CREATE INDEX prices_date
    ON prices
    (skuId, storeId, supplierId, dateTime)

Функсионалӣ

INDEX Sku sk, Store st, Supplier sp, dateTime(sk, st, sp);

вазифаҳо

Биёед бо масъалаҳои нисбатан соддаи аз мувофиқ гирифташуда оғоз кунем мақолаҳо дар Хабр.

Аввалан, биёед мантиқи доменро эълон кунем (барои пойгоҳи додаҳои релятсионӣ ин бевосита дар мақолаи боло анҷом дода мешавад).

CLASS Department;
name = DATA STRING[100] (Department);

CLASS Employee;
department = DATA Department (Employee);
chief = DATA Employee (Employee);
name = DATA STRING[100] (Employee);
salary = DATA NUMERIC[14,2] (Employee);

Вазифаи 1.1

Рӯйхати кормандонеро, ки аз маоши роҳбари бевоситаи худ зиёдтар мегиранд, нишон диҳед.

Муносибатӣ

select a.*
from   employee a, employee b
where  b.id = a.chief_id
and    a.salary > b.salary

Функсионалӣ

SELECT name(Employee a) WHERE salary(a) > salary(chief(a));

Вазифаи 1.2

Кормандонеро номбар кунед, ки дар шӯъбаи худ музди ҳадди аксар мегиранд

Муносибатӣ

select a.*
from   employee a
where  a.salary = ( select max(salary) from employee b
                    where  b.department_id = a.department_id )

Функсионалӣ

maxSalary 'Максимальная зарплата' (Department s) = 
    GROUP MAX salary(Employee e) IF department(e) = s;
SELECT name(Employee a) WHERE salary(a) = maxSalary(department(a));

// или если "заинлайнить"
SELECT name(Employee a) WHERE 
    salary(a) = maxSalary(GROUP MAX salary(Employee e) IF department(e) = department(a));

Ҳарду татбиқ баробаранд. Дар ҳолати аввал, дар пойгоҳи додаҳои релятсионӣ шумо метавонед CREATE VIEW-ро истифода баред, ки ба ҳамин тариқ аввал музди ҳадди аксарро барои як шӯъбаи мушаххас дар он ҳисоб мекунад. Дар оянда, барои равшанӣ, ман ҳолати аввалро истифода хоҳам кард, зеро он ҳалли беҳтарро инъикос мекунад.

Вазифаи 1.3

Рӯйхати ID-ҳои шӯъбаро нишон диҳед, ки шумораи кормандон аз 3 нафар зиёд нест.

Муносибатӣ

select department_id
from   employee
group  by department_id
having count(*) <= 3

Функсионалӣ

countEmployees 'Количество сотрудников' (Department d) = 
    GROUP SUM 1 IF department(Employee e) = d;
SELECT Department d WHERE countEmployees(d) <= 3;

Вазифаи 1.4

Рӯйхати кормандонеро нишон диҳед, ки дар як шӯъба мудири таъиншуда надоранд.

Муносибатӣ

select a.*
from   employee a
left   join employee b on (b.id = a.chief_id and b.department_id = a.department_id)
where  b.id is null

Функсионалӣ

SELECT name(Employee a) WHERE NOT (department(chief(a)) = department(a));

Вазифаи 1.5

Рӯйхати ID-ҳои шӯъбаро бо ҳадди ниҳоии музди меҳнати кормандон пайдо кунед.

Муносибатӣ

with sum_salary as
  ( select department_id, sum(salary) salary
    from   employee
    group  by department_id )
select department_id
from   sum_salary a       
where  a.salary = ( select max(salary) from sum_salary )

Функсионалӣ

salarySum 'Максимальная зарплата' (Department d) = 
    GROUP SUM salary(Employee e) IF department(e) = d;
maxSalarySum 'Максимальная зарплата отделов' () = 
    GROUP MAX salarySum(Department d);
SELECT Department d WHERE salarySum(d) = maxSalarySum();

Биёед, ба вазифахои мураккабтар аз дигараш мегузарем мақолаҳо. Он дорои таҳлили муфассали чӣ гуна амалӣ кардани ин вазифа дар MS SQL мебошад.

Вазифаи 2.1

Соли 1997 кадом фурӯшандагон зиёда аз 30 адад маҳсулоти рақами 1 фурӯхтаанд?

Мантиқи домен (мисли қаблӣ дар RDBMS мо эъломияро мегузарем):

CLASS Employee 'Продавец';
lastName 'Фамилия' = DATA STRING[100] (Employee);

CLASS Product 'Продукт';
id = DATA INTEGER (Product);
name = DATA STRING[100] (Product);

CLASS Order 'Заказ';
date = DATA DATE (Order);
employee = DATA Employee (Order);

CLASS Detail 'Строка заказа';

order = DATA Order (Detail);
product = DATA Product (Detail);
quantity = DATA NUMERIC[10,5] (Detail);

Муносибатӣ

select LastName
from Employees as e
where (
  select sum(od.Quantity)
  from [Order Details] as od
  where od.ProductID = 1 and od.OrderID in (
    select o.OrderID
    from Orders as o
    where year(o.OrderDate) = 1997 and e.EmployeeID = o.EmployeeID)
) > 30

Функсионалӣ

sold (Employee e, INTEGER productId, INTEGER year) = 
    GROUP SUM quantity(OrderDetail d) IF 
        employee(order(d)) = e AND 
        id(product(d)) = productId AND 
        extractYear(date(order(d))) = year;
SELECT lastName(Employee e) WHERE sold(e, 1, 1997) > 30;

Вазифаи 2.2

Барои ҳар як харидор (ном, насаб) ду молеро (ном) пайдо кунед, ки харидор дар соли 1997 бештар пул сарф кардааст.

Мо мантиқи доменро аз мисоли қаблӣ васеъ мекунем:

CLASS Customer 'Клиент';
contactName 'ФИО' = DATA STRING[100] (Customer);

customer = DATA Customer (Order);

unitPrice = DATA NUMERIC[14,2] (Detail);
discount = DATA NUMERIC[6,2] (Detail);

Муносибатӣ

SELECT ContactName, ProductName FROM (
SELECT c.ContactName, p.ProductName
, ROW_NUMBER() OVER (
    PARTITION BY c.ContactName
    ORDER BY SUM(od.Quantity * od.UnitPrice * (1 - od.Discount)) DESC
) AS RatingByAmt
FROM Customers c
JOIN Orders o ON o.CustomerID = c.CustomerID
JOIN [Order Details] od ON od.OrderID = o.OrderID
JOIN Products p ON p.ProductID = od.ProductID
WHERE YEAR(o.OrderDate) = 1997
GROUP BY c.ContactName, p.ProductName
) t
WHERE RatingByAmt < 3

Функсионалӣ

sum (Detail d) = quantity(d) * unitPrice(d) * (1 - discount(d));
bought 'Купил' (Customer c, Product p, INTEGER y) = 
    GROUP SUM sum(Detail d) IF 
        customer(order(d)) = c AND 
        product(d) = p AND 
        extractYear(date(order(d))) = y;
rating 'Рейтинг' (Customer c, Product p, INTEGER y) = 
    PARTITION SUM 1 ORDER DESC bought(c, p, y), p BY c, y;
SELECT contactName(Customer c), name(Product p) WHERE rating(c, p, 1997) < 3;

Оператори PARTITION аз рӯи принсипи зерин кор мекунад: он ифодаи пас аз SUM (дар ин ҷо 1) зикршударо ҷамъбаст мекунад, дар доираи гурӯҳҳои муайяншуда (дар ин ҷо Муштарӣ ва Сол, аммо метавонад ҳама гуна ифода бошад), дар дохили гурӯҳҳо аз рӯи ибораҳои дар ТАРТИБ (ДАР ТАРТИБ) нишондодашуда ҷудо карда мешавад. дар ин ҷо харида шудааст ва агар баробар бошад, пас мувофиқи коди дохилии маҳсулот).

Вазифаи 2.3

Барои ичрои заказхои чориро аз таъминкунандагон чй кадар мол заказ кардан лозим аст.

Биёед мантиқи доменро боз васеъ кунем:

CLASS Supplier 'Поставщик';
companyName = DATA STRING[100] (Supplier);

supplier = DATA Supplier (Product);

unitsInStock 'Остаток на складе' = DATA NUMERIC[10,3] (Product);
reorderLevel 'Норма продажи' = DATA NUMERIC[10,3] (Product);

Муносибатӣ

select s.CompanyName, p.ProductName, sum(od.Quantity) + p.ReorderLevel — p.UnitsInStock as ToOrder
from Orders o
join [Order Details] od on o.OrderID = od.OrderID
join Products p on od.ProductID = p.ProductID
join Suppliers s on p.SupplierID = s.SupplierID
where o.ShippedDate is null
group by s.CompanyName, p.ProductName, p.UnitsInStock, p.ReorderLevel
having p.UnitsInStock < sum(od.Quantity) + p.ReorderLevel

Функсионалӣ

orderedNotShipped 'Заказано, но не отгружено' (Product p) = 
    GROUP SUM quantity(OrderDetail d) IF product(d) = p;
toOrder 'К заказу' (Product p) = orderedNotShipped(p) + reorderLevel(p) - unitsInStock(p);
SELECT companyName(supplier(Product p)), name(p), toOrder(p) WHERE toOrder(p) > 0;

Мушкилот бо ситорача

Ва мисоли охирин аз худи ман аст. Мантиқи шабакаи иҷтимоӣ вуҷуд дорад. Одамон метавонанд бо якдигар дӯст бошанд ва ба ҳамдигар маъқул бошанд. Аз нуқтаи назари функсионалии пойгоҳи додаҳо он чунин хоҳад буд:

CLASS Person;
likes = DATA BOOLEAN (Person, Person);
friends = DATA BOOLEAN (Person, Person);

Номзадҳои имконпазирро барои дӯстӣ пайдо кардан лозим аст. Ба таври расмӣ, шумо бояд ҳамаи одамони A, B, C-ро пайдо кунед, ки А бо B дӯст бошад ва В бо C дӯст бошад, А ба C маъқул бошад, аммо A бо C дӯстӣ надошта бошад.
Аз нуқтаи назари функсионалии пойгоҳи додаҳо, дархост чунин хоҳад буд:

SELECT Person a, Person b, Person c WHERE 
    likes(a, c) AND NOT friends(a, c) AND 
    friends(a, b) AND friends(b, c);

Хонанда ташвиқ карда мешавад, ки ин масъаларо дар SQL мустақилона ҳал кунад. Гумон меравад, ки дӯстон нисбат ба одамоне, ки ба шумо маъқуланд, хеле камтаранд. Аз ин рӯ, онҳо дар ҷадвалҳои алоҳида ҷойгиранд. Агар бомуваффақият анҷом дода шавад, вазифае низ бо ду ситора вуҷуд дорад. Дар он дӯстӣ симметрӣ нест. Дар базаи функсионалӣ он чунин хоҳад буд:

SELECT Person a, Person b, Person c WHERE 
    likes(a, c) AND NOT friends(a, c) AND 
    (friends(a, b) OR friends(b, a)) AND 
    (friends(b, c) OR friends(c, b));

UPD: ҳалли мушкилот бо ситораи якум ва дуюм аз dss_kalika:

SELECT 
   pl.PersonAID
  ,pf.PersonAID
  ,pff.PersonAID
FROM Persons                 AS p
--Лайки                      
JOIN PersonRelationShip      AS pl ON pl.PersonAID = p.PersonID
                                  AND pl.Relation  = 'Like'
--Друзья                     
JOIN PersonRelationShip      AS pf ON pf.PersonAID = p.PersonID 
                                  AND pf.Relation = 'Friend'
--Друзья Друзей              
JOIN PersonRelationShip      AS pff ON pff.PersonAID = pf.PersonBID
                                   AND pff.PersonBID = pl.PersonBID
                                   AND pff.Relation = 'Friend'
--Ещё не дружат         
LEFT JOIN PersonRelationShip AS pnf ON pnf.PersonAID = p.PersonID
                                   AND pnf.PersonBID = pff.PersonBID
                                   AND pnf.Relation = 'Friend'
WHERE pnf.PersonAID IS NULL 

;WITH PersonRelationShipCollapsed AS (
  SELECT pl.PersonAID
        ,pl.PersonBID
        ,pl.Relation 
  FROM #PersonRelationShip      AS pl 
  
  UNION 

  SELECT pl.PersonBID AS PersonAID
        ,pl.PersonAID AS PersonBID
        ,pl.Relation
  FROM #PersonRelationShip      AS pl 
)
SELECT 
   pl.PersonAID
  ,pf.PersonBID
  ,pff.PersonBID
FROM #Persons                      AS p
--Лайки                      
JOIN PersonRelationShipCollapsed  AS pl ON pl.PersonAID = p.PersonID
                                 AND pl.Relation  = 'Like'                                  
--Друзья                          
JOIN PersonRelationShipCollapsed  AS pf ON pf.PersonAID = p.PersonID 
                                 AND pf.Relation = 'Friend'
--Друзья Друзей                   
JOIN PersonRelationShipCollapsed  AS pff ON pff.PersonAID = pf.PersonBID
                                 AND pff.PersonBID = pl.PersonBID
                                 AND pff.Relation = 'Friend'
--Ещё не дружат                   
LEFT JOIN PersonRelationShipCollapsed AS pnf ON pnf.PersonAID = p.PersonID
                                   AND pnf.PersonBID = pff.PersonBID
                                   AND pnf.Relation = 'Friend'
WHERE pnf.[PersonAID] IS NULL 

хулоса

Бояд қайд кард, ки синтаксиси забони додашуда танҳо яке аз вариантҳои татбиқи консепсияи додашуда мебошад. SQL ҳамчун асос гирифта шуд ва ҳадаф он буд, ки он то ҳадди имкон ба он монанд бошад. Албатта, ба баъзеҳо номи калимаҳои калидӣ, регистрҳои калимаҳо ва ғайра маъқул нашаванд. Дар ин ҷо чизи асосӣ худи консепсия аст. Агар хоҳед, шумо метавонед ҳам C++ ва ҳам Python-ро синтаксиси шабеҳ созед.

Консепсияи пойгоҳи додашуда, ба андешаи ман, бартариҳои зерин дорад:

  • шароит. Ин нишондиҳандаи нисбатан субъективӣ аст, ки дар ҳолатҳои оддӣ равшан нест. Аммо агар шумо ба ҳолатҳои мураккабтар назар андозед (масалан, мушкилот бо ситорача), пас, ба андешаи ман, навиштани чунин дархостҳо хеле осонтар аст.
  • Инкапцияция. Дар баъзе мисолҳо ман функсияҳои фосилавӣ эълон кардам (масалан, фурӯхта шудааст, харид ва ғайра), ки аз онҳо вазифаҳои минбаъда сохта шудаанд. Ин ба шумо имкон медиҳад, ки мантиқи баъзе функсияҳоро, агар лозим бошад, бидуни тағир додани мантиқи функсияҳое, ки ба онҳо вобастаанд, тағир диҳед. Масалан, шумо метавонед фурӯш кунед фурӯхта шудааст аз объектҳои тамоман гуногун ҳисоб карда шуданд, дар ҳоле ки боқимондаи мантиқ тағир намеёбад. Бале, ин метавонад дар RDBMS бо истифода аз CREATE VIEW амалӣ карда шавад. Аммо агар тамоми мантиқ ин тавр навишта шавад, он қадар хонданӣ наменамояд.
  • Тафовути семантикӣ вуҷуд надорад. Чунин пойгоҳи додаҳо дар функсияҳо ва синфҳо амал мекунад (ба ҷои ҷадвалҳо ва майдонҳо). Мисли барномасозии классикӣ (агар фарз кунем, ки усул функсия бо параметри аввал дар шакли синфе, ки он ба он тааллуқ дорад). Аз ин рӯ, бо забонҳои универсалии барномасозӣ “дӯстӣ пайдо кардан” бояд хеле осонтар бошад. Илова бар ин, ин консепсия имкон медиҳад, ки функсияҳои хеле мураккабтар амалӣ карда шаванд. Масалан, шумо метавонед операторҳоро ба монанди:

    CONSTRAINT sold(Employee e, 1, 2019) > 100 IF name(e) = 'Петя' MESSAGE  'Что-то Петя продает слишком много одного товара в 2019 году';

  • Ирсият ва полиморфизм. Дар пойгоҳи додаҳои функсионалӣ, шумо метавонед мероси сершуморро тавассути CLASS ClassP: Сохтмони Class1, Class2 ҷорӣ кунед ва полиморфизми сершуморро амалӣ кунед. Эҳтимол ман дар мақолаҳои оянда чӣ гуна аниқ хоҳам навишт.

Гарчанде ки ин танҳо як мафҳум аст, мо аллакай дар Java каме татбиқ дорем, ки тамоми мантиқи функсионалиро ба мантиқи робитавӣ тарҷума мекунад. Илова бар ин, мантиқи намояндагӣ ва бисёр чизҳои дигар ба он зебо пайваст карда шудаанд, ки ба шарофати он мо як чизи пурраро ба даст меорем. платформа. Аслан, мо RDBMS-ро (айни замон танҳо PostgreSQL) ҳамчун "мошини виртуалӣ" истифода мебарем. Баъзан бо ин тарҷума мушкилот ба миён меоянд, зеро оптимизатори дархости RDBMS омори муайянеро, ки FDBMS медонад, намедонад. Дар назария метавон системаи идоракунии пойгоҳи додаҳоро татбиқ кард, ки сохтори муайянро ҳамчун анбор истифода мебарад, ки махсус барои мантиқи функсионалӣ мутобиқ карда шудааст.

Манбаъ: will.com

Илова Эзоҳ