Ang mundo ng mga database ay matagal nang pinangungunahan ng mga relational na DBMS, na gumagamit ng wikang SQL. Kaya't ang mga umuusbong na variant ay tinatawag na NoSQL. Nagawa nilang mag-ukit ng isang partikular na lugar para sa kanilang sarili sa merkado na ito, ngunit ang mga relational na DBMS ay hindi mamamatay, at patuloy na aktibong ginagamit para sa kanilang mga layunin.
Sa artikulong ito gusto kong ilarawan ang konsepto ng isang functional database. Para sa isang mas mahusay na pag-unawa, gagawin ko ito sa pamamagitan ng paghahambing nito sa classical relational model. Gagamitin bilang mga halimbawa ang mga problema mula sa iba't ibang mga pagsubok sa SQL na matatagpuan sa Internet.
Pagpapakilala
Gumagana ang mga relational database sa mga talahanayan at field. Sa isang functional na database, ang mga klase at function ang gagamitin, ayon sa pagkakabanggit. Ang isang field sa isang talahanayan na may mga N key ay kakatawanin bilang isang function ng N parameter. Sa halip na mga ugnayan sa pagitan ng mga talahanayan, gagamitin ang mga function na nagbabalik ng mga bagay ng klase kung saan ginawa ang koneksyon. Gagamitin ang komposisyon ng function sa halip na SUMALI.
Bago lumipat nang direkta sa mga gawain, ilalarawan ko ang gawain ng lohika ng domain. Para sa DDL gagamit ako ng PostgreSQL syntax. Para sa functional mayroon itong sariling syntax.
Mga talahanayan at mga patlang
Isang simpleng object ng Sku na may mga field ng pangalan at presyo:
Relational
CREATE TABLE Sku
(
id bigint NOT NULL,
name character varying(100),
price numeric(10,5),
CONSTRAINT id_pkey PRIMARY KEY (id)
)
Magagamit
CLASS Sku;
name = DATA STRING[100] (Sku);
price = DATA NUMERIC[10,5] (Sku);
Inaanunsyo namin ang dalawa ΡΡΠ½ΠΊΡΠΈΠΈ, na kumukuha ng isang parameter na Sku bilang input at nagbabalik ng primitive na uri.
Ipinapalagay na sa isang functional na DBMS ang bawat bagay ay magkakaroon ng ilang panloob na code na awtomatikong nabuo at maaaring ma-access kung kinakailangan.
Itakda natin ang presyo para sa produkto/tindahan/supplier. Maaari itong magbago sa paglipas ng panahon, kaya magdagdag tayo ng field ng oras sa talahanayan. Laktawan ko ang pagdedeklara ng mga talahanayan para sa mga direktoryo sa isang relational database upang paikliin ang code:
Relational
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)
)
Magagamit
CLASS Sku;
CLASS Store;
CLASS Supplier;
dateTime = DATA DATETIME (Sku, Store, Supplier);
price = DATA NUMERIC[10,5] (Sku, Store, Supplier);
Mga Index
Para sa huling halimbawa, bubuo kami ng index sa lahat ng key at petsa para mabilis naming mahanap ang presyo para sa isang partikular na oras.
Relational
CREATE INDEX prices_date
ON prices
(skuId, storeId, supplierId, dateTime)
Magagamit
INDEX Sku sk, Store st, Supplier sp, dateTime(sk, st, sp);
gawain
Magsimula tayo sa medyo simpleng mga problema na kinuha mula sa kaukulang
Una, ideklara natin ang lohika ng domain (para sa relational database ito ay direktang ginagawa sa artikulo sa itaas).
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);
Gawain 1.1
Magpakita ng listahan ng mga empleyado na tumatanggap ng suweldo na mas malaki kaysa sa kanilang agarang superbisor.
Relational
select a.*
from employee a, employee b
where b.id = a.chief_id
and a.salary > b.salary
Magagamit
SELECT name(Employee a) WHERE salary(a) > salary(chief(a));
Gawain 1.2
Ilista ang mga empleyado na tumatanggap ng pinakamataas na suweldo sa kanilang departamento
Relational
select a.*
from employee a
where a.salary = ( select max(salary) from employee b
where b.department_id = a.department_id )
Magagamit
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));
Ang parehong pagpapatupad ay katumbas. Para sa unang kaso, sa isang relational database maaari mong gamitin ang CREATE VIEW, na sa parehong paraan ay unang kalkulahin ang maximum na suweldo para sa isang partikular na departamento sa loob nito. Sa kung ano ang sumusunod, para sa kalinawan, gagamitin ko ang unang kaso, dahil ito ay mas mahusay na sumasalamin sa solusyon.
Gawain 1.3
Magpakita ng listahan ng mga department ID, ang bilang ng mga empleyado kung saan hindi lalampas sa 3 tao.
Relational
select department_id
from employee
group by department_id
having count(*) <= 3
Magagamit
countEmployees 'ΠΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΡΠΎΡΡΡΠ΄Π½ΠΈΠΊΠΎΠ²' (Department d) =
GROUP SUM 1 IF department(Employee e) = d;
SELECT Department d WHERE countEmployees(d) <= 3;
Gawain 1.4
Magpakita ng listahan ng mga empleyado na walang itinalagang manager na nagtatrabaho sa parehong departamento.
Relational
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
Magagamit
SELECT name(Employee a) WHERE NOT (department(chief(a)) = department(a));
Gawain 1.5
Maghanap ng listahan ng mga department ID na may pinakamataas na kabuuang suweldo ng empleyado.
Relational
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 )
Magagamit
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();
Lumipat tayo sa mas kumplikadong mga gawain mula sa isa pa
Gawain 2.1
Aling mga nagbebenta ang nagbenta ng higit sa 1997 yunit ng produkto No. 30 noong 1?
Logic ng domain (tulad ng dati sa RDBMS nilaktawan namin ang deklarasyon):
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);
Relational
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
Magagamit
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;
Gawain 2.2
Para sa bawat mamimili (pangalan, apelyido), hanapin ang dalawang kalakal (pangalan) kung saan ginastos ng mamimili ang pinakamaraming pera noong 1997.
Pinapalawak namin ang lohika ng domain mula sa nakaraang halimbawa:
CLASS Customer 'ΠΠ»ΠΈΠ΅Π½Ρ';
contactName 'Π€ΠΠ' = DATA STRING[100] (Customer);
customer = DATA Customer (Order);
unitPrice = DATA NUMERIC[14,2] (Detail);
discount = DATA NUMERIC[6,2] (Detail);
Relational
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
Magagamit
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;
Gumagana ang operator ng PARTITION sa sumusunod na prinsipyo: binubuo nito ang expression na tinukoy pagkatapos ng SUM (dito 1), sa loob ng tinukoy na mga grupo (dito Customer at Taon, ngunit maaaring anumang expression), pag-uuri sa loob ng mga grupo ayon sa mga expression na tinukoy sa ORDER ( dito binili, at kung pantay, pagkatapos ay ayon sa panloob na code ng produkto).
Gawain 2.3
Gaano karaming mga kalakal ang kailangang i-order mula sa mga supplier upang matupad ang mga kasalukuyang order.
Palawakin natin muli ang logic ng domain:
CLASS Supplier 'ΠΠΎΡΡΠ°Π²ΡΠΈΠΊ';
companyName = DATA STRING[100] (Supplier);
supplier = DATA Supplier (Product);
unitsInStock 'ΠΡΡΠ°ΡΠΎΠΊ Π½Π° ΡΠΊΠ»Π°Π΄Π΅' = DATA NUMERIC[10,3] (Product);
reorderLevel 'ΠΠΎΡΠΌΠ° ΠΏΡΠΎΠ΄Π°ΠΆΠΈ' = DATA NUMERIC[10,3] (Product);
Relational
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
Magagamit
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;
Problema sa isang asterisk
At ang huling halimbawa ay mula sa akin nang personal. Mayroong lohika ng isang social network. Ang mga tao ay maaaring maging magkaibigan sa isa't isa at magkagusto sa isa't isa. Mula sa isang functional na pananaw sa database, magiging ganito ang hitsura:
CLASS Person;
likes = DATA BOOLEAN (Person, Person);
friends = DATA BOOLEAN (Person, Person);
Ito ay kinakailangan upang makahanap ng mga posibleng kandidato para sa pagkakaibigan. Sa mas pormal na paraan, kailangan mong hanapin ang lahat ng tao A, B, C na si A ay kaibigan ni B, at si B ay kaibigan ni C, gusto ni A si C, ngunit si A ay hindi kaibigan ni C.
Mula sa isang functional na pananaw sa database, ang query ay magiging ganito:
SELECT Person a, Person b, Person c WHERE
likes(a, c) AND NOT friends(a, c) AND
friends(a, b) AND friends(b, c);
Hinihikayat ang mambabasa na lutasin ang problemang ito sa SQL sa kanyang sarili. Ipinapalagay na mas kaunti ang mga kaibigan kaysa sa mga taong gusto mo. Samakatuwid sila ay nasa magkahiwalay na mga talahanayan. Kung matagumpay, mayroon ding gawain na may dalawang bituin. Sa loob nito, ang pagkakaibigan ay hindi simetriko. Sa isang functional na database ay magiging ganito ang hitsura:
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: solusyon sa problema sa una at pangalawang asterisk mula sa
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
Konklusyon
Dapat tandaan na ang ibinigay na syntax ng wika ay isa lamang sa mga opsyon para sa pagpapatupad ng ibinigay na konsepto. Ang SQL ay kinuha bilang batayan, at ang layunin ay para ito ay maging katulad nito hangga't maaari. Siyempre, maaaring hindi gusto ng ilan ang mga pangalan ng mga keyword, mga rehistro ng salita, atbp. Ang pangunahing bagay dito ay ang konsepto mismo. Kung ninanais, maaari mong gawin ang parehong C++ at Python na magkatulad na syntax.
Ang inilarawan na konsepto ng database, sa palagay ko, ay may mga sumusunod na pakinabang:
- Ang pagiging simple. Ito ay isang medyo subjective indicator na hindi halata sa mga simpleng kaso. Ngunit kung titingnan mo ang mas kumplikadong mga kaso (halimbawa, mga problema sa mga asterisk), kung gayon, sa palagay ko, ang pagsusulat ng mga naturang query ay mas madali.
- ΠΠ½ΠΊΠ°ΠΏΡΡΠ»ΡΡΠΈΡ. Sa ilang mga halimbawa, ipinahayag ko ang mga intermediate function (halimbawa, despatsado, bili atbp.), kung saan itinayo ang mga kasunod na pag-andar. Pinapayagan ka nitong baguhin ang lohika ng ilang mga pag-andar, kung kinakailangan, nang hindi binabago ang lohika ng mga umaasa sa kanila. Halimbawa, maaari kang gumawa ng mga benta despatsado ay kinakalkula mula sa ganap na magkakaibang mga bagay, habang ang natitirang bahagi ng lohika ay hindi magbabago. Oo, maaari itong ipatupad sa isang RDBMS gamit ang CREATE VIEW. Ngunit kung ang lahat ng lohika ay isinulat sa ganitong paraan, ito ay hindi masyadong nababasa.
- Walang semantic gap. Ang nasabing database ay gumagana sa mga function at klase (sa halip na mga table at field). Tulad ng sa klasikal na programming (kung ipinapalagay natin na ang isang pamamaraan ay isang function na may unang parameter sa anyo ng klase kung saan ito nabibilang). Alinsunod dito, dapat na mas madaling "makipagkaibigan" sa mga unibersal na programming language. Bilang karagdagan, ang konseptong ito ay nagbibigay-daan para sa mas kumplikadong pag-andar na maipatupad. Halimbawa, maaari kang mag-embed ng mga operator tulad ng:
CONSTRAINT sold(Employee e, 1, 2019) > 100 IF name(e) = 'ΠΠ΅ΡΡ' MESSAGE 'Π§ΡΠΎ-ΡΠΎ ΠΠ΅ΡΡ ΠΏΡΠΎΠ΄Π°Π΅Ρ ΡΠ»ΠΈΡΠΊΠΎΠΌ ΠΌΠ½ΠΎΠ³ΠΎ ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΡΠΎΠ²Π°ΡΠ° Π² 2019 Π³ΠΎΠ΄Ρ';
- Pamana at polymorphism. Sa isang functional na database, maaari kang magpakilala ng maramihang pamana sa pamamagitan ng CLASS ClassP: Class1, Class2 constructs at ipatupad ang maramihang polymorphism. Marahil ay isusulat ko kung gaano ka eksakto sa mga susunod na artikulo.
Kahit na ito ay isang konsepto lamang, mayroon na kaming ilang pagpapatupad sa Java na nagsasalin ng lahat ng functional logic sa relational logic. Dagdag pa, ang lohika ng mga representasyon at maraming iba pang mga bagay ay napakagandang nakakabit dito, salamat sa kung saan nakuha namin ang kabuuan
Pinagmulan: www.habr.com