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

เราประกาศสอง คุณสมบัติซึ่งรับพารามิเตอร์หนึ่งตัวเป็นอินพุตและส่งกลับเป็นประเภทดั้งเดิม

สันนิษฐานว่าใน 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

แสดงรายการ ID แผนก จำนวนพนักงานไม่เกิน 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 รู้ ตามทฤษฎี มีความเป็นไปได้ที่จะนำระบบการจัดการฐานข้อมูลไปใช้ซึ่งจะใช้โครงสร้างบางอย่างเป็นที่เก็บข้อมูล ซึ่งดัดแปลงมาโดยเฉพาะสำหรับตรรกะการทำงาน

ที่มา: will.com

เพิ่มความคิดเห็น