Funkcionalna DBMS

Svet podatkov je že dolgo zavzet s relacijskimi SGBD, kjer se uporablja jezik SQL. Tako močno, da se nove različice imenujejo NoSQL. Uspele so si priboriti določeno mesto na tem trgu, vendar se relacijski SGBD ne nameravajo umakniti in se še naprej aktivno uporabljajo za svoje namene.

V tem članku želim opisati koncept funkcionalne podatkovne baze. Za boljše razumevanje to bom storil z primerjavo s klasičnim relacijskim modelom. Kot primere bodo uporabljene naloge iz različnih testov po SQL, ki jih je mogoče najti na internetu.

Uvod

Relacijske podatkovne baze delujejo s tabelami in polji. V funkcionalni podatkovni bazi namesto teh uporabimo razrede in funkcije. Polje v tabeli z N ključi bo predstavljeno kot funkcija s N parametri. Namesto povezav med tabelami bodo uporabljene funkcije, ki vračajo objekte razreda, na katerega se nanaša povezava. Namesto JOIN-a bo uporabljena kompozicija funkcij.

Preden se neposredno lotimo nalog, bom opisal nalogo domenne logike. Za DDL bom uporabil sintakso PostgreSQL. Za funkcionalno bom uporabil svojo sintakso.

Tabele in polja

Preprost predmet Sku s polji ime in cena:

Grafični, dokumentni

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

Funkcionalna

Razred Sku;
ime = PODATKOVA NIZ[100] (Sku);
cena = PODATKOVNI NUMERIC[10,5] (Sku);

Izjavimo dva funkcije, ki sprejemata en parameter Sku in vračata primitiven tip.

Predpostavlja se, da bo v funkcionalni SGBD imel vsak predmet nek notranji kodo, ki se samodejno generira in ji lahko v primeru potrebe dostopamo.

Določimo ceno za izdelek / trgovino / dobavitelja. Ta se lahko sčasoma spreminja, zato dodamo v tabelo polje čas. Napoved tabel za referenčne podatke v relacijski podatkovni bazi bom izpustil, da skrajšam kodo:

Grafični, dokumentni

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

Funkcionalna

Razred Sku;
KLASSA Trgovina;
KLASSA Dobavitelj;
datumInUra = PODATKI DATUM IN URO (Sku, Trgovina, Dobavitelj);
cena = PODATKI ŠTEVILČNO[10,5] (Sku, Trgovina, Dobavitelj);

Indeksi

Za zadnji primer bomo zgradili indeks po vseh ključih in datumu, da bomo lahko hitro našli ceno ob določenem času.

Grafični, dokumentni

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

Funkcionalna

DIZAJN Sku sk, Trgovina st, Dobavljač sp, datumVrijeme(sk, st, sp);

Zadaci

Začnimo z razmeroma preprostimi nalogami, izzetimi iz ustreznih članka na Hbru.

Najprej bomo opredelili domeno (za relacijske baze je to storjeno neposredno v tem članku).

ODDELENJE
ime = NIZ ZAPISOV[100] (Oddelek);

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

Naloga 1.1

Izpišite seznam zaposlenih, ki prejemajo plačo višjo od svoje neposredne nadrejenega.

Grafični, dokumentni

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

Funkcionalna

IZBERI ime(Zaposleni a) KJER plača(a) > plača(povednik(a));

Naloga 1.2

Izpišite seznam zaposlenih, ki prejemajo najvišjo plačo v svojem oddelku

Grafični, dokumentni

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

Funkcionalna

maxSalary 'Najvišja plača' (Oddelek s) = 
    GROUP MAX plača(Zaposleni e) IF oddelek(e) = s;
SELECT ime(Zaposleni a) WHERE plača(a) = maxSalary(oddelek(a));

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

Obe implementaciji sta ekvivalentni. V prvem primeru lahko v relacijski bazi uporabimo CREATE VIEW, ki tako izračuna najvišjo plačo za določen oddelek. Kasneje bom za jasno sliko uporabljal prvi primer, saj bolj odraža rešitev.

Naloga 1.3

Izpišite seznam ID-jev oddelkov, v katerih število zaposlenih ne presega 3.

Grafični, dokumentni

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

Funkcionalna

countEmployees 'Število zaposlenih' (Oddelek d) = 
    GRUPA SUM 1 ČE oddelek(Zaposleni e) = d;
IZBERI Oddelek d KJER countEmployees(d) <= 3;

Naloga 1.4

Izpišite seznam zaposlenih, ki nimajo dodeljenega nadrejenega, ki dela v istem oddelku.

Grafični, dokumentni

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

Funkcionalna

IZBERI ime(Zaposleni a) KJER NI (department(področje(a)) = department(a));

Naloga 1.5

Najdite seznam ID-jev oddelkov z najvišjo skupno plačo zaposlenih.

Grafični, dokumentni

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 )

Funkcionalna

salarySum 'Najvišja plača' (Oddelek d) = 
    SKUPINA SUM salary(Zaposleni e) KOMEM oddelek(e) = d;
maxSalarySum 'Najvišja plača oddelkov' () = 
    SKUPINA MAX salarySum(Oddelek d);
IZBERI Oddelek d KJER salarySum(d) = maxSalarySum();

Prejdimo na bolj zapletene naloge iz druge članka. V njej je podroben pregled tega, kako to nalogo izvesti v MS SQL.

Naloga 2.1

Kateri prodajalci so v letu 1997 prodali več kot 30 kosov blaga št. 1?

Domena (kot prej pri RDBMS izpustimo izjavo):

CLASS Employee 'Prodajalec';
lastName 'Priimek' = DATA STRING[100] (Employee);

CLASS Product 'Izdelek';
id = DATA INTEGER (Product);
name = DATA STRING[100] (Product);

CLASS Order 'Naročilo';
date = DATA DATE (Order);
employee = DATA Employee (Order);

CLASS Detail 'Vrstica naročila';

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

Grafični, dokumentni

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

Funkcionalna

prodano (Zaposleni e, CELA številka productId, CELA številka year) = 
    SKUPAJ SUMA količina (PodrobnostiNaročila d) AKO 
        zaposleni(naročilo(d)) = e IN 
        id(proizvod(d)) = productId IN 
        izvlečiLeto(datum(naročilo(d))) = year;
IZBERI priimek (Zaposleni e) KJER prodano(e, 1, 1997) > 30;

Naloga 2.2

Za vsakega kupca (ime, priimek) najdite dva izdelka (ime), na katera je kupec v letu 1997 porabil največ denarja.

Širimo logiku domena iz prethodnog primera:

CLASS Customer 'Klient';
contactName 'IME I PREZIME' = DATA STRING[100] (Customer);

customer = DATA Customer (Order);

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

Grafični, dokumentni

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

Funkcionalna

suma (Podrobnosti d) = količina(d) * enotnaCena(d) * (1 - popust(d));
kupljeno 'Kupljeno' (Stranka c, Izdelek p, ŠTEVILKA y) = 
    SKUPAJ SUMA suma(Podrobnosti d) ČE 
        stranka(naročilo(d)) = c IN 
        izdelek(d) = p IN 
        izvlečiLeto(datум(naročilo(d))) = y;
ocena 'Ocena' (Stranka c, Izdelek p, ŠTEVILKA y) = 
    PARTICIJA SUMA 1 USKLADITE DESC kupljeno(c, p, y), p PO c, y;
IZBERI kontaktnoIme(Stranka c), ime(Izdelek p) KJER ocena(c, p, 1997) < 3;

Operator PARTITION radi na sledeći način: sumira izraz naveden nakon SUM (ovde 1) unutar navedenih grupa (ovde Customer i Year, ali može biti bilo koji izraz), sortirajući unutar grupa prema izrazima navedenim u ORDER (ovde bought, a ako su jednaki, prema unutrašnjem kodu proizvoda).

Zadatak 2.3

Koliko proizvoda treba naručiti od dobavljača za ispunjenje trenutnih narudžbina.

Ponovo proširujemo logiku domena:

CLASS Supplier 'Dobavitelj';
companyName = DATA STRING[100] (Dobavitelj);

supplier = DATA Supplier (Product);

unitsInStock 'Ostalo na skladištu' = DATA NUMERIC[10,3] (Product);
reorderLevel 'Nivo ponovnog naručivanja' = DATA NUMERIC[10,3] (Product);

Grafični, dokumentni

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

Funkcionalna

'Naročeno, a še ni odposlano' (izdelek p) = 
    SKUPAJ SUMA količina(PodrobnostiNaročila d) ČE izdelek(d) = p;
'Za naročilo' (izdelek p) = naročenoNeOdpeljano(p) + nivoNaročila(p) - enoteNaZalogi(p);
IZBERI imePodjetja(dobavitelj(izdelek p)), ime(p), zaNaročilo(p) KJER zaNaročilo(p) > 0;

Zadatak sa zvezdicom

A poslednji primer lično od mene. Postoji logika društvene mreže. Ljudi se mogu prijateljiti jedni s drugima i dopadati jedni drugima. Sa stanovišta funkcionalne baze podataka to će izgledati ovako:

RAZRED Oseba;
všeč = PODATKI BOOLEAN (Oseba, Oseba);
prijatelji = PODATKI BOOLEAN (Oseba, Oseba);

Potrebno je pronaći moguće kandidate za prijateljstvo. Formalnije, treba pronaći sve ljude A, B, C takve da A prijateljuje s B, a B prijateljuje s C, A se dopada C, ali A ne prijateljuje s C.
Sa stanovišta funkcionalne baze podataka, upit će izgledati ovako:

IZBERITE Oseba a, Oseba b, Oseba c KJER 
    ima rad(a, c) IN NE prijatelji(a, c) IN 
    prijatelji(a, b) IN prijatelji(b, c);

Čitaocu se predlaže da samostalno reši ovaj zadatak na SQL-u. PREDPOSTAVLJA se da prijatelja ima mnogo manje nego onih kojima se dopadaju. Stoga su oni u posebnim tabelama. U slučaju uspešnog rešenja, postoji i zadatak s dve zvezdice. U njemu prijateljstvo nije simetrično. Na funkcionalnoj bazi podataka to će izgledati ovako:

IZBERITE Oseba a, Oseba b, Oseba c KJER 
    ima rad(a, c) IN NE prijatelji(a, c) IN 
    (prijatelji(a, b) ALi prijatelji(b, a)) IN 
    (prijatelji(b, c) ALi prijatelji(c, b));

UPD: rešenje zadatka sa prvom i drugom zvezdicom od dss_kalika:

SELECT 
   pl.PersonAID
  ,pf.PersonAID
  ,pff.PersonAID
FROM Persons                 AS p
--Likes                      
JOIN PersonRelationShip      AS pl ON pl.PersonAID = p.PersonID
                                  AND pl.Relation  = 'Like'
--Friends                     
JOIN PersonRelationShip      AS pf ON pf.PersonAID = p.PersonID 
                                  AND pf.Relation = 'Friend'
--Friends of Friends              
JOIN PersonRelationShip      AS pff ON pff.PersonAID = pf.PersonBID
                                   AND pff.PersonBID = pl.PersonBID
                                   AND pff.Relation = 'Friend'
--Not Friends Yet         
LEFT JOIN PersonRelationShip AS pnf ON pnf.PersonAID = p.PersonID
                                   AND pnf.PersonBID = pff.PersonBID
                                   AND pnf.Relation = 'Friend'
WHERE pnf.PersonAID IS NULL 

;WITH PersonRelationShipCollapsed AS (
  SELECT pl.PersonAID
        ,pl.PersonBID
        ,pl.Relation 
  FROM #PersonRelationShip      AS pl 
  
  UNION 

  SELECT pl.PersonBID AS PersonAID
        ,pl.PersonAID AS PersonBID
        ,pl.Relation
  FROM #PersonRelationShip      AS pl 
)
SELECT 
   pl.PersonAID
  ,pf.PersonBID
  ,pff.PersonBID
FROM #Persons                      AS p
--Likes                      
JOIN PersonRelationShipCollapsed  AS pl ON pl.PersonAID = p.PersonID
                                 AND pl.Relation  = 'Like'                                  
--Friends                          
JOIN PersonRelationShipCollapsed  AS pf ON pf.PersonAID = p.PersonID 
                                 AND pf.Relation = 'Friend'
--Friends of Friends                   
JOIN PersonRelationShipCollapsed  AS pff ON pff.PersonAID = pf.PersonBID
                                 AND pff.PersonBID = pl.PersonBID
                                 AND pff.Relation = 'Friend'
--Not Friends Yet                   
LEFT JOIN PersonRelationShipCollapsed AS pnf ON pnf.PersonAID = p.PersonID
                                   AND pnf.PersonBID = pff.PersonBID
                                   AND pnf.Relation = 'Friend'
WHERE pnf.[PersonAID] IS NULL 

Zaključek

It should be noted that the syntax of the language presented is just one of the possible implementations of the given concept. SQL has been taken as the basis, and the goal was to make it as similar to it as possible. Of course, some may not like the names of the keywords, capitalization, and so on. The main point here is the concept itself. If desired, similar syntax can be created for C++ and Python.

In my opinion, the described database concept has the following advantages:

  • Enostavnost. This is a relatively subjective indicator that is not obvious in simple cases. However, if you consider more complex cases (for example, star tasks), in my opinion, writing such queries is significantly easier.
  • Encapsulation. In some examples, I declared intermediate functions (for example, sold, bought etc.), from which subsequent functions were built. This allows the logic of certain functions to be changed without altering the logic of dependent functions, for example, sales can be altered, sold Se računajo od povsem drugih objektov, pri tem pa ostala logika ne bo spremenjena. Da, v RSUBD je to mogoče uresničiti s CREATE VIEW. Ampak, če bi vso logiko pisali na ta način, bi bila videti precej težko berljiva.
  • Odsotnost semantičnega pretrga. Takšna baza podatkov deluje s funkcijami in razredi (namesto tabel in polj). Točno tako, kot v klasičnem programiranju (če upoštevamo, da je metoda funkcija s prvim parametrom v obliki razreda, na katerem se nanaša). Tako bi moralo biti precej enostavneje 'prijateljiti' se z univerzalnimi programskimi jeziki. Poleg tega ta koncept dovoljuje implementacijo veliko bolj kompleksnih funkcij. Na primer, mogoče je vgrajevati v bazo podatkov operatorje v oblikah:

    CONSTRAINT sold(Employee e, 1, 2019) > 100 IF name(e) = 'Petja' MESSAGE 'Nekaj Petja prodaja preveč enega blaga v letu 2019.';

  • Dedovanje in polimorfizem. V funkcionalni bazi podatkov je mogoče uvesti večkratno dedovanje prek konstrukcij CLASS ClassP: Class1, Class2 in uresničiti večkratni polimorfizem. Kako natančno, morda napišem v naslednjih člankih.

Kljub temu, da je to le koncept, imamo že nekaj realizacije v Javi, ki prevodi vso funkcionalno logiko v relacijsko logiko. Poleg tega je lepo pripeta logika predstavitev in še marsikaj drugega, kar ustvarja celo platforma. V bistvu uporabljamo RSUBD (zaenkrat le PostgreSQL) kot 'virtualni stroj'. Pri takšni translatiji se včasih pojavijo težave, saj optimizator poizvedb RSUBD ne pozna določene statistike, ki jo pozna FSUBD. Teоретično bi bilo mogoče uresničiti sistem upravljanja podatkovne baze, ki bi za shranjevanje uporabljal neko strukturo, prilagojeno prav funkcionalni logiki.

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster