نظم إدارة قواعد البيانات الوظيفية

لطالما تم الاستيلاء على عالم قاعدة البيانات بواسطة أنظمة DBMS العلائقية التي تستخدم لغة SQL. لدرجة أن الأنواع الناشئة تسمى NoSQL. لقد تمكنوا من الفوز بمكان معين لأنفسهم في هذا السوق ، لكن نظم إدارة قواعد البيانات العلائقية لن تموت ، وسيستمر استخدامها بنشاط لأغراضهم الخاصة.

في هذه المقالة ، أريد أن أصف مفهوم قاعدة البيانات الوظيفية. لفهم أفضل ، سأفعل ذلك من خلال المقارنة مع النموذج العلائقي الكلاسيكي. كأمثلة ، سيتم استخدام المهام من اختبارات 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);

المهام

لنبدأ بمسائل بسيطة نسبيًا مأخوذة من المقابلة مقالات على هبر.

أولاً ، دعنا نعلن عن منطق المجال (بالنسبة لقاعدة البيانات العلائقية ، يتم ذلك مباشرةً في المقالة أعلاه).

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

محدث: حل المشكلة بعلامة النجمة الأولى والثانية من 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 يمكن القيام بذلك من خلال إنشاء عرض. ولكن إذا كتبت كل المنطق بهذه الطريقة ، فلن يبدو مقروءًا جدًا.
  • لا فجوة دلالية. تعمل قاعدة البيانات هذه مع الوظائف والفئات (بدلاً من الجداول والحقول). بنفس الطريقة كما هو الحال في البرمجة الكلاسيكية (بافتراض أن الطريقة هي وظيفة ذات المعلمة الأولى في شكل فئة تنتمي إليها). وفقًا لذلك ، يجب أن يكون من الأسهل بكثير "تكوين صداقات" مع لغات البرمجة العالمية. بالإضافة إلى ذلك ، يتيح لك هذا المفهوم تنفيذ وظائف أكثر تعقيدًا. على سبيل المثال ، يمكنك تضمين عبارات مثل هذه في قاعدة البيانات:

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

  • الوراثة وتعدد الأشكال. في قاعدة بيانات وظيفية ، يمكنك تقديم وراثة متعددة من خلال CLASS ClassP: Class1 ، Class2 بنيات وتنفيذ تعدد الأشكال. كيف بالضبط ، ربما سأكتب في المقالات التالية.

على الرغم من أن هذا مجرد مفهوم ، فلدينا بالفعل بعض التنفيذ في Java الذي يترجم كل المنطق الوظيفي إلى منطق علائقي. بالإضافة إلى ذلك ، فإن منطق التمثيلات والكثير من الأشياء الأخرى مشدود بشكل جميل إليه ، وبفضل ذلك حصلنا على платформа. بشكل أساسي ، نحن نستخدم RDBMS (فقط PostgreSQL حتى الآن) كـ "آلة افتراضية". تسبب هذه الترجمة أحيانًا مشاكل لأن مُحسِّن استعلام RDBMS لا يعرف بعض الإحصائيات التي يعرفها FDBMS. من الناحية النظرية ، من الممكن تنفيذ نظام إدارة قواعد البيانات الذي سيستخدم هيكلًا معينًا كتخزين ، ومكيف خصيصًا للمنطق الوظيفي.

المصدر: www.habr.com

إضافة تعليق