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
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
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
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.
Source: www.habr.com