Bota e bazave të të dhënave ka qenë prej kohësh e dominuar nga DBMS-të relacionale, të cilat përdorin gjuhën SQL. Aq shumë sa që variantet në zhvillim quhen NoSQL. Ata arritën të krijojnë një vend të caktuar për veten e tyre në këtë treg, por DBMS-të relacionale nuk do të vdesin dhe do të vazhdojnë të përdoren në mënyrë aktive për qëllimet e tyre.
Në këtë artikull dua të përshkruaj konceptin e një baze të dhënash funksionale. Për një kuptim më të mirë, këtë do ta bëj duke e krahasuar me modelin klasik relacional. Problemet nga teste të ndryshme SQL të gjetura në internet do të përdoren si shembuj.
Paraqitje
Bazat e të dhënave relacionale funksionojnë në tabela dhe fusha. Në një bazë të dhënash funksionale, klasat dhe funksionet do të përdoren në vend të tyre, përkatësisht. Një fushë në një tabelë me N çelësa do të përfaqësohet si një funksion i parametrave N. Në vend të marrëdhënieve ndërmjet tabelave, do të përdoren funksione që kthejnë objektet e klasës me të cilën është bërë lidhja. Përbërja e funksionit do të përdoret në vend të JOIN.
Para se të kaloj drejtpërdrejt në detyrat, unë do të përshkruaj detyrën e logjikës së domenit. Për DDL do të përdor sintaksën PostgreSQL. Për funksionalitet ka sintaksën e vet.
Tabelat dhe fushat
Një objekt i thjeshtë Sku me fushat e emrit dhe çmimit:
Relacionale
CREATE TABLE Sku
(
id bigint NOT NULL,
name character varying(100),
price numeric(10,5),
CONSTRAINT id_pkey PRIMARY KEY (id)
)
funksionale
CLASS Sku;
name = DATA STRING[100] (Sku);
price = DATA NUMERIC[10,5] (Sku);
Ne shpallim dy funksionet, të cilat marrin një parametër Sku si hyrje dhe kthejnë një tip primitiv.
Supozohet se në një DBMS funksionale çdo objekt do të ketë një kod të brendshëm që gjenerohet automatikisht dhe mund të aksesohet nëse është e nevojshme.
Le të vendosim çmimin për produktin/dyqanin/furnizuesin. Mund të ndryshojë me kalimin e kohës, kështu që le të shtojmë një fushë kohore në tabelë. Do të kapërcej deklarimin e tabelave për drejtoritë në një bazë të dhënash relacionale për të shkurtuar kodin:
Relacionale
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)
)
funksionale
CLASS Sku;
CLASS Store;
CLASS Supplier;
dateTime = DATA DATETIME (Sku, Store, Supplier);
price = DATA NUMERIC[10,5] (Sku, Store, Supplier);
Tregues
Për shembullin e fundit, ne do të ndërtojmë një indeks për të gjithë çelësat dhe datën në mënyrë që të mund të gjejmë shpejt çmimin për një kohë të caktuar.
Relacionale
CREATE INDEX prices_date
ON prices
(skuId, storeId, supplierId, dateTime)
funksionale
INDEX Sku sk, Store st, Supplier sp, dateTime(sk, st, sp);
detyrat
Le të fillojmë me probleme relativisht të thjeshta të marra nga ato përkatëse
Së pari, le të deklarojmë logjikën e domenit (për bazën e të dhënave relacionale kjo bëhet drejtpërdrejt në artikullin e mësipërm).
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);
Problemi 1.1
Shfaq një listë të punonjësve që marrin një pagë më të madhe se ajo e mbikëqyrësit të tyre të menjëhershëm.
Relacionale
select a.*
from employee a, employee b
where b.id = a.chief_id
and a.salary > b.salary
funksionale
SELECT name(Employee a) WHERE salary(a) > salary(chief(a));
Problemi 1.2
Listoni punonjësit që marrin pagën maksimale në departamentin e tyre
Relacionale
select a.*
from employee a
where a.salary = ( select max(salary) from employee b
where b.department_id = a.department_id )
funksionale
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));
Të dy implementimet janë ekuivalente. Për rastin e parë, në një bazë të dhënash relacionale mund të përdorni CREATE VIEW, i cili në të njëjtën mënyrë fillimisht do të llogarisë pagën maksimale për një departament të caktuar në të. Në vijim, për qartësi, do të përdor rastin e parë, pasi pasqyron më mirë zgjidhjen.
Problemi 1.3
Shfaq një listë të ID-ve të departamentit, numri i punonjësve në të cilin nuk i kalon 3 persona.
Relacionale
select department_id
from employee
group by department_id
having count(*) <= 3
funksionale
countEmployees 'Количество сотрудников' (Department d) =
GROUP SUM 1 IF department(Employee e) = d;
SELECT Department d WHERE countEmployees(d) <= 3;
Problemi 1.4
Shfaq një listë të punonjësve që nuk kanë një menaxher të caktuar që punon në të njëjtin departament.
Relacionale
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
funksionale
SELECT name(Employee a) WHERE NOT (department(chief(a)) = department(a));
Problemi 1.5
Gjeni një listë të ID-ve të departamentit me pagën maksimale totale të punonjësve.
Relacionale
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 )
funksionale
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();
Le të kalojmë në detyra më komplekse nga një tjetër
Problemi 2.1
Cilët shitës shitën më shumë se 1997 njësi të produktit nr. 30 në 1?
Logjika e domenit (si më parë në RDBMS ne e kapërcejmë deklaratën):
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);
Relacionale
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
funksionale
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;
Problemi 2.2
Për çdo blerës (emër, mbiemër), gjeni dy mallrat (emri) për të cilat blerësi shpenzoi më shumë para në vitin 1997.
Ne zgjerojmë logjikën e domenit nga shembulli i mëparshëm:
CLASS Customer 'Клиент';
contactName 'ФИО' = DATA STRING[100] (Customer);
customer = DATA Customer (Order);
unitPrice = DATA NUMERIC[14,2] (Detail);
discount = DATA NUMERIC[6,2] (Detail);
Relacionale
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
funksionale
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;
Operatori PARTITION punon në parimin e mëposhtëm: ai përmbledh shprehjen e specifikuar pas SUM (këtu 1), brenda grupeve të specifikuara (këtu Klienti dhe Viti, por mund të jetë çdo shprehje), duke renditur brenda grupeve sipas shprehjeve të specifikuara në ORDER ( blerë këtu, dhe nëse është e barabartë, atëherë sipas kodit të brendshëm të produktit).
Problemi 2.3
Sa mallra duhet të porositen nga furnitorët për të përmbushur porositë aktuale.
Le të zgjerojmë përsëri logjikën e domenit:
CLASS Supplier 'Поставщик';
companyName = DATA STRING[100] (Supplier);
supplier = DATA Supplier (Product);
unitsInStock 'Остаток на складе' = DATA NUMERIC[10,3] (Product);
reorderLevel 'Норма продажи' = DATA NUMERIC[10,3] (Product);
Relacionale
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
funksionale
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;
Problem me një yll
Dhe shembulli i fundit është nga unë personalisht. Ekziston logjika e një rrjeti social. Njerëzit mund të jenë miq me njëri-tjetrin dhe të pëlqejnë njëri-tjetrin. Nga një këndvështrim funksional i bazës së të dhënave do të duket kështu:
CLASS Person;
likes = DATA BOOLEAN (Person, Person);
friends = DATA BOOLEAN (Person, Person);
Është e nevojshme të gjenden kandidatë të mundshëm për miqësi. Më formalisht, ju duhet t'i gjeni të gjithë njerëzit A, B, C në mënyrë që A të jetë shok me B, dhe B të jetë shok me C, A të pëlqen C, por A nuk është mik me C.
Nga një këndvështrim funksional i bazës së të dhënave, pyetja do të dukej kështu:
SELECT Person a, Person b, Person c WHERE
likes(a, c) AND NOT friends(a, c) AND
friends(a, b) AND friends(b, c);
Lexuesi inkurajohet ta zgjidhë vetë këtë problem në SQL. Supozohet se ka shumë më pak miq se njerëzit që ju pëlqen. Prandaj ato janë në tabela të veçanta. Nëse është e suksesshme, ekziston edhe një detyrë me dy yje. Në të, miqësia nuk është simetrike. Në një bazë të dhënash funksionale do të duket kështu:
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: zgjidhja e problemit me yllin e parë dhe të dytë nga
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
Përfundim
Duhet të theksohet se sintaksa e gjuhës së dhënë është vetëm një nga opsionet për zbatimin e konceptit të dhënë. SQL u mor si bazë dhe qëllimi ishte që ajo të ishte sa më e ngjashme me të. Sigurisht, disave mund të mos ju pëlqejnë emrat e fjalëve kyçe, regjistrat e fjalëve, etj. Gjëja kryesore këtu është vetë koncepti. Nëse dëshironi, mund të bëni sintaksë të ngjashme si C++ ashtu edhe Python.
Koncepti i përshkruar i bazës së të dhënave, për mendimin tim, ka përparësitë e mëposhtme:
- Lehtësi. Ky është një tregues relativisht subjektiv që nuk është i dukshëm në raste të thjeshta. Por nëse shikoni raste më komplekse (për shembull, problemet me yjet), atëherë, për mendimin tim, shkrimi i pyetjeve të tilla është shumë më i lehtë.
- Инкапсуляция. Në disa shembuj kam deklaruar funksione të ndërmjetme (për shembull, shitur, blerë etj.), nga i cili u ndërtuan funksionet e mëvonshme. Kjo ju lejon të ndryshoni logjikën e funksioneve të caktuara, nëse është e nevojshme, pa ndryshuar logjikën e atyre që varen prej tyre. Për shembull, ju mund të bëni shitje shitur janë llogaritur nga objekte krejtësisht të ndryshme, ndërsa pjesa tjetër e logjikës nuk do të ndryshojë. Po, kjo mund të zbatohet në një RDBMS duke përdorur CREATE VIEW. Por nëse e gjithë logjika shkruhet në këtë mënyrë, nuk do të duket shumë e lexueshme.
- Nuk ka boshllëk semantik. Një bazë e tillë e të dhënave funksionon në funksione dhe klasa (në vend të tabelave dhe fushave). Ashtu si në programimin klasik (nëse supozojmë se një metodë është një funksion me parametrin e parë në formën e klasës së cilës i përket). Prandaj, duhet të jetë shumë më e lehtë të "krijosh miq" me gjuhët universale të programimit. Për më tepër, ky koncept lejon zbatimin e funksionalitetit shumë më kompleks. Për shembull, mund të futni operatorë si:
CONSTRAINT sold(Employee e, 1, 2019) > 100 IF name(e) = 'Петя' MESSAGE 'Что-то Петя продает слишком много одного товара в 2019 году';
- Trashëgimia dhe polimorfizmi. Në një bazë të dhënash funksionale, ju mund të prezantoni trashëgimi të shumëfishtë përmes KLASËS ClassP: Class1, Class2 ndërton dhe implementon polimorfizëm të shumëfishtë. Ndoshta do të shkruaj saktësisht se si në artikujt e ardhshëm.
Edhe pse ky është vetëm një koncept, ne tashmë kemi një zbatim në Java që përkthen të gjithë logjikën funksionale në logjikë relacionale. Plus, logjika e përfaqësimeve dhe shumë gjëra të tjera janë ngjitur bukur me të, falë së cilës ne marrim një të tërë.
Burimi: www.habr.com