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