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