Ma'lumotlar bazalari dunyosida uzoq vaqtdan beri SQL tilidan foydalanadigan relyatsion DBMSlar hukmronlik qiladi. Shunday qilib, paydo bo'lgan variantlar NoSQL deb ataladi. Ular ushbu bozorda o'zlari uchun ma'lum o'rinni egallashga muvaffaq bo'lishdi, ammo relyatsion DBMS o'lmaydi va o'z maqsadlari uchun faol foydalanishda davom etadi.
Ushbu maqolada men funktsional ma'lumotlar bazasi tushunchasini tasvirlamoqchiman. Yaxshiroq tushunish uchun men buni klassik munosabatlar modeli bilan taqqoslash orqali qilaman. Misol tariqasida Internetda topilgan turli xil SQL testlaridagi muammolardan foydalaniladi.
kirish
Relyatsion ma'lumotlar bazalari jadvallar va maydonlarda ishlaydi. Funktsional ma'lumotlar bazasida o'rniga mos ravishda sinflar va funktsiyalar qo'llaniladi. N tugmali jadvaldagi maydon N parametr funksiyasi sifatida taqdim etiladi. Jadvallar o'rtasidagi munosabatlar o'rniga ulanish amalga oshirilgan sinf ob'ektlarini qaytaradigan funktsiyalar qo'llaniladi. JOIN o‘rniga funksiya tarkibi ishlatiladi.
To'g'ridan-to'g'ri vazifalarga o'tishdan oldin men domen mantig'ining vazifasini tasvirlab beraman. DDL uchun men PostgreSQL sintaksisidan foydalanaman. Funktsionallik uchun u o'z sintaksisiga ega.
Jadvallar va maydonlar
Nom va narx maydonlariga ega oddiy Sku obyekti:
Aloqaviy
CREATE TABLE Sku
(
id bigint NOT NULL,
name character varying(100),
price numeric(10,5),
CONSTRAINT id_pkey PRIMARY KEY (id)
)
funktsional
CLASS Sku;
name = DATA STRING[100] (Sku);
price = DATA NUMERIC[10,5] (Sku);
Biz ikkitasini e'lon qilamiz vazifalari, ular bitta Sku parametrini kirish sifatida qabul qiladi va ibtidoiy turni qaytaradi.
Funktsional ma'lumotlar bazasida har bir ob'ekt avtomatik ravishda yaratiladigan va kerak bo'lganda kirish mumkin bo'lgan ichki kodga ega bo'lishi taxmin qilinadi.
Keling, mahsulot/do'kon/yetkazib beruvchi uchun narxni belgilaylik. Vaqt o'tishi bilan u o'zgarishi mumkin, shuning uchun jadvalga vaqt maydonini qo'shamiz. Kodni qisqartirish uchun relyatsion ma'lumotlar bazasidagi kataloglar uchun jadvallarni e'lon qilishni o'tkazib yuboraman:
Aloqaviy
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)
)
funktsional
CLASS Sku;
CLASS Store;
CLASS Supplier;
dateTime = DATA DATETIME (Sku, Store, Supplier);
price = DATA NUMERIC[10,5] (Sku, Store, Supplier);
Ko'rsatkichlar
Oxirgi misol uchun biz ma'lum bir vaqt uchun narxni tezda topishimiz uchun barcha kalitlar va sana bo'yicha indeks yaratamiz.
Aloqaviy
CREATE INDEX prices_date
ON prices
(skuId, storeId, supplierId, dateTime)
funktsional
INDEX Sku sk, Store st, Supplier sp, dateTime(sk, st, sp);
vazifalar
Keling, mos keladiganlardan olingan nisbatan oddiy masalalardan boshlaylik
Birinchidan, domen mantig'ini e'lon qilaylik (relatsion ma'lumotlar bazasi uchun bu to'g'ridan-to'g'ri yuqoridagi maqolada amalga oshiriladi).
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);
Vazifa 1.1
To'g'ridan-to'g'ri rahbaridan ko'proq maosh oladigan xodimlar ro'yxatini ko'rsating.
Aloqaviy
select a.*
from employee a, employee b
where b.id = a.chief_id
and a.salary > b.salary
funktsional
SELECT name(Employee a) WHERE salary(a) > salary(chief(a));
Vazifa 1.2
O'z bo'limida maksimal ish haqi oladigan xodimlarni sanab o'ting
Aloqaviy
select a.*
from employee a
where a.salary = ( select max(salary) from employee b
where b.department_id = a.department_id )
funktsional
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));
Ikkala dastur ham bir xil. Birinchi holda, relyatsion ma'lumotlar bazasida siz CREATE VIEW-dan foydalanishingiz mumkin, u xuddi shu tarzda birinchi navbatda undagi ma'lum bir bo'lim uchun maksimal ish haqini hisoblab chiqadi. Keyinchalik aniqlik uchun men birinchi holatdan foydalanaman, chunki u yechimni yaxshiroq aks ettiradi.
Vazifa 1.3
Xodimlar soni 3 kishidan oshmaydigan bo'lim identifikatorlari ro'yxatini ko'rsating.
Aloqaviy
select department_id
from employee
group by department_id
having count(*) <= 3
funktsional
countEmployees 'Количество сотрудников' (Department d) =
GROUP SUM 1 IF department(Employee e) = d;
SELECT Department d WHERE countEmployees(d) <= 3;
Vazifa 1.4
Xuddi shu bo'limda ishlaydigan tayinlangan menejeri bo'lmagan xodimlar ro'yxatini ko'rsating.
Aloqaviy
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
funktsional
SELECT name(Employee a) WHERE NOT (department(chief(a)) = department(a));
Vazifa 1.5
Xodimlarning maksimal ish haqiga ega bo'lim identifikatorlari ro'yxatini toping.
Aloqaviy
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 )
funktsional
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();
Keling, boshqasidan murakkabroq vazifalarga o'tamiz
Vazifa 2.1
Qaysi sotuvchilar 1997 yilda 30 donadan ortiq mahsulot No1 sotgan?
Domen mantig'i (oldingi RDBMS da bo'lgani kabi biz deklaratsiyani o'tkazib yuboramiz):
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);
Aloqaviy
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
funktsional
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;
Vazifa 2.2
Har bir xaridor (ismi, familiyasi) uchun 1997 yilda xaridor eng ko'p pul sarflagan ikkita tovarni (ism) toping.
Biz oldingi misoldan domen mantig'ini kengaytiramiz:
CLASS Customer 'Клиент';
contactName 'ФИО' = DATA STRING[100] (Customer);
customer = DATA Customer (Order);
unitPrice = DATA NUMERIC[14,2] (Detail);
discount = DATA NUMERIC[6,2] (Detail);
Aloqaviy
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
funktsional
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 operatori quyidagi printsip bo'yicha ishlaydi: u SUMdan keyin ko'rsatilgan ifodani (bu erda 1), ko'rsatilgan guruhlar ichida (bu erda Mijoz va Yil, lekin har qanday ifoda bo'lishi mumkin) jamlaydi, ORDER (bu erda ko'rsatilgan ifodalar bo'yicha guruhlar ichida saralanadi) bu erda sotib olingan va agar teng bo'lsa, ichki mahsulot kodiga muvofiq).
Vazifa 2.3
Joriy buyurtmalarni bajarish uchun etkazib beruvchilardan qancha tovarlarga buyurtma berish kerak.
Domen mantig'ini yana kengaytiramiz:
CLASS Supplier 'Поставщик';
companyName = DATA STRING[100] (Supplier);
supplier = DATA Supplier (Product);
unitsInStock 'Остаток на складе' = DATA NUMERIC[10,3] (Product);
reorderLevel 'Норма продажи' = DATA NUMERIC[10,3] (Product);
Aloqaviy
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
funktsional
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;
Yulduzcha bilan muammo
Va oxirgi misol shaxsan mendan. Ijtimoiy tarmoqning mantig'i bor. Odamlar bir-birlari bilan do'st bo'lishlari va bir-birlarini yoqtirishlari mumkin. Funktsional ma'lumotlar bazasi nuqtai nazaridan u quyidagicha ko'rinadi:
CLASS Person;
likes = DATA BOOLEAN (Person, Person);
friends = DATA BOOLEAN (Person, Person);
Do'stlik uchun mumkin bo'lgan nomzodlarni topish kerak. Rasmiyroq aytganda, siz A, B, C barcha odamlarni shunday topishingiz kerakki, A B bilan do'st bo'lsin va B C bilan do'st, A C bilan do'st bo'lsin, lekin A C bilan do'st emas.
Funktsional ma'lumotlar bazasi nuqtai nazaridan so'rov quyidagicha ko'rinadi:
SELECT Person a, Person b, Person c WHERE
likes(a, c) AND NOT friends(a, c) AND
friends(a, b) AND friends(b, c);
O'quvchi ushbu muammoni SQLda mustaqil ravishda hal qilishga taklif qilinadi. Do'stlar sizga yoqadigan odamlarga qaraganda ancha kam deb taxmin qilinadi. Shuning uchun ular alohida jadvallarda joylashgan. Muvaffaqiyatli bo'lsa, ikkita yulduzli vazifa ham bor. Unda do'stlik nosimmetrik emas. Funktsional ma'lumotlar bazasida u quyidagicha ko'rinadi:
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: birinchi va ikkinchi yulduzcha bilan muammoni hal qilish
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
xulosa
Shuni ta'kidlash kerakki, berilgan til sintaksisi berilgan kontseptsiyani amalga oshirish variantlaridan biridir. SQL asos qilib olindi va maqsad unga imkon qadar o'xshash bo'lishi edi. Albatta, ba'zilarga kalit so'zlarning nomlari, so'z registrlari va boshqalar yoqmasligi mumkin. Bu erda asosiy narsa kontseptsiyaning o'zi. Agar xohlasangiz, siz C++ va Python-ni o'xshash sintaksis qilishingiz mumkin.
Ta'riflangan ma'lumotlar bazasi kontseptsiyasi, mening fikrimcha, quyidagi afzalliklarga ega:
- sukunat. Bu oddiy holatlarda aniq bo'lmagan nisbatan sub'ektiv ko'rsatkich. Ammo agar siz murakkabroq holatlarga qarasangiz (masalan, yulduzcha bilan bog'liq muammolar), menimcha, bunday so'rovlarni yozish ancha oson.
- Inkapsulyatsiya. Ba'zi misollarda men oraliq funktsiyalarni e'lon qildim (masalan, sotilgan, sotib oldi va boshqalar), undan keyingi funktsiyalar qurilgan. Bu, agar kerak bo'lsa, ularga bog'liq bo'lganlarning mantig'ini o'zgartirmasdan, ma'lum funktsiyalarning mantiqini o'zgartirishga imkon beradi. Masalan, siz savdo qilishingiz mumkin sotilgan butunlay boshqa ob'ektlardan hisoblangan, qolgan mantiq esa o'zgarmaydi. Ha, buni RDBMS da CREATE VIEW yordamida amalga oshirish mumkin. Ammo agar barcha mantiq shu tarzda yozilgan bo'lsa, u juda o'qilishi mumkin bo'lmaydi.
- Semantik bo'shliq yo'q. Bunday ma'lumotlar bazasi funksiyalar va sinflar (jadvallar va maydonlar o'rniga) ustida ishlaydi. Klassik dasturlashda bo'lgani kabi (agar metod u mansub bo'lgan sinf ko'rinishidagi birinchi parametrga ega bo'lgan funksiya deb faraz qilsak). Shunga ko'ra, universal dasturlash tillari bilan "do'stlashish" ancha oson bo'lishi kerak. Bundan tashqari, ushbu kontseptsiya yanada murakkab funktsiyalarni amalga oshirishga imkon beradi. Masalan, siz quyidagi kabi operatorlarni joylashtirishingiz mumkin:
CONSTRAINT sold(Employee e, 1, 2019) > 100 IF name(e) = 'Петя' MESSAGE 'Что-то Петя продает слишком много одного товара в 2019 году';
- Irsiyat va polimorfizm. Funktsional ma'lumotlar bazasida siz CLASS ClassP: Class1, Class2 konstruktsiyalari orqali bir nechta merosni kiritishingiz va bir nechta polimorfizmni amalga oshirishingiz mumkin. Kelgusi maqolalarda aniq qanday qilib yozaman.
Bu shunchaki tushuncha bo'lsa ham, bizda Java-da barcha funktsional mantiqni relyatsion mantiqqa aylantiruvchi ba'zi ilovalar mavjud. Bundan tashqari, vakillik mantig'i va boshqa ko'plab narsalar unga juda yaxshi biriktirilgan, buning natijasida biz butunlikni olamiz
Manba: www.habr.com