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
Ə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
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
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.
Mənbə: www.habr.com