Sistemi i menaxhimit të funksionalitetit për baza të dhënash

Bota e të dhënave është pushtuar prej kohësh nga DBMS-të relacionale, ku përdoret gjuha SQL. Ka arritur në një pikë sa që variacione të reja quhen NoSQL. Atyre iu është mundësuar të gjejnë një vend në këtë treg, por DBMS-të relacionale nuk po shuhen dhe vazhdojnë të përdoren aktivisht për qëllimet e tyre.

Në këtë artikull dëshiroj të përshkruaj konceptin e një baze të dhënash funksionale. Për një kuptim më të mirë, do ta bëj këtë nëpërmjet krahasimit me modelin klasik relacionale. Si shembuj do të përdoren detyra nga teste të ndryshme për SQL, të gjetura në internet.

Hyrje

Baza të dhënash relacionale operojnë me tabela dhe fusha. Në një bazë të dhënash funksionale, ato do të zëvendësohen me klasa dhe funksione përkatësisht. Një fushë në tabelë me N çelësa do të përfaqësohej si një funksion me N parametra. Në vend të lidhjeve ndërmjet tabelave do të përdoren funksione që kthejnë objekte të klasës në të cilën po ndodhet lidhja. Në vend të JOIN do të përdoret kompozita e funksioneve.

Para se të kalojmë drejtpërdrejt te detyrat, do të përshkruaj logjikën e domenit. Për DDL do të përdor sintaksën e PostgreSQL. Për funksional, do të përdor sintaksën time.

Tabelat dhe fushat

Një objekt të thjeshtë Sku me fushat emri dhe çmimi:

Relacional

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

Funksionale

KLASA Sku;
emri = STRINGA TË DHËNASH[100] (Sku);
çmimi = NUMRI TË DHËNASH[10,5] (Sku);

Ne shpallim dy funksione, të cilat pranojnë një parametrin Sku dhe kthejnë një tip primitiv.

Supozimi është se në DBMS-në funksionale, çdo objekt do të ketë një kod të brendshëm, i cili gjenerohet automatikisht dhe i cili mund të merret nëse është e nevojshme.

Do të caktojmë çmimin për produktin / dyqanin / furnizuesin. Ai mund të ndryshojë me kalimin e kohës, prandaj do të shtojmë në tabelë një fushë kohe. Do ta anashkaloj shpalljen e tabelave për referencat në bazën e të dhënave relacionale për të shkurtuar kodin:

Relacional

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

KLASA Sku;
CLASS Dyqani;
CLASS Furnizues;
dateTime = TË DHËNAT DATETIME (Sku, Dyqani, Furnizues);
çmimi = TË DHËNAT NUMERIK[10,5] (Sku, Dyqani, Furnizues);

Indeksat

Për shembullin e fundit, do të ndërtojmë një indeks sipas të gjitha çelësave dhe datës, në mënyrë që të mund të gjejmë shpejt çmimin në një kohë të caktuar.

Relacional

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

Funksionale

INDIKS Sku sk, Dyqani st, Furnizuesi sp, dataKoha(sk, st, sp);

Detyrat

Le të fillojmë me detyra relativisht të thjeshta, të marrë nga përkatësia artikullit në Habr.

Fillimisht do të shpallim logjikën e domenit (për bazën relacionale kjo është bërë drejtpërdrejt në artikullin e dhënë).

Departamenti CLASS;
emri = STRING DATA[100] (Departamenti);

CLASS Employee;
department = DATA Department (Employee);
chief = DATA Employee (Employee);
name = DATA STRING[100] (Employee);
salary = DATA NUMERIC[14,2] (Employee);

Detyra 1.1

Shfaqni listën e punonjësve që marrin pagë më të madhe se ajo e shefit të tyre të drejtpërdrejtë.

Relacional

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

Funksionale

Zgjidh emrin (Punonjës a) KU paga(a) > paga(kreu(a));

Detyra 1.2

Shfaqni listën e punonjësve që marrin pagën maksimale në departamentin e tyre.

Relacional

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

Funksionale

maxSalary 'Paga maksimale' (Departamentet) = 
    GRUPO MAX paga (Punonjësi e) NËSE departamenti(e) = s;
Zgjidh emrin (Punonjësi a) KU paga(a) = maxSalary(departamenti(a));

// или если "заинлайнить"
SELECT name(Employee a) WHERE 
    salary(a) = maxSalary(GROUP MAX salary(Employee e) IF department(e) = department(a));

Të dy implementimet janë ekuivalente. Në rastin e parë në bazën relacionale mund të përdoret CREATE VIEW, i cili do të llogarisë në të njëjtën mënyrë pagën maksimale për një departament të veçantë. Në vazhdim, për ilustrim do të përdor rastin e parë, pasi ai tregon më mirë zgjidhjen.

Detyra 1.3

Shfaqni listën e ID-ve të departamenteve, numri i punonjësve në të cilët nuk kalon 3 persona.

Relacional

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

Funksionale

numriPunonjësve 'Numri i punonjësve' (Departamenti d) = 
    GROUP SUM 1 IF department(Employee e) = d;
SELECT Departamenti d KUJDES numriPunonjësve(d) <= 3;

Detyra 1.4

Shfaqni listën e punonjësve, të cilët nuk kanë një shef të caktuar duke punuar në të njëjtin departament.

Relacional

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

Zgjidh emrin(Empeloyee a) KUJDESI (nuk janë (departamenti(kreu(a)) = departamenti(a));

Detyra 1.5

Gjeni listën e ID-ve të departamenteve me pagën totale maksimale të punonjësve.

Relacional

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

pagaMaksimale 'Paga Maksmale' (Departamenti d) = 
    GROUP SUM salary(Employee e) IF department(e) = d;
pagaMaksimaleDepartamentesh 'Paga Maksmale e Departamentit' () = 
    GRUPI MAX pagaMaksimale(Departamenti d);
Zgjidh Departamentin d KUJ pagatMaksimale(d) = pagaMaksimaleDepartamentesh();

Të kalojmë në detyra më të vështira nga një tjetër artikullit. Ajo ofron një analizë të detajuar mbi mënyrën se si të realizoni këtë detyrë në MS SQL.

Detyra 2.1

Cilët shitës e shitën në vitin 1997 mbi 30 copë të produktit nr. 1?

Logjika e domenit (ashtu si më parë në DBMS, do të anashkaloj shpalljen):

CLASS Employee 'Shitës';
lastName 'Mbiemri' = DATA STRING[100] (Punonjës);

CLASS Product 'Produkt';
id = DATA INTEGER (Product);
name = DATA STRING[100] (Product);

CLASS Order 'Porosi';
date = DATA DATE (Order);
employee = DATA Employee (Order);

CLASS Detail 'Rreshti i porosisë';

order = DATA Order (Detail);
product = DATA Product (Detail);
quantity = DATA NUMERIC[10,5] (Detail);

Relacional

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

shitur (Punonjësi e, INTEGER idProdukti, INTEGER vit) = 
    GRUPI SUM sasia(OrderDetail d) NËSE 
        punonjës(order(d)) = e DHE 
        id(produkti(d)) = idProdukti DHE 
        ekstraktoVitin(data(order(d))) = vit;
ZGJIDH lastName(Punonjësi e) KU shitur(e, 1, 1997) > 30;

Detyra 2.2

Për çdo klient (emri, mbiemri) gjej dy produkte (tituj), për të cilat klienti ka shpenzuar më shumë para në vitin 1997.

Zgjasim logjikën e domenit nga shembulli i mëparshëm:

CLASS Customer 'Klienti';
contactName 'Emri dhe MBIEMRI' = DATA STRING[100] (Customer);

customer = DATA Customer (Order);

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

Relacional

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

shuma (Detaj d) = sasia(d) * çmimiNjësisë(d) * (1 - zbritje(d));
bleu 'Blerë' (Kundër c, Produkt p, INTEGER y) = 
    GRUPI SHUMË SUM (Detaj d) NËSE 
        klienti(order(d)) = c DHE 
        produkti(d) = p DHE 
        nxirrVit(inorders(d)) = y;
vlerësimi 'Vlerësimi' (Kundër c, Produkt p, INTEGER y) = 
    SHUMA E PARTISIONIT 1 RENDIT DESC blerë(c, p, y), p NGA c, y;
ZGJEDH emrinKontaktil(Customer c), emrin(Produkt p) KU vlerësimi(c, p, 1997) < 3;

Оператор PARTITION работает по следующему принципу: он суммирует выражение, указанное после SUM (здесь 1), внутри указанных групп (здесь Customer и Year, но может быть любое выражение), сортируя внутри групп по выражениям, указанным в ORDER (здесь bought, а если равны, то по внутреннему коду продукта).

Задача 2.3

Сколько товаров нужно заказать у поставщиков для выполнения текущих заказов.

Опять расширяем доменную логику:

KOMPANIA Furnizues 'Furnizues';
companyName = DATA STRING[100] (Furnizues);

supplier = DATA Supplier (Product);

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

Relacional

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

ePorositur 'Porositur, por nuk është dërguar' (Product p) = 
    GRUPI SHUMA sasia(OrderDetail d) NSE produkt(d) = p;
përPorositur 'Këtu për porosinë' (Product p) = ePorositur(p) + niveliRiporosis(p) - njësitëNëStok(p);
Zgjidh emrinKompany(suppliers(Product p)), emri(p), përPorositur(p) KU përPorositur(p) > 0;

Задача со звездочкой

И последней пример лично от меня. Есть логика социальной сети. Люди могут дружить друг с другом и нравится друг другу. С точки зрения функциональной базы данных это будет выглядеть следующим образом:

KLASA Person;
pëlqen = TË DHËNA BOOLEAN (Person, Person);
miq = TË DHËNA BOOLEAN (Person, Person);

Необходимо найти возможных кандидатов на дружбу. Более формализовано нужно найти всех людей A, B, C таких, что A дружит с B, а B дружит с C, A нравится C, но A не дружит с C.
С точки зрения функциональной базы данных запрос будет выглядеть следующим образом:

Zgjidh Person a, Person b, Person c ku 
    pëlqen(a, c) DHE NUK janë miq(a, c) DHE 
    janë miq(a, b) DHE janë miq(b, c);

Читателю предлагается самостоятельно решить эту задачу на SQL. Предполагается, что друзей гораздо меньше чем тех, кто нравится. Поэтому они лежат в отдельных таблицах. В случае успешного решения есть также задача с двумя звездочками. В ней дружба не симметрична. На функциональной базе данных это будет выглядеть так:

Zgjidh Person a, Person b, Person c ku 
    pëlqen(a, c) DHE NUK janë miq(a, c) DHE 
    (miqte(a, b) OSE miqte(b, a)) DHE 
    (miqte(b, c) OSE miqte(c, b));

UPD: решение задачи с первой и второй звездочкой от 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 

Përfundimi

Следует отметить, что приведенный синтаксис языка — это всего лишь один из вариантов реализации приведенной концепции. За основу был взят именно SQL, и целью было, чтобы он максимально был похож на него. Конечно, кому-то могут не понравится названия ключевых слов, регистры слов и прочее. Здесь главное — именно сама концепция. При желании можно сделать и C++, и Python подобный синтаксис.

Описанная концепция базы данных, на мой взгляд обладает следующими преимуществами:

  • Thjeshtësia. Это относительно субъективный показатель, который не очевиден на простых случаях. Но если посмотреть более сложные случаи (например, задачи со звездочками), то, на мой взгляд, писать такие запросы значительно проще.
  • Инкапсуляция. В некоторых примерах я объявлял промежуточные функции (например, sold, bought и т.д.), от которых строились последующие функции. Это позволяет при необходимости изменять логику определенных функций без изменения логики зависящих от них. Например, можно сделать, чтобы продажи sold считались от совершенно других объектов, при этом остальная логика не изменится. Да, в РСУБД это можно реализовать при помощи CREATE VIEW. Но если всю логику писать таким образом, то она будет выглядеть не очень читабельной.
  • Отсутствие семантического разрыва. Kjo bazë të dhënash operon me funksione dhe klasa (përveç tabelave dhe fushave). Po ashtu si në programimin klasik (nëse konsiderohet se metoda është një funksion me parametrin e parë si klasën që i përket). Si rezultat, është shumë më e lehtë ta “bashkëpunosh” me gjuhët universale të programimit. Për më tepër, kjo koncept i lejon të implementohen funksione shumë më komplekse. Përshembull, është e mundur të integrohen operatorë të tillë në bazën e të dhënave:

    KUSHTESË shes(Employee e, 1, 2019) > 100 NËSE emri(e) = 'Petja' MESAZH  'diçka Petja po shes shumë nga një mall në vitin 2019';

  • Trashëgimia dhe polimorfizmi. Në një bazë të dhënash funksionale mund të futet trashëgimia e shumëfishtë përmes konstruktimeve CLASS ClassP: Class1, Class2 dhe të implementohet polimorfizmi i shumëfishtë. Si pikë dhe mënyrë, ndoshta do të shkruaj në artikujt e ardhshëm.

Megjithëse kjo është vetëm një koncept, ne tashmë kemi një implementim të caktuar në Java, i cili përkthen logjikën funksionale në logjikën relacionale. Plus, është e lidhur bukur logjika e paraqitjeve dhe shumë gjëra të tjera, për shkak të të cilave rezulton një e tërë platforma. Në thelb, ne përdorim RDBMS (deri tani vetëm PostgreSQL) si një "makinë virtuale". Me këtë përkthim, ndonjëherë paraqiten probleme, pasi optimizuesi i kërkesave RDBMS nuk di njohuritë e caktuara që e di FSDBMS. Në teori, është e mundur të implementohet një sistem menaxhimi të bazës së të dhënave, i cili do të përdorë si depo një strukturë të përshtatur saktësisht për logjikën funksionale.

Burimi: habr.com

Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS 🔥 Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS | ProHoster