Λειτουργικό 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 ως είσοδο και επιστρέφουν έναν πρωτόγονο τύπο.

Υποτίθεται ότι σε ένα λειτουργικό ΣΔΒΔ, κάθε αντικείμενο θα έχει κάποιον εσωτερικό κώδικα που δημιουργείται αυτόματα και μπορεί να προσπελαστεί εάν είναι απαραίτητο.

Ας ορίσουμε την τιμή για το προϊόν / κατάστημα / προμηθευτή. Μπορεί να αλλάξει με την πάροδο του χρόνου, οπότε ας προσθέσουμε ένα πεδίο χρόνου στον πίνακα. Θα παραλείψω τη δήλωση πινάκων για καταλόγους σε μια σχεσιακή βάση δεδομένων για να συντομεύσω τον κώδικα:

σχετικός

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

Είναι απαραίτητο να βρεθούν πιθανοί υποψήφιοι για φιλία. Πιο επίσημα, πρέπει να βρείτε όλα τα άτομα Α, Β, Γ έτσι ώστε ο Α να είναι φίλος με τον Β και ο Β να είναι φίλος με τον Γ, στον Α αρέσει ο Γ, αλλά στον Α να μην είναι φίλος με τον Γ.
Από την άποψη της λειτουργικής βάσης δεδομένων, το ερώτημα θα μοιάζει με αυτό:

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. Θεωρητικά, είναι δυνατό να εφαρμοστεί ένα σύστημα διαχείρισης βάσης δεδομένων που θα χρησιμοποιεί μια συγκεκριμένη δομή ως αποθήκευση, προσαρμοσμένη ειδικά για τη λειτουργική λογική.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο