Funksjonele DBMS

De wrâld fan databases is al lang dominearre troch relationele DBMS's, dy't de SQL-taal brûke. Safolle dat opkommende farianten NoSQL wurde neamd. Se slagge te snije út in bepaald plak foar harsels yn dizze merk, mar relational DBMSs sille net stjerre, en bliuwe aktyf brûkt foar harren doelen.

Yn dit artikel wol ik it konsept fan in funksjonele databank beskriuwe. Foar in better begryp sil ik dit dwaan troch it te fergelykjen mei it klassike relaasjemodel. Problemen fan ferskate SQL-tests fûn op it ynternet sille as foarbylden brûkt wurde.

Ynlieding

Relasjonele databases wurkje op tabellen en fjilden. Yn in funksjonele databank sille respektivelik klassen en funksjes brûkt wurde. In fjild yn in tabel mei N kaaien wurdt fertsjintwurdige as in funksje fan N parameters. Yn stee fan relaasjes tusken tabellen sille funksjes brûkt wurde dy't objekten werombringe fan 'e klasse wêrmei't de ferbining makke is. Funksje gearstalling sil brûkt wurde ynstee fan JOIN.

Foardat jo direkt nei de taken gean, sil ik de taak fan domeinlogika beskriuwe. Foar DDL sil ik PostgreSQL-syntaksis brûke. Foar funksjoneel hat it in eigen syntaksis.

Tabellen en fjilden

In ienfâldich Sku-objekt mei namme- en priisfjilden:

Relasjonele

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

funksjoneel

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

Wy kundigje twa oan funksjes, dy't nimme ien parameter Sku as ynfier en werom in primitive type.

Der wurdt fan útgien dat yn in funksjoneel DBMS elk foarwerp sil hawwe wat ynterne koade dy't wurdt automatysk oanmakke en kin tagonklik wurde as it nedich is.

Litte wy de priis ynstelle foar it produkt / winkel / leveransier. It kin yn 'e rin fan' e tiid feroarje, dus litte wy in tiidfjild tafoegje oan 'e tabel. Ik sil it ferklearjen fan tabellen foar mappen yn in relasjonele databank oerslaan om de koade te koartsjen:

Relasjonele

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)
)

funksjoneel

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

Yndeksen

Foar it lêste foarbyld sille wy in yndeks bouwe op alle kaaien en de datum, sadat wy de priis fluch fine kinne foar in spesifike tiid.

Relasjonele

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

funksjoneel

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

taken

Lit ús begjinne mei relatyf ienfâldige problemen nommen út de oerienkommende artikels op Habr.

Litte wy earst de domeinlogika ferklearje (foar de relationele databank wurdt dit direkt dien yn it boppesteande artikel).

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);

Opdracht 1.1

Lit in list sjen mei meiwurkers dy't in salaris krije dat grutter is as dat fan har direkte supervisor.

Relasjonele

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

funksjoneel

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

Opdracht 1.2

List de meiwurkers dy't it maksimale salaris krije yn har ôfdieling

Relasjonele

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

funksjoneel

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));

Beide ymplemintaasjes binne lykweardich. Foar it earste gefal kinne jo yn in relational databank CREATE VIEW brûke, dy't op deselde manier earst it maksimale salaris foar in spesifike ôfdieling dêryn berekkenje sil. Yn wat folget sil ik foar de dúdlikens it earste gefal brûke, om't it de oplossing better wjerspegelet.

Opdracht 1.3

Lit in list mei ôfdielings-ID's sjen, it oantal meiwurkers wêryn net mear as 3 minsken is.

Relasjonele

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

funksjoneel

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

Opdracht 1.4

Lit in list sjen mei meiwurkers dy't gjin oanwiisde manager hawwe dy't yn deselde ôfdieling wurkje.

Relasjonele

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

funksjoneel

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

Opdracht 1.5

Fyn in list mei ôfdielings-ID's mei it maksimale totale salaris fan meiwurkers.

Relasjonele

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 )

funksjoneel

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();

Lit ús gean nei mear komplekse taken fan in oar artikels. It befettet in detaillearre analyze fan hoe't jo dizze taak útfiere yn MS SQL.

Opdracht 2.1

Hokker ferkeapers ferkochten mear as 1997 ienheden fan produkt nûmer 30 yn 1?

Domeinlogika (lykas earder op RDBMS slaan wy de ferklearring oer):

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);

Relasjonele

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

funksjoneel

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;

Opdracht 2.2

Foar elke keaper (namme, efternamme) fine jo de twa guod (namme) dêr't de keaper yn 1997 it measte jild oan bestege.

Wy wreidzje de domeinlogika út fan it foarige foarbyld:

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

customer = DATA Customer (Order);

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

Relasjonele

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

funksjoneel

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;

De PARTITION-operator wurket op it folgjende prinsipe: it somt de útdrukking opjûn nei SUM (hjir 1), binnen de opjûne groepen (hjir Klant en Jier, mar kin elke útdrukking wêze), sortearjen binnen de groepen troch de útdrukkingen spesifisearre yn 'e ORDER ( hjir kocht, en as gelyk, dan neffens de ynterne produktkoade).

Opdracht 2.3

Hoefolle guod moatte wurde besteld fan leveransiers om hjoeddeistige oarders te ferfoljen.

Litte wy de domeinlogika wer útwreidzje:

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

supplier = DATA Supplier (Product);

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

Relasjonele

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

funksjoneel

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;

Probleem mei in asterisk

En it lêste foarbyld is fan my persoanlik. D'r is de logika fan in sosjaal netwurk. Minsken kinne freonen mei elkoar wêze en elkoar leuk fine. Fanút in funksjoneel databankperspektyf soe it der sa útsjen:

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

It is nedich om mooglike kandidaten foar freonskip te finen. Formeeler moatte jo alle minsken A, B, C fine, sadat A befreone is mei B, en B befreone is mei C, A liket C, mar A is gjin befreone mei C.
Fanút in funksjoneel databankperspektyf soe de query der sa útsjen:

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

De lêzer wurdt stimulearre om dit probleem yn SQL op syn eigen op te lossen. Der wurdt oannommen dat d'r folle minder freonen binne as minsken dy't jo leuk fine. Dêrom binne se yn aparte tabellen. As suksesfol, is der ek in taak mei twa stjerren. Dêryn is freonskip net symmetrysk. Op in funksjonele databank soe it der sa útsjen:

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: oplossing foar it probleem mei de earste en twadde asterisk út 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 

konklúzje

It moat opmurken wurde dat de opjûne taalsyntaksis mar ien fan 'e opsjes is foar it útfieren fan it opjûne konsept. SQL waard nommen as de basis, en it doel wie dat it soe wêze sa ferlykber mooglik mei it. Fansels kinne guon de nammen fan trefwurden, wurdregisters, ensfh. It wichtichste ding hjir is it konsept sels. As jo ​​​​wolle, kinne jo sawol C ++ as Python ferlykbere syntaksis meitsje.

It beskreaune databankkonsept hat nei myn miening de folgjende foardielen:

  • gemak. Dit is in relatyf subjektive yndikator dy't net dúdlik is yn ienfâldige gefallen. Mar as jo nei mear komplekse gefallen sjogge (bygelyks problemen mei asterisken), dan is neffens my it skriuwen fan sokke fragen folle makliker.
  • Инкапсуляция. Yn guon foarbylden haw ik tuskenfunksjes ferklearre (bygelyks, ferkocht, kocht ensfh.), wêrfan folgjende funksjes waarden boud. Hjirmei kinne jo de logika fan bepaalde funksjes feroarje, as it nedich is, sûnder de logika te feroarjen fan dyjingen dy't derfan ôfhingje. Jo kinne bygelyks ferkeap meitsje ferkocht waarden berekkene út folslein ferskillende objekten, wylst de rest fan de logika sil net feroarje. Ja, dit kin wurde ymplementearre yn in RDBMS mei CREATE VIEW. Mar as alle logika op dizze manier skreaun is, sil it net heul lêsber útsjen.
  • Gjin semantyske gat. Sa'n databank wurket op funksjes en klassen (ynstee fan tabellen en fjilden). Krekt as yn klassike programmearring (as wy oannimme dat in metoade is in funksje mei de earste parameter yn de foarm fan de klasse dêr't it heart). Dêrtroch soe it folle makliker wêze moatte om "freonen te meitsjen" mei universele programmeartalen. Derneist makket dit konsept it mooglik om folle kompleksere funksjonaliteit te ymplementearjen. Jo kinne bygelyks operators ynbêde lykas:

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

  • Erfskip en polymorfisme. Yn in funksjonele databank kinne jo meardere erfskip ynfiere fia de CLASS ClassP: Klasse1, Klasse2-konstruksjes en meardere polymorfisme ymplementearje. Ik sil wierskynlik skriuwe hoe krekt yn takomstige artikels.

Hoewol dit gewoan in konsept is, hawwe wy al wat ymplemintaasje yn Java dy't alle funksjonele logika oerset yn relasjonele logika. Plus, de logika fan foarstellings en in protte oare dingen binne der prachtich oan ferbûn, wêrtroch't wy in gehiel krije platfoarm. Yn essinsje brûke wy de RDBMS (allinich PostgreSQL foar no) as in "firtuele masine". Problemen ûntsteane soms mei dizze oersetting om't de RDBMS-query-optimizer gjin bepaalde statistiken ken dy't de FDBMS ken. Yn teory is it mooglik om in databankbehearsysteem te ymplementearjen dat in bepaalde struktuer sil brûke as opslach, spesifyk oanpast foar funksjonele logika.

Boarne: www.habr.com

Add a comment