Funktionell DBMS

D'Welt vun den Datenbanken ass laang dominéiert vu relationalen DBMSen, déi d'SQL Sprooch benotzen. Sou vill sou datt opkomende Varianten NoSQL genannt ginn. Si hunn et fäerdeg bruecht eng bestëmmte Plaz fir sech selwer an dësem Maart ze schneiden, awer relational DBMSs stierwen net, a ginn weider aktiv fir hir Zwecker benotzt.

An dësem Artikel wëll ech d'Konzept vun enger funktioneller Datebank beschreiwen. Fir e bessert Verständnis ze maachen, wäert ech dat maachen andeems ech et mam klassesche relationale Modell vergläichen. Problemer vu verschiddene SQL Tester um Internet fonnt ginn als Beispiller benotzt.

Aféierung

Relational Datenbanken funktionnéieren op Dëscher a Felder. An enger funktioneller Datebank ginn Klassen a Funktiounen amplaz benotzt. E Feld an enger Tabell mat N Schlësselen gëtt als Funktioun vun N Parameter representéiert. Amplaz Relatiounen tëscht Dëscher, Funktiounen wäert benotzt ginn, datt Objete vun der Klass zréckginn, op déi d'Verbindung gemaach ass. Funktioun Zesummesetzung wäert amplaz JOIN benotzt ginn.

Ier Dir direkt op d'Aufgaben plënnert, wäert ech d'Aufgab vun der Domain Logik beschreiwen. Fir DDL wäert ech PostgreSQL Syntax benotzen. Fir funktionell huet et seng eege Syntax.

Dëscher a Felder

En einfachen Sku Objet mat Numm- a Präisfelder:

Relational

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

funktionell

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

Mir annoncéieren zwee Funktiounen, déi ee Parameter Sku als Input huelen an e primitiven Typ zréckginn.

Et gëtt ugeholl datt an engem funktionnellen DBMS all Objet e puer internen Code huet, deen automatesch generéiert gëtt a kann zougänglech sinn wann néideg.

Loosst eis de Präis fir de Produit / Buttek / Fournisseur festleeën. Et kann mat der Zäit änneren, also loosst eis en Zäitfeld op den Dësch setzen. Ech sprangen d'Deklaratioun vun Dëscher fir Verzeichnisser an enger relationaler Datebank fir de Code ze verkierzen:

Relational

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

funktionell

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

Indexen

Fir dat lescht Beispill wäerte mir en Index op all Schlësselen an den Datum bauen fir datt mir séier de Präis fir eng spezifesch Zäit fannen.

Relational

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

funktionell

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

Aufgaben

Loosst d'mat relativ einfach Problemer ufänken aus dem entspriechend geholl Artikelen op Habr.

Als éischt, loosst eis d'Domainlogik erklären (fir déi relational Datebank gëtt dat direkt am Artikel hei uewen gemaach).

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

Problem 1.1

Weist eng Lëscht vu Mataarbechter déi e Loun méi grouss kréien wéi dee vun hirem direkten Supervisor.

Relational

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

funktionell

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

Problem 1.2

Lëscht d'Mataarbechter déi maximal Pai an hirem Departement kréien

Relational

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

funktionell

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

Béid Implementatioune sinn gläichwäerteg. Fir den éischte Fall, an enger relationaler Datebank kënnt Dir CREATE VIEW benotzen, déi am selwechte Wee fir d'éischt d'maximal Pai fir eng spezifesch Departement an der Berechent. An deem folgenden, fir Kloerheet, wäert ech den éischte Fall benotzen, well et besser d'Léisung reflektéiert.

Problem 1.3

Weist eng Lëscht vun Departement IDen, d'Zuel vun de Mataarbechter an deenen net däerfte 3 Leit.

Relational

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

funktionell

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

Problem 1.4

Weist eng Lëscht vu Mataarbechter, déi keen designéierte Manager hunn, deen an der selwechter Departement schafft.

Relational

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

funktionell

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

Problem 1.5

Fannt eng Lëscht vun Departementer IDen mat dem maximalen Gesamtgehalt vum Employé.

Relational

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 )

funktionell

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

Loosst eis op méi komplex Aufgaben vun engem aneren weidergoen Artikelen. Et enthält eng detailléiert Analyse wéi Dir dës Aufgab an MS SQL ëmsetzt.

Problem 2.1

Wéi eng Verkeefer hunn 1997 méi wéi 30 Unitéiten vum Produkt Nummer 1 verkaf?

Domain Logik (wéi virdrun op RDBMS sprange mir d'Deklaratioun iwwer):

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

Relational

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

funktionell

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;

Problem 2.2

Fir all Keefer (Numm, Familljennumm), fannt Dir déi zwee Wueren (Numm), op deenen de Keefer am meeschte Sue 1997 ausginn huet.

Mir verlängeren d'Domain Logik aus dem virege Beispill:

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

customer = DATA Customer (Order);

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

Relational

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

funktionell

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 Bedreiwer funktionnéiert nom folgende Prinzip: et summéiert den Ausdrock deen nom SUM spezifizéiert ass (hei 1), bannent de spezifizéierte Gruppen (hei Client a Joer, awer kéint all Ausdrock sinn), sortéiert bannent de Gruppen no den Ausdréck, déi an der ORDER spezifizéiert sinn ( hei kaaft, a wann gläich, dann no der intern Produit Code).

Problem 2.3

Wéi vill Wueren musse vu Liwweranten bestallt ginn fir aktuell Bestellungen ze erfëllen.

Loosst eis d'Domain Logik erweideren:

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

supplier = DATA Supplier (Product);

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

Relational

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

funktionell

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;

Problem mat engem Stär

An dat lescht Beispill ass vu mir perséinlech. Et gëtt d'Logik vun engem sozialen Netzwierk. D'Leit kënne matenee Frënn sinn a sech gär hunn. Aus enger funktionell Datebank Perspektiv géif et esou ausgesinn:

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

Et ass néideg méiglech Kandidaten fir Frëndschaft ze fannen. Méi formell musst Dir all Leit A, B, C fannen, sou datt A Frënn mat B ass, a B Frënn mat C ass, A gär C, awer A ass net Frënn mat C.
Aus enger funktioneller Datebankperspektive géif d'Ufro esou ausgesinn:

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 Lieser gëtt encouragéiert dëse Problem an SQL eleng ze léisen. Et gëtt ugeholl datt et vill manner Frënn sinn wéi Leit déi Dir gär hutt. Dofir sinn se an getrennten Dëscher. Wann et erfollegräich ass, gëtt et och eng Aufgab mat zwee Stären. An et ass Frëndschaft net symmetresch. Op enger funktioneller Datebank géif et esou ausgesinn:

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: Léisung fir de Problem mat der éischter an zweeter Asterisk aus 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 

Konklusioun

Et sollt bemierkt datt déi gegebene Sproochsyntax just eng vun den Optiounen ass fir dat gegebene Konzept ëmzesetzen. SQL gouf als Basis geholl, an d'Zil war datt et esou ähnlech wéi méiglech ass. Natierlech kënnen e puer d'Nimm vu Schlësselwieder net gär hunn, Wuertregisteren, asw. Den Haapt Saach hei ass d'Konzept selwer. Wann Dir wëllt, kënnt Dir souwuel C ++ a Python ähnlech Syntax maachen.

Dat beschriwwen Datebankkonzept, menger Meenung no, huet déi folgend Virdeeler:

  • Liichtegkeet. Dëst ass e relativ subjektiven Indikator deen an einfache Fäll net offensichtlech ass. Awer wann Dir méi komplexe Fäll kuckt (zum Beispill Probleemer mat Sterren), dann ass menger Meenung no sou Ufroen vill méi einfach ze schreiwen.
  • Инкапсуляция. An e puer Beispiller hunn ech Zwëschenfunktiounen deklaréiert (zum Beispill, verkaaft, kaaft hunn etc.), aus deem spéider Funktiounen gebaut goufen. Dëst erlaabt Iech d'Logik vu bestëmmte Funktiounen z'änneren, wann néideg, ouni d'Logik vun deenen ze änneren, déi vun hinnen ofhänken. Zum Beispill kënnt Dir Verkaf maachen verkaaft goufen aus komplett verschiddenen Objeten berechent, während de Rescht vun der Logik wäert net änneren. Jo, dëst kann an engem RDBMS implementéiert ginn mat CREATE VIEW. Awer wann all d'Logik esou geschriwwen ass, wäert et net ganz liesbar ausgesinn.
  • Kee semantesche Spalt. Esou eng Datebank funktionnéiert op Funktiounen a Klassen (amplaz vun Dëscher a Felder). Just wéi an der klassescher Programméierung (wa mir dovun ausgoen datt eng Method eng Funktioun ass mat dem éischte Parameter a Form vun der Klass zu där se gehéiert). Deementspriechend sollt et vill méi einfach sinn mat universelle Programméierungssproochen "Frënn ze maachen". Zousätzlech erlaabt dëst Konzept vill méi komplex Funktionalitéit ëmzesetzen. Zum Beispill kënnt Dir Betreiber wéi:

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

  • Ierfschaft a Polymorphismus. An enger funktioneller Datebank kënnt Dir Multiple Ierfschaft duerch d'CLASS ClassP aféieren: Class1, Class2 Konstruktiounen a Multiple Polymorphismus ëmsetzen. Ech wäert wahrscheinlech schreiwen wéi genau an zukünfteg Artikelen.

Och wann dëst just e Konzept ass, hu mir schonn eng Implementatioun am Java déi all funktionell Logik a relational Logik iwwersetzt. Plus, d'Logik vun de Representatioune a vill aner Saache si schéin domat verbonnen, duerch déi mir e Ganzt kréien der Plattform. Wesentlech benotze mir d'RDBMS (nëmmen PostgreSQL fir de Moment) als "virtuell Maschinn". Probleemer entstinn heiansdo mat dëser Iwwersetzung well de RDBMS Ufrooptimizer keng bestëmmte Statistike kennt déi d'FDBMS kennt. An der Theorie ass et méiglech en Datebankmanagementsystem ëmzesetzen deen eng gewësse Struktur als Späichere benotzt, speziell fir funktionell Logik ugepasst.

Source: will.com

Setzt e Commentaire