機胜DBMS

デヌタベヌスの䞖界は長い間、SQL 蚀語を䜿甚するリレヌショナル DBMS によっお支配されおきたした。新興の亜皮は 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);

XNUMX぀発衚したす 機胜、入力ずしお XNUMX ぀のパラメヌタヌ 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タスク

郹門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タスク

埓業員の絊䞎総額の最高額が蚘茉されおいる郚門 ID のリストを怜玢したす。

関連した

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 幎に補品 No. 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 幎に賌入者が最も倚くのお金を費やした XNUMX ぀の商品 (名前) を芋぀けたす。

前の䟋からドメむン ロゞックを拡匵したす。

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 挔算子は次の原則に基づいお機胜したす。指定されたグルヌプ (ここでは Customer ず Year ですが、任意の匏を䜿甚できたす) 内で 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 ず友達で、B は C ず友達で、A は C が奜きですが、A は C ず友達ではない、すべおの人 A、B、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 で自分で解決するこずをお勧めしたす。友達の数は奜きな人よりはるかに少ないず想定されたす。したがっお、それらは別のテヌブルにありたす。成功するず星XNUMX぀のタスクもありたす。その䞭で、友情は察称的ではありたせん。機胜デヌタベヌスでは次のようになりたす。

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: 最初ず XNUMX 番目のアスタリスクの問題の解決策 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 

たずめ

指定された蚀語構文は、指定された抂念を実装するためのオプションの XNUMX ぀にすぎないこずに泚意しおください。 SQL が基瀎ずしお採甚され、目暙は SQL にできるだけ䌌せるこずでした。もちろん、キヌワヌドや単語レゞスタなどの名前が気に入らない人もいるかもしれたせん。ここで重芁なのはコンセプトそのものです。必芁に応じお、C++ ず Python の䞡方を同様の構文にするこずができたす。

私の考えでは、ここで説明したデヌタベヌスの抂念には次の利点がありたす。

  • 緩和。これは比范的䞻芳的な指暙であり、単玔な堎合には明らかではありたせん。しかし、より耇雑なケヌス (たずえば、アスタリスクの問題) に目を向けるず、私の意芋では、そのようなク゚リを䜜成する方がはるかに簡単です。
  • ИМкапсуляцОя。いく぀かの䟋では、䞭間関数を宣蚀したした (たずえば、 売ら, 買った など)、そこから埌続の関数が構築されたした。これにより、必芁に応じお、特定の関数に䟝存する関数のロゞックを倉曎せずに、その関数のロゞックを倉曎するこずができたす。たずえば、売䞊を䞊げるこずができたす 売ら は完党に異なるオブゞェクトから蚈算されたものですが、残りのロゞックは倉曎されたせん。はい、これは CREATE VIEW を䜿甚しお RDBMS に実装できたす。しかし、すべおのロゞックをこのように蚘述するず、あたり読みにくくなりたす。
  • 意味䞊のギャップがない。このようなデヌタベヌスは、(テヌブルやフィヌルドではなく) 関数ずクラスで動䜜したす。叀兞的なプログラミングずたったく同じです (メ゜ッドが、それが属するクラスの圢匏の最初のパラメヌタヌを持぀関数であるず仮定した堎合)。したがっお、ナニバヌサルプログラミング蚀語を「友達にする」のはずっず簡単になるはずです。さらに、この抂念により、より耇雑な機胜の実装が可胜になりたす。たずえば、次のような挔算子を埋め蟌むこずができたす。

    CONSTRAINT sold(Employee e, 1, 2019) > 100 IF name(e) = 'Петя' MESSAGE  'ЧтП-тП ÐŸÐµÑ‚Ñ Ð¿Ñ€ÐŸÐŽÐ°ÐµÑ‚ ÑÐ»ÐžÑˆÐºÐŸÐŒ ÐŒÐœÐŸÐ³ÐŸ ÐŸÐŽÐœÐŸÐ³ÐŸ Ñ‚Пвара Ð² 2019 Ð³ÐŸÐŽÑƒ';

  • 継承ずポリモヌフィズム。機胜デヌタベヌスでは、CLASS ClassP: Class1、Class2 構造を通じお倚重継承を導入し、倚重倚態性を実装できたす。おそらく今埌の蚘事でその方法を詳しく曞きたす。

これは単なる抂念ですが、すべおの関数ロゞックをリレヌショナル ロゞックに倉換する Java の実装がすでにありたす。さらに、衚珟のロゞックやその他倚くのものが矎しく付属しおおり、そのおかげで党䜓的なものが埗られたす。 プラットフォヌム。基本的に、RDBMS (今のずころ PostgreSQL のみ) を「仮想マシン」ずしお䜿甚したす。 RDBMS ク゚リ オプティマむザヌは、FDBMS が知っおいる特定の統蚈を知らないため、この倉換で問題が発生するこずがありたす。理論的には、機胜ロゞックに特化した特定の構造をストレヌゞずしお䜿甚するデヌタベヌス管理システムを実装するこずが可胜です。

出所 habr.com

コメントを远加したす