Funkcia DBMS

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 artikoloj sur Habr.

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 artikoloj. Ĝi enhavas detalan analizon pri kiel efektivigi ĉi tiun taskon en MS SQL.

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

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. platformo. Esence, ni uzas RDBMS (nur PostgreSQL ĝis nun) kiel "virtuala maŝino". Ĉi tiu traduko foje kaŭzas problemojn ĉar la RDBMS-demanda optimumiganto ne konas certajn statistikojn, kiujn la FDBMS faras. En teorio, estas eble efektivigi datumbazan administradsistemon kiu uzos certan strukturon kiel stokadon, adaptitan specife por funkcia logiko.

fonto: www.habr.com

Aldoni komenton