Die databasiswêreld is lank reeds oorgeneem deur relasionele DBBS'e wat die SQL-taal gebruik. Soveel so dat opkomende variëteite NoSQL genoem word. Hulle het daarin geslaag om 'n sekere plek vir hulself in hierdie mark te wen, maar relasionele DBMS gaan nie doodgaan nie, en gaan voort om aktief vir hul eie doeleindes gebruik te word.
In hierdie artikel wil ek die konsep van 'n funksionele databasis beskryf. Vir 'n beter begrip sal ek dit doen deur met die klassieke relasionele model te vergelyk. As voorbeelde sal take van verskeie SQL-toetse wat op die internet gevind word, gebruik word.
Inleiding
Relasionele databasisse werk op tabelle en velde. In 'n funksionele databasis sal klasse en funksies onderskeidelik in plaas daarvan gebruik word. 'n Veld in 'n tabel met N sleutels sal as 'n funksie van N parameters voorgestel word. In plaas van skakels tussen tabelle, sal funksies gebruik word wat voorwerpe van die klas waarheen die skakel gaan terugstuur. Funksie samestelling sal gebruik word in plaas van JOIN.
Voordat ek direk na die take voortgaan, sal ek die taak van domeinlogika beskryf. Vir DDL sal ek PostgreSQL-sintaksis gebruik. Vir funksionele sy eie sintaksis.
Tabelle en velde
'n Eenvoudige Sku-voorwerp met naam- en prysvelde:
relasionele
CREATE TABLE Sku
(
id bigint NOT NULL,
name character varying(100),
price numeric(10,5),
CONSTRAINT id_pkey PRIMARY KEY (id)
)
Funksioneel
CLASS Sku;
name = DATA STRING[100] (Sku);
price = DATA NUMERIC[10,5] (Sku);
Ons kondig twee aan funksies, wat een Sku-parameter as invoer neem en 'n primitiewe tipe terugstuur.
Daar word aanvaar dat in 'n funksionele DBBS elke voorwerp 'n interne kode sal hê wat outomaties gegenereer word en toegang kan verkry word indien nodig.
Kom ons stel die prys vir die produk / winkel / verskaffer vas. Dit kan met verloop van tyd verander, so kom ons voeg 'n tydveld by die tabel. Ek sal die verklaring van tabelle vir gidse in 'n relasionele databasis oorslaan om die kode te verkort:
relasionele
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)
)
Funksioneel
CLASS Sku;
CLASS Store;
CLASS Supplier;
dateTime = DATA DATETIME (Sku, Store, Supplier);
price = DATA NUMERIC[10,5] (Sku, Store, Supplier);
Indekse
Vir die laaste voorbeeld, kom ons bou 'n indeks op alle sleutels en datum sodat ons vinnig die prys vir 'n sekere tyd kan vind.
relasionele
CREATE INDEX prices_date
ON prices
(skuId, storeId, supplierId, dateTime)
Funksioneel
INDEX Sku sk, Store st, Supplier sp, dateTime(sk, st, sp);
take
Kom ons begin met relatief eenvoudige probleme geneem uit die ooreenstemmende
Kom ons verklaar eers die domeinlogika (vir 'n relasionele databasis word dit direk in die bogenoemde artikel gedoen).
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);
1.1 uitdaging
Vertoon 'n lys van werknemers wat lone ontvang wat groter is as dié van die onmiddellike toesighouer.
relasionele
select a.*
from employee a, employee b
where b.id = a.chief_id
and a.salary > b.salary
Funksioneel
SELECT name(Employee a) WHERE salary(a) > salary(chief(a));
1.2 uitdaging
Vertoon 'n lys van werknemers wat die hoogste salaris in hul departement verdien
relasionele
select a.*
from employee a
where a.salary = ( select max(salary) from employee b
where b.department_id = a.department_id )
Funksioneel
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));
Beide implementerings is gelykstaande. Vir die eerste geval in die relasionele databasis kan jy CREATE VIEW gebruik, wat op dieselfde manier eers die maksimum salaris vir 'n spesifieke departement daarin sal bereken. In die toekoms, vir duidelikheid, sal ek die eerste geval gebruik, aangesien dit die oplossing beter weerspieël.
1.3 uitdaging
Vertoon 'n lys van departement-ID's, die aantal werknemers waarin nie 3 mense oorskry nie.
relasionele
select department_id
from employee
group by department_id
having count(*) <= 3
Funksioneel
countEmployees 'Количество сотрудников' (Department d) =
GROUP SUM 1 IF department(Employee e) = d;
SELECT Department d WHERE countEmployees(d) <= 3;
1.4 uitdaging
Vertoon 'n lys van werknemers wat nie 'n toegewese bestuurder het wat in dieselfde departement werk nie.
relasionele
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
Funksioneel
SELECT name(Employee a) WHERE NOT (department(chief(a)) = department(a));
1.5 uitdaging
Vind die lys van departement-ID's met die maksimum totale werknemersalaris.
relasionele
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 )
Funksioneel
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();
Kom ons gaan oor na meer komplekse take van 'n ander
2.1 uitdaging
Watter verkopers het meer as 1997 stukke item #30 in 1 verkoop?
Domeinlogika (soos voorheen, slaan ons die verklaring op die RDBMS oor):
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);
relasionele
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
Funksioneel
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;
2.2 uitdaging
Vir elke kliënt (voornaam, van), vind die twee items (naam) waaraan die kliënt die meeste geld in 1997 bestee het.
Die uitbreiding van die domeinlogika van die vorige voorbeeld:
CLASS Customer 'Клиент';
contactName 'ФИО' = DATA STRING[100] (Customer);
customer = DATA Customer (Order);
unitPrice = DATA NUMERIC[14,2] (Detail);
discount = DATA NUMERIC[6,2] (Detail);
relasionele
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
Funksioneel
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;
Die PARTITION-operateur werk volgens die volgende beginsel: dit som die uitdrukking gespesifiseer na SUM (hier 1) binne die gespesifiseerde groepe (hier Klant en Jaar, maar kan enige uitdrukking wees), sorteer binne die groepe volgens die uitdrukkings gespesifiseer in ORDER ( hier gekoop, en as dit gelyk is, dan volgens die interne produkkode).
2.3 uitdaging
Hoeveel goedere moet van verskaffers bestel word om huidige bestellings te vervul.
Kom ons brei die domeinlogika weer uit:
CLASS Supplier 'Поставщик';
companyName = DATA STRING[100] (Supplier);
supplier = DATA Supplier (Product);
unitsInStock 'Остаток на складе' = DATA NUMERIC[10,3] (Product);
reorderLevel 'Норма продажи' = DATA NUMERIC[10,3] (Product);
relasionele
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
Funksioneel
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;
Taak met 'n asterisk
En die laaste voorbeeld is van my persoonlik. Daar is die logika van 'n sosiale netwerk. Mense kan vriende met mekaar wees en van mekaar hou. Vanuit 'n funksionele databasisperspektief sou dit soos volg lyk:
CLASS Person;
likes = DATA BOOLEAN (Person, Person);
friends = DATA BOOLEAN (Person, Person);
Dit is nodig om moontlike kandidate vir vriendskap te vind. Meer formeel moet jy alle mense A, B, C so vind dat A vriende is met B, en B vriende is met C, A hou van C, maar A is nie vriende met C nie.
Vanuit 'n funksionele databasis oogpunt sal die navraag soos volg lyk:
SELECT Person a, Person b, Person c WHERE
likes(a, c) AND NOT friends(a, c) AND
friends(a, b) AND friends(b, c);
Die leser word uitgenooi om hierdie probleem onafhanklik in SQL op te los. Daar word aanvaar dat daar baie minder vriende is as diegene wat daarvan hou. Daarom is hulle in aparte tabelle. In die geval van 'n suksesvolle oplossing is daar ook 'n probleem met twee sterretjies. Haar vriendskap is nie simmetries nie. Op 'n funksionele databasis sal dit soos volg lyk:
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: oplossing van die probleem met die eerste en tweede sterretjie van
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
Gevolgtrekking
Daar moet kennis geneem word dat die bogenoemde sintaksis van die taal net een van die opsies is vir die implementering van bogenoemde konsep. Dit was SQL wat as die basis geneem is, en die doel was om dit so soortgelyk as moontlik daaraan te maak. Natuurlik hou iemand dalk nie van die name van sleutelwoorde, hoofletters van woorde, ensovoorts nie. Die belangrikste ding hier is die konsep self. As jy wil, kan jy beide C ++ en Python soortgelyke sintaksis maak.
Die beskryfde databasiskonsep het na my mening die volgende voordele:
- Eenvoud. Dit is 'n relatief subjektiewe aanwyser wat nie voor die hand liggend is in eenvoudige gevalle nie. Maar as jy na meer komplekse gevalle kyk (byvoorbeeld take met sterretjies), dan is dit na my mening baie makliker om sulke navrae te skryf.
- Инкапсуляция. In sommige voorbeelde het ek intermediêre funksies verklaar (byvoorbeeld, verkoop, gekoop ens.), waaruit daaropvolgende funksies gebou is. Dit laat jou toe om die logika van sekere funksies te verander, indien nodig, sonder om die logika van diegene wat daarvan afhanklik is, te verander. Byvoorbeeld, jy kan verkope maak verkoop is uit heeltemal verskillende voorwerpe bereken, terwyl die res van die logika nie sal verander nie. Ja, in RDBMS kan dit gedoen word met CREATE VIEW. Maar as jy al die logika op hierdie manier skryf, dan sal dit nie baie leesbaar lyk nie.
- Geen semantiese gaping nie. So 'n databasis werk met funksies en klasse (in plaas van tabelle en velde). Op dieselfde manier as in klassieke programmering (met die veronderstelling dat 'n metode 'n funksie is met die eerste parameter in die vorm van 'n klas waaraan dit behoort). Gevolglik behoort dit baie makliker te wees om "vriende te maak" met universele programmeertale. Daarbenewens laat hierdie konsep jou toe om baie meer komplekse funksies te implementeer. Byvoorbeeld, jy kan stellings soos hierdie in die databasis insluit:
CONSTRAINT sold(Employee e, 1, 2019) > 100 IF name(e) = 'Петя' MESSAGE 'Что-то Петя продает слишком много одного товара в 2019 году';
- Oorerwing en polimorfisme. In 'n funksionele databasis kan jy meervoudige oorerwing instel deur die KLAS KlasP: Klas1, Klas2-konstrukte en meervoudige polimorfisme implementeer. Hoe presies, miskien sal ek in die volgende artikels skryf.
Alhoewel dit net 'n konsep is, het ons reeds 'n mate van implementering in Java wat alle funksionele logika in relasionele logika vertaal. Boonop is die logika van voorstellings en baie ander dinge pragtig daaraan vasgeskroef, waardeur ons 'n geheel kry
Bron: will.com