Database
 sql >> база данни >  >> RDS >> Database

Вашето окончателно ръководство за SQL присъединявания:OUTER JOIN – част 2

Външното присъединяване е в центъра днес. И това е част 2 от вашето окончателно ръководство за SQL присъединявания. Ако сте пропуснали част 1, ето връзката.

На пръв поглед външното е обратното на вътрешното. Ако обаче разгледате външното съединение по този начин, ще бъдете объркани. Освен това не е нужно да включвате думата външен във вашия синтаксис изрично. Не е задължително!

Но преди да се потопим, нека обсъдим нулевите стойности относно външните съединения.

Нули и OUTER JOIN

Когато се присъедините към 2 таблици, една от стойностите от всяка таблица може да бъде нула. За INNER JOIN записи с нулеви стойности няма да съвпадат и те ще бъдат отхвърлени и няма да се появят в набора от резултати. Ако искате да получите записите, които не съвпадат, единствената ви опция е OUTER JOIN.

Връщайки се към антонимите, не е ли това обратното на INNER JOIN? Не изцяло, както ще видите в следващия раздел.

Всичко за OUTER JOIN на SQL Server

Разбирането на външните съединения започва с изхода. Ето пълен списък на това, което можете да очаквате:

  • Всички записи, които съответстват на условието за присъединяване или предиката. Това е изразът точно след ключовата дума ON, подобно на изхода INNER JOIN. Ние наричаме този проблем вътрешен ред .
  • Стойности, които не са NULL от ляво таблица с нулевите екземпляри от ддясно маса. Ние наричаме този проблем външни редове .
  • Стойности, които не са NULL от дясно таблица с нулевите двойници отляво маса. Това е друга форма на външни редове.
  • Накрая може да е комбинация от всички неща, описани по-горе.

С този списък можем да кажем, че OUTER JOIN връща вътрешни и външни редове .

  • Вътрешно – защото точните резултати от INNER JOIN могат да бъдат върнати.
  • Външен – защото външните редове също могат да бъдат върнати.

Това е разликата от INNER JOIN.

ВЪТРЕШНИТЕ СЪЕДИНЕНИЯ ВРЪЩАТ САМО ВЪТРЕШНИ РЕДОВЕ. ВЪНШНИТЕ СЪЕДИНЕНИЯ МОГАТ ДА ВРЪЩАТ И ВЪТРЕШНИ И ВЪНШНИ РЕДОВЕ

Забележете, че използвах „може да бъде“ и „също може да бъде“. Зависи от вашата клауза WHERE (или ако някога включите клауза WHERE) дали тя връща както вътрешни, така и/или външни редове.

Но от оператор SELECT как можете да определите коя е лявата или дясната таблица ? Добър въпрос!

Как да разбера коя е лявата или дясната маса в едно присъединяване?

Можем да отговорим на този въпрос с примери:

SELECT *
FROM Table1 a
LEFT OUTER JOIN Table2 b on a.column1 = b.column1

От примера по-горе, Таблица1 е лявата таблица и Таблица2 е правилната маса. Сега, нека имаме още един пример. Този път това е просто многократно присъединяване.

SELECT *
FROM Table1 a
LEFT OUTER JOIN Table2 b on a.column1 = b.column1
LEFT OUTER JOIN Table3 c on b.column2 = c.column1

В този случай, за да знаете какво е ляво или дясно, не забравяйте, че присъединяването работи на 2 таблици.

Таблица1 все още е лявата таблица и Таблица2 е правилната маса. Това се отнася за свързване на 2 таблици:Таблица1 и Таблица2 . Какво ще кажете за присъединяването към Table2 и Таблица3 ? Таблица 2 става лявата таблица и Таблица3 е правилната маса.

Ако добавим четвърта таблица, Таблица3 става лявата таблица и Таблица4 е правилната маса. Но това не свършва дотук. Можем да присъединим друга маса към Таблица1 . Ето един пример:

SELECT *
FROM Table1 a
LEFT OUTER JOIN Table2 b on a.column1 = b.column1
LEFT OUTER JOIN Table3 c on b.column2 = c.column1
LEFT OUTER JOIN Table4 d on c.column1 = d.column2
LEFT OUTER JOIN Table5 e on a.column2 = e.column1

Таблица1 е лявата таблица и Таблица5 е правилната маса. Можете също да направите същото с другите таблици.

Добре, нека се върнем към списъка с очакваните резултати по-горе. Можем също да извлечем типовете външни съединения от тях.

Типове външни съединения

Има 3 типа въз основа на изходите OUTER JOIN.

ЛЯВО ВЪНШНО ПРИЕДИНЕНИЕ (ЛЯВО ПРИЕДИНЕНИЕ)

LEFT JOIN връща вътрешни редове + стойности, които не са NULL от ляво таблица с нулеви двойници на дясната таблица. Следователно това е LEFT JOIN, защото лявата таблица е доминиращата от двете таблици в обединението, имащи стойности, различни от нула.

ЛЯВО ВЪНШНО ПРИЕДИНЕНИЕ ПРИМЕР 1
-- Return all customerIDs with orders and no orders

USE AdventureWorks
GO

SELECT
 c.CustomerID
,soh.OrderDate
FROM Sales.Customer c
LEFT OUTER JOIN Sales.SalesOrderHeader soh ON c.CustomerID = soh.CustomerID 

В примера по-горе Клиентът е лявата таблица и SalesOrderHeader е правилната маса. Резултатът от заявката е 32 166 записа – включва както вътрешни, така и външни редове. Можете да видите част от него на фигура 1:

Да предположим, че искаме да върнем само външните редове или клиентите без поръчки. За да направите това, добавете клауза WHERE, за да включите само редове с нулеви стойности от SalesOrderHeader .

SELECT
 c.CustomerID
,soh.OrderDate
FROM Sales.Customer c
LEFT OUTER JOIN Sales.SalesOrderHeader soh ON c.CustomerID = soh.CustomerID
WHERE soh.SalesOrderID IS NULL

Резултатът, който получих, е 701 записа . Всички те харесват нулевата OrderDate от фигура 1.

Ако получа само вътрешните редове, резултатът ще бъде 31 465 записа . Мога да го направя, като променя клаузата WHERE, за да включва тези SalesOrderIDs които не са нулеви. Или мога да променя присъединяването на INNER JOIN и да премахна клаузата WHERE.

За да видим дали се извежда от изхода на първия пример без клаузата WHERE, нека обобщим записите.

Вътрешни редове Външни редове Общо редове
31 465 записа 701 записа 32 166 записа

От общите редове по-горе с 32 166 записа можете да видите, че се проверява с първите примерни резултати. Това също показва как работи LEFT OUTER JOIN.

ЛЯВО ВЪНШНО ПРИЕДИНЕНИЕ ПРИМЕР 2

Този път примерът е мулти-съединяване. Забележете също, че премахваме ключовата дума OUTER.

-- show the people with and without addresses from AdventureWorks
USE AdventureWorks
GO

SELECT
 P.FirstName
,P.MiddleName
,P.LastName
,a.AddressLine1
,a.AddressLine2
,a.City
,adt.Name AS AddressType
FROM Person.Person p
LEFT JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
LEFT JOIN Person.Address a ON bea.AddressID = a.AddressID
LEFT JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID 

Той генерира 19 996 записа. Можете да разгледате частта от изхода на фигура 2 по-долу. Записите с нула AddressLine1 са външни редове. Над него има вътрешни редове.

ДЯСНО ВЪНШНО ПРИЕДИНЕНИЕ (ДЯСНО ПРИЕДИНЕНИЕ)

RIGHT JOIN връща вътрешни редове + стойности, които не са NULL отдясно таблица с нулевите копия на лявата таблица.

ПРИМЕР 1 ДЯСНО ВЪНШНО ПРИЕДИНЕНИЕ
-- From the product reviews, return the products without product reviews
USE AdventureWorks
GO

SELECT
P.Name
FROM Production.ProductReview pr
RIGHT OUTER JOIN Production.Product p ON pr.ProductID = p.ProductID
WHERE pr.ProductReviewID IS NULL 

Фигура 3 показва 10 от 501 записа в набора от резултати.

В горния пример, ProductReview е лявата таблица и продуктът е правилната маса. Тъй като това е DIGHT OUTER JOIN, възнамеряваме да включим стойностите, които не са NULL от дясната таблица.

Въпреки това, изборът между LEFT JOIN или RIGHT JOIN зависи от вас. Защо? Защото можете да изразите заявката, независимо дали LEFT или RIGHT JOIN, и да получите същите резултати. Нека опитаме с LEFT JOIN.

-- return the products without product reviews using LEFT OUTER JOIN
USE AdventureWorks
GO

SELECT
P.Name
FROM Production.Product p
LEFT OUTER JOIN Production.ProductReview pr ON pr.ProductID = p.ProductID
WHERE pr.ProductReviewID IS NULL

Опитайте да изпълните горното и ще получите същия резултат като на фигура 3. Но мислите ли, че оптимизаторът на заявки ще ги третира по различен начин? Нека разберем в плана за изпълнение и на двете на фигура 4.

Ако сте нов в това, има няколко изненади в плана за изпълнение.

  1. Диаграмите изглеждат еднакви и те са:опитайте Сравнете шоуплан , и ще видите същия QueryPlanHash .
  2. Забележете горната диаграма с обединяване за сливане. Използвахме DIGHT OUTER JOIN, но SQL Server го промени на LEFT OUTER JOIN. Освен това сменя лявата и дясната маса. Това го прави равен на втората заявка с LEFT JOIN.

Както виждате сега, резултатите са същите. Така че, изберете кое от OUTER JOIN ще бъде по-удобно.

Защо SQL Server промени RIGHT JOIN на LEFT JOIN?

Двигателят на базата данни не трябва да следва начина, по който изразявате логическите съединения. Докато може да даде правилни резултати по най-бързия начин, който смята за възможен, той ще направи промени. Дори преки пътища.

Не правете заключението, че RGHT JOIN е лошо, а LEFT JOIN е добро.

ПРИМЕР НА ДЯСНО ВЪНШНО ПРИЕДИНЕНИЕ 2

Разгледайте примера по-долу:

-- Get the unassigned addresses and the address types with no addresses
SELECT
 P.FirstName
,P.MiddleName
,P.LastName
,a.AddressLine1
,a.AddressLine2
,a.City
,adt.Name AS AddressType
FROM Person.Person p
RIGHT JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
RIGHT JOIN Person.Address a ON bea.AddressID = a.AddressID
RIGHT JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID
WHERE P.BusinessEntityID IS NULL 

Има 2 неща, които можете да получите от тази заявка, както можете да видите на фигура 5 по-долу:

Резултатите от заявката показват следното:

  1. Неприсвоените адреси – тези записи са тези с нулеви имена.
  2. Типове адреси без адреси. Типовете архив, фактуриране и основен адрес нямат съответни адреси. Те са от записи 817 до 819.

ПЪЛНО ВЪНШНО ПРИСЪЕДИНЯВАНЕ (ПЪЛНО ПРИЕДИНЕНИЕ)

FULL JOIN връща комбинация от вътрешни и външни редове, отляво и отдясно.

-- Get people with and without addresses, unassigned addresses, and address types without addresses
SELECT
 P.FirstName
,P.MiddleName
,P.LastName
,a.AddressLine1
,a.AddressLine2
,a.City
,adt.Name AS AddressType
FROM Person.Person p
FULL JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
FULL JOIN Person.Address a ON bea.AddressID = a.AddressID
FULL JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID

Резултатът включва 20 815 записа. Подобно на това, което бихте очаквали, това е общ брой записи от набора от резултати от INNER JOIN, LEFT JOIN и RIGHT JOIN.

LEFT и RIGHT JOIN включват клауза WHERE за показване само на резултатите с нулеви стойности в лявата или дясната таблица.

INNER JOIN ЛЯВО ПРИСЪЕДИНЯВАНЕ
(КЪДЕТО a.AddressID Е NULL)
ДЯСНО ПРИСЪЕДИНЯВАНЕ
(КЪДЕТО P.BusinessEntityID Е NULL)
ОБЩО (също като ПЪЛНО ПРИСЪЕДИНЕНИЕ)
18 798 записа 1198 записа 819 записа 20 815 записа

Имайте предвид, че FULL JOIN може да доведе до огромен набор от резултати от големи таблици. Така че, използвайте го само когато имате нужда от него.

Практическа употреба на OUTER JOIN

Ако все още се колебаете кога можете и трябва да използвате OUTER JOIN, ето няколко идеи.

Външно се присъединява, което извежда както вътрешни, така и външни редове

Примерите могат да бъдат:

  • Азбучен списък с платени и неплатени клиентски поръчки.
  • Азбучен списък на служителите със закъснение или без запис на закъснение.
  • Списък на притежателите на полици, които са подновили и не са подновили последните си застрахователни полици.

Външният се присъединява само към извежданите външни редове

Примерите включват:

  • азбучен списък на служителите без запис на закъснения за наградата за нулево закъснение
  • списък с територии без клиенти
  • списък с търговски агенти без продажби на конкретен продукт
  • получаване на резултати от липсващи стойности, като дати без поръчки за продажба в даден период (пример по-долу)
  • възли без дете във връзка родител-дете (пример по-долу)

Получаване на резултати от липсващи стойности

Да предположим, че трябва да изготвите отчет. Този отчет трябва да показва броя на дните за всеки месец в даден период, когато не е имало поръчки. SalesOrderHeader в AdventureWorks съдържа Дати на поръчка , но те нямат дати без поръчки. Какво можете да направите?

1. Създайте таблица с всички дати в период

Примерен скрипт по-долу ще създаде таблица с дати за цялата 2014 г.:

DECLARE @StartDate date = '20140101', @EndDate date = '20141231';

CREATE TABLE dbo.Dates
(
	d DATE NOT null PRIMARY KEY
)

WHILE @StartDate <= @EndDate
BEGIN
  INSERT Dates([d]) SELECT @StartDate;
  SET @StartDate = DATEADD(DAY, 1, @StartDate);
END

SELECT d FROM Dates ORDER BY [d];
2. Използвайте LEFT JOIN, за да изведете дните без поръчки
SELECT
 MONTH(d.d) AS [month]
,YEAR(d.d) AS [year]
,COUNT(*) AS NoOrderDays
FROM Dates d
LEFT JOIN Sales.SalesOrderHeader soh ON d.d = soh.OrderDate
WHERE soh.OrderDate IS NULL
GROUP BY YEAR(d.d), MONTH(d.d)
ORDER BY [year], [month]

Кодът по-горе отчита броя на дните, в които не са направени поръчки. SalesOrderHeader съдържа датите с поръчки. Така че върнатите нули в обединението ще се броят като дни без поръчки.

Междувременно, ако искате да знаете точните дати, можете да премахнете броя и групирането.

SELECT
 d.d
,soh.OrderDate
FROM Dates d
LEFT JOIN Sales.SalesOrderHeader soh ON d.d = soh.OrderDate
WHERE soh.OrderDate IS NULL

Или, ако искате да преброите поръчки за даден период и да видите коя дата има нулеви поръчки, ето как:

SELECT DISTINCT
 D.d AS SalesDate
,COUNT(soh.OrderDate) AS NoOfOrders
FROM Dates d
LEFT JOIN Sales.SalesOrderHeader soh ON d.d = soh.OrderDate
WHERE d.d BETWEEN '02/01/2014' AND '02/28/2014'
GROUP BY d.d
ORDER BY d.d

Горният код отчита поръчките за февруари 2014 г. Вижте резултата:

Защо се откроява 3 февруари 2014 г.? В моето копие на AdventureWorks няма поръчки за продажба за тази дата.

Сега забележете COUNT(soh.OrderDate) в кода. По-късно ще изясним защо това е толкова важно.

Получаване на бездетни възли в отношенията родител-дете

Понякога трябва да знаем възлите без дете във връзка родител-дете.

Нека използваме базата данни, която използвах в статията си за HierarchyID. Трябва да получите възли без деца в таблица за взаимоотношения родител-дете, като използвате самоприсъединяване.

SELECT 
 r1.RankParentId
,r1.Rank AS RankParent
,r.RankId
FROM Ranks r
RIGHT JOIN Ranks r1 ON r.RankParentId = r1.RankId
WHERE r.RankId is NULL 

Предупреждения при използването на OUTER JOIN

Тъй като OUTER JOIN може да върне вътрешни редове като INNER JOIN, това може да обърка. Проблеми с производителността също могат да се промъкнат. Така че, обърнете внимание на 3-те точки по-долу (от време на време се връщам към тях – не ставам по-млад, така че и аз забравям).

Филтриране на дясната таблица в LEFT JOIN с ненулева стойност в клаузата WHERE

Може да е проблем, ако сте използвали LEFT OUTER JOIN, но филтрирахте дясната таблица с ненулева стойност в клаузата WHERE. Причината е, че ще стане функционално еквивалентен на INNER JOIN. Помислете за примера по-долу:

USE AdventureWorks
GO

SELECT
 P.FirstName
,P.MiddleName
,P.LastName
,a.AddressLine1
,a.AddressLine2
,a.City
,adt.Name AS AddressType
FROM Person.Person p
LEFT JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
LEFT JOIN Person.Address a ON bea.AddressID = a.AddressID
LEFT JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID
WHERE bea.AddressTypeID = 5 

От кода по-горе, нека разгледаме двете таблици:Лице и BusinessEntityAddress . Личността е лявата таблица и BusinessEntityAddress е правилната маса.

Използва се LEFT JOIN, така че приема нулев BusinessEntityID някъде в BusinessEntityAddress . Тук обърнете внимание на клаузата WHERE. Той филтрира правилната таблица с AddressTypeID =5. Той напълно отхвърля всички външни редове в BusinessEntityAddress .

Това може да бъде някое от следните:

  • Разработчикът тества нещо в резултата, но е забравил да го премахне.
  • ВЪТРЕШНО ПРИСЪЕДИНЕНИЕ беше предназначено, но по някаква причина беше използвано LEFT JOIN.
  • Разработчикът не разбира разликата между LEFT JOIN и INNER JOIN. Той приема, че всяко от двете ще работи и това няма значение, защото резултатите са едни и същи в този случай.

Всяко от 3-те по-горе е лошо, но третият запис има друго значение. Нека сравним кода по-горе с еквивалента на INNER JOIN:

SELECT
 P.FirstName
,P.MiddleName
,P.LastName
,a.AddressLine1
,a.AddressLine2
,a.City
,adt.Name AS AddressType
FROM Person.Person p
INNER JOIN Person.BusinessEntityAddress bea ON P.BusinessEntityID = bea.BusinessEntityID
INNER JOIN Person.Address a ON bea.AddressID = a.AddressID
INNER JOIN person.AddressType adt ON bea.AddressTypeID = adt.AddressTypeID
WHERE bea.AddressTypeID = 5

Изглежда подобно на предишния код, с изключение на типа на присъединяване. Резултатът също е същият, но трябва да отбележите логическите показания в STATISTICS IO:

На фигура 7, първите I/O статистики са от използването на INNER JOIN. Общо логически четения е 177. Втората статистика обаче е за LEFT JOIN с по-висока стойност на логическо четене от 223. По този начин неправилното използване на LEFT JOIN в този пример ще изисква повече страници или ресурси от SQL Server. Следователно той ще работи по-бавно.

Вземане за вкъщи

Ако възнамерявате да изведете вътрешни редове, използвайте INNER JOIN. В противен случай не филтрирайте дясната таблица в LEFT JOIN с ненулева стойност. Ако това се случи, ще получите по-бавна заявка, отколкото ако използвате INNER JOIN.

БОНУС СЪВЕТ :Тази ситуация се случва и при RIGHT JOIN, когато лявата таблица е филтрирана с ненулева стойност.

Неправилно използване на типове присъединяване в многообединение

Да предположим, че искаме да получим всички доставчиците и броя на поръчките за покупка на продукти за всеки. Ето кода:

USE AdventureWorks
GO

SELECT
 v.BusinessEntityID
,v.Name AS Vendor
,pod.ProductID
,pod.OrderQty
FROM Purchasing.Vendor v
LEFT JOIN Purchasing.PurchaseOrderHeader poh ON v.BusinessEntityID = poh.VendorID
LEFT JOIN Purchasing.PurchaseOrderDetail pod ON poh.PurchaseOrderID = pod.PurchaseOrderID 

Горният код връща както доставчиците с поръчки за покупка, така и тези без. Фигура 8 показва действителния план за изпълнение на горния код.

Мислейки, че всяка поръчка за покупка има гарантирани подробности за поръчката, ВЪТРЕШНО ПРИСЪЕДИНЕНИЕ би било по-добре. Но дали наистина е така?

Първо, нека имаме модифицирания код с INNER JOIN.

USE AdventureWorks
GO

SELECT
 v.BusinessEntityID
,v.Name AS Vendor
,pod.ProductID
,pod.OrderQty
FROM Purchasing.Vendor v
LEFT JOIN Purchasing.PurchaseOrderHeader poh ON v.BusinessEntityID = poh.VendorID
INNER JOIN Purchasing.PurchaseOrderDetail pod ON poh.PurchaseOrderID = pod.PurchaseOrderID 

Не забравяйте, че изискването по-горе казва „всички“ доставчици. Тъй като използвахме LEFT JOIN в предишния код, ще получим доставчици без върнати поръчки за покупка. Това е заради нулевия PurchaseOrderID .

Промяната на присъединяването към INNER JOIN ще отхвърли всички нулеви PurchaseOrderIDs. Той също така ще отмени всички нулеви ИД на доставчик от Продавача маса. На практика се превръща във ВЪТРЕШНО ПРИСЪЕДИНЕНИЕ.

Това правилно ли е предположението? Планът за изпълнение ще разкрие отговора:

Както виждате, всички таблици бяха обработени с INNER JOIN. Следователно нашето предположение е правилно. Но за най-лошата част, наборът от резултати вече е неправилен, защото доставчиците без поръчки не бяха включени.

Вземане за вкъщи

Както в предишния случай, ако възнамерявате INNER JOIN, използвайте го. Но знаете какво да направите, ако срещнете ситуация като тази тук.

В този случай INNER JOIN ще отхвърли всички външни редове до горната таблица във връзката. Дори ако другото ви присъединяване е LEFT JOIN, това няма да има значение. Доказахме това в плановете за изпълнение.

Неправилно използване на COUNT() във външни присъединявания

Помните ли нашия примерен код, който отчита броя на поръчките на дата и резултата на фигура 6?

Тук ще изясним защо 02/03/2014 е подчертан и връзката му с COUNT(soh.OrderDate) .

Ако опитате да използвате COUNT(*), броят на поръчките за тази дата става 1, което е погрешно. На тази дата няма поръчки. Така че, когато използвате COUNT() с OUTER JOIN, използвайте правилната колона за броене.

В нашия случай soh.OrderDate може да бъде нула или не. Когато не е нула, COUNT() ще включва реда в броя. COUNT(*) ще го накара да брои всичко, включително нулевите стойности. И в крайна сметка грешни резултати.

Външното JOIN Извлечения

Нека обобщим точките:

  • OUTER JOIN може да върне както вътрешни, така и външни редове. Вътрешните редове са резултатът, който е подобен на резултата на INNER JOIN. Външните редове са ненулеви стойности с техните нулеви двойници въз основа на условието за присъединяване.
  • OUTER JOIN може да бъде ЛЯВО, ДЯСНО или ПЪЛНО. Имахме примери за всеки.
  • Външните редове, върнати от OUTER JOIN, могат да се използват по различни практически начини. Имахме идеи кога можете да използвате тези неща.
  • Имахме и предупреждения при използването на OUTER JOIN. Имайте предвид 3-те точки по-горе, за да избегнете грешки и проблеми с производителността.

Последната част от тази серия ще обсъди CROSS JOIN. Така че дотогава. И ако ви харесва тази публикация, споделете малко любов, като щракнете върху бутоните на социалните медии. Приятно кодиране!


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да замените част от низ в SQL

  2. ML{.NET} Въведение

  3. Миграция на схема:Връзка със звезда

  4. Създаване на по-усъвършенстван модел със статуси на потребител, нишка и публикация

  5. Как да създадете клъстер на Amazon Aurora