Sistemi funksional i Menaxhimit të të Dhënave

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 artikulli 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 artikulli. 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 dss_kalika:

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 platforma. 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

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster