Funksional DBMS

Verilənlər bazası dünyası çoxdan SQL dilindən istifadə edən relational DBMS-lər tərəfindən ələ keçirilib. O qədər ki, ortaya çıxan növlər NoSQL adlanır. Onlar bu bazarda özləri üçün müəyyən yer qazana bildilər, lakin relational DBMS ölmək fikrində deyil və öz məqsədləri üçün fəal şəkildə istifadə olunmağa davam edirlər.

Bu yazıda mən funksional verilənlər bazası anlayışını təsvir etmək istəyirəm. Daha yaxşı başa düşmək üçün bunu klassik əlaqə modeli ilə müqayisə edəcəyəm. Nümunə olaraq, İnternetdə tapılan müxtəlif SQL testlərindən alınan tapşırıqlar istifadə olunacaq.

Giriş

Əlaqəli verilənlər bazaları cədvəllər və sahələr üzərində işləyir. Funksional verilənlər bazasında bunun əvəzinə müvafiq olaraq siniflər və funksiyalar istifadə olunacaq. N düyməsi olan cədvəldəki sahə N parametrin funksiyası kimi təqdim olunacaq. Cədvəllər arasında bağlantılar əvəzinə, keçidin getdiyi sinfin obyektlərini qaytaran funksiyalardan istifadə ediləcək. JOIN əvəzinə funksiya tərkibi istifadə olunacaq.

Birbaşa tapşırıqlara keçməzdən əvvəl domen məntiqinin tapşırığını təsvir edəcəyəm. DDL üçün PostgreSQL sintaksisindən istifadə edəcəyəm. Funksional üçün öz sintaksisi.

Cədvəllər və sahələr

Adı və qiymət sahələri olan sadə Sku obyekti:

əlaqəli

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

Funksional

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

İkisini elan edirik funksiyaları, bir Sku parametrini giriş kimi qəbul edir və primitiv növü qaytarır.

Ehtimal olunur ki, funksional DBMS-də hər bir obyekt avtomatik olaraq yaradılan və lazım olduqda daxil olmaq mümkün olan bəzi daxili koda malik olacaqdır.

Məhsul / mağaza / təchizatçı üçün qiyməti təyin edək. Zamanla dəyişə bilər, ona görə də cədvələ vaxt sahəsi əlavə edək. Kodu qısaltmaq üçün əlaqəli verilənlər bazasındakı kataloqlar üçün cədvəllərin elanını atlayacağam:

əlaqəli

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

Funksional

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

Indeksləri

Sonuncu misal üçün, müəyyən bir müddət üçün qiyməti tez tapa bilməmiz üçün bütün açarlar və tarixlər üzrə bir indeks quraq.

əlaqəli

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

Funksional

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

vəzifələri

Müvafiq məsələlərdən götürülmüş nisbətən sadə məsələlərlə başlayaq Məqalə Habr haqqında.

Əvvəlcə domen məntiqini elan edək (əlaqəli verilənlər bazası üçün bu, birbaşa yuxarıdakı məqalədə edilir).

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

Tapşırıq 1.1

Dərhal rəhbərin maaşından daha çox əmək haqqı alan işçilərin siyahısını göstərin.

əlaqəli

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

Funksional

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

Tapşırıq 1.2

Şöbələrində ən yüksək maaş alan işçilərin siyahısını göstərin

əlaqəli

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

Funksional

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

Hər iki tətbiq ekvivalentdir. Relational verilənlər bazasındakı ilk hal üçün, CREATE VIEW-dən istifadə edə bilərsiniz, bu da eyni şəkildə əvvəlcə orada müəyyən bir şöbə üçün maksimum əmək haqqını hesablayacaqdır. Gələcəkdə aydınlıq üçün birinci halda istifadə edəcəyəm, çünki o, həlli daha yaxşı əks etdirir.

Tapşırıq 1.3

İşçilərin sayı 3 nəfərdən çox olmayan şöbə şəxsiyyət vəsiqələrinin siyahısını göstərin.

əlaqəli

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

Funksional

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

Tapşırıq 1.4

Eyni şöbədə işləyən təyin edilmiş meneceri olmayan işçilərin siyahısını göstərin.

əlaqəli

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

Funksional

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

Tapşırıq 1.5

İşçilərin maksimum əmək haqqı ilə şöbə şəxsiyyət vəsiqələrinin siyahısını tapın.

əlaqəli

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 )

Funksional

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

Gəlin digərindən daha mürəkkəb tapşırıqlara keçək Məqalə. Bu tapşırığın MS SQL-də necə həyata keçiriləcəyinə dair ətraflı təhlili ehtiva edir.

Tapşırıq 2.1

Hansı satıcılar 1997-ci ildə 30 ədəddən çox №1 mal satıblar?

Domen məntiqi (əvvəlki kimi, RDBMS-də bəyannaməni atlayırıq):

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

əlaqəli

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

Funksional

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;

Tapşırıq 2.2

Hər bir müştəri (ad, soyad) üçün müştərinin 1997-ci ildə ən çox pul xərclədiyi iki maddəni (ad) tapın.

Əvvəlki nümunədən domen məntiqinin genişləndirilməsi:

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

customer = DATA Customer (Order);

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

əlaqəli

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

Funksional

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;

PARTITION operatoru aşağıdakı prinsipə əsasən işləyir: o, SUM-dan sonra göstərilən ifadəni (burada 1) müəyyən edilmiş qruplar daxilində (burada Müştəri və İl, lakin istənilən ifadə ola bilər) cəmləşdirir, SİFARİŞ-də göstərilən ifadələrə uyğun olaraq qruplar daxilində çeşidlənir (burada XNUMX). burada alınıb və bərabərdirsə, daxili məhsul kodu ilə).

Tapşırıq 2.3

Cari sifarişləri yerinə yetirmək üçün təchizatçılardan nə qədər mal sifariş etmək lazımdır.

Domen məntiqini yenidən genişləndirək:

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

supplier = DATA Supplier (Product);

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

əlaqəli

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

Funksional

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;

Ulduz işarəsi ilə tapşırıq

Və son nümunə şəxsən məndəndir. Sosial şəbəkənin məntiqi var. İnsanlar bir-biri ilə dost ola, bir-birini bəyənə bilər. Funksional verilənlər bazası baxımından bu belə görünür:

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

Dostluğa mümkün namizədləri tapmaq lazımdır. Daha rəsmi desək, bütün A, B, C insanları elə tapmaq lazımdır ki, A B ilə dost olsun, B isə C ilə dost olsun, A C ilə dost olsun, A isə C ilə dost olmasın.
Funksional verilənlər bazası baxımından sorğu belə görünür:

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

Oxucu SQL-də bu problemi müstəqil həll etməyə dəvət olunur. Dostların bəyənənlərdən qat-qat az olduğu güman edilir. Buna görə də onlar ayrı-ayrı cədvəllərdədirlər. Uğurlu bir həll olması halında, iki ulduzla da problem var. Onun dostluğu simmetrik deyil. Funksional verilənlər bazasında bu belə görünür:

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: birinci və ikinci ulduzla problemin həlli 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 

Nəticə

Qeyd etmək lazımdır ki, dilin yuxarıdakı sintaksisi yuxarıdakı konsepsiyanın həyata keçirilməsi variantlarından yalnız biridir. Əsas götürülən SQL idi və məqsəd onu mümkün qədər ona oxşar etmək idi. Təbii ki, kiminsə xoşuna gəlməyə bilər açar sözlərin adları, sözlərin hərfi və s. Burada əsas şey konsepsiyanın özüdür. İstəyirsinizsə, həm C ++, həm də Python-u oxşar sintaksis edə bilərsiniz.

Təsvir edilən verilənlər bazası konsepsiyası, mənim fikrimcə, aşağıdakı üstünlüklərə malikdir:

  • Sadəlik. Bu, sadə hallarda aşkar olmayan nisbətən subyektiv göstəricidir. Ancaq daha mürəkkəb hallara (məsələn, ulduzlu tapşırıqlara) baxsanız, mənim fikrimcə, bu cür sorğuları yazmaq daha asandır.
  • Encapsulation. Bəzi nümunələrdə mən ara funksiyaları elan etdim (məsələn, satılır, alıb və s.), ondan sonrakı funksiyalar quruldu. Bu, lazım gələrsə, onlardan asılı olanların məntiqini dəyişdirmədən müəyyən funksiyaların məntiqini dəyişdirməyə imkan verir. Məsələn, siz satış edə bilərsiniz satılır tamamilə fərqli obyektlərdən hesablanıb, qalan məntiq isə dəyişməyəcək. Bəli, RDBMS-də bunu CREATE VIEW ilə etmək olar. Amma bütün məntiqi bu şəkildə yazsanız, o zaman çox oxunaqlı görünməyəcək.
  • Semantik boşluq yoxdur. Belə verilənlər bazası funksiyalar və siniflərlə işləyir (cədvəllər və sahələr əvəzinə). Klassik proqramlaşdırmada olduğu kimi (metodun aid olduğu sinif formasında birinci parametrə malik funksiya olduğunu fərz etsək). Müvafiq olaraq, universal proqramlaşdırma dilləri ilə "dostluq etmək" daha asan olmalıdır. Bundan əlavə, bu konsepsiya daha mürəkkəb funksiyaları həyata keçirməyə imkan verir. Məsələn, verilənlər bazasına bu kimi ifadələri yerləşdirə bilərsiniz:

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

  • İrsiyyət və polimorfizm. Funksional verilənlər bazasında siz CLASS ClassP: Class1, Class2 konstruksiyaları vasitəsilə çoxsaylı miras təqdim edə və çoxsaylı polimorfizmi həyata keçirə bilərsiniz. Necə dəqiq, bəlkə də sonrakı məqalələrdə yazacam.

Bu sadəcə bir konsepsiya olsa da, bizim Java-da bütün funksional məntiqi əlaqə məntiqinə çevirən bəzi tətbiqetmələrimiz var. Üstəlik, təmsillərin məntiqi və bir çox başqa şeylər ona gözəl şəkildə bağlanır, bunun sayəsində bütöv bir şey əldə edirik. platforma. Əslində, biz RDBMS-dən (indiyə qədər yalnız PostgreSQL) "virtual maşın" kimi istifadə edirik. Bu tərcümə bəzən problemlərə səbəb olur, çünki RDBMS sorğu optimallaşdırıcısı FDBMS-nin etdiyi müəyyən statistik məlumatları bilmir. Nəzəri olaraq, funksional məntiq üçün xüsusi olaraq uyğunlaşdırılmış, müəyyən bir strukturdan yaddaş kimi istifadə edəcək verilənlər bazası idarəetmə sistemini həyata keçirmək mümkündür.

Mənbə: www.habr.com

Добавить комментарий