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

Изкуството на агрегирането на данни в SQL от прости до плъзгащи се агрегации

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

Преди да преминем към агрегирането, си струва да помислим за интересни факти, често пропускани от някои разработчици, когато става въпрос за SQL като цяло и за агрегирането в частност.

В тази статия SQL се отнася до T-SQL, който е версията на SQL на Microsoft и има повече функции от стандартния SQL.

Математиката зад SQL

Много е важно да се разбере, че T-SQL се основава на някои солидни математически концепции, въпреки че не е твърд език, базиран на математиката.

Според книгата „Microsoft_SQL_Server_2008_T_SQL_Fundamentals“ от Ицик Бен-Ган, SQL е предназначен да заявява и управлява данни в система за управление на релационна база данни (RDBMS).

Самата система за управление на релационна база данни се основава на два солидни математически клона:

  • Теория на множеството
  • Предикатна логика

Теория на множеството

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

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

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

Предикатна логика

Предикатната логика е булева логика, която връща true или false в зависимост от условието или стойностите на променливите.

Предикатната логика може да се използва за налагане на правила за интегритет (цената трябва да е по-голяма от 0,00) или филтриране на данни (където цената е повече от 10,00), но в контекста на T-SQL имаме три логически стойности, както следва:

  1. Вярно
  2. Невярно
  3. Неизвестно (Null)

Това може да се илюстрира по следния начин:

Пример за предикат е „Където цената на книгата е по-голяма от 10,00“.

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

Защо агрегирането на данни в SQL е лесно

Обобщаването на данни в SQL в най-простата им форма е свързано с опознаването на общите суми наведнъж.

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

Както беше обсъдено по-рано, ние мислим за набор като един елемент, така че просто прилагаме агрегатна функция към таблицата, за да получим общите суми.

Тъй като SQL първоначално е език, базиран на набори (както беше обсъдено по-рано), така че е относително по-лесно да се прилагат агрегатни функции към него в сравнение с други езици.

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

Рецепта за агрегиране на данни

За да агрегираме данни в SQL, ни трябват най-малко следните неща:

  1. Данни (таблица) с колони, които, когато са обобщени, имат смисъл
  2. Агрегирана функция, която да се приложи към данните

Подготовка на примерни данни (Таблица)

Нека вземем пример за проста таблица с поръчки, която съдържа три неща (колони):

  1. Номер на поръчката (OrderId)
  2. Дата на направена поръчка (OrderDate)
  3. Сума на поръчката (TotalAmount)

Нека създадем базата данни AggregateSample, за да продължим по-нататък:

-- Create aggregate sample database 
CREATE DATABASE AggregateSample

Сега създайте таблицата на поръчките в примерната база данни, както следва:

-- Create order table in the aggregate sample database
USE AggregateSample

CREATE TABLE SimpleOrder
  (OrderId INT PRIMARY KEY IDENTITY(1,1),
  OrderDate DATETIME2,
  TotalAmount DECIMAL(10,2)
  )

Попълване на примерни данни

Попълнете таблицата, като добавите един ред:

INSERT INTO dbo.SimpleOrder
(
  OrderDate
 ,TotalAmount
)
VALUES
(
  '20180101' -- OrderDate - datetime2
 ,20.50 -- TotalAmount - decimal(10, 2)
);
GO

Нека сега да разгледаме таблицата:

-- View order table 
SELECT OrderId ,OrderDate ,TotalAmount FROM SimpleOrder

Моля, обърнете внимание, че използвам dbForge Studio за SQL Server в тази статия, така че само изходният вид може да се различава, ако стартирате същия код в SSMS (SQL Server Management Studio), няма разлика що се отнася до скриптовете и техните резултати.

Основни агрегатни функции

Основните агрегатни функции, които могат да бъдат приложени към таблицата, са както следва:

  1. Сбор
  2. Преброяване
  3. Мин.
  4. Макс.
  5. Средно

Агрегираща таблица с единични записи

Сега интересният въпрос е „можем ли да обобщим (сумираме или броим) данни (записи) в таблица, ако тя има само един ред, както в нашия случай?“ Отговорът е „Да“, можем, въпреки че няма много смисъл, но може да ни помогне да разберем как данните се подготвят за агрегиране.

За да получим общия брой поръчки, използваме функцията count() с таблицата, както беше обсъдено по-рано, можем просто да приложим агрегатната функция към таблицата, тъй като SQL е език, базиран на набори и операциите могат да се прилагат към набор директно.

-- Getting total number of orders placed so far
SELECT COUNT(*) AS Total_Orders FROM SimpleOrder

Сега, какво ще кажете за поръчката с минимална, максимална и средна сума за един запис:

-- Getting order with minimum amount, maximum amount, average amount and total orders
SELECT
  COUNT(*) AS Total_Orders
 ,MIN(TotalAmount) AS Min_Amount
 ,MAX(TotalAmount) AS Max_Amount
 ,AVG(TotalAmount) Average_Amount
FROM SimpleOrder

Както можем да видим от изхода, минималната, максималната и средната сума са еднакви, ако имаме един запис, така че прилагането на агрегатна функция към един запис е възможно, но ни дава същите резултати.

Нуждаем се от поне повече от един запис, за да разберем обобщените данни.

Таблица за обобщаване на множество записи

Нека сега добавим още четири записа, както следва:

INSERT INTO dbo.SimpleOrder
(
  OrderDate
 ,TotalAmount
)
VALUES
(
  '20180101' -- OrderDate - datetime2
 ,20.50 -- TotalAmount - decimal(10, 2)
),
(
  '20180102' -- OrderDate - datetime2
 ,30.50 -- TotalAmount - decimal(10, 2)
),
(
  '20180103' -- OrderDate - datetime2
 ,10.50 -- TotalAmount - decimal(10, 2)
),
(
  '20180110' -- OrderDate - datetime2
 ,100.50 -- TotalAmount - decimal(10, 2)
);

GO

Сега таблицата изглежда по следния начин:

Ако приложим агрегатните функции към таблицата сега, ще получим добри резултати:

-- Getting order with minimum amount, maximum amount, average amount and total orders
SELECT
  COUNT(*) AS Total_Orders
 ,MIN(TotalAmount) AS Min_Amount
 ,MAX(TotalAmount) AS Max_Amount
 ,AVG(TotalAmount) Average_Amount
FROM SimpleOrder

Групиране на обобщени данни

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

Например, ако искаме да знаем общия брой поръчки на дата, трябва да групираме таблицата по дата, като използваме клауза Group by, както следва:

-- Getting total orders per date
SELECT
  OrderDate
 ,COUNT(*) AS Total_Orders
FROM SimpleOrder
GROUP BY OrderDate

Резултатът е както следва:

Така че, ако искаме да видим сумата от цялата сума на поръчката, можем просто да приложим функцията сума към колоната с обща сума без групиране, както следва:

-- Sum of all the orders amount
SELECT
  SUM(TotalAmount) AS Sum_of_Orders_Amount
FROM SimpleOrder

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

-- Sum of	all	the	orders amount per date
SELECT
  OrderDate
 ,SUM(TotalAmount) AS Sum_of_Orders
FROM SimpleOrder
GROUP BY OrderDate

Получаване на суми без групиране на данни

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

-- Getting order with minimum amount, maximum amount, average amount, sum of amount and total orders
SELECT
  COUNT(*) AS Total_Orders
 ,MIN(TotalAmount) AS Min_Amount
 ,MAX(TotalAmount) AS Max_Amount
 ,AVG(TotalAmount) AS Average_Amount
 ,SUM(TotalAmount) AS Sum_of_Amount
FROM SimpleOrder

Добавяне на клиенти към поръчките

Нека добавим малко забавление, като добавим клиенти в нашата таблица. Можем да направим това, като създадем друга таблица с клиенти и предадем идентификатор на клиента към таблицата с поръчки, но за да бъде опростено и да се подигравам на стила на склад за данни (където таблиците са денормализирани), добавям колоната с име на клиента в таблицата с поръчки, както следва :

-- Adding CustomerName column and data to the order table
ALTER TABLE SimpleOrder 
ADD CustomerName VARCHAR(40) NULL 
  GO
  
UPDATE SimpleOrder
SET CustomerName = 'Eric'
WHERE OrderId = 1
GO

UPDATE SimpleOrder
SET CustomerName = 'Sadaf'
WHERE OrderId = 2
GO

UPDATE SimpleOrder
SET CustomerName = 'Peter'
WHERE OrderId = 3
GO

UPDATE SimpleOrder
SET CustomerName = 'Asif'
WHERE OrderId = 4
GO

UPDATE SimpleOrder
SET CustomerName = 'Peter'
WHERE OrderId = 5
GO

Получаване на общи поръчки на клиент

Можете ли да познаете сега как да получите общите поръчки на клиент? Трябва да групирате по клиент (CustomerName) и да приложите обобщената функция count() към всички записи, както следва:

-- Total orders per customer
  SELECT CustomerName,COUNT(*) AS Total_Orders FROM SimpleOrder 
    GROUP BY CustomerName

Добавяне на още пет записа към таблицата с поръчки

Сега ще добавим още пет реда към простата таблица за поръчки, както следва:

-- Adding 5 more records to order table
INSERT INTO SimpleOrder (OrderDate, TotalAmount, CustomerName)
  VALUES 
  ('01-Jan-2018', 70.50, 'Sam'),
  ('02-Jan-2018', 170.50, 'Adil'),
  ('03-Jan-2018',50.00,'Sarah'),
  ('04-Jan-2018',50.00,'Asif'),
  ('11-Jan-2018',50.00,'Peter')
GO

Разгледайте данните сега:

-- Viewing order table after adding customer name and five more rows
SELECT OrderId,CustomerName,OrderDate,TotalAmount FROM SimpleOrder 
GO

Получаване на общите поръчки на клиент, сортирани по максимални към минимални поръчки

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

-- (1) Getting total orders
SELECT COUNT(*) AS Total_Orders FROM SimpleOrder

-- (2) Getting total orders per customer
SELECT CustomerName,COUNT(*) AS Total_Orders FROM SimpleOrder
GROUP BY CustomerName

За да сортираме броя на поръчките от максимум до минимум, трябва да използваме клаузата Order By DESC (низходящ ред) с count() в края, както следва:

-- (3) Getting total orders per customer from maximum to minimum orders
SELECT CustomerName,COUNT(*) AS Total_Orders FROM SimpleOrder
GROUP BY CustomerName
ORDER BY COUNT(*) DESC

Получаване на общите поръчки на дата, сортирани първо по най-новата поръчка

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

-- Getting total orders per date from most recent first
SELECT CAST(OrderDate AS DATE) AS OrderDate,COUNT(*) AS Total_Orders FROM SimpleOrder
GROUP BY OrderDate
ORDER BY OrderDate DESC

Функцията CAST ни помага да получим само частта за дата. Резултатът е както следва:

Можете да използвате колкото се може повече комбинации, стига да имат смисъл.

Изпълнение на агрегати

След като вече сме запознати с прилагането на агрегатни функции към нашите данни, нека да преминем към усъвършенстваната форма на агрегиране и едно такова агрегиране е текущото агрегиране.

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

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

Очевидно това означава, че можем да приложим агрегатната функция, без да използваме клаузата Group By, което е малко странно за онези начинаещи в SQL (или понякога някои разработчици пренебрегват това), които не са запознати с функциите за прозорец и изпълняването на агрегати.

Windows on Data

Както беше казано по-рано, текущото агрегиране се прилага към подмножество от набор от данни или (с други думи) към малки прозорци от данни.

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

Ако сортираме таблицата с поръчки (SimpleOrder) по дата на поръчка (OrderDate), както следва:

-- View order table sorted by order date
SELECT so.OrderId
      ,so.OrderDate
      ,so.TotalAmount
      ,so.CustomerName FROM SimpleOrder so
  ORDER BY so.OrderDate

Windows върху данни, готови за изпълнение на агрегации, можете да видите по-долу:

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

Използване на дял от вътре OVER() Клауза

Изпълняващите се агрегации могат да се прилагат чрез разделяне на таблицата с помощта на „Разделяне по“ в клаузата OVER().

Например, ако искаме да разделим таблицата с поръчки по дати, като всяка дата е подтаблица или прозорец в набор от данни, тогава трябва да разделим данните по дата на поръчка и това може да се постигне с помощта на агрегатна функция като COUNT( ) с OVER() и разделяне от вътре OVER(), както следва:

-- Running Aggregation on Order table by partitioning by dates
SELECT OrderDate, Total_Orders=COUNT(*) OVER(PARTITION BY OrderDate)  FROM SimpleOrder

Получаване на текущи суми за прозорец за дата (раздел)

Текущите агрегирания ни помагат да ограничим обхвата на агрегиране само до определения прозорец и можем да получим текущи суми на прозорец, както следва:

-- Getting total orders, minimum amount, maximum amount, average amount and sum of all amounts per date window (partition by date)
SELECT CAST (OrderDate AS DATE) AS OrderDate,
  Count=COUNT(*) OVER (PARTITION BY OrderDate),
  Min_Amount=MIN(TotalAmount) OVER (PARTITION BY OrderDate) ,
  Max_Amount=MAX(TotalAmount) OVER (PARTITION BY OrderDate) ,
  Average_Amount=AVG(TotalAmount) OVER (PARTITION BY OrderDate),
  Sum_Amount=SUM(TotalAmount) OVER (PARTITION BY OrderDate)
  FROM SimpleOrder

Получаване на текущи суми на клиентски прозорец (раздел)

Точно както текущите суми за прозорец с дата, ние също можем да изчислим текущите суми за прозорец на клиент, като разделим набора от поръчки (таблица) на малки подмножества от клиенти (раздели), както следва:

-- Getting total orders, minimum amount, maximum amount, average amount and sum of all amounts per customer window (partition by customer)
SELECT CustomerName,
CAST (OrderDate AS DATE) AS OrderDate,
  Count=COUNT(*) OVER (PARTITION BY CustomerName),
  Min_Amount=MIN(TotalAmount) OVER (PARTITION BY CustomerName) ,
  Max_Amount=MAX(TotalAmount) OVER (PARTITION BY CustomerName) ,
  Average_Amount=AVG(TotalAmount) OVER (PARTITION BY CustomerName),
  Sum_Amount=SUM(TotalAmount) OVER (PARTITION BY CustomerName)
  FROM SimpleOrder
  ORDER BY Count DESC,OrderDate

Плъзгащи се агрегати

Плъзгащите се агрегирания са агрегатите, които могат да бъдат приложени към рамките в рамките на прозорец, което означава допълнително стесняване на обхвата в рамките на прозореца (раздел).

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

Например, ако създадем прозорец върху данни, базирани на (разделяне по клиент) клиент, тогава можем да видим, че клиентът „Питър“ има три записа в прозореца си и всички агрегирания се прилагат към тези три записа. Сега, ако искаме да създадем рамка само за два реда наведнъж, това означава, че агрегирането се стеснява допълнително и след това се прилага към първия и втория ред и след това към втория и третия ред и така нататък.

Използване на ROWS ПРЕДШЕСТВУВАЩИ с клауза Order By вътре OVER()

Плъзгащите се агрегирания могат да бъдат приложени чрез добавяне на ROWS <брой редове> ПРЕДИШНИ с Подреждане по (след разделяне по), докато ROWS <число> ПРЕДИШНИ определят обхвата на рамката в прозореца.

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

-- Getting minimum amount, maximum amount, average amount per frame per customer window 
SELECT CustomerName,
 Min_Amount=Min(TotalAmount) OVER (PARTITION BY CustomerName ORDER BY OrderDate ROWS 1 PRECEDING), 
 Max_Amount=Max(TotalAmount) OVER (PARTITION BY CustomerName ORDER BY OrderDate ROWS 1 PRECEDING) ,
 Average_Amount=AVG(TotalAmount) OVER (PARTITION BY CustomerName ORDER BY OrderDate  ROWS 1 PRECEDING)
 FROM SimpleOrder so
 ORDER BY CustomerName

За да разберем как работи, нека разгледаме оригиналната таблица в контекста на рамки и прозорци:

В първия ред на прозореца на клиента Peter той направи поръчка със сумата от 30,50, тъй като това е началото на рамката в прозореца на клиента, така че min и max са същите, тъй като няма предходен ред за сравнение.

След това минималната сума остава същата, но максималната става 100,50, тъй като сумата на предишния ред (първи ред) е 30,50, а сумата на този ред е 100,50, така че максималната от двете е 100,50.

След това, преминавайки към третия ред, сравнението ще се извърши с втория ред, така че минималната сума на двата е 50,00, а максималната сума на двата реда е 100,50.

MDX функция от година до дата (от началото на годината) и работещи агрегати

MDX е език за многоизмерни изрази, използван за запитване на многоизмерни данни (като куб) и се използва в решенията за бизнес разузнаване (BI).

Според https://docs.microsoft.com/en-us/sql/mdx/ytd-mdx, функцията от година до дата (YTD) в MDX работи по същия начин, както работят изпълняваните или плъзгащите се агрегати. Например YTD, често използван в комбинация без предоставен параметър, показва текущата сума към момента.

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

Това е много полезно в SSRS отчети.

Неща за правене

Това е! Готови сте да направите някои основни анализи на данни, след като преминете през тази статия и можете да подобрите уменията си допълнително чрез следните неща:

  1. Моля, опитайте да напишете работещ скрипт за агрегати, като създадете прозорци в други колони, като например Обща сума.
  2. Моля, опитайте също да напишете скрипт за плъзгащи се агрегати, като създадете рамки в други колони, като например Обща сума.
  3. Можете да добавите още колони и записи към таблицата (или дори повече таблици), за да опитате други комбинации за агрегиране.
  4. Примерните скриптове, споменати в тази статия, могат да бъдат превърнати в съхранени процедури, които да се използват в SSRS отчети зад набор(и) от данни.

Препратки:

  • Ytd (MDX)
  • dbForge Studio за SQL Server

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Стартирането на базата данни на RAC се проваля с грешка ORA-12547

  2. Как да добавя чужд ключ в SQL?

  3. Разтоварване на много големи бази данни

  4. SQL DELETE за начинаещи

  5. Какво представляват SQL ограниченията и различните му типове?