De wrâld fan databases is al lang dominearre troch relationele DBMS's, dy't de SQL-taal brûke. Safolle dat opkommende farianten NoSQL wurde neamd. Se slagge te snije út in bepaald plak foar harsels yn dizze merk, mar relational DBMSs sille net stjerre, en bliuwe aktyf brûkt foar harren doelen.
Yn dit artikel wol ik it konsept fan in funksjonele databank beskriuwe. Foar in better begryp sil ik dit dwaan troch it te fergelykjen mei it klassike relaasjemodel. Problemen fan ferskate SQL-tests fûn op it ynternet sille as foarbylden brûkt wurde.
Ynlieding
Relasjonele databases wurkje op tabellen en fjilden. Yn in funksjonele databank sille respektivelik klassen en funksjes brûkt wurde. In fjild yn in tabel mei N kaaien wurdt fertsjintwurdige as in funksje fan N parameters. Yn stee fan relaasjes tusken tabellen sille funksjes brûkt wurde dy't objekten werombringe fan 'e klasse wêrmei't de ferbining makke is. Funksje gearstalling sil brûkt wurde ynstee fan JOIN.
Foardat jo direkt nei de taken gean, sil ik de taak fan domeinlogika beskriuwe. Foar DDL sil ik PostgreSQL-syntaksis brûke. Foar funksjoneel hat it in eigen syntaksis.
Tabellen en fjilden
In ienfâldich Sku-objekt mei namme- en priisfjilden:
Relasjonele
CREATE TABLE Sku
(
id bigint NOT NULL,
name character varying(100),
price numeric(10,5),
CONSTRAINT id_pkey PRIMARY KEY (id)
)
funksjoneel
CLASS Sku;
name = DATA STRING[100] (Sku);
price = DATA NUMERIC[10,5] (Sku);
Wy kundigje twa oan funksjes, dy't nimme ien parameter Sku as ynfier en werom in primitive type.
Der wurdt fan útgien dat yn in funksjoneel DBMS elk foarwerp sil hawwe wat ynterne koade dy't wurdt automatysk oanmakke en kin tagonklik wurde as it nedich is.
Litte wy de priis ynstelle foar it produkt / winkel / leveransier. It kin yn 'e rin fan' e tiid feroarje, dus litte wy in tiidfjild tafoegje oan 'e tabel. Ik sil it ferklearjen fan tabellen foar mappen yn in relasjonele databank oerslaan om de koade te koartsjen:
Relasjonele
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)
)
funksjoneel
CLASS Sku;
CLASS Store;
CLASS Supplier;
dateTime = DATA DATETIME (Sku, Store, Supplier);
price = DATA NUMERIC[10,5] (Sku, Store, Supplier);
Yndeksen
Foar it lêste foarbyld sille wy in yndeks bouwe op alle kaaien en de datum, sadat wy de priis fluch fine kinne foar in spesifike tiid.
Relasjonele
CREATE INDEX prices_date
ON prices
(skuId, storeId, supplierId, dateTime)
funksjoneel
INDEX Sku sk, Store st, Supplier sp, dateTime(sk, st, sp);
taken
Lit ús begjinne mei relatyf ienfâldige problemen nommen út de oerienkommende
Litte wy earst de domeinlogika ferklearje (foar de relationele databank wurdt dit direkt dien yn it boppesteande artikel).
CLASS Department;
name = DATA STRING[100] (Department);
CLASS Employee;
department = DATA Department (Employee);
chief = DATA Employee (Employee);
name = DATA STRING[100] (Employee);
salary = DATA NUMERIC[14,2] (Employee);
Opdracht 1.1
Lit in list sjen mei meiwurkers dy't in salaris krije dat grutter is as dat fan har direkte supervisor.
Relasjonele
select a.*
from employee a, employee b
where b.id = a.chief_id
and a.salary > b.salary
funksjoneel
SELECT name(Employee a) WHERE salary(a) > salary(chief(a));
Opdracht 1.2
List de meiwurkers dy't it maksimale salaris krije yn har ôfdieling
Relasjonele
select a.*
from employee a
where a.salary = ( select max(salary) from employee b
where b.department_id = a.department_id )
funksjoneel
maxSalary 'Максимальная зарплата' (Department s) =
GROUP MAX salary(Employee e) IF department(e) = s;
SELECT name(Employee a) WHERE salary(a) = maxSalary(department(a));
// или если "заинлайнить"
SELECT name(Employee a) WHERE
salary(a) = maxSalary(GROUP MAX salary(Employee e) IF department(e) = department(a));
Beide ymplemintaasjes binne lykweardich. Foar it earste gefal kinne jo yn in relational databank CREATE VIEW brûke, dy't op deselde manier earst it maksimale salaris foar in spesifike ôfdieling dêryn berekkenje sil. Yn wat folget sil ik foar de dúdlikens it earste gefal brûke, om't it de oplossing better wjerspegelet.
Opdracht 1.3
Lit in list mei ôfdielings-ID's sjen, it oantal meiwurkers wêryn net mear as 3 minsken is.
Relasjonele
select department_id
from employee
group by department_id
having count(*) <= 3
funksjoneel
countEmployees 'Количество сотрудников' (Department d) =
GROUP SUM 1 IF department(Employee e) = d;
SELECT Department d WHERE countEmployees(d) <= 3;
Opdracht 1.4
Lit in list sjen mei meiwurkers dy't gjin oanwiisde manager hawwe dy't yn deselde ôfdieling wurkje.
Relasjonele
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
funksjoneel
SELECT name(Employee a) WHERE NOT (department(chief(a)) = department(a));
Opdracht 1.5
Fyn in list mei ôfdielings-ID's mei it maksimale totale salaris fan meiwurkers.
Relasjonele
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 )
funksjoneel
salarySum 'Максимальная зарплата' (Department d) =
GROUP SUM salary(Employee e) IF department(e) = d;
maxSalarySum 'Максимальная зарплата отделов' () =
GROUP MAX salarySum(Department d);
SELECT Department d WHERE salarySum(d) = maxSalarySum();
Lit ús gean nei mear komplekse taken fan in oar
Opdracht 2.1
Hokker ferkeapers ferkochten mear as 1997 ienheden fan produkt nûmer 30 yn 1?
Domeinlogika (lykas earder op RDBMS slaan wy de ferklearring oer):
CLASS Employee 'Продавец';
lastName 'Фамилия' = DATA STRING[100] (Employee);
CLASS Product 'Продукт';
id = DATA INTEGER (Product);
name = DATA STRING[100] (Product);
CLASS Order 'Заказ';
date = DATA DATE (Order);
employee = DATA Employee (Order);
CLASS Detail 'Строка заказа';
order = DATA Order (Detail);
product = DATA Product (Detail);
quantity = DATA NUMERIC[10,5] (Detail);
Relasjonele
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
funksjoneel
sold (Employee e, INTEGER productId, INTEGER year) =
GROUP SUM quantity(OrderDetail d) IF
employee(order(d)) = e AND
id(product(d)) = productId AND
extractYear(date(order(d))) = year;
SELECT lastName(Employee e) WHERE sold(e, 1, 1997) > 30;
Opdracht 2.2
Foar elke keaper (namme, efternamme) fine jo de twa guod (namme) dêr't de keaper yn 1997 it measte jild oan bestege.
Wy wreidzje de domeinlogika út fan it foarige foarbyld:
CLASS Customer 'Клиент';
contactName 'ФИО' = DATA STRING[100] (Customer);
customer = DATA Customer (Order);
unitPrice = DATA NUMERIC[14,2] (Detail);
discount = DATA NUMERIC[6,2] (Detail);
Relasjonele
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
funksjoneel
sum (Detail d) = quantity(d) * unitPrice(d) * (1 - discount(d));
bought 'Купил' (Customer c, Product p, INTEGER y) =
GROUP SUM sum(Detail d) IF
customer(order(d)) = c AND
product(d) = p AND
extractYear(date(order(d))) = y;
rating 'Рейтинг' (Customer c, Product p, INTEGER y) =
PARTITION SUM 1 ORDER DESC bought(c, p, y), p BY c, y;
SELECT contactName(Customer c), name(Product p) WHERE rating(c, p, 1997) < 3;
De PARTITION-operator wurket op it folgjende prinsipe: it somt de útdrukking opjûn nei SUM (hjir 1), binnen de opjûne groepen (hjir Klant en Jier, mar kin elke útdrukking wêze), sortearjen binnen de groepen troch de útdrukkingen spesifisearre yn 'e ORDER ( hjir kocht, en as gelyk, dan neffens de ynterne produktkoade).
Opdracht 2.3
Hoefolle guod moatte wurde besteld fan leveransiers om hjoeddeistige oarders te ferfoljen.
Litte wy de domeinlogika wer útwreidzje:
CLASS Supplier 'Поставщик';
companyName = DATA STRING[100] (Supplier);
supplier = DATA Supplier (Product);
unitsInStock 'Остаток на складе' = DATA NUMERIC[10,3] (Product);
reorderLevel 'Норма продажи' = DATA NUMERIC[10,3] (Product);
Relasjonele
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
funksjoneel
orderedNotShipped 'Заказано, но не отгружено' (Product p) =
GROUP SUM quantity(OrderDetail d) IF product(d) = p;
toOrder 'К заказу' (Product p) = orderedNotShipped(p) + reorderLevel(p) - unitsInStock(p);
SELECT companyName(supplier(Product p)), name(p), toOrder(p) WHERE toOrder(p) > 0;
Probleem mei in asterisk
En it lêste foarbyld is fan my persoanlik. D'r is de logika fan in sosjaal netwurk. Minsken kinne freonen mei elkoar wêze en elkoar leuk fine. Fanút in funksjoneel databankperspektyf soe it der sa útsjen:
CLASS Person;
likes = DATA BOOLEAN (Person, Person);
friends = DATA BOOLEAN (Person, Person);
It is nedich om mooglike kandidaten foar freonskip te finen. Formeeler moatte jo alle minsken A, B, C fine, sadat A befreone is mei B, en B befreone is mei C, A liket C, mar A is gjin befreone mei C.
Fanút in funksjoneel databankperspektyf soe de query der sa útsjen:
SELECT Person a, Person b, Person c WHERE
likes(a, c) AND NOT friends(a, c) AND
friends(a, b) AND friends(b, c);
De lêzer wurdt stimulearre om dit probleem yn SQL op syn eigen op te lossen. Der wurdt oannommen dat d'r folle minder freonen binne as minsken dy't jo leuk fine. Dêrom binne se yn aparte tabellen. As suksesfol, is der ek in taak mei twa stjerren. Dêryn is freonskip net symmetrysk. Op in funksjonele databank soe it der sa útsjen:
SELECT Person a, Person b, Person c WHERE
likes(a, c) AND NOT friends(a, c) AND
(friends(a, b) OR friends(b, a)) AND
(friends(b, c) OR friends(c, b));
UPD: oplossing foar it probleem mei de earste en twadde asterisk út
SELECT
pl.PersonAID
,pf.PersonAID
,pff.PersonAID
FROM Persons AS p
--Лайки
JOIN PersonRelationShip AS pl ON pl.PersonAID = p.PersonID
AND pl.Relation = 'Like'
--Друзья
JOIN PersonRelationShip AS pf ON pf.PersonAID = p.PersonID
AND pf.Relation = 'Friend'
--Друзья Друзей
JOIN PersonRelationShip AS pff ON pff.PersonAID = pf.PersonBID
AND pff.PersonBID = pl.PersonBID
AND pff.Relation = 'Friend'
--Ещё не дружат
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
--Лайки
JOIN PersonRelationShipCollapsed AS pl ON pl.PersonAID = p.PersonID
AND pl.Relation = 'Like'
--Друзья
JOIN PersonRelationShipCollapsed AS pf ON pf.PersonAID = p.PersonID
AND pf.Relation = 'Friend'
--Друзья Друзей
JOIN PersonRelationShipCollapsed AS pff ON pff.PersonAID = pf.PersonBID
AND pff.PersonBID = pl.PersonBID
AND pff.Relation = 'Friend'
--Ещё не дружат
LEFT JOIN PersonRelationShipCollapsed AS pnf ON pnf.PersonAID = p.PersonID
AND pnf.PersonBID = pff.PersonBID
AND pnf.Relation = 'Friend'
WHERE pnf.[PersonAID] IS NULL
konklúzje
It moat opmurken wurde dat de opjûne taalsyntaksis mar ien fan 'e opsjes is foar it útfieren fan it opjûne konsept. SQL waard nommen as de basis, en it doel wie dat it soe wêze sa ferlykber mooglik mei it. Fansels kinne guon de nammen fan trefwurden, wurdregisters, ensfh. It wichtichste ding hjir is it konsept sels. As jo wolle, kinne jo sawol C ++ as Python ferlykbere syntaksis meitsje.
It beskreaune databankkonsept hat nei myn miening de folgjende foardielen:
- gemak. Dit is in relatyf subjektive yndikator dy't net dúdlik is yn ienfâldige gefallen. Mar as jo nei mear komplekse gefallen sjogge (bygelyks problemen mei asterisken), dan is neffens my it skriuwen fan sokke fragen folle makliker.
- Инкапсуляция. Yn guon foarbylden haw ik tuskenfunksjes ferklearre (bygelyks, ferkocht, kocht ensfh.), wêrfan folgjende funksjes waarden boud. Hjirmei kinne jo de logika fan bepaalde funksjes feroarje, as it nedich is, sûnder de logika te feroarjen fan dyjingen dy't derfan ôfhingje. Jo kinne bygelyks ferkeap meitsje ferkocht waarden berekkene út folslein ferskillende objekten, wylst de rest fan de logika sil net feroarje. Ja, dit kin wurde ymplementearre yn in RDBMS mei CREATE VIEW. Mar as alle logika op dizze manier skreaun is, sil it net heul lêsber útsjen.
- Gjin semantyske gat. Sa'n databank wurket op funksjes en klassen (ynstee fan tabellen en fjilden). Krekt as yn klassike programmearring (as wy oannimme dat in metoade is in funksje mei de earste parameter yn de foarm fan de klasse dêr't it heart). Dêrtroch soe it folle makliker wêze moatte om "freonen te meitsjen" mei universele programmeartalen. Derneist makket dit konsept it mooglik om folle kompleksere funksjonaliteit te ymplementearjen. Jo kinne bygelyks operators ynbêde lykas:
CONSTRAINT sold(Employee e, 1, 2019) > 100 IF name(e) = 'Петя' MESSAGE 'Что-то Петя продает слишком много одного товара в 2019 году';
- Erfskip en polymorfisme. Yn in funksjonele databank kinne jo meardere erfskip ynfiere fia de CLASS ClassP: Klasse1, Klasse2-konstruksjes en meardere polymorfisme ymplementearje. Ik sil wierskynlik skriuwe hoe krekt yn takomstige artikels.
Hoewol dit gewoan in konsept is, hawwe wy al wat ymplemintaasje yn Java dy't alle funksjonele logika oerset yn relasjonele logika. Plus, de logika fan foarstellings en in protte oare dingen binne der prachtich oan ferbûn, wêrtroch't wy in gehiel krije
Boarne: www.habr.com