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
Đầ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
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ừ
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ể
Nguồn: www.habr.com