Bota e bazave të dhënash është zënë prej kohësh nga DBB-të relacional, ku përdoret gjuha SQL. Aq shumë, saqë llojet që po shfaqen quhen NoSQL. Ato kanë arritur të fitojnë një vend të caktuar në këtë treg, por DBB-të relacional nuk do të vdesin dhe vazhdojnë të përdoren aktivisht për qëllimet e tyre.
Në këtë artikull, dua të përshkruaj konceptin e një baze funksionale të të dhënave. Për një kuptim më të mirë, do ta bëj këtë përmes krahasimit me modelin klasik relacional. Si shembuj do të përdoren detyra nga teste të ndryshme SQL, të gjetura në internet.
Hyrje
Baza të dhënash relacional operojnë me tabela dhe fusha. Në një bazë funksionale të të dhënave, në vend të tyre do të përdoren klasa dhe funksione përkatësisht. Një fushë në një tabelë me N çelësa do të paraqitet si një funksion me N parametra. Në vend të lidhjeve midis tabelave do të përdoren funksione që kthejnë objekte të klasës, në të cilën bëhet lidhja. Në vend të JOIN do të përdoret kompozimi i funksioneve.
Para se të kalojmë drejtpërdrejt në detyrat, do të përshkruaj detyrën e logjikës së domenit. Për DDL do të përdor sintaksën PostgreSQL. Për funksionalin, sintaksën time.
Tabelat dhe fushat
Një objekt i thjeshtë Sku me fushat emri dhe çmimi:
Grafik, dokumentar
CREATE TABLE Sku
(
id bigint NOT NULL,
name character varying(100),
price numeric(10,5),
CONSTRAINT id_pkey PRIMARY KEY (id)
)
Funksionale
KLAS Sku;
emri = STRING DATASH[100] (Sku);
çmimi = NUMRI DATASH[10,5] (Sku);
Ne shpallim dy funkcione, të cilat pranojnë si parametër një Sku dhe kthejnë një tip primitiv.
Supozoni se në DBB-në funksionale çdo objekt do të ketë një kod të brendshëm, i cili gjenerohet automatikisht, dhe nëse është e nevojshme mund të referohet.
Të caktuar çmimin për produktin / dyqanin / furnizuesin. Ai mund të ndryshojë me kalimin e kohës, prandaj do të shtojmë në tabelë fushën e kohës. Do të shmangg titujt e tabelave për referencat në bazën relacional për të shkurtuar kodin:
Grafik, dokumentar
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)
)
Funksionale
KLAS Sku;
KATEGORIA Dyqani;
KATEGORIA Furnizuesi;
dataKoha = TĂ DHĂNAT DATETIME (Sku, Dyqani, Furnizuesi);
çmimi = TĂ DHĂNAT NUMERIC[10,5] (Sku, Dyqani, Furnizuesi);
Indeksi
Për shembullin e fundit, do të ndërtojmë një indeks mbi të gjithë çelësa dhe datën, në mënyrë që të mund të gjendet shpejt çmimi për një kohë të caktuar.
Grafik, dokumentar
CREATE INDEX prices_date
ON prices
(skuId, storeId, supplierId, dateTime)
Funksionale
INDEX Sku sk, Store st, Supplier sp, dateTime(sk, st, sp);
Detyrat
Të fillojmë me detyra relativisht të thjeshta, të marra nga përkatësja në Habr.
Së pari, do të shpallim logjikën domene (për bazën e të dhënave relazionale, kjo është bërë direkt në artikullin e paraqitur).
KLASA Departamenti;
emri = STRINGA DHE DATE[100] (Departamenti);
CLASS Employee;
department = DATA Department (Employee);
chief = DATA Employee (Employee);
name = DATA STRING[100] (Employee);
salary = DATA NUMERIC[14,2] (Employee);
Detyra 1.1
Shfaq listën e punonjësve që marrin pagë më të madhe se ajo e shefit të tyre të drejtpërdrejtë.
Grafik, dokumentar
select a.*
from employee a, employee b
where b.id = a.chief_id
and a.salary > b.salary
Funksionale
Zgjidh emrin(e) e Punonjësit(a) kur paga(e) e tij është > paga(e) e kryetarit(a).
Detyra 1.2
Shfaq listën e punonjësve që marrin pagën maksimale në departamentin e tyre
Grafik, dokumentar
select a.*
from employee a
where a.salary = ( select max(salary) from employee b
where b.department_id = a.department_id )
Funksionale
maxSalary 'Paga maksimale' (Departamenti s) =Â
    GRUPI MAKSIMI i pagĂ«s (PunonjĂ«si e) NĂSE departamenti(e) = s;
ZGJIDH emrin (Punonjësi a) KU paga(a) = maxSalary(departamenti(a));
// ОлО ДŃлО "Đ·Đ°ĐžĐœĐ»Đ°ĐčĐœĐžŃŃ"
SELECT name(Employee a) WHEREÂ
    salary(a) = maxSalary(GROUP MAX salary(Employee e) IF department(e) = department(a));
Të dy realizimet janë ekuivalente. Për rastin e parë në bazën e të dhënave relazionale, mund të përdorim CREATE VIEW, e cila në të njëjtën mënyrë më parë do të llogariste pagën maksimale për një departament të caktuar. Në vazhdim, për qartësinë do të përdor rastin e parë sepse ai pasqyron më mirë zgjidhjen.
Detyra 1.3
Shfaq listën e ID-ve të departamenteve, numri i punonjësve në të cilat nuk kalon 3 persona.
Grafik, dokumentar
select department_id
from employee
group by department_id
having count(*) <= 3
Funksionale
numriPunonjĂ«sve 'Numri i punonjĂ«sve' (Departamenti d) =Â
    GROUP SUM 1 IF department(Employee e) = d;
SELECT Departamenti d WHERE numriPunonjësve(d) <= 3;
Detyra 1.4
Shfaq listën e punonjësve që nuk kanë shef të caktuar, duke punuar në të njëjtin departament.
Grafik, dokumentar
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
Funksionale
Zgjedh emrin(Punëtorin a) ku NUK (departamenti(kreu(a)) = departamenti(a));
Detyra 1.5
Gjej listën e ID-ve të departamenteve me pagën totale maksimale të punonjësve.
Grafik, dokumentar
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 )
Funksionale
pagaMaksimale 'Paga Maksimale' (Departamenti d) =Â
    GROUP SUM salary(Employee e) IF department(e) = d;
maksPagaMaksimale 'Paga Maksimale e Departamenteve' () =Â
    GRUP MAX pagaMaksimale(Departamenti d);
SELECT Departamenti d KUJ PagaMaksimale(d) = maksPagaMaksimale();
Të kalojmë në detyra më të komplikuara nga një tjetër . Në të përfshihet një analizë e hollësishme se si ta implementoni këtë detyrë në MS SQL.
Detyra 2.1
Cilat shitës kanë shitur më shumë se 30 copë të produktit nr. 1 gjatë vitit 1997?
Logjika domene (si më parë për RDBMS, anashkalojmë shpalljen):
CLASS Employee 'Shitësi';
lastName 'Mbiemri' = DATA STRING[100] (Përgjegjës);
CLASS Product 'Produkt';
id = DATA INTEGER (Product);
name = DATA STRING[100] (Product);
KLASA Porosia 'Porosi';
date = DATA DATE (Order);
employee = DATA Employee (Order);
CLASS Detail 'Struktura e porosisë';
order = DATA Order (Detail);
product = DATA Product (Detail);
quantity = DATA NUMERIC[10,5] (Detail);
Grafik, dokumentar
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
Funksionale
shitur (PunĂ«simi e, NUMRI i produktit, NUMRI i vitit) =Â
    GRUPI SUM sasia(Detalet e porosisĂ« d) NĂSEÂ
        punonjĂ«s(order(d)) = e DHEÂ
        id(product(d)) = produktiId DHEÂ
        ekstraktoVitin(data(order(d))) = viti;
ZBRIT (emriMbiemër(Punësimi e) KU shitur(e, 1, 1997) > 30;
Detyra 2.2
Për çdo blerës (emri, mbiemri) gjej dy produkte (emri), për të cilat blerësi ka shpenzuar më shumë para gjatë vitit 1997.
Zgjerim logjikën domene nga shembulli i mëparshëm:
KLASi Klienti 'Klient';
contactName 'Emri i Plotë' = STRINGA NDA[100] (Klienti);
klienti = TĂ DHĂNAT Klient (Porosia);
çmimiNjĂ«sisĂ« = TĂ DHĂNAT NUMERIK[14,2] (Detaji);
zbritja = TĂ DHĂNAT NUMERIK[6,2] (Detaji);
Grafik, dokumentar
SELECT EmriKontaktit, EmriProdukti FROM (
SELECT c.EmriKontaktit, p.EmriProdukti
, ROW_NUMBER() OVER (
PARTITION BY c.EmriKontaktit
ORDER BY SUM(od.Sasia * od.CmimiNjësisë * (1 - od.Zbritja)) DESC
) AS VlerësimiNgaShuma
FROM Klientët c
JOIN Porositë o ON o.IDKlient = c.IDKlient
JOIN [Detajet e Porosive] od ON od.IDPorosie = o.IDPorosie
JOIN Produktet p ON p.IDProdukti = od.IDProdukti
WHERE VITI(o.DataEporosisë) = 1997
GROUP BY c.EmriKontaktit, p.EmriProdukti
) t
WHERE VlerësimiNgaShuma < 3
Funksionale
shuma (Detaj d) = sasia(d) * çmimiNjësisë(d) * (1 - zbritja(d));
bleu 'Bleu' (Klienti c, Produkti p, INTEGER y) =Â
    GRUPI SHUMĂ SUM(material d) NĂSEÂ
        klienti(urdhĂ«r(d)) = c DHEÂ
        produkti(d) = p DHEÂ
        nxir vitin(data(urdhri(d))) = y;
vlerĂ«simi 'VlerĂ«simi' (Klienti c, Produkti p, INTEGER y) =Â
    SHUMA PARTICIONO 1 ZBRITJA DESC blerë(c, p, y), p NGA c, y;
ZGJOJ kontaktEmrin(Klienti c), emrin(Produkti p) KU vlerësimi(c, p, 1997) < 3;
Operatori PARTITION funksionon sipas parimit: ai përmbledh shprehjen e caktuar pas SUM (këtu 1), brenda grupeve të caktuara (këtu Klienti dhe Viti, por mund të jetë çdo shprehje), duke renditur brenda grupeve sipas shprehjeve të caktuara në ORDER (këtu blerjet, dhe nëse janë të barabarta, sipas kodit të brendshëm të produktit).
Detyra 2.3
Sa produkte duhet të porositen nga furnizuesit për të përmbushur porositë aktuale.
Përsëri po zgjeron logjikën e domainit:
KLASA Përdorues 'Furnizues';
emriIShkellez = STRINGA E TĂ DHENAVE[100] (Furnizues);
furnizuesi = TĂ DHĂNAT Furnizues (Produkt);
njĂ«sitĂ«NĂ«Depot 'Stoku nĂ« depo' = TĂ DHĂNAT NUMERIK[10,3] (Produkt);
niveliIPorosisĂ« 'Norma e shitjes' = TĂ DHĂNAT NUMERIK[10,3] (Produkt);
Grafik, dokumentar
select s.EmriKompagnie, p.EmriProdukti, sum(od.Sasia) + p.NiveliIPorosisë - p.NjësiNëDepo si TePorositur
from Porositë o
join [Detajet e Porosive] od on o.IDPorosie = od.IDPorosie
join Produktet p on od.IDProdukti = p.IDProdukti
join Furnizuesit s on p.IDFurnizuesi = s.IDFurnizuesi
where o.DitaETransportit is null
group by s.EmriKompagnie, p.EmriProdukti, p.NjësiNëDepo, p.NiveliIPorosisë
having p.NjësiNëDepo < sum(od.Sasia) + p.NiveliIPorosisë
Funksionale
porositurPor por t`Ă«ngĂ«tuar, por nuk Ă«shtĂ« dĂ«rguar' (Produkti p) =Â
    GRUPI SUM sasia(OrderDetail d) NĂSE produkti(d) = p;
tëPorosisësh 'Për të porositur' (Produkti p) = porositur(p) + niveliRipor(p) - njësiNëStock(p);
Zgjidh emrin e kompanisë(furnizuesi(Produkti p)), emri(p), tëPorosisësh(p) KU tëPorosisësh(p) > 0;
Detyra me yll
Dhe shembulli i fundit personal nga unë. Ka logjikën e një rrjeti social. Njerëzit mund të bëjnë miqësi me njëri-tjetrin dhe t'i pëlqejnë njëri-tjetrit. Nga perspektiva e bazës funksionale të të dhënave, kjo do të dukej kështu:
KLASA Person;
pĂ«lqen = TĂ DHĂNA BOOLEAN (Person, Person);
miq = TĂ DHĂNA BOOLEAN (Person, Person);
ĂshtĂ« e nevojshme tĂ« gjenden kandidatĂ«t e mundshĂ«m pĂ«r miqĂ«si. MĂ« formalisht duhet tĂ« gjejmĂ« tĂ« gjithĂ« njerĂ«zit A, B, C tĂ« tillĂ« qĂ« A Ă«shtĂ« mik me B, B Ă«shtĂ« mik me C, A e pĂ«lqen C, por A nuk Ă«shtĂ« mik me C.
Nga perspektiva e bazës funksionale të të dhënave, pyetja do të dukej kështu:
Zgjidh Person a, Person b, Person c kuÂ
    i pĂ«lqen(a, c) DHE NUK janë miq(a, c) DHEÂ
    janë miq(a, b) DHE janë miq(b, c);
Lexuesit i propozohet të zgjidhë këtë detyrë në SQL vetë. Supozohet se miqtë janë shumë më pak se ata që pëlqejnë. Prandaj ata ndodhen në tabela të veçanta. Në rast zgjidhje të suksesshme ka gjithashtu një detyrë me dy yje. Në atë rast, miqësia nuk është simetrike. Në bazën funksionale të të dhënave do të dukej kështu:
Zgjidh Person a, Person b, Person c kuÂ
    i pĂ«lqen(a, c) DHE NUK janë miq(a, c) DHEÂ
    (mikut(a, b) OSE mikut(b, a)) DHEÂ
    (mikut(b, c) OSE mikut(c, b));
UPD: zgjidhja e detyrës me yllin e parë dhe të dytë nga :
Zgjidhni
pl.PersonAID
,pf.PersonAID
,pff.PersonAID
Nga Persona si p
--Pëlqime
Bashkoni PersonRelationShip si pl ON pl.PersonAID = p.PersonID
DHE pl.Relation = 'Pëlqim'
--Miq
Bashkoni PersonRelationShip si pf ON pf.PersonAID = p.PersonID
DHE pf.Relation = 'Mik'
--Miq të Miqve
Bashkoni PersonRelationShip si pff ON pff.PersonAID = pf.PersonBID
DHE pff.PersonBID = pl.PersonBID
DHE pff.Relation = 'Mik'
--Ende nuk janë mik
LEFT JOIN PersonRelationShip si pnf ON pnf.PersonAID = p.PersonID
DHE pnf.PersonBID = pff.PersonBID
DHE pnf.Relation = 'Mik'
KU pnf.PersonAID ĂSHTE NULL
;ME PersonRelationShipCollapsed SI (
Zgjidhni pl.PersonAID
,pl.PersonBID
,pl.Relation
Nga #PersonRelationShip si pl
UNION
Zgjidhni pl.PersonBID SI PersonAID
,pl.PersonAID SI PersonBID
,pl.Relation
Nga #PersonRelationShip si pl
)
Zgjidhni
pl.PersonAID
,pf.PersonBID
,pff.PersonBID
Nga #Persona si p
--Pëlqime
Bashkoni PersonRelationShipCollapsed si pl ON pl.PersonAID = p.PersonID
DHE pl.Relation = 'Pëlqim'
--Miq
Bashkoni PersonRelationShipCollapsed si pf ON pf.PersonAID = p.PersonID
DHE pf.Relation = 'Mik'
--Miq të Miqve
Bashkoni PersonRelationShipCollapsed si pff ON pff.PersonAID = pf.PersonBID
DHE pff.PersonBID = pl.PersonBID
DHE pff.Relation = 'Mik'
--Ende nuk janë mik
LEFT JOIN PersonRelationShipCollapsed si pnf ON pnf.PersonAID = p.PersonID
DHE pnf.PersonBID = pff.PersonBID
DHE pnf.Relation = 'Mik'
KU pnf.[PersonAID] ĂSHTE NULL
Përfundim
Duhet theksuar se sintaksa e dhënë e gjuhës është vetëm një nga variantet e implementimit të konceptit të dhënë. U bazua në SQL, dhe qëllimi ishte që të ishte sa më i ngjashëm me të. Sigurisht, disa mund të mos pëlqejnë emrat e fjalëve kyçe, regjistrat e fjalëve dhe të tjera. Këtu e rëndësishme është koncepcioni vetë. Nëse dëshirohet, mund të bëhet një sintaksë e ngjashme edhe me C++, edhe me Python.
Koncesioni i përshkruar i bazës së të dhënave, sipas mendimit tim, ka këto avantazhe:
- Thjeshtësia. Kjo është një tregues relativisht subjektiv, që nuk është i dukshëm në raste të thjeshta. Por nëse shikoni raste më të komplikuara (për shembull, problemet me yje), atëherë, sipas mendimit tim, është shumë më e lehtë të shkruani kërkesa të tilla.
- Inxhinieria. Në disa shembuj kam shpallur funksione ndërmjetëse (për shembull, shitur, blerë etj.), nga të cilat ndërtohen funksionet e mëpasshme. Kjo lejon, nëse nevojitet, që të ndryshoni logjikën e disa funksioneve pa ndryshuar logjikën e atyre që varen nga ato. Për shembull, mund të bëhet që shitjet shitur ata janë konsideruar në objekte krejt ndryshe, ndërsa logjika tjetër nuk do të ndryshojë. Po, në RDBMS këtë mund ta realizojmë me ndihmën e CREATE VIEW. Por, nëse shkruajmë gjithë logjikën në këtë mënyrë, ajo do të duket jo shumë e lexueshme.
- Mungesa e shkëputjes semantike. Kjo bazë të dhënash operon me funksione dhe klasa (në vend të tabelave dhe fushave). Në të njëjtën mënyrë si në programimin klasik (nëse e konsiderojmë metodën si një funksion me parametrin e parë si klasën, të cilës i përket). Prandaj, do të jetë ndjeshëm më e lehtë "ta lidhësh" me gjuhët universale të programimit. Për më tepër, kjo koncept do të lejojë realizimin e funksioneve shumë më komplekse. Për shembull, mund të integrojmë operatorë të tillë në bazën e të dhënave:
KUSHTETI sold(Employee e, 1, 2019) > 100 NĂSE name(e) = 'Petja' MESAZH 'Diçka Petja po shet shumĂ« nga njĂ« mall nĂ« vitin 2019';
- Trashëgimia dhe polimorfizmi. Në një bazë të dhënash funksionale, mund të prezantojmë trashëgiminë e shumëfishtë përmes konstruktimeve CLASS ClassP: Class1, Class2 dhe të realizojmë polimorfizmin e shumëfishtë. Si pikërisht, ndoshta do të shkruaj në artikujt e ardhshëm.
Megjithatë, edhe pse është vetëm një koncept, ne tashmë kemi një realizim të caktuar në Java, i cili translyon gjithë logjikën funksionale në logjikën relacionalre. Përveç saj, lidhet bukur logjika e prezantimeve dhe shumë gjëra të tjera, duke krijuar një tërësi . Në thelb, ne po e përdorim RDBMS (deri tani vetëm PostgreSQL) si "makinë virtuale". Me këtë transkriptim, ndonjëherë lindin probleme, pasi optimizuesi i kërkesave të RDBMS nuk e di një statistikë të caktuar, të cilën e di FDBMS. Në teori, është e mundur të realizohet një sistem i menaxhimit të bazës së të dhënave, i cili do të përdorë si magazinë ndonjë strukturë të përshtatur veçanërisht për logjikën funksionale.
Burimi: habr.com
