Funkcionális DBMS

A közelmúltban a relációs DBMS-ek dominálták az adatbázisok világát, ahol az SQL nyelvet alkalmazzák. Olyannyira, hogy az új típusokat NoSQL néven emlegetik. Sikerült megszerezniük a maguk helyét ezen a piacon, de a relációs DBMS-ek nem szándékoznak eltűnni, és továbbra is aktívan használják őket a saját céljaikra.

Ebben a cikkben a funkcionális adatbázis koncepcióját szeretném leírni. A jobb megértés érdekében ezt a klasszikus relációs modellel való összehasonlítással fogom tenni. Példaként különböző online SQL tesztfeladatokat fogok használni.

Bevezetés

A relációs adatbázisok táblákkal és mezőkkel dolgoznak. A funkcionális adatbázisban ezek helyett osztályokat és funkciókat használnak. A N kulcsot tartalmazó táblamezőt N paraméterből álló funkcióként fogják ábrázolni. A táblák közötti kapcsolatok helyett funkciók lesznek, amelyek visszaadják azt az osztályt, amellyel a kapcsolat van. A JOIN helyett a funkciók összetételét használják.

Mielőtt közvetlenül a feladatokhoz hozzákezdenék, leírom a domain logika feladatát. A DDL-hez a PostgreSQL szintaxisát fogom használni. A funkcionálishoz a saját szintaxisomat.

Táblák és mezők

Egy egyszerű Sku objektum neve és ára mezőkkel:

Relációs

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

Funkcionális

OSZTÁLY Sku;
név = ADAT SZÖVEG[100] (Sku);
ár = ADAT SZÁM[10,5] (Sku);

Két funkciót, amelyek egy Sku paramétert vesznek fel, és primitív típust adnak vissza.

Feltételezzük, hogy a funkcionális DBMS-ben minden objektumnak lesz egy belső kódja, amely automatikusan generálódik, és amelyhez szükség esetén hozzáférhetünk.

Állítsuk be az árát a terméknek / boltnak / beszállítónak. Mivel ez idővel változhat, hozzáadunk egy mezőt az időnek. A relációs adatbázisban a referenciák tábláinak létrehozását kihagyom, hogy csökkentsem a kódot:

Relációs

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

Funkcionális

OSZTÁLY Sku;
OSZTÁLY Bolt;
OSZTÁLY Szállító;
dátumIdő = ADAT DÁTUMIDŐ (Sku, Bolt, Szállító);
ár = ADAT SZÁM[10,5] (Sku, Bolt, Szállító);

Indexek

Az utolsó példához készítsünk egy indexet az összes kulcs és a dátum alapján, hogy gyorsan megtalálhassuk az árakat egy adott időpontra.

Relációs

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

Funkcionális

INDEX Sku sk, Bolt st, Bes供应商 sp, dateTime(sk, st, sp);

Feladatok

Kezdjük olyan viszonylag egyszerű feladatokkal, amelyek az illetékes cikkből származnak a Habr-nál.

Először hirdessük meg a domain logikát (relációs adatbázis esetén ezt közvetlenül a cikkben tesszük).

OSZTÁLY Részleg;
név = ADAT STRING[100] (Részleg);

CLASS Employee;
department = DATA Department (Employee);
chief = DATA Employee (Employee);
name = DATA STRING[100] (Employee);
salary = DATA NUMERIC[14,2] (Employee);

Feladat 1.1

Listázza azokat a dolgozókat, akiknek a fizetése magasabb, mint közvetlen felettesüké.

Relációs

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

Funkcionális

VÁLASSZA ki a nevet(Employee a) AHOL fizetés(a) > fizetés(vezető(a));

Feladat 1.2

Listázza azokat a dolgozókat, akik a legmagasabb fizetést kapják osztályukban.

Relációs

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

Funkcionális

maxSalary 'Legmaximum bér' (Osztály s) = 
    GROUP MAX bér(Munkavállaló e) HA osztály(e) = s;
KIVÁLASZTÁS név(Munkavállaló a) AHOL bér(a) = maxSalary(osztály(a));

// или если "заинлайнить"
SELECT name(Employee a) WHERE 
    salary(a) = maxSalary(GROUP MAX salary(Employee e) IF department(e) = department(a));

Mindkét megvalósítás ekvivalens. Az első esetben a relációs adatbázisban használható a CREATE VIEW, amely ugyanígy először számítja ki egy adott osztály legmagasabb bérét. Később érthetőség kedvéért az első esetet fogom használni, mivel jobban tükrözi a megoldást.

Feladat 1.3

Listázza az osztályok ID-ját, amelyekben a dolgozók száma nem haladja meg a 3 főt.

Relációs

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

Funkcionális

countEmployees 'Munkavállalók száma' (Department d) = 
    GROUP SUM 1 IF department(Employee e) = d;
SELECT Department d WHERE countEmployees(d) <= 3;

Feladat 1.4

Listázza azokat a dolgozókat, akiknek nincs kijelölt felettese, és ugyanabban az osztályban dolgoznak.

Relációs

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

Funkcionális

VÁLASSZA a nevet(Employee a) AHOL NEM (department(chief(a)) = department(a));

Feladat 1.5

Keresse meg az osztályok ID-ját, amelyekben a dolgozók összszerződési bérének maximális összege található.

Relációs

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 )

Funkcionális

fizetésiÖsszeg 'Maximális bér' (Osztály d) = 
    Csoport ÖSSZEG fizetés(Munkavállaló e) HA osztály(e) = d;
maxFizetésiÖsszeg 'Maximális bér osztályok' () = 
    Csoport MAX fizetésiÖsszeg(Osztály d);
VÁLOGASSA KI Osztály d HOL fizetésiÖsszeg(d) = maxFizetésiÖsszeg();

Lépjünk át a másik bonyolultabb feladatra. cikkből származnak. Ebben részletesen elemzik, hogyan lehet ezt a feladatot MS SQL-en végrehajtani.

Feladat 2.1

Mely eladók adtak el 1997-ben több mint 30 darabot az 1-es termékből?

A domain logika (ahogy korábban a RDBMS-nél is, itt kihagyjuk a bejelentést):

CLASS Employee 'Eladó';
lastName 'Vezetéknév' = DATA STRING[100] (Employee);

CLASS Product 'Termék';
id = DATA INTEGER (Product);
name = DATA STRING[100] (Product);

CLASS Order 'Rendelés';
date = DATA DATE (Order);
employee = DATA Employee (Order);

CLASS Detail 'Rendelési tétel';

order = DATA Order (Detail);
product = DATA Product (Detail);
quantity = DATA NUMERIC[10,5] (Detail);

Relációs

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

Funkcionális

eladott (Alkalmazott e, EGYES termékId, EGYES év) = 
    Csoport Összeg mennyiség(RendelésiRészlet d) HA 
        alkalmazott(rendelés(d)) = e ÉS 
        id(termék(d)) = termékId ÉS 
        kivon(at(rendelés(d))) = év;
VÁLASSZ utolsóNév(Alkalmazott e) HOL eladott(e, 1, 1997) > 30;

Feladat 2.2

Minden vásárlónál (kereszt- és vezetéknév) keresse meg azokat a két terméket (terméknév), amelyekre a vásárló a legtöbb pénzt költötte 1997-ben.

Kiterjesztjük a korábbi példa domain logikáját:

CLASS Customer 'Ügyfél';
contactName 'Név' = DATA STRING[100] (Ügyfél);

customer = DATA Customer (Order);

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

Relációs

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

Funkcionális

összeg (Részlet d) = mennyiség(d) * egységár(d) * (1 - kedvezmény(d));
vásárolt 'Vásárolt' (Vevő c, Termék p, EGYES y) = 
    CSOPORT ÖSSZEG összeg(Részlet d) HA 
        vevő(rendelés(d)) = c ÉS 
        termék(d) = p ÉS 
        kivon(év(dátum(rendelés(d)))) = y;
értékelés 'Értékelés' (Vevő c, Termék p, EGYES y) = 
    PARTÍCIO ÖSSZEG 1 CSÖKKENŐ vásárolt(c, p, y), p BY c, y;
VÁLASZTOZZ contactNév(Vevő c), név(Termék p) HA értékelés(c, p, 1997) < 3;

A PARTITION operátor a következő elv szerint működik: a megadott kifejezést, amely a SUM után szerepel (itt 1), az adott csoportokban (itt Customer és Year, de bármilyen kifejezés lehet) összesíti, és csoporton belül az ORDER-ben megadott kifejezések szerint rendez.

Feladat 2.3

Hány terméket kell rendelni a beszállítóktól a meglévő rendelések teljesítéséhez?

Újra kiterjesztjük a domain logikát:

CLASS Supplier 'Szállító';
companyName = DATA STRING[100] (Szállító);

supplier = DATA Supplier (Product);

unitsInStock 'Készleten lévő mennyiség' = DATA NUMERIC[10,3] (Product);
reorderLevel 'Rendelési szint' = DATA NUMERIC[10,3] (Product);

Relációs

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

Funkcionális

rendelésNemKüldve 'Rendelve, de nem szállítva' (Termék p) = 
    Csoport Összeg mennyiség(RendelésiRészlet d) HA termék(d) = p;
megrendelésre 'A rendeléshez' (Termék p) = rendelésNemKüldve(p) + utánrendelésiSzint(p) - készletenLévőEgységek(p);
VÁLASZD cégNev(szállító(Termék p)), név(p), megrendelésre(p) HOL megrendelésre(p) > 0;

Csillagos feladat

És végül egy példa tőlem. Van egy közösségi hálózati logika. Az emberek barátkozhatnak egymással és tetszhetnek egymásnak. Funkcionális adatbázis szempontjából ez a következőképpen néz ki:

OSZTÁLY Személy;
kedvelések = ADAT LOGIKAI (Személy, Személy);
barátok = ADAT LOGIKAI (Személy, Személy);

Meg kell találni a lehetséges baráti jelölteket. Formálisabban meg kell találni minden A, B, C embert, ahol A barátkozik B-vel, B barátkozik C-vel, A tetszik C, de A nem barátkozik C-vel.
Funkcionális adatbázis szempontjából a lekérdezés a következőképpen néz ki:

VÁLASZD a, b, c SZEMÉLYEKET HOL 
    kedveli(a, c) ÉS NEM barátok(a, c) ÉS 
    barátok(a, b) ÉS barátok(b, c);

Az olvasónak felajánljuk a feladat megoldását SQL-ben. Feltehető, hogy a barátok száma jóval kevesebb, mint a barátoknak tetszők száma. Ezért külön táblázatokban találhatók. Sikeres megoldás esetén van egy két csillagos feladat is. Ebben a barátság nem szimmetrikus. Funkcionális adatbázisban ez így néz ki:

VÁLASZD a, b, c SZEMÉLYEKET HOL 
    kedveli(a, c) ÉS NEM barátok(a, c) ÉS 
    (barátok(a, b) VAGY barátok(b, a)) ÉS 
    (barátok(b, c) VAGY barátok(c, b));

UPD: az első és második csillagos feladat megoldása től dss_kalika:

VÁLASZTHATÓ 
   pl.PersonAID
  ,pf.PersonAID
  ,pff.PersonAID
BELÉPÉS Személyek    AS p
--Tetszések             
CSATLAKOZZON SzemélyKapcsolat   AS pl ON pl.PersonAID = p.PersonID
                                  ÉS pl.Relation  = 'Like'
--Barátok              
CSATLAKOZZON SzemélyKapcsolat   AS pf ON pf.PersonAID = p.PersonID 
                                  ÉS pf.Relation = 'Friend'
--Barátok Barátai     
CSATLAKOZZON SzemélyKapcsolat   AS pff ON pff.PersonAID = pf.PersonBID
                                   ÉS pff.PersonBID = pl.PersonBID
                                   ÉS pff.Relation = 'Friend'
--Még nem barátok     
BAL FELÉ CSATLAKOZÁS SzemélyKapcsolat AS pnf ON pnf.PersonAID = p.PersonID
                                   ÉS pnf.PersonBID = pff.PersonBID
                                   ÉS pnf.Relation = 'Friend'
HOL pnf.PersonAID IS NULL 

;A HALLGATÓ Reménytár Kpülszön a Férfiak szérsketésed a Szentmiás Szerepe 
  VÁLASZT pl.PersonAID
        ,pl.PersonBID
        ,pl.Relation 
  FROM #SzemélyKapcsolat      AS pl 
  
  ÖSSZEGZÉS 

  VÁLASZT pl.PersonBID AS PersonAID
        ,pl.PersonAID AS PersonBID
        ,pl.Relation
  FROM #SzemélyKapcsolat      AS pl 
)
VÁLASZT 
   pl.PersonAID
  ,pf.PersonBID
  ,pff.PersonBID
FROM #Személyek                      AS p
--Tetszések             
CSATLAKOZZON SzemélyKapcsolatOsszevonva  AS pl ON pl.PersonAID = p.PersonID
                                 ÉS pl.Relation  = 'Like'                                  
--Barátok                          
CSATLAKOZZON SzemélyKapcsolatOsszevonva  AS pf ON pf.PersonAID = p.PersonID 
                                 ÉS pf.Relation = 'Friend'
--Barátok Barátai                   
CSATLAKOZZON SzemélyKapcsolatOsszevonva  AS pff ON pff.PersonAID = pf.PersonBID
                                 ÉS pff.PersonBID = pl.PersonBID
                                 ÉS pff.Relation = 'Friend'
--Még nem barátok                   
BAL FELÉ CSATLAKOZÁS SzemélyKapcsolatOsszevonva AS pnf ON pnf.PersonAID = p.PersonID
                                   ÉS pnf.PersonBID = pff.PersonBID
                                   ÉS pnf.Relation = 'Friend'
HOL pnf.[PersonAID] IS NULL 

Zárás

Meg kell jegyezni, hogy a megadott szintaxis az egyik megvalósási lehetőség. Az SQL-t alapul véve úgy próbáltuk megoldani, hogy a lehető legjobban hasonlítson rá. Természetesen nem mindenkinek tetszhetnek a kulcsszavak nevei, a kis- és nagybetűk és a többi. Itt a fő dolog a koncepció maga. Igény szerint C++-ot vagy Python-t is készíthetünk hasonló szintaxissal.

A leírt adatbázis koncepció véleményem szerint az alábbi előnyökkel bír:

  • Egyszerűség. Ez egy viszonylag szubjektív mutató, amely nem nyilvánvaló az egyszerű esetekben. De ha bonyolultabb eseteket nézünk (például csillagos feladatok), akkor véleményem szerint sokkal könnyebb ilyen lekérdezéseket írni.
  • Elrejtés. Néhány példában köztes funkciókat írtam (például, eladva, megvéve és így tovább), amelyek alapján további funkciók épültek. Ez lehetővé teszi bizonyos funkciók logikájának megváltoztatását anélkül, hogy a hozzájuk kapcsolódó logika megváltozna. Például lehetőséget ad arra, hogy az értékesítések eladva teljesen más objektumokból számítsanak, miközben a többi logika nem változik. Igen, a RDBMS-ben ezt a CREATE VIEW segítségével lehet megvalósítani. De ha az egész logikát így írjuk, akkor az nem lesz túl olvasható.
  • A szemantikai szakadék hiánya. Az ilyen adatbázisok funkciókkal és osztályokkal dolgoznak (a táblák és mezők helyett). Pontosan úgy, mint a klasszikus programozásban (ha úgy tekintjük, hogy a metódus egy olyan függvény, amelynek az első paramétere az a osztály, amelyhez tartozik). Ennek megfelelően "könnyebben egyeztethető" a univerzális programozási nyelvekkel. Ezenkívül ez a koncepció lehetővé teszi sokkal bonyolultabb funkciók megvalósítását is. Például beágyazhatunk az adatbázisba olyan operátorokat, mint:

    KORLÁTOZÁS elad(Employee e, 1, 2019) > 100 HA name(e) = 'Petya' ÜZENET  'Azt mondja, hogy Petya túl sok egy terméket árusít 2019 ben.';

  • Öröklődés és polimorfizmus. A funkcionális adatbázisokban bevezethetjük a többszörös öröklődést a CLASS ClassP: Class1, Class2 konstrukciók révén, és megvalósíthatjuk a többszörös polimorfizmust. Hogy pontosan hogyan, azt talán a következő cikkekben írom le.

Annak ellenére, hogy ez csupán egy koncepció, már van egy megvalósításunk Java-ban, amely az összes funkcionális logikát relációs logikává transzformálja. Ezenkívül szépen hozzá van kapcsolva a megjelenítési logika és sok más tényező, amelyek révén egy teljes platform. Lényegében az RDBMS-t (jelenleg csak PostgreSQL) „virtuális gépként” használjuk. Ilyen transzformáció során néha problémák merülnek fel, mivel az RDBMS lekérdezés-optimálója nem ismeri azokat a specifikus statisztikákat, amelyeket az FDBMS ismer. Elméletileg létrehozható egy adatbázis-kezelő rendszer, amely tárolásához egy olyan struktúrát használ, amely kifejezetten a funkcionális logikához van alkalmazva.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster