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