Ֆունկցիոնալ DBMS

Տվյալների բազաների աշխարհում վաղուց գերիշխում են հարաբերական DBMS-ները, որոնք օգտագործում են SQL լեզուն: Այնքան, որ առաջացող տարբերակները կոչվում են NoSQL: Նրանք կարողացան իրենց համար որոշակի տեղ հատկացնել այս շուկայում, բայց հարաբերական DBMS-ները չեն մեռնի և կշարունակեն ակտիվորեն օգտագործվել իրենց նպատակների համար:

Այս հոդվածում ես ուզում եմ նկարագրել ֆունկցիոնալ տվյալների բազայի հայեցակարգը: Ավելի լավ հասկանալու համար ես դա կանեմ՝ համեմատելով այն դասական հարաբերական մոդելի հետ: Որպես օրինակ կօգտագործվեն համացանցում հայտնաբերված տարբեր SQL թեստերի խնդիրները:

Ներածություն

Հարաբերական տվյալների բազաները գործում են աղյուսակների և դաշտերի վրա: Ֆունկցիոնալ տվյալների բազայում, համապատասխանաբար, կօգտագործվեն դասեր և գործառույթներ: N ստեղներով աղյուսակի դաշտը կներկայացվի որպես N պարամետրերի ֆունկցիա: Աղյուսակների միջև փոխհարաբերությունների փոխարեն կօգտագործվեն գործառույթներ, որոնք վերադարձնում են այն դասի օբյեկտները, որոնց հետ կապված է կապը: Ֆունկցիայի կոմպոզիցիան կօգտագործվի JOIN-ի փոխարեն:

Նախքան ուղղակիորեն առաջադրանքներին անցնելը, ես նկարագրելու եմ տիրույթի տրամաբանության խնդիրը: DDL-ի համար ես կօգտագործեմ PostgreSQL շարահյուսությունը: Ֆունկցիոնալության համար այն ունի իր շարահյուսությունը:

Աղյուսակներ և դաշտեր

Պարզ Sku օբյեկտ՝ անվանման և գնի դաշտերով.

Հարաբերական

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

ֆունկցիոնալ

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

Հայտարարում ենք երկու գործառույթները, որոնք ընդունում են մեկ պարամետր Sku որպես մուտքագրում և վերադարձնում պարզունակ տեսակ։

Ենթադրվում է, որ ֆունկցիոնալ DBMS-ում յուրաքանչյուր օբյեկտ կունենա որոշակի ներքին կոդ, որը ավտոմատ կերպով ստեղծվում է և անհրաժեշտության դեպքում կարող է մուտք գործել:

Եկեք սահմանենք ապրանքի/խանութի/մատակարարի գինը: Այն կարող է փոխվել ժամանակի ընթացքում, ուստի եկեք աղյուսակին ավելացնենք ժամանակի դաշտ: Ես բաց կթողնեմ դիրեկտորիաների աղյուսակների հայտարարումը հարաբերական տվյալների բազայում՝ կոդը կրճատելու համար.

Հարաբերական

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

ֆունկցիոնալ

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

Ցուցանիշները

Վերջին օրինակի համար մենք կկառուցենք ինդեքս բոլոր ստեղների և ամսաթվի վրա, որպեսզի կարողանանք արագ գտնել որոշակի ժամանակի գինը:

Հարաբերական

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

ֆունկցիոնալ

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

խնդիրները

Սկսենք համեմատաբար պարզ խնդիրներից՝ վերցված համապատասխանից Հոդված վրա Habr.

Նախ, եկեք հայտարարենք տիրույթի տրամաբանությունը (հարաբերական տվյալների բազայի համար դա արվում է ուղղակիորեն վերը նշված հոդվածում):

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 խնդիր

Ցուցադրել այն աշխատակիցների ցուցակը, ովքեր ավելի մեծ աշխատավարձ են ստանում, քան իրենց անմիջական ղեկավարի աշխատավարձը:

Հարաբերական

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

ֆունկցիոնալ

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

1.2 խնդիր

Թվարկե՛ք իրենց բաժնում առավելագույն աշխատավարձ ստացող աշխատակիցներին

Հարաբերական

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

ֆունկցիոնալ

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

Երկու իրականացումներն էլ համարժեք են: Առաջին դեպքում հարաբերական տվյալների բազայում կարող եք օգտագործել CREATE VIEW-ը, որը նույն կերպ նախ կհաշվարկի իր կոնկրետ բաժնի համար առավելագույն աշխատավարձը։ Հետևյալում, պարզության համար, ես կօգտագործեմ առաջին դեպքը, քանի որ այն ավելի լավ է արտացոլում լուծումը:

1.3 խնդիր

Ցուցադրել բաժինների նույնականացման ցուցակը, որոնցում աշխատողների թիվը չի գերազանցում 3 հոգին:

Հարաբերական

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

ֆունկցիոնալ

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

1.4 խնդիր

Ցուցադրել այն աշխատակիցների ցուցակը, ովքեր չունեն նշանակված ղեկավար, որն աշխատում է նույն բաժնում:

Հարաբերական

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

ֆունկցիոնալ

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

1.5 խնդիր

Գտեք ստորաբաժանման անձը հաստատող փաստաթղթերի ցանկը աշխատողների առավելագույն ընդհանուր աշխատավարձով:

Հարաբերական

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 )

ֆունկցիոնալ

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

Եկեք անցնենք մյուսից ավելի բարդ առաջադրանքների Հոդված. Այն պարունակում է մանրամասն վերլուծություն, թե ինչպես իրականացնել այս առաջադրանքը MS SQL-ում:

2.1 խնդիր

Ո՞ր վաճառողներն են վաճառել ավելի քան 1997 միավոր թիվ 30 ապրանքը 1թ.

Դոմենի տրամաբանությունը (ինչպես նախկինում RDBMS-ում մենք բաց ենք թողնում հռչակագիրը).

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

Հարաբերական

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

ֆունկցիոնալ

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 խնդիր

Յուրաքանչյուր գնորդի (անուն, ազգանուն) համար գտեք այն երկու ապրանքը (անունը), որոնց վրա գնորդը 1997 թվականին ամենաշատ գումար է ծախսել։

Մենք ընդլայնում ենք տիրույթի տրամաբանությունը նախորդ օրինակից.

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

customer = DATA Customer (Order);

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

Հարաբերական

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

ֆունկցիոնալ

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 օպերատորն աշխատում է հետևյալ սկզբունքով. այն ամփոփում է SUM-ից հետո նշված արտահայտությունը (այստեղ 1), նշված խմբերում (այստեղ Հաճախորդ և Տարի, բայց կարող է լինել ցանկացած արտահայտություն), խմբերի ներսում տեսակավորելով ORDER-ում նշված արտահայտություններով ( այստեղ գնված, իսկ եթե հավասար է, ապա ըստ ներքին ապրանքի կոդի):

2.3 խնդիր

Քանի ապրանք պետք է պատվիրել մատակարարներից ընթացիկ պատվերները կատարելու համար:

Եկեք նորից ընդլայնենք տիրույթի տրամաբանությունը.

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

supplier = DATA Supplier (Product);

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

Հարաբերական

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

ֆունկցիոնալ

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;

Աստղանիշի հետ կապված խնդիր

Իսկ վերջին օրինակը անձամբ ինձնից է։ Կա սոցցանցի տրամաբանություն. Մարդիկ կարող են ընկերներ լինել միմյանց հետ և հավանել միմյանց: Ֆունկցիոնալ տվյալների բազայի տեսանկյունից այն կունենա հետևյալ տեսքը.

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

Պետք է գտնել ընկերության հնարավոր թեկնածուներ։ Ավելի պաշտոնական, դուք պետք է գտնեք բոլոր A, B, C մարդկանց այնպես, որ A-ն ընկեր լինի B-ի հետ, և B-ն ընկեր լինի C-ի հետ, A-ն հավանի C-ին, բայց A-ն ընկեր չէ C-ի հետ:
Ֆունկցիոնալ տվյալների բազայի տեսանկյունից հարցումը կունենա հետևյալ տեսքը.

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

Ընթերցողին խրախուսվում է ինքնուրույն լուծել այս խնդիրը SQL-ում: Ենթադրվում է, որ շատ ավելի քիչ ընկերներ կան, քան այն մարդիկ, ում սիրում եք: Հետևաբար դրանք ներկայացված են առանձին աղյուսակներում: Հաջողության դեպքում կա նաև առաջադրանք երկու աստղով: Դրանում ընկերությունը սիմետրիկ չէ։ Ֆունկցիոնալ տվյալների բազայում այն ​​կունենա հետևյալ տեսքը.

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՝ խնդրի լուծում առաջին և երկրորդ աստղանիշով 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 

Ամփոփում

Հարկ է նշել, որ տվյալ լեզվի շարահյուսությունը տվյալ հայեցակարգի իրականացման տարբերակներից մեկն է ընդամենը։ Որպես հիմք ընդունվել է SQL-ը, և նպատակն այն էր, որ այն հնարավորինս նման լինի դրան։ Իհարկե, ոմանց դուր չեն գալիս հիմնաբառերի անվանումները, բառերի ռեգիստրները և այլն: Այստեղ գլխավորը հենց հայեցակարգն է: Ցանկության դեպքում կարող եք և՛ C++-ին, և՛ Python-ին նման շարահյուսություն դարձնել:

Նկարագրված տվյալների բազայի հայեցակարգը, իմ կարծիքով, ունի հետևյալ առավելությունները.

  • թեթեւացնել. Սա համեմատաբար սուբյեկտիվ ցուցանիշ է, որը պարզ դեպքերում ակնհայտ չէ։ Բայց եթե նայեք ավելի բարդ դեպքերին (օրինակ՝ աստղանիշների հետ կապված խնդիրներ), ապա, իմ կարծիքով, նման հարցումներ գրելը շատ ավելի հեշտ է։
  • Инкапсуляция. Որոշ օրինակներում ես հայտարարել եմ միջանկյալ ֆունկցիաներ (օրինակ. վաճառվում, գնել և այլն), որոնցից կառուցվել են հետագա գործառույթները։ Սա թույլ է տալիս անհրաժեշտության դեպքում փոխել որոշակի գործառույթների տրամաբանությունը՝ չփոխելով դրանցից կախվածների տրամաբանությունը: Օրինակ, դուք կարող եք կատարել վաճառք վաճառվում հաշվարկվել են բոլորովին այլ օբյեկտներից, մինչդեռ մնացած տրամաբանությունը չի փոխվի։ Այո, սա կարող է իրականացվել RDBMS-ում՝ օգտագործելով CREATE VIEW: Բայց եթե ամբողջ տրամաբանությունը գրվի այսպես, այն այնքան էլ ընթեռնելի տեսք չի ունենա։
  • Ոչ մի իմաստային բաց. Նման տվյալների բազան գործում է ֆունկցիաների և դասերի վրա (աղյուսակների և դաշտերի փոխարեն): Ճիշտ այնպես, ինչպես դասական ծրագրավորման դեպքում (եթե ենթադրենք, որ մեթոդը առաջին պարամետրով ֆունկցիա է այն դասի տեսքով, որին պատկանում է): Համապատասխանաբար, շատ ավելի հեշտ պետք է լինի «ընկերանալը» ունիվերսալ ծրագրավորման լեզուների հետ։ Բացի այդ, այս հայեցակարգը թույլ է տալիս իրականացնել շատ ավելի բարդ գործառույթներ: Օրինակ, դուք կարող եք տեղադրել այնպիսի օպերատորներ, ինչպիսիք են.

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

  • Ժառանգականություն և պոլիմորֆիզմ. Ֆունկցիոնալ տվյալների բազայում դուք կարող եք ներմուծել բազմակի ժառանգություն CLASS ClassP-ի միջոցով. Class1, Class2 կառուցում և իրականացնում է բազմակի պոլիմորֆիզմ: Հավանաբար, ինչպես կգրեմ հետագա հոդվածներում:

Թեև սա պարզապես հայեցակարգ է, մենք արդեն ունենք որոշ իրականացում Java-ում, որը ամբողջ ֆունկցիոնալ տրամաբանությունը վերածում է հարաբերական տրամաբանության: Գումարած դրան, ներկայացումների տրամաբանությունը և շատ այլ բաներ գեղեցիկ են կցված դրան, ինչի շնորհիվ մենք ստանում ենք մի ամբողջություն. հարթակ. Ըստ էության, մենք օգտագործում ենք RDBMS (այժմ միայն PostgreSQL) որպես «վիրտուալ մեքենա»: Երբեմն խնդիրներ են առաջանում այս թարգմանության հետ կապված, քանի որ RDBMS հարցումների օպտիմիզատորը չգիտի որոշակի վիճակագրություն, որը գիտի FDBMS-ը: Տեսականորեն հնարավոր է ներդնել տվյալների բազայի կառավարման համակարգ, որը կօգտագործի որոշակի կառուցվածք՝ որպես պահեստ՝ հատուկ հարմարեցված ֆունկցիոնալ տրամաբանության համար:

Source: www.habr.com

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