Funktsionaalne andmebaasi haldustarkvara

Andmebaasid on kaua olnud relatsiooniliste andmebaaside käes, kus kasutatakse SQL-i keelt. Nii tugev on see mõju, et ilmnevad uued variandid, mida nimetatakse NoSQL-iks. Need on suutnud endale teatud koha turul kindlustada, kuid relatsioonilised andmebaasid ei plaani kaduda ja jätkavad aktiivset kasutamist oma eesmärkide jaoks.

Selles artiklis soovin ma tutvustada funktsionaalse andmebaasi kontseptsiooni. Parema arusaamise nimel teen seda, võrreldes seda klassikalise relatsioonilise mudeliga. Näidetena kasutatakse erinevate SQL-testide ülesandeid, mis leitud internetis.

Sissejuhatus

Relatsioonilised andmebaasid töötavad tabelite ja väljadega. Funktsionaalses andmebaasis kasutatakse nende asemel klasse ja funktsioone. Väli tabelis N võtmega esindatakse funktsioonina, millel on N parameetrit. Tabelite vaheliste seoste asemel kasutatakse funktsioone, mis tagastavad klassi objekti, millele seos osutab. JOINi asemel kasutatakse funktsioonide kompositsiooni.

Enne kui liikuda otse ülesannete juurde, kirjeldan domeeniloogika ülesannet. DDL-i jaoks kasutan PostgreSQL-i süntaksit. Funktsionaalse jaoks oma süntaksit.

Tabelid ja väljad

Lihtne objekt Sku väljadega nimetus ja hind:

Relatsiooniline

CREATE TABLE Sku
(
    id bigint NOT NULL,
    name character varying(100),
    price numeric(10,5),
    CONSTRAINT id_pkey PRIMARY KEY (id)
)

Funktsionaalne

Klass Sku;
nimi = ANDME STRING[100] (Sku);
hind = ANDME NUMERIC[10,5] (Sku);

Me kuulutame välja kaks funktsioone, mis võtavad ühe parameetri Sku ja tagastavad primitiivse tüübi.

Eeldatakse, et funktsionaalses andmebaasis on igal objektil mingi sisemine kood, mis genereeritakse automaatselt ja millele saab vajadusel viidata.

Seame toote / poe / tarnija hinna. See võib aja jooksul muutuda, seega lisame tabelisse väljad ajaks. Jätan relatsioonilise andmebaasi soovituslikud tabelite väljakuulutamised vahele koodi lühendamiseks:

Relatsiooniline

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)
)

Funktsionaalne

Klass Sku;
KLASS Kauplus;
KLASS Tarnija;
kuupäevAeg = ANDMED AEG (Sku, Kauplus, Tarnija);
hind = ANDMED NUMERIK[10,5] (Sku, Kauplus, Tarnija);

Indeksid

Viimase näite jaoks loome indeksi kõikide võtmete ja kuupäeva järgi, et saaksime kiiresti leida hinna teatud aja hetkel.

Relatsiooniline

CREATE INDEX prices_date
    ON prices
    (skuId, storeId, supplierId, dateTime)

Funktsionaalne

INDEX Sku sk, Kauplus st, Tarnija sp, dateTime(sk, st, sp);

Ülesanded

Alustame suhteliselt lihtsatest ülesannetest, mis on võetud vastavast artikleid Habrist.

Esiteks kuulutame välja domeeniloogika (releeriva andmebaasi jaoks on see tehtud otse artikelis).

Klass Osakond;
nimi = ANDMED STRING[100] (Osakond);

Klass Employee;
osakond = ANDMED Department (Employee);
juht = ANDMED Employee (Employee);
nimi = ANDMED STRING[100] (Employee);
palk = ANDMED NUMERIC[14,2] (Employee);

Ülesanne 1.1

Väljastage töötajate nimekiri, kelle palk on suurem kui otseülemal.

Relatsiooniline

vali a.*
from   employee a, employee b
where  b.id = a.chief_id
and    a.salary > b.salary

Funktsionaalne

VALI name(Töötaja a) KUS salary(a) > salary(pealik(a));

Ülesanne 1.2

Väljastage töötajate nimekiri, kellel on oma osakonnas kõrgeim palk.

Relatsiooniline

vali a.*
from   employee a
where  a.salary = ( vali max(salary) from employee b
                    where  b.department_id = a.department_id )

Funktsionaalne

maxSalary 'Maksimaalne palk' (Osakond s) = 
    GROUP MAX palk(Töötaja e) KUI osakond(e) = s;
SELECT name(Töötaja a) KUS palk(a) = maxSalary(osakond(a));

// или если "заинлайнить"
VALI nimi(Employee a) KUS 
    palk(a) = maxSalary(GROUP MAX palk(Employee e) KUI osakond(e) = osakond(a));

Mõlemad teostused on ekvivalentsed. Esimese juhtumi jaoks saab releerivas andmebaasis kasutada CREATE VIEW, mis arvutab samamoodi esmalt konkreetse osakonna maksimaalse palga. Edasi lisan ma selguse huvides esimese juhtumi, kuna see peegeldab paremini lahendust.

Ülesanne 1.3

Väljastage osakondade ID-d, kus töötajate arv ei ületa 3 inimest.

Relatsiooniline

vali department_id
from   employee
group  by department_id
having count(*) <= 3

Funktsionaalne

countEmployees 'Töötajate arv' (Department d) = 
    GROUP SUM 1 IF department(Employee e) = d;
VALI Department d KUS countEmployees(d) <= 3;

Ülesanne 1.4

Väljastage töötajate nimekiri, kellel pole välja määratud juhti, kes töötab samas osakonnas.

Relatsiooniline

vali 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

Funktsionaalne

VALI nimi(Töötaja a) KUS EI ole (department(juhataja(a)) = department(a));

Ülesanne 1.5

Leidke osakondade ID-d, mille töötajate kogupalk on maksimaalne.

Relatsiooniline

with sum_salary as
  ( vali department_id, sum(salary) palk
    from   employee
    group  by department_id )
vali department_id
from   sum_salary a       
where  a.salary = ( vali max(salary) from sum_salary )

Funktsionaalne

palkasumma 'Maksimaalne palk' (Osakond d) = 
    GROUP SUM salary(Employee e) IF department(e) = d;
maxPalkSum 'Maksimaalne palk osakondade kaupa' () = 
    GRUPI MAX palkasumma(Osakond d);
VALI Osakond d KUS palkasumma(d) = maxPalkSum();

Liigume keerukamate ülesannete juurde teisest artikleid. Seal on üksikasjalik ülevaade sellest, kuidas seda ülesannet MS SQL-is rakendada.

Ülesanne 2.1

Millised müüjad müüsid 1997. aastal rohkem kui 30 tükki toodet nr 1?

Domeeniloogika (nagu ennem RDBS-is, jätame kuulutuse vahele):

CLASS Employee 'Müüja';
lastName 'Perekonnanimi' = DATA STRING[100] (Employee);

Klass Product 'Toode';
id = ANDMED INTEGER (Product);
nimi = ANDMED STRING[100] (Product);

Klass Tellimus 'Tellimus';
kuupäev = ANDMED DATE (Order);
töötaja = ANDMED Employee (Order);

Klass Detail 'Tellimuse rida';

tellimus = ANDMED Order (Detail);
toode = ANDMED Product (Detail);
kogus = ANDMED NUMERIC[10,5] (Detail);

Relatsiooniline

vali Perekonnanimi
from Employees as e
where (
  vali sum(od.Quantity)
  from [Order Details] as od
  where od.ProductID = 1 and od.OrderID in (
    vali o.OrderID
    from Orders as o
    where year(o.OrderDate) = 1997 and e.EmployeeID = o.EmployeeID)
) > 30

Funktsionaalne

müüdud (Töötaja e, INTEGER tooteId, INTEGER aasta) = 
    RÜHMA SUMMA kogus(TellimusDetail d) KUI 
        töötaja(tellimus(d)) = e JA 
        id(toode(d)) = tooteId JA 
        aasta(käibedatum(tellimus(d))) = aasta;
VALI perekonnanimi(Töötaja e) KUS müüdud(e, 1, 1997) > 30;

Ülesanne 2.2

Iga kliendi (nimi, perekonnanimi) jaoks leidke kaks toodet (nimetust), millele klient kulutas 1997. aastal kõige rohkem raha.

Kohandame eelnevas näites domeeniloogikat:

CLASS Customer 'Klient';
contactName 'Eesnimi' = DATA STRING[100] (Customer);

customer = DATA Customer (Order);

unitPrice = DATA NUMERIC[14,2] (Detail);
discount = DATA NUMERIC[6,2] (Detail);

Relatsiooniline

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

Funktsionaalne

sum(Detail d) = quantity(d) * unitPrice(d) * (1 - discount(d));
ostetud 'Ostis' (Klient c, Toode p, KOGUS y) = 
    RÜHM SUM sum(Detaail d) KUI 
        klient(keerake(d)) = c JA 
        toode(d) = p JA 
        extractYear(date(order(d))) = y;
hinnang 'Hinnang' (Klient c, Toode p, KOGUS y) = 
    PARTITION SUM 1 ORDER DESC bought(c, p, y), p BY c, y;
VALI kontaktNimi(Klient c), nimi(Toode p) KUIDAS hinnang(c, p, 1997) < 3;

PARTITION operator works as follows: it sums the expression specified after SUM (here 1), within the specified groups (here Customer and Year, but it can be any expression), sorting within groups according to the expressions specified in ORDER (here bought, and if equal, by the internal product code).

Task 2.3

How many products need to be ordered from suppliers to fulfill current orders.

Again, we are expanding the domain logic:

KLASS Tarnija 'Tarnija';
companyName = ANDMED STRING[100] (Tarnija);

supplier = DATA Supplier (Product);

unitsInStock 'Stock remaining' = DATA NUMERIC[10,3] (Product);
reorderLevel 'Sales norm' = DATA NUMERIC[10,3] (Product);

Relatsiooniline

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

Funktsionaalne

tellitud, kuid mitte saadetud (Toode p) = 
    GRUPI SUMMA kogus(OrderDetail d) KUI toode(d) = p;
tellida 'Tellimiseks' (Toode p) = tellitud(p) + uue tellimise tase(p) - laos olevad üksused(p);
VALI ettevõtte nimi(tootja(Toode p)), nimi(p), tellida(p) KUS tellida(p) > 0;

Star task

And finally a personal example from me. There is a social network logic. People can be friends with each other and can like each other. From the perspective of a functional database, it would look like this:

CLASS Inimene;
meeldib = ANDMED BOOLEAN (Inimene, Inimene);
sõbrad = ANDMED BOOLEAN (Inimene, Inimene);

It is necessary to find possible candidates for friendship. More formally, it is required to find all people A, B, C such that A is friends with B, and B is friends with C, A likes C, but A is not friends with C.
From the perspective of a functional database, the query would look like this:

VALI Inimene a, Inimene b, Inimene c KUS 
    meeldib(a, c) JA EI ole sõbrad(a, c) JA 
    sõbrad(a, b) JA sõbrad(b, c);

The reader is invited to solve this SQL task on their own. It is assumed that friends are much fewer than those who are liked. Therefore, they are placed in separate tables. In case of a successful solution, there is also a task with two stars. In it, friendship is not symmetrical. From the perspective of a functional database, it would look like this:

VALI Inimene a, Inimene b, Inimene c KUS 
    meeldib(a, c) JA EI ole sõbrad(a, c) JA 
    (sõbrad(a, b) VÕI sõbrad(b, a)) JA 
    (sõbrad(b, c) VÕI sõbrad(c, b));

UPD: solution to the task with the first and second stars from dss_kalika:

VALI 
   pl.PersonAID
  ,pf.PersonAID
  ,pff.PersonAID
KUST VALITUD INIMESTE TABELIST AS p
--Meeldimised                      
LIITU InimeseSuhe AS pl ON pl.PersonAID = p.PersonID
                                  JA pl.Suhe = 'Meeldimine'
--Sõbrad                     
LIITU InimeseSuhe AS pf ON pf.PersonAID = p.PersonID 
                                  JA pf.Suhe = 'Sõber'
--Sõprade Sõbrad              
LIITU InimeseSuhe AS pff ON pff.PersonAID = pf.PersonBID
                                   JA pff.PersonBID = pl.PersonBID
                                   JA pff.Suhe = 'Sõber'
--Pole veel sõbrad         
VASAKLIIT InimeseSuhe AS pnf ON pnf.PersonAID = p.PersonID
                                   JA pnf.PersonBID = pff.PersonBID
                                   JA pnf.Suhe = 'Sõber'
KUS pnf.PersonAID ON NULL 

;KOOS PersonSuheKoondatud KUI (
  VALI pl.PersonAID
        ,pl.PersonBID
        ,pl.Suhe 
  KUST #PersonSuhe      AS pl 
  
  ÜHENDUS 

  VALI pl.PersonBID KUI PersonAID
        ,pl.PersonAID KUI PersonBID
        ,pl.Suhe
  KUST #PersonSuhe      AS pl 
)
VALI 
   pl.PersonAID
  ,pf.PersonBID
  ,pff.PersonBID
KUST #Inimesed                      AS p
--Meeldimised                      
LIITU PersonSuheKoondatud  AS pl ON pl.PersonAID = p.PersonID
                                 JA pl.Suhe = 'Meeldimine'                                  
--Sõbrad                          
LIITU PersonSuheKoondatud  AS pf ON pf.PersonAID = p.PersonID 
                                 JA pf.Suhe = 'Sõber'
--Sõprade Sõbrad                   
LIITU PersonSuheKoondatud  AS pff ON pff.PersonAID = pf.PersonBID
                                 JA pff.PersonBID = pl.PersonBID
                                 JA pff.Suhe = 'Sõber'
--Pole veel sõbrad                   
VASAKLIIT PersonSuheKoondatud AS pnf ON pnf.PersonAID = p.PersonID
                                   JA pnf.PersonBID = pff.PersonBID
                                   JA pnf.Suhe = 'Sõber'
KUS pnf.[PersonAID] ON NULL 

Kokkuvõte

Tasub märkida, et antud keelestrimni süntaks on vaid üks võimalus antud kontseptsiooni rakendamiseks. Aluseks on võetud just SQL, et muuta see võimalikult sarnaseks. Loomulikult võivad mõned inimesed olla rahulolematu märksõnade nimetuse, sõnade registreerimise ja muu üle. Peamine on siin kontseptsioon. Soovi korral on võimalik luua ka C++, ja Python'i sarnane süntaks.

Kirjeldatud andmebaasi kontseptsioonil on minu arvates järgmised eelised:

  • Lihtsus. See on suhteliselt subjektiivne näitaja, mis ei ole lihtsates olukordades ilmne. Kuid kui vaadata keerulisemaid olukordi (näiteks täheülesanne), siis on minu arvates selliste päringute kirjutamine oluliselt lihtsam.
  • Kaapseldamine. Mõnedes näidetes olen kuulutanud vahefunktsioone (näiteks, müüdud, ostetud jne), millest järgnevad funktsioonid sõltuvad. See võimaldab vajadusel teatud funktsioonide loogikat muuta, mõjutamata sõltuvaid funktsioone. Näiteks on võimalik korraldada, et müügid müüdud peeti teistest objektidest, samal ajal kui ülejäänud loogika ei muutu. Jah, RDBMS-is on seda võimalik ellu viia kasutades CREATE VIEW. Kuid kui kogu loogika nii kirjutada, siis ei ole see väga loetav.
  • Semantilise lõhe puudumine. Selline andmebaas opereerib funktsioonide ja klassidega (lauad ja väljad asemel). Just nagu klassikalises programmeerimises (kui arvestada, et meetod on funktsioon, mille esimeseks parameetriks on klass, mille juurde see kuulub). Seega, „sõbrustada“ universaalsete programmeerimiskeeltega peaks oluliselt lihtsam olema. Lisaks võimaldab see kontseptsioon rakendada palju keerukamaid funktsioone. Näiteks saab andmebaasi integreerida järgmisi tüüpi operaatorid:

    CONSTRAINT sold(Employee e, 1, 2019) > 100 IF name(e) = 'Petja' MESSAGE 'Midagi Petja müüb 2019. aastal liiga palju ühte toodet;'}

  • Pärimine ja polimorfism. Funktsionaalses andmebaasis saab mitmekordset pärimist tutvustada struktuuridega CLASS ClassP: Class1, Class2 ja rakendada mitmekordset polimorfismi. Kuidas täpselt, võib-olla kirjutan järgmistes artiklites.

Kuigi see on vaid kontseptsioon, on meil juba olemasolev teostus Java-s, mis tõlgib kogu funktsionaalse loogika suhtelise loogikani. Pluss sellele on kaunilt seotud esitluste loogika ja palju muud, mille tõttu saadakse terviklik platvorm. Põhimõtteliselt kasutame RDBMS-i (praegu ainult PostgreSQL) kui „virtuaalmasinat“. Sellise tõlke korral esinevad mõnikord probleemid, kuna RDBMS-i küsimuste optimeerija ei tunne teatud statistikat, mida tunneb FDBMS. Teoreetiliselt on võimalik ellu viia andmebaasisüsteem, mis kasutab salvestusena mingit struktuuri, mis on kohandatud just funktsionaalse loogika jaoks.

Allikas: habr.com

Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster