DBMS fonksyonèl

Mond baz done depi lontan te domine pa DBMS relasyon, ki sèvi ak lang SQL. Se konsa, se konsa ke variants émergentes yo rele NoSQL. Yo jere yo skilte yon sèten plas pou tèt yo nan mache sa a, men DBMS relasyon yo pa pral mouri, epi yo kontinye aktivman itilize pou rezon yo.

Nan atik sa a mwen vle dekri konsèp nan yon baz done fonksyonèl. Pou yon pi bon konpreyansyon, mwen pral fè sa nan konpare li ak modèl la relasyon klasik. Pwoblèm ki soti nan plizyè tès SQL yo jwenn sou Entènèt la pral sèvi kòm egzanp.

Entwodiksyon

Baz done relasyon yo opere sou tab ak jaden. Nan yon baz done fonksyonèl, klas ak fonksyon yo pral itilize olye, respektivman. Y ap reprezante yon jaden nan yon tablo ki gen N kle kòm yon fonksyon N paramèt. Olye de relasyon ki genyen ant tab yo, yo pral sèvi ak fonksyon ki retounen objè nan klas kote yo fè koneksyon an. Konpozisyon fonksyon yo pral itilize olye pou yo JOIN.

Anvan ou deplase dirèkteman nan travay yo, mwen pral dekri travay la nan lojik domèn. Pou DDL mwen pral sèvi ak sentaks PostgreSQL. Pou fonksyonèl li gen sentaks pwòp li yo.

Tablo ak jaden

Yon objè Sku senp ak non ak pri jaden:

Relasyonèl

CREATE TABLE Sku
(
    id bigint NOT NULL,
    name character varying(100),
    price numeric(10,5),
    CONSTRAINT id_pkey PRIMARY KEY (id)
)

Fonksyonèl

CLASS Sku;
name = DATA STRING[100] (Sku);
price = DATA NUMERIC[10,5] (Sku);

Nou anonse de fonksyon, ki pran yon paramèt Sku kòm opinyon epi retounen yon kalite primitif.

Li sipoze ke nan yon DBMS fonksyonèl chak objè pral gen kèk kòd entèn ki se otomatikman pwodwi epi yo ka jwenn aksè si sa nesesè.

Ann fikse pri pou pwodwi/magazen/founisè a. Li ka chanje sou tan, kidonk ann ajoute yon jaden tan sou tab la. Mwen pral sote deklare tab pou anyè nan yon baz done relasyon pou diminye kòd la:

Relasyonèl

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)
)

Fonksyonèl

CLASS Sku;
CLASS Store;
CLASS Supplier;
dateTime = DATA DATETIME (Sku, Store, Supplier);
price = DATA NUMERIC[10,5] (Sku, Store, Supplier);

Endèks

Pou dènye egzanp lan, nou pral bati yon endèks sou tout kle ak dat la pou nou ka byen vit jwenn pri a pou yon tan espesifik.

Relasyonèl

CREATE INDEX prices_date
    ON prices
    (skuId, storeId, supplierId, dateTime)

Fonksyonèl

INDEX Sku sk, Store st, Supplier sp, dateTime(sk, st, sp);

travay

Ann kòmanse ak pwoblèm relativman senp pran nan korespondan an atik sou Habr.

Premyèman, ann deklare lojik domèn (pou baz done relasyon an fè sa dirèkteman nan atik ki anwo a).

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);

Travay 1.1

Montre yon lis anplwaye ki resevwa yon salè ki pi gran pase sipèvizè imedya yo.

Relasyonèl

select a.*
from   employee a, employee b
where  b.id = a.chief_id
and    a.salary > b.salary

Fonksyonèl

SELECT name(Employee a) WHERE salary(a) > salary(chief(a));

Travay 1.2

Lis anplwaye yo ki resevwa salè maksimòm nan depatman yo

Relasyonèl

select a.*
from   employee a
where  a.salary = ( select max(salary) from employee b
                    where  b.department_id = a.department_id )

Fonksyonèl

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));

Tou de aplikasyon yo ekivalan. Pou premye ka a, nan yon baz done relasyon ou ka itilize CREATE VIEW, ki nan menm fason an pral premye kalkile salè maksimòm pou yon depatman espesifik nan li. Nan sa ki anba a, pou klè, mwen pral sèvi ak premye ka a, paske li pi byen reflete solisyon an.

Travay 1.3

Montre yon lis idantifikasyon depatman, kantite anplwaye ki pa depase 3 moun.

Relasyonèl

select department_id
from   employee
group  by department_id
having count(*) <= 3

Fonksyonèl

countEmployees 'Количество сотрудников' (Department d) = 
    GROUP SUM 1 IF department(Employee e) = d;
SELECT Department d WHERE countEmployees(d) <= 3;

Travay 1.4

Montre yon lis anplwaye ki pa gen yon manadjè deziyen k ap travay nan menm depatman an.

Relasyonèl

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

Fonksyonèl

SELECT name(Employee a) WHERE NOT (department(chief(a)) = department(a));

Travay 1.5

Jwenn yon lis idantite depatman ak salè maksimòm total anplwaye yo.

Relasyonèl

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 )

Fonksyonèl

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();

Ann ale nan travay ki pi konplèks nan yon lòt atik. Li gen yon analiz detaye sou fason pou aplike travay sa a nan MS SQL.

Travay 2.1

Ki vandè ki te vann plis pase 1997 inite pwodwi nimewo 30 an 1?

Lojik domèn (tankou anvan sou RDBMS nou sote deklarasyon an):

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);

Relasyonèl

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

Fonksyonèl

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;

Travay 2.2

Pou chak achtè (non, ti non), jwenn de machandiz (non) kote achtè a te depanse plis lajan an 1997.

Nou pwolonje lojik domèn nan egzanp anvan an:

CLASS Customer 'Клиент';
contactName 'ФИО' = DATA STRING[100] (Customer);

customer = DATA Customer (Order);

unitPrice = DATA NUMERIC[14,2] (Detail);
discount = DATA NUMERIC[6,2] (Detail);

Relasyonèl

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

Fonksyonèl

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;

Operatè a PARTITION travay sou prensip sa a: li sòm ekspresyon ki espesifye apre SUM (isit la 1), nan gwoup yo espesifye (isit la Kliyan ak Ane, men li ta ka nenpòt ekspresyon), klasman nan gwoup yo pa ekspresyon yo espesifye nan LÒD la ( isit la te achte, epi si egal, Lè sa a, dapre kòd la pwodwi entèn).

Travay 2.3

Konbyen machandiz yo bezwen bay lòd nan men founisè yo satisfè lòd aktyèl yo.

Ann elaji lojik domèn ankò:

CLASS Supplier 'Поставщик';
companyName = DATA STRING[100] (Supplier);

supplier = DATA Supplier (Product);

unitsInStock 'Остаток на складе' = DATA NUMERIC[10,3] (Product);
reorderLevel 'Норма продажи' = DATA NUMERIC[10,3] (Product);

Relasyonèl

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

Fonksyonèl

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;

Pwoblèm ak yon asterisk

Ak dènye egzanp lan soti nan mwen pèsonèlman. Gen lojik yon rezo sosyal. Moun yo ka zanmi youn ak lòt epi youn renmen lòt. Soti nan yon pèspektiv baz done fonksyonèl li ta sanble sa a:

CLASS Person;
likes = DATA BOOLEAN (Person, Person);
friends = DATA BOOLEAN (Person, Person);

Li nesesè pou jwenn kandida posib pou amitye. Plis fòmèlman, ou bezwen jwenn tout moun A, B, C tankou A se zanmi ak B, ak B se zanmi ak C, A renmen C, men A se pa zanmi ak C.
Soti nan yon pèspektiv baz done fonksyonèl, rechèch la ta sanble sa a:

SELECT Person a, Person b, Person c WHERE 
    likes(a, c) AND NOT friends(a, c) AND 
    friends(a, b) AND friends(b, c);

Yo ankouraje lektè a pou rezoud pwoblèm sa a nan SQL poukont li. Li sipoze ke gen anpil mwens zanmi pase moun ou renmen. Se poutèt sa yo nan tab separe. Si siksè, gen tou yon travay ak de zetwal. Nan li, amitye pa simetrik. Sou yon baz done fonksyonèl li ta sanble sa a:

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: solisyon a pwoblèm nan ak premye ak dezyèm etwal soti nan 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 

Konklizyon

Li ta dwe remake ke sentaks lang yo bay la se jis youn nan opsyon yo pou aplike konsèp yo bay la. SQL te pran kòm baz la, ak objektif la se te pou li sanble ak li ke posib. Natirèlman, kèk ka pa renmen non mo kle, rejis mo, elatriye. Bagay pwensipal lan isit la se konsèp nan tèt li. Si ou vle, ou ka fè tou de C++ ak Python sentaks menm jan an.

Konsèp baz done ki dekri a, nan opinyon mwen, gen avantaj sa yo:

  • Senplisite. Sa a se yon endikatè relativman subjectif ki pa evidan nan ka senp. Men, si ou gade nan ka ki pi konplèks (pa egzanp, pwoblèm ak asterisk), Lè sa a, nan opinyon mwen, ekri demann sa yo se pi fasil.
  • Enkapasans. Nan kèk egzanp mwen te deklare fonksyon entèmedyè (pa egzanp, vann, te achte elatriye), ki soti nan ki fonksyon ki vin apre yo te bati. Sa a pèmèt ou chanje lojik nan sèten fonksyon, si sa nesesè, san yo pa chanje lojik la nan sa yo ki depann sou yo. Pou egzanp, ou ka fè lavant vann yo te kalkile soti nan objè konplètman diferan, pandan y ap rès la nan lojik la pa pral chanje. Wi, sa ka aplike nan yon RDBMS lè l sèvi avèk CREATE VIEW. Men, si tout lojik la ekri nan fason sa a, li pa pral gade trè lizib.
  • Pa gen diferans semantik. Yon baz done sa yo opere sou fonksyon ak klas (olye pou yo tab ak jaden). Menm jan ak nan pwogram klasik (si nou sipoze ke yon metòd se yon fonksyon ki gen premye paramèt nan fòm klas la ki li fè pati). An konsekans, li ta dwe pi fasil pou "fè zanmi" ak langaj pwogramasyon inivèsèl. Anplis de sa, konsèp sa a pèmèt pou fonksyonalite pi konplèks yo dwe aplike. Pou egzanp, ou ka entegre operatè tankou:

    CONSTRAINT sold(Employee e, 1, 2019) > 100 IF name(e) = 'Петя' MESSAGE  'Что-то Петя продает слишком много одного товара в 2019 году';

  • Eritaj ak polimorfism. Nan yon baz done fonksyonèl, ou ka prezante eritaj miltip atravè CLASS ClassP a: Klas1, Klas2 konstwi epi aplike polimorfism miltip. Mwen pral pwobableman ekri ki jan egzakteman nan atik nan lavni.

Menm si sa a se jis yon konsèp, nou deja gen kèk aplikasyon nan Java ki tradui tout lojik fonksyonèl nan lojik relasyon. Anplis de sa, lojik nan reprezantasyon ak anpil lòt bagay yo trè byen tache ak li, gras a ki nou jwenn yon antye. platfòm. Esansyèlman, nou itilize RDBMS (sèlman PostgreSQL pou kounye a) kòm yon "machin vityèl". Pwoblèm pafwa rive ak tradiksyon sa a paske RDBMS kery optimizer pa konnen sèten estatistik ke FDBMS a konnen. Nan teyori, li posib pou aplike yon sistèm jesyon baz done ki pral sèvi ak yon estrikti sèten kòm depo, adapte espesyalman pou lojik fonksyonèl.

Sous: www.habr.com

Add nouvo kòmantè