DBMS chức năng

Thế giới cơ sở dữ liệu từ lâu đã bị thống trị bởi các DBMS quan hệ sử dụng ngôn ngữ SQL. Nhiều đến mức các biến thể mới nổi được gọi là NoSQL. Họ đã cố gắng tạo dựng được một vị trí nhất định cho mình trong thị trường này, nhưng các DBMS quan hệ sẽ không chết và tiếp tục được sử dụng tích cực cho mục đích của họ.

Trong bài viết này tôi muốn mô tả khái niệm về cơ sở dữ liệu chức năng. Để hiểu rõ hơn, tôi sẽ thực hiện điều này bằng cách so sánh nó với mô hình quan hệ cổ điển. Các vấn đề từ các bài kiểm tra SQL khác nhau được tìm thấy trên Internet sẽ được sử dụng làm ví dụ.

Giới thiệu

Cơ sở dữ liệu quan hệ hoạt động trên các bảng và các trường. Trong cơ sở dữ liệu chức năng, các lớp và hàm sẽ được sử dụng tương ứng. Một trường trong bảng có N khóa sẽ được biểu diễn dưới dạng hàm của N tham số. Thay vì mối quan hệ giữa các bảng, các hàm sẽ được sử dụng để trả về các đối tượng của lớp mà kết nối được thực hiện. Thành phần chức năng sẽ được sử dụng thay vì THAM GIA.

Trước khi chuyển trực tiếp sang các nhiệm vụ, tôi sẽ mô tả nhiệm vụ của miền logic. Đối với DDL tôi sẽ sử dụng cú pháp PostgreSQL. Về chức năng, nó có cú pháp riêng.

Bảng và trường

Một đối tượng Sku đơn giản với các trường tên và giá:

quan hệ

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

Chức năng

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

Chúng tôi công bố hai chức năng, lấy một tham số Sku làm đầu vào và trả về kiểu nguyên thủy.

Giả định rằng trong một DBMS chức năng, mỗi đối tượng sẽ có một số mã nội bộ được tạo tự động và có thể được truy cập nếu cần.

Hãy đặt giá cho sản phẩm/cửa hàng/nhà cung cấp. Nó có thể thay đổi theo thời gian, vì vậy hãy thêm trường thời gian vào bảng. Tôi sẽ bỏ qua việc khai báo bảng cho các thư mục trong cơ sở dữ liệu quan hệ để rút ngắn mã:

quan hệ

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

Chức năng

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

Chỉ số

Đối với ví dụ cuối cùng, chúng tôi sẽ xây dựng một chỉ mục trên tất cả các khóa và ngày để có thể nhanh chóng tìm thấy giá trong một thời gian cụ thể.

quan hệ

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

Chức năng

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

nhiệm vụ

Hãy bắt đầu với những bài toán tương đối đơn giản được lấy từ ví dụ tương ứng Điều trên Habr.

Đầu tiên, hãy khai báo logic miền (đối với cơ sở dữ liệu quan hệ, việc này được thực hiện trực tiếp trong bài viết trên).

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

Nhiệm vụ 1.1

Hiển thị danh sách các nhân viên nhận được mức lương cao hơn mức lương của người quản lý trực tiếp của họ.

quan hệ

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

Chức năng

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

Nhiệm vụ 1.2

Liệt kê những nhân viên nhận được mức lương tối đa trong bộ phận của họ

quan hệ

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

Chức năng

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

Cả hai cách thực hiện đều tương đương. Đối với trường hợp đầu tiên, trong cơ sở dữ liệu quan hệ, bạn có thể sử dụng TẠO XEM, theo cách tương tự, trước tiên sẽ tính mức lương tối đa cho một bộ phận cụ thể trong đó. Trong phần tiếp theo, để rõ ràng, tôi sẽ sử dụng trường hợp đầu tiên, vì nó phản ánh giải pháp tốt hơn.

Nhiệm vụ 1.3

Hiển thị danh sách ID phòng ban, số lượng nhân viên trong đó không quá 3 người.

quan hệ

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

Chức năng

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

Nhiệm vụ 1.4

Hiển thị danh sách nhân viên không có người quản lý được chỉ định làm việc trong cùng bộ phận.

quan hệ

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

Chức năng

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

Nhiệm vụ 1.5

Tìm danh sách ID bộ phận có tổng lương nhân viên tối đa.

quan hệ

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 )

Chức năng

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

Hãy chuyển sang các nhiệm vụ phức tạp hơn từ nhiệm vụ khác Điều. Nó chứa phân tích chi tiết về cách triển khai tác vụ này trong MS SQL.

Nhiệm vụ 2.1

Người bán nào đã bán được hơn 1997 đơn vị sản phẩm số 30 vào năm 1?

Logic miền (như trước đây trên RDBMS, chúng tôi bỏ qua phần khai báo):

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

quan hệ

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

Chức năng

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;

Nhiệm vụ 2.2

Với mỗi người mua (tên, họ), hãy tìm hai hàng hóa (tên) mà người mua đã chi nhiều tiền nhất vào năm 1997.

Chúng tôi mở rộng logic miền từ ví dụ trước:

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

customer = DATA Customer (Order);

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

quan hệ

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

Chức năng

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;

Toán tử PARTITION hoạt động theo nguyên tắc sau: nó tính tổng biểu thức được chỉ định sau SUM (ở đây là 1), trong các nhóm được chỉ định (ở đây là Khách hàng và Năm, nhưng có thể là bất kỳ biểu thức nào), sắp xếp trong các nhóm theo các biểu thức được chỉ định trong ĐẶT HÀNG ( mua ở đây, nếu bằng thì theo mã sản phẩm nội bộ).

Nhiệm vụ 2.3

Cần đặt bao nhiêu hàng hóa từ nhà cung cấp để hoàn thành đơn hàng hiện tại.

Hãy mở rộng logic miền một lần nữa:

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

supplier = DATA Supplier (Product);

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

quan hệ

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

Chức năng

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;

Vấn đề với dấu hoa thị

Và ví dụ cuối cùng là từ cá nhân tôi. Có logic của một mạng xã hội. Mọi người có thể làm bạn với nhau và thích nhau. Từ góc độ cơ sở dữ liệu chức năng, nó sẽ trông như thế này:

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

Cần phải tìm những ứng cử viên khả thi cho tình bạn. Chính thức hơn, bạn cần tìm tất cả những người A, B, C sao cho A là bạn của B, B là bạn của C, A thích C nhưng A không phải là bạn của C.
Từ góc độ cơ sở dữ liệu chức năng, truy vấn sẽ trông như thế này:

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

Người đọc được khuyến khích tự mình giải quyết vấn đề này bằng SQL. Người ta cho rằng có ít bạn bè hơn nhiều so với những người bạn thích. Vì vậy chúng nằm trong các bảng riêng biệt. Nếu thành công còn có nhiệm vụ hai sao. Trong đó, tình bạn không có tính đối xứng. Trên cơ sở dữ liệu chức năng, nó sẽ trông như thế này:

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

CẬP NHẬT: giải pháp cho vấn đề với dấu hoa thị thứ nhất và thứ hai từ 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 

Kết luận

Cần lưu ý rằng cú pháp ngôn ngữ đã cho chỉ là một trong những lựa chọn để triển khai khái niệm đã cho. SQL được lấy làm cơ sở và mục tiêu là để nó giống với nó nhất có thể. Tất nhiên, một số có thể không thích tên của từ khóa, bảng ghi từ, v.v. Điều chính ở đây là khái niệm. Nếu muốn, bạn có thể tạo cú pháp tương tự cho cả C++ và Python.

Theo tôi, khái niệm cơ sở dữ liệu được mô tả có những ưu điểm sau:

  • Đơn giản. Đây là một chỉ số tương đối chủ quan, không rõ ràng trong những trường hợp đơn giản. Nhưng nếu bạn xem xét các trường hợp phức tạp hơn (ví dụ: các vấn đề về dấu hoa thị), thì theo tôi, việc viết các truy vấn như vậy sẽ dễ dàng hơn nhiều.
  • Инкапсуляя cải. Trong một số ví dụ tôi đã khai báo các hàm trung gian (ví dụ: bán, mua v.v.), từ đó các chức năng tiếp theo được xây dựng. Điều này cho phép bạn thay đổi logic của một số chức năng nhất định, nếu cần, mà không thay đổi logic của những chức năng phụ thuộc vào chúng. Ví dụ: bạn có thể bán hàng bán được tính toán từ các đối tượng hoàn toàn khác nhau, trong khi phần còn lại của logic sẽ không thay đổi. Có, điều này có thể được triển khai trong RDBMS bằng cách sử dụng TẠO XEM. Nhưng nếu tất cả logic được viết theo cách này, nó sẽ trông không dễ đọc lắm.
  • Không có khoảng cách ngữ nghĩa. Cơ sở dữ liệu như vậy hoạt động trên các hàm và lớp (thay vì bảng và trường). Giống như trong lập trình cổ điển (nếu chúng ta giả sử rằng một phương thức là một hàm có tham số đầu tiên ở dạng lớp mà nó thuộc về). Theo đó, việc “kết bạn” với các ngôn ngữ lập trình phổ quát sẽ dễ dàng hơn nhiều. Ngoài ra, khái niệm này cho phép thực hiện nhiều chức năng phức tạp hơn. Ví dụ: bạn có thể nhúng các toán tử như:

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

  • Kế thừa và đa hình. Trong cơ sở dữ liệu chức năng, bạn có thể giới thiệu tính năng đa kế thừa thông qua các cấu trúc CLASS ClassP: Class1, Class2 và triển khai đa hình. Có lẽ tôi sẽ viết chính xác như thế nào trong các bài viết sau.

Mặc dù đây chỉ là một khái niệm nhưng chúng tôi đã có một số triển khai trong Java để chuyển tất cả logic chức năng thành logic quan hệ. Thêm vào đó, logic của các biểu diễn và rất nhiều thứ khác được gắn liền với nó một cách tuyệt vời, nhờ đó chúng ta có được một tổng thể nền tảng. Về cơ bản, chúng tôi sử dụng RDBMS (hiện chỉ có PostgreSQL) như một “máy ảo”. Đôi khi có vấn đề phát sinh với bản dịch này do trình tối ưu hóa truy vấn RDBMS không biết số liệu thống kê nhất định mà FDBMS biết. Về lý thuyết, có thể triển khai một hệ thống quản lý cơ sở dữ liệu sẽ sử dụng một cấu trúc nhất định làm bộ lưu trữ, được điều chỉnh riêng cho logic chức năng.

Nguồn: www.habr.com

Thêm một lời nhận xét