La datumbaza mondo estas delonge transprenita de interrilataj DBMS-oj, kiuj uzas la SQL-lingvon. Tiel tiom ke emerĝantaj varioj nomiĝas NoSQL. Ili sukcesis gajni certan lokon por si en ĉi tiu merkato, sed interrilataj DBMS ne mortos, kaj daŭre estas aktive uzataj por siaj propraj celoj.
En ĉi tiu artikolo, mi volas priskribi la koncepton de funkcia datumbazo. Por pli bona kompreno, mi faros tion komparante kun la klasika interrilata modelo. Kiel ekzemploj, taskoj de diversaj SQL-testoj trovitaj en Interreto estos uzataj.
Enkonduko
Rilataj datumbazoj funkcias sur tabeloj kaj kampoj. En funkcia datumbazo, klasoj kaj funkcioj estos uzataj respektive. Kampo en tabelo kun N klavoj estos reprezentita kiel funkcio de N parametroj. Anstataŭ ligiloj inter tabeloj, funkcioj estos uzataj, kiuj resendas objektojn de la klaso al kiu la ligo iras. Funkcia kunmetaĵo estos uzata anstataŭ JOIN.
Antaŭ ol iri rekte al la taskoj, mi priskribos la taskon de domajna logiko. Por DDL, mi uzos PostgreSQL-sintakso. Por funkcia sia propra sintakso.
Tabeloj kaj kampoj
Simpla Sku-objekto kun nomo kaj prezo-kampoj:
rilataj
CREATE TABLE Sku
(
id bigint NOT NULL,
name character varying(100),
price numeric(10,5),
CONSTRAINT id_pkey PRIMARY KEY (id)
)
funkcia
CLASS Sku;
name = DATA STRING[100] (Sku);
price = DATA NUMERIC[10,5] (Sku);
Ni anoncas du funkcioj, kiuj prenas unu Sku-parametron kiel enigaĵon kaj resendas primitivan tipon.
Oni supozas, ke en funkcia DBMS, ĉiu objekto havos iun internan kodon, kiu estas aŭtomate generita kaj alirebla se necese.
Ni fiksu la prezon por la produkto / vendejo / provizanto. Ĝi povas ŝanĝiĝi laŭlonge de la tempo, do ni aldonu tempokampon al la tabelo. Mi preterlasos la deklaron de tabeloj por dosierujoj en rilata datumbazo por mallongigi la kodon:
rilataj
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)
)
funkcia
CLASS Sku;
CLASS Store;
CLASS Supplier;
dateTime = DATA DATETIME (Sku, Store, Supplier);
price = DATA NUMERIC[10,5] (Sku, Store, Supplier);
Indeksoj
Por la lasta ekzemplo, ni konstruu indekson sur ĉiuj ŝlosiloj kaj dato por ke ni povu rapide trovi la prezon por certa tempo.
rilataj
CREATE INDEX prices_date
ON prices
(skuId, storeId, supplierId, dateTime)
funkcia
INDEX Sku sk, Store st, Supplier sp, dateTime(sk, st, sp);
taskoj
Ni komencu per relative simplaj problemoj prenitaj el la respondaj
Unue, ni deklaru la domajnan logikon (por rilata datumbazo, tio estas farita rekte en la supra artikolo).
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);
Tasko 1.1
Montru liston de dungitoj, kiuj ricevas salajrojn pli grandajn ol tiuj de la tuja kontrolisto.
rilataj
select a.*
from employee a, employee b
where b.id = a.chief_id
and a.salary > b.salary
funkcia
SELECT name(Employee a) WHERE salary(a) > salary(chief(a));
Tasko 1.2
Montru liston de dungitoj gajnantaj la plej altan salajron en sia fako
rilataj
select a.*
from employee a
where a.salary = ( select max(salary) from employee b
where b.department_id = a.department_id )
funkcia
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));
Ambaŭ efektivigoj estas ekvivalentaj. Por la unua kazo en la rilata datumbazo, vi povas uzi CREATE VIEW, kiu sammaniere unue kalkulos la maksimuman salajron por specifa fako en ĝi. Estonte, por klareco, mi uzos la unuan kazon, ĉar ĝi pli bone reflektas la solvon.
Tasko 1.3
Montru liston de fakaj identigiloj, la nombro da dungitoj en kiuj ne superas 3 homojn.
rilataj
select department_id
from employee
group by department_id
having count(*) <= 3
funkcia
countEmployees 'Количество сотрудников' (Department d) =
GROUP SUM 1 IF department(Employee e) = d;
SELECT Department d WHERE countEmployees(d) <= 3;
Tasko 1.4
Montru liston de dungitoj, kiuj ne havas asignitan administranton laboranta en la sama fako.
rilataj
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
funkcia
SELECT name(Employee a) WHERE NOT (department(chief(a)) = department(a));
Tasko 1.5
Trovu la liston de fakaj identigiloj kun la maksimuma totala salajro de dungitoj.
rilataj
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 )
funkcia
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();
Ni transiru al pli kompleksaj taskoj de alia
Tasko 2.1
Kiuj vendistoj vendis pli ol 1997 pecojn de ero #30 en 1?
Domajna logiko (kiel antaŭe, ni preterlasas la deklaron pri la RDBMS):
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);
rilataj
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
funkcia
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;
Tasko 2.2
Por ĉiu kliento (antaŭnomo, familia nomo), trovu la du erojn (nomo) sur kiuj la kliento elspezis la plej multe da mono en 1997.
Etendante la domajnan logikon de la antaŭa ekzemplo:
CLASS Customer 'Клиент';
contactName 'ФИО' = DATA STRING[100] (Customer);
customer = DATA Customer (Order);
unitPrice = DATA NUMERIC[14,2] (Detail);
discount = DATA NUMERIC[6,2] (Detail);
rilataj
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
funkcia
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;
La operatoro PARTITION funkcias laŭ la sekva principo: ĝi sumigas la esprimon specifita post SUM (ĉi tie 1) ene de la specifitaj grupoj (ĉi tie Kliento kaj Jaro, sed povas esti ajna esprimo), ordigante ene de la grupoj laŭ la esprimoj specifitaj en ORDO ( ĉi tie aĉetita, kaj se estas egalaj, tiam per la interna produktokodo).
Tasko 2.3
Kiom da varoj devas esti menditaj de provizantoj por plenumi aktualajn mendojn.
Ni etendu la domajnan logikon denove:
CLASS Supplier 'Поставщик';
companyName = DATA STRING[100] (Supplier);
supplier = DATA Supplier (Product);
unitsInStock 'Остаток на складе' = DATA NUMERIC[10,3] (Product);
reorderLevel 'Норма продажи' = DATA NUMERIC[10,3] (Product);
rilataj
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
funkcia
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;
Tasko kun asterisko
Kaj la lasta ekzemplo estas de mi persone. Estas la logiko de socia reto. Homoj povas esti amikoj unu kun la alia kaj ŝati unu la alian. De funkcia datumbaza perspektivo, ĉi tio aspektus jene:
CLASS Person;
likes = DATA BOOLEAN (Person, Person);
friends = DATA BOOLEAN (Person, Person);
Necesas trovi eblajn kandidatojn por amikeco. Pli formale, vi devas trovi ĉiujn homojn A, B, C tiajn, ke A estas amikoj kun B, kaj B estas amikoj kun C, A ŝatas C, sed A ne estas amikoj kun C.
De funkcia datumbaza vidpunkto, la demando aspektus jene:
SELECT Person a, Person b, Person c WHERE
likes(a, c) AND NOT friends(a, c) AND
friends(a, b) AND friends(b, c);
La leganto estas invitita sendepende solvi ĉi tiun problemon en SQL. Oni supozas, ke estas multe malpli da amikoj ol tiuj, kiuj ŝatas. Tial ili estas en apartaj tabeloj. En kazo de sukcesa solvo, estas ankaŭ problemo kun du steletoj. Ŝia amikeco ne estas simetria. Sur funkcia datumbazo ĝi aspektus jene:
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: solvo de la problemo kun la unua kaj dua asterisko de
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
konkludo
Oni devas rimarki, ke la supra sintakso de la lingvo estas nur unu el la ebloj por efektivigi la supran koncepton. Estis SQL kiu estis prenita kiel la bazo, kaj la celo estis fari ĝin kiel eble plej simila al ĝi. Kompreneble, iu eble ne ŝatas la nomojn de ŝlosilvortoj, kazo de vortoj ktp. La ĉefa afero ĉi tie estas la koncepto mem. Se vi volas, vi povas fari ambaŭ C ++ kaj Python simila sintakso.
La priskribita datumbaza koncepto, laŭ mi, havas la jenajn avantaĝojn:
- faciligi. Ĉi tio estas relative subjektiva indikilo, kiu ne estas evidenta en simplaj kazoj. Sed se vi rigardas pli kompleksajn kazojn (ekzemple taskojn kun asteriskoj), tiam, laŭ mi, skribi tiajn demandojn estas multe pli facila.
- Инкапсуляция. En kelkaj ekzemploj, mi deklaris mezajn funkciojn (ekzemple, vendita, aĉetis ktp.), el kiuj postaj funkcioj estis konstruitaj. Ĉi tio ebligas al vi ŝanĝi la logikon de certaj funkcioj, se necese, sen ŝanĝi la logikon de tiuj kiuj dependas de ili. Ekzemple, vi povas fari vendojn vendita estis kalkulitaj el tute malsamaj objektoj, dum la resto de la logiko ne ŝanĝiĝos. Jes, en RDBMS tio povas esti farita per CREATE VIEW. Sed se vi skribas la tutan logikon tiamaniere, tiam ĝi ne aspektos tre legebla.
- Neniu Semantika Interspaco. Tia datumbazo funkcias kun funkcioj kaj klasoj (anstataŭ tabeloj kaj kampoj). Same kiel en klasika programado (supoze ke metodo estas funkcio kun la unua parametro en formo de klaso al kiu ĝi apartenas). Sekve, devus esti multe pli facile "amikiĝi" kun universalaj programlingvoj. Krome, ĉi tiu koncepto permesas efektivigi multe pli kompleksajn funkciojn. Ekzemple, vi povas enmeti deklarojn kiel ĉi tion en la datumbazon:
CONSTRAINT sold(Employee e, 1, 2019) > 100 IF name(e) = 'Петя' MESSAGE 'Что-то Петя продает слишком много одного товара в 2019 году';
- Heredo kaj polimorfismo. En funkcia datumbazo, vi povas enkonduki multoblan heredon per la CLASS ClassP: Class1, Class2-konstruaĵoj kaj efektivigi multoblan polimorfismon. Kiel ĝuste, eble mi skribos en la sekvaj artikoloj.
Kvankam ĉi tio estas nur koncepto, ni jam havas ian efektivigon en Java kiu tradukas ĉiun funkcian logikon en interrilatan logikon. Krome, la logiko de reprezentadoj kaj multaj aliaj aferoj estas bele ŝraŭbitaj al ĝi, dank' al kio ni ricevas tuton.
fonto: www.habr.com