Datu baseen mundua SQL lengoaia erabiltzen duten DBMS erlazionalek hartu dute aspalditik. Hainbeste non sortzen ari diren barietateei NoSQL deitzen zaie. Merkatu honetan toki jakin bat irabaztea lortu zuten, baina DBMS erlazionalak ez dira hilko, eta aktiboki erabiltzen jarraitzen dute beren helburuetarako.
Artikulu honetan, datu-base funtzionalaren kontzeptua deskribatu nahi dut. Hobeto ulertzeko, eredu erlazional klasikoarekin alderatuz egingo dut. Adibide gisa, Interneten aurkitutako SQL proba ezberdinetako zereginak erabiliko dira.
Sarrera
Erlazio datu-baseek tauletan eta eremuetan funtzionatzen dute. Datu-base funtzional batean, klaseak eta funtzioak erabiliko dira horren ordez, hurrenez hurren. N gakoak dituen taula bateko eremu bat N parametroen arabera irudikatuko da. Taulen arteko esteken ordez, esteka doan klaseko objektuak itzultzen dituzten funtzioak erabiliko dira. Funtzio-konposizioa JOIN-en ordez erabiliko da.
Zereginetara zuzenean jarraitu aurretik, domeinu-logikaren zeregina deskribatuko dut. DDLrako, PostgreSQL sintaxia erabiliko dut. Funtzionalerako bere sintaxia.
Taulak eta eremuak
Sku objektu sinple bat izen eta prezio eremuekin:
erlazionala
CREATE TABLE Sku
(
id bigint NOT NULL,
name character varying(100),
price numeric(10,5),
CONSTRAINT id_pkey PRIMARY KEY (id)
)
funtzionala
CLASS Sku;
name = DATA STRING[100] (Sku);
price = DATA NUMERIC[10,5] (Sku);
Bi iragartzen ditugu funtzio, Sku parametro bat sarrera gisa hartzen dutenak eta mota primitibo bat itzultzen dutenak.
Suposatzen da DBMS funtzional batean, objektu bakoitzak automatikoki sortzen den barne-koderen bat izango duela eta behar izanez gero atzitu daitekeela.
Ezar dezagun produktuaren / dendaren / hornitzailearen prezioa. Denborarekin alda daiteke, beraz, gehi diezaiogun denbora-eremu bat taulari. Datu-base erlazional bateko taulen deklarazioa saltatuko dut kodea laburtzeko:
erlazionala
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)
)
funtzionala
CLASS Sku;
CLASS Store;
CLASS Supplier;
dateTime = DATA DATETIME (Sku, Store, Supplier);
price = DATA NUMERIC[10,5] (Sku, Store, Supplier);
Indizeak
Azken adibiderako, eraiki dezagun indize bat gako eta data guztietan, denbora jakin baterako prezioa azkar aurkitu ahal izateko.
erlazionala
CREATE INDEX prices_date
ON prices
(skuId, storeId, supplierId, dateTime)
funtzionala
INDEX Sku sk, Store st, Supplier sp, dateTime(sk, st, sp);
zereginak
Has gaitezen dagokionetik hartutako problema erraz samarrak
Lehenik eta behin, deklaratu dezagun domeinuaren logika (erlaziozko datu-base baterako, hau zuzenean goiko artikuluan egiten da).
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);
1.1. ataza
Erakutsi berehalako arduradunarenak baino soldata handiagoak jasotzen dituzten langileen zerrenda.
erlazionala
select a.*
from employee a, employee b
where b.id = a.chief_id
and a.salary > b.salary
funtzionala
SELECT name(Employee a) WHERE salary(a) > salary(chief(a));
1.2. ataza
Erakutsi beren sailean soldatarik handiena irabazten duten langileen zerrenda
erlazionala
select a.*
from employee a
where a.salary = ( select max(salary) from employee b
where b.department_id = a.department_id )
funtzionala
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));
Bi inplementazio baliokideak dira. Datu-base erlazionaleko lehen kasurako, SORTU IKUSPEGIA erabil dezakezu, era berean, lehenik eta behin, bertan dagoen sail jakin baterako gehienezko soldata kalkulatuko duena. Etorkizunean, argitzeko, lehen kasua erabiliko dut, irtenbidea hobeto islatzen baitu.
1.3. ataza
Bistaratu saileko IDen zerrenda, zeinetan langile kopurua 3 pertsona baino gehiagokoa ez den.
erlazionala
select department_id
from employee
group by department_id
having count(*) <= 3
funtzionala
countEmployees 'ΠΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΡΠΎΡΡΡΠ΄Π½ΠΈΠΊΠΎΠ²' (Department d) =
GROUP SUM 1 IF department(Employee e) = d;
SELECT Department d WHERE countEmployees(d) <= 3;
1.4. ataza
Erakutsi sail berean lanean esleitutako zuzendaririk ez duten langileen zerrenda.
erlazionala
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
funtzionala
SELECT name(Employee a) WHERE NOT (department(chief(a)) = department(a));
1.5. ataza
Bilatu langileen gehienezko soldata osoa duten saileko IDen zerrenda.
erlazionala
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 )
funtzionala
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();
Joan gaitezen zeregin konplexuagoetara beste batetik
2.1. ataza
Zein saltzaileek 1997. elementuaren 30 pieza baino gehiago saldu zituzten 1an?
Domeinu-logika (lehen bezala, RDBMS-en deklarazioa saltatzen dugu):
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);
erlazionala
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
funtzionala
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;
2.2. ataza
Bezero bakoitzeko (izen-abizenak), bilatu bezeroak diru gehien gastatu zuen bi elementu (izena) 1997an.
Aurreko adibideko domeinu-logika hedatuz:
CLASS Customer 'ΠΠ»ΠΈΠ΅Π½Ρ';
contactName 'Π€ΠΠ' = DATA STRING[100] (Customer);
customer = DATA Customer (Order);
unitPrice = DATA NUMERIC[14,2] (Detail);
discount = DATA NUMERIC[6,2] (Detail);
erlazionala
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
funtzionala
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;
PARTITION operadoreak printzipio honen arabera funtzionatzen du: SUM (hemen 1) ondoren zehaztutako adierazpena batutzen du zehaztutako taldeen barruan (hemen Bezeroa eta Urtea, baina edozein adierazpen izan daiteke), ORDENA atalean zehaztutako esamoldeen arabera ordenatuz taldeetan ( hemen erosi, eta berdinak badira, barruko produktuaren kodearen arabera).
2.3. ataza
Hornitzaileei zenbat salgai eskatu behar zaizkien unean uneko eskaerak betetzeko.
Zabal dezagun berriro domeinuaren logika:
CLASS Supplier 'ΠΠΎΡΡΠ°Π²ΡΠΈΠΊ';
companyName = DATA STRING[100] (Supplier);
supplier = DATA Supplier (Product);
unitsInStock 'ΠΡΡΠ°ΡΠΎΠΊ Π½Π° ΡΠΊΠ»Π°Π΄Π΅' = DATA NUMERIC[10,3] (Product);
reorderLevel 'ΠΠΎΡΠΌΠ° ΠΏΡΠΎΠ΄Π°ΠΆΠΈ' = DATA NUMERIC[10,3] (Product);
erlazionala
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
funtzionala
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;
Zeregin izartxo batekin
Eta azken adibidea pertsonalki nirea da. Sare sozial baten logika dago. Jendea elkarren lagun izan daiteke eta elkar gustatu. Datu-base funtzionalaren ikuspegitik, hau itxura hau izango litzateke:
CLASS Person;
likes = DATA BOOLEAN (Person, Person);
friends = DATA BOOLEAN (Person, Person);
Beharrezkoa da adiskidetasunerako hautagai posibleak aurkitzea. Formalkiago, A, B, C pertsona guztiak aurkitu behar dituzu, A B-ren laguna dela eta B C-ren laguna dela, A-k C gustatzen zaiola, baina A ez da C-ren laguna.
Datu-base funtzionalaren ikuspuntutik, kontsultak honela izango luke:
SELECT Person a, Person b, Person c WHERE
likes(a, c) AND NOT friends(a, c) AND
friends(a, b) AND friends(b, c);
Irakurleari arazo hau modu independentean konpontzera gonbidatzen da SQLn. Suposatzen da gustuko dutenak baino askoz lagun gutxiago daudela. Hori dela eta, taula bereizietan daude. Konponbide arrakastatsua izanez gero, bi izarrekin arazo bat ere badago. Bere adiskidetasuna ez da simetrikoa. Datu-base funtzional batean honela izango litzateke:
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: lehenengo eta bigarren izartxoarekin arazoaren konponbidea
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
Ondorioa
Kontuan izan behar da hizkuntzaren goiko sintaxia aurreko kontzeptua ezartzeko aukeretako bat besterik ez dela. SQL izan zen oinarritzat hartu zena, eta helburua ahalik eta antzekoena egitea zen. Jakina, norbaitek hitz gakoen izenak, hitzen kasuak, etab. Hemen gauza nagusia kontzeptua bera da. Nahi izanez gero, C ++ eta Python antzeko sintaxia egin ditzakezu.
Deskribatutako datu-basearen kontzeptuak, nire ustez, abantaila hauek ditu:
- arintzeko. Adierazle nahiko subjektiboa da, kasu errazetan agerikoa ez dena. Baina kasu konplexuagoak aztertzen badituzu (adibidez, izartxoak dituzten atazak), orduan, nire ustez, horrelako kontsultak idaztea askoz errazagoa da.
- ΠΠ½ΠΊΠ°ΠΏΡΡΠ»ΡΡΠΈΡ. Adibide batzuetan, bitarteko funtzioak deklaratu ditut (adibidez, saldu, erosi etab.), zeinetatik ondorengo funtzioak eraiki ziren. Horrek funtzio batzuen logika aldatzeko aukera ematen du, behar izanez gero, horien menpe daudenen logika aldatu gabe. Adibidez, salmentak egin ditzakezu saldu objektu guztiz desberdinetatik kalkulatu ziren, gainerako logika ez da aldatuko. Bai, RDBMSn hau CREATE VIEW-rekin egin daiteke. Baina logika guztia horrela idazten baduzu, ez da oso irakurgarria izango.
- Hutsune Semantikorik ez. Datu-base horrek funtzio eta klaseekin funtzionatzen du (taulen eta eremuen ordez). Programazio klasikoan gertatzen den moduan (metodo bat dagokion klase moduan lehen parametroa duen funtzioa dela suposatuz). Horren arabera, askoz errazagoa izan beharko litzateke programazio lengoaia unibertsalekin "lagunak egitea". Gainera, kontzeptu honek askoz funtzio konplexuagoak ezartzeko aukera ematen du. Adibidez, honelako adierazpenak txerta ditzakezu datu-basean:
CONSTRAINT sold(Employee e, 1, 2019) > 100 IF name(e) = 'ΠΠ΅ΡΡ' MESSAGE 'Π§ΡΠΎ-ΡΠΎ ΠΠ΅ΡΡ ΠΏΡΠΎΠ΄Π°Π΅Ρ ΡΠ»ΠΈΡΠΊΠΎΠΌ ΠΌΠ½ΠΎΠ³ΠΎ ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΡΠΎΠ²Π°ΡΠ° Π² 2019 Π³ΠΎΠ΄Ρ';
- Herentzia eta polimorfismoa. Datu-base funtzional batean, herentzia anitza sar dezakezu CLASS ClassP-ren bidez: Class1, Class2 eraikitzen eta polimorfismo anitz inplementatu. Nola zehazki, beharbada hurrengo artikuluetan idatziko dut.
Nahiz eta kontzeptu bat besterik ez den, dagoeneko badugu Java-n logika funtzional guztia erlazio-logika bihurtzen duen inplementazioren bat. Gainera, irudikapenen logika eta beste gauza asko ederki izorratuta daude, horri esker osotasun bat lortzen dugu
Iturria: www.habr.com