DBMS funzionale

U mondu di a basa di dati hè statu longu pigliatu da i DBMS relazionali chì utilizanu a lingua SQL. Tantu cusì chì e varietà emergenti sò chjamati NoSQL. Anu riesciutu à vince un certu postu per elli stessi in questu mercatu, ma i DBMS relazionali ùn anu micca more, è cuntinueghjanu à esse attivamente utilizati per i so propiu scopi.

In questu articulu, vogliu discrive u cuncettu di una basa di dati funziunale. Per un megliu capiscenu, aghju da fà questu paragunendu cù u mudellu relazionale classicu. Comu esempi, i travaglii di diversi testi SQL truvati in Internet seranu utilizati.

Introduzione

E basa di dati relazionale operanu nantu à tavule è campi. In una basa di dati funziunale, classi è funzioni seranu utilizati invece, rispettivamente. Un campu in una tavula cù N chjavi serà rapprisintatu cum'è una funzione di N paràmetri. Invece di ligami trà e tavule, e funzioni seranu usate chì tornanu l'uggetti di a classa à quale u ligame va. A cumpusizioni di a funzione serà usata invece di JOIN.

Prima di prucede direttamente à i travaglii, descriveraghju u compitu di a logica di u duminiu. Per DDL, aduprà a sintassi PostgreSQL. Per funziunale a so propria sintassi.

Tavule è campi

Un oggettu Sku simplice cù campi di nome è prezzu:

relazionale

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

Funziunale

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

Annunziamu dui funzioni, chì piglianu un paràmetru Sku cum'è input è torna un tipu primitivu.

Hè presumitu chì in un DBMS funziunale, ogni ughjettu avarà qualchì codice internu chì hè generatu automaticamente è pò esse accessu se necessariu.

Fighjemu u prezzu per u pruduttu / magazzinu / fornitore. Puderà cambià cù u tempu, cusì aghjunghje un campu di tempu à a tavula. Saltaraghju a dichjarazione di e tabelle per i cartulari in una basa di dati relazionale per accurtà u codice:

relazionale

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

Funziunale

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

Indici

Per l'ultimu esempiu, custruemu un indice nantu à tutte e chjave è a data per pudè truvà rapidamente u prezzu per un certu tempu.

relazionale

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

Funziunale

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

fatti

Accuminciamu cù prublemi relativamente simplici pigliati da u currispundente articuli nantu à Habr.

Prima, dichjarà a logica di u duminiu (per una basa di dati relazionale, questu hè fattu direttamente in l'articulu sopra).

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

Task 1.1

Mostra una lista di l'impiegati chì ricevenu salari più grande di quelli di u supervisore immediata.

relazionale

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

Funziunale

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

Task 1.2

Mostra una lista di l'impiegati chì guadagnanu u salariu più altu in u so dipartimentu

relazionale

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

Funziunale

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

E duie implementazioni sò equivalenti. Per u primu casu in a basa di dati relazionale, pudete aduprà CREATE VIEW, chì in a listessa manera hà da calculà prima u salariu massimu per un dipartimentu specificu in questu. In u futuru, per a clarità, aghju aduprà u primu casu, postu chì riflette megliu a suluzione.

Task 1.3

Mostra una lista di ID di u dipartimentu, u numeru di l'impiegati in quale ùn supera micca 3 persone.

relazionale

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

Funziunale

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

Task 1.4

Mostra una lista di l'impiegati chì ùn anu micca un capu assignatu chì travaglia in u stessu dipartimentu.

relazionale

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

Funziunale

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

Task 1.5

Truvate a lista di l'ID di dipartimentu cù u salariu tutale massimu di l'impiegati.

relazionale

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 )

Funziunale

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

Passemu à i travaglii più cumplessi da un altru articuli. Contene un analisi detallatu di cumu implementà sta attività in MS SQL.

Task 2.1

Chì venditori anu vindutu più di 1997 pezzi di l'articulu #30 in u 1?

Lògica di u duminiu (cum'è prima, saltamu a dichjarazione nantu à u 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);

relazionale

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

Funziunale

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;

Task 2.2

Per ogni cliente (nome, cognome), truvate i dui elementi (nome) nantu à quale u cliente hà spesu più soldi in 1997.

Estende a logica di u duminiu da l'esempiu precedente:

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

customer = DATA Customer (Order);

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

relazionale

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

Funziunale

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;

L'operatore PARTITION travaglia secondu u principiu seguente: somma l'espressione specificata dopu SUM (qui 1) in i gruppi specificati (qui Cliente è Annu, ma pò esse qualsiasi espressione), sortendu in i gruppi secondu l'espressioni specificate in ORDINE ( quì compru, è se sò uguali, allora da u codice di u produttu internu).

Task 2.3

Quante merchenzie devenu esse urdinate da i fornituri per cumpiendu l'ordine attuale.

Allargemu di novu a logica di u duminiu:

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

supplier = DATA Supplier (Product);

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

relazionale

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

Funziunale

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;

Task cù un asteriscu

È l'ultimu esempiu hè da mè personalmente. Ci hè a logica di una reta suciale. A ghjente pò esse amici è cum'è l'altri. Da una perspettiva di basa di dati funziunale, questu seria cusì:

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

Hè necessariu di truvà pussibuli candidati per l'amicizia. Più formalmente, avete bisognu di truvà tutte e persone A, B, C tali chì A hè amicu di B, è B hè amicu di C, A piace C, ma A ùn hè micca amicu di C.
Da un puntu di vista di basa di dati funziunale, a dumanda seria cusì:

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

U lettore hè invitatu à risolve in modu indipendenti stu prublema in SQL. Si assume chì ci sò assai menu amici cà quelli chì piacenu. Per quessa, sò in tavule separati. In casu di una suluzione riescita, ci hè ancu un prublema cù dui asterischi. A so amicizia ùn hè micca simmetrica. Nant'à una basa di dati funziunale, parerebbe cusì:

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: suluzione di u prublema cù u primu è u sicondu asteriscu da 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 

cunchiusioni

Si deve esse nutatu chì a sintassi sopra di a lingua hè solu una di l'opzioni per implementà u cuncettu sopra. Hè stata SQL chì hè stata presa cum'è a basa, è u scopu era di fà u più simili pussibule. Di sicuru, qualchissia ùn pò micca piace i nomi di e parolle chjave, casu di parolle, è cusì. A cosa principal hè quì u cuncettu stessu. Se vulete, pudete fà sia C ++ è Python sintassi simili.

U cuncettu di basa di dati descrittu, in my opinion, hà i seguenti vantaghji:

  • Semplicità. Questu hè un indicatore relativamente subjectivu chì ùn hè micca evidenti in casi simplici. Ma s'è vo circate casi più cumplessi (per esempiu, compiti cù asterischi), allura, in my opinion, scrive tali dumande hè assai più faciule.
  • Инкапсуляция. In certi esempi, aghju dichjaratu funzioni intermedie (per esempiu, venduti, compru etc.), da quale funziunamentu sussegwente sò stati custruiti. Questu permette di cambià a logica di certe funzioni, se ne necessariu, senza cambià a logica di quelli chì dependenu di elli. Per esempiu, pudete fà vendita venduti sò stati calculati da uggetti cumplitamenti diffirenti, mentri u restu di a logica ùn cambia micca. Iè, in RDBMS questu pò esse fattu cù CREATE VIEW. Ma s'è vo scrivite tutta a logica in questu modu, allora ùn pare micca assai leggibile.
  • Nisun Gap Semanticu. Una tale basa di dati opera cù funzioni è classi (inveci di tavule è campi). In u listessu modu cum'è in a prugrammazione classica (assumendu chì un metudu hè una funzione cù u primu paràmetru in a forma di una classa à quale appartene). In cunsiquenza, deve esse assai più faciule per "fate amici" cù linguaggi di prugrammazione universale. Inoltre, stu cuncettu permette di implementà funzioni assai più cumplesse. Per esempiu, pudete incrustà dichjarazioni cum'è questu in a basa di dati:

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

  • Eredità è polimorfismu. In una basa di dati funzionale, pudete intruduce l'eredità multipla à traversu a CLASS ClassP: Class1, Class2 custruzzioni è implementà polimorfismu multiplu. Cumu esattamente, forse scriveraghju in l'articuli seguenti.

Ancu s'ellu hè solu un cuncettu, avemu digià qualchì implementazione in Java chì traduce tutta a logica funziunale in logica relazionale. In più, a logica di rapprisintazioni è assai altre cose sò belli sfondate à questu, grazie à quale avemu un sanu sanu. piattaforma. Essenzialmente, usemu un RDBMS (solu PostgreSQL finu à avà) cum'è "macchina virtuale". Sta traduzzione à volte causa prublemi perchè l'ottimisatore di query RDBMS ùn cunnosci micca certe statistiche chì u FDBMS faci. In teoria, hè pussibule implementà un sistema di gestione di basa di dati chì aduprà una certa struttura cum'è almacenamiento, adattatu specificamente per a logica funziunale.

Source: www.habr.com

Add a comment