Database
 sql >> Base de données >  >> RDS >> Database

L'art d'agréger des données en SQL, des agrégations simples aux agrégations glissantes

Commençons notre parcours SQL pour comprendre l'agrégation de données dans SQL et les types d'agrégations, y compris les agrégations simples et glissantes.

Avant de passer aux agrégations, il convient de considérer des faits intéressants souvent manqués par certains développeurs en ce qui concerne SQL en général et l'agrégation en particulier.

Dans cet article, SQL fait référence à T-SQL qui est la version Microsoft de SQL et a plus de fonctionnalités que le SQL standard.

Les maths derrière SQL

Il est très important de comprendre que T-SQL est basé sur des concepts mathématiques solides bien qu'il ne s'agisse pas d'un langage mathématique rigide.

Selon le livre "Microsoft_SQL_Server_2008_T_SQL_Fundamentals" par Itzik Ben-Gan, SQL est conçu pour interroger et gérer des données dans un système de gestion de base de données relationnelle (RDBMS).

Le système de gestion de bases de données relationnelles lui-même repose sur deux solides branches mathématiques :

  • Théorie des ensembles
  • Logique des prédicats

Théorie des ensembles

La théorie des ensembles, comme son nom l'indique, est une branche des mathématiques sur les ensembles qui peuvent aussi être appelées collections d'objets distincts définis.

En bref, dans la théorie des ensembles, nous pensons aux choses ou aux objets dans leur ensemble de la même manière que nous pensons à un élément individuel.

Par exemple, un livre est un ensemble de tous les livres distincts définis, donc, nous prenons un livre dans son ensemble, ce qui est suffisant pour obtenir des détails sur tous les livres qu'il contient.

Logique des prédicats

La logique de prédicat est une logique booléenne qui renvoie vrai ou faux selon la condition ou les valeurs des variables.

La logique de prédicat peut être utilisée pour appliquer des règles d'intégrité (le prix doit être supérieur à 0,00) ou filtrer les données (où le prix est supérieur à 10,00), cependant, dans le contexte de T-SQL, nous avons trois valeurs logiques comme suit :

  1. Vrai
  2. Faux
  3. Inconnu (Nul)

Ceci peut être illustré comme suit :

Un exemple de prédicat est "Où le prix du livre est supérieur à 10,00".

C'en est assez des maths, mais gardez à l'esprit que je vais y faire référence plus tard dans l'article.

Pourquoi l'agrégation de données en SQL est facile

L'agrégation de données en SQL dans sa forme la plus simple consiste à connaître les totaux en une seule fois.

Par exemple, si nous avons une table des clients qui contient une liste de tous les clients avec leurs détails, les données agrégées de la table des clients peuvent nous donner le nombre total de clients que nous avons.

Comme indiqué précédemment, nous considérons un ensemble comme un élément unique, nous appliquons donc simplement une fonction d'agrégation au tableau pour obtenir les totaux.

Étant donné que SQL est à l'origine un langage basé sur des ensembles (comme indiqué précédemment), il est donc relativement plus facile de lui appliquer des fonctions d'agrégation par rapport à d'autres langages.

Par exemple, si nous avons une table de produits qui contient des enregistrements de tous les produits de la base de données, nous pouvons immédiatement appliquer la fonction de comptage à une table de produits pour obtenir le nombre total de produits plutôt que de les compter un par un dans une boucle.

Recette d'agrégation de données

Afin d'agréger des données en SQL, nous avons besoin au minimum des éléments suivants :

  1. Données (tableau) avec des colonnes qui, une fois agrégées, ont un sens
  2. Une fonction d'agrégation à appliquer sur les données

Préparation des exemples de données (tableau)

Prenons un exemple de table de commande simple qui contient trois éléments (colonnes) :

  1. Numéro de commande (OrderId)
  2. Date à laquelle la commande a été passée (OrderDate)
  3. Montant de la commande (TotalAmount)

Créons la base de données AggregateSample pour aller plus loin :

-- Create aggregate sample database 
CREATE DATABASE AggregateSample

Créez maintenant la table de commandes dans l'exemple de base de données comme suit :

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

Remplir des exemples de données

Remplissez le tableau en ajoutant une ligne :

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

Regardons le tableau maintenant :

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

Veuillez noter que j'utilise dbForge Studio pour SQL Server dans cet article, donc seul l'aspect de la sortie peut différer si vous exécutez le même code dans SSMS (SQL Server Management Studio), il n'y a aucune différence en ce qui concerne les scripts et leurs résultats.

Fonctions d'agrégation de base

Les fonctions d'agrégation de base pouvant être appliquées au tableau sont les suivantes :

  1. Somme
  2. Compter
  3. Min
  4. Maximum
  5. Moyenne

Tableau d'agrégation d'un seul enregistrement

Maintenant, la question intéressante est la suivante :"pouvons-nous agréger (additionner ou compter) des données (enregistrements) dans une table si elle ne comporte qu'une seule ligne, comme dans notre cas ?" La réponse est "Oui", nous le pouvons, même si cela n'a pas beaucoup de sens, mais cela peut nous aider à comprendre comment les données se préparent pour l'agrégation.

Afin d'obtenir le nombre total de commandes, nous utilisons la fonction count () avec la table, comme indiqué précédemment, nous pouvons simplement appliquer la fonction d'agrégation à la table car SQL est un langage basé sur un ensemble et les opérations peuvent être appliquées à un ensemble directement.

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

Maintenant, qu'en est-il de la commande avec un montant minimum, maximum et moyen pour un seul enregistrement :

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

Comme nous pouvons le voir sur la sortie, le montant minimum, maximum et moyen est le même si nous avons un seul enregistrement, donc appliquer une fonction d'agrégation à un seul enregistrement est possible mais cela nous donne les mêmes résultats.

Nous avons besoin d'au moins plusieurs enregistrements pour donner un sens aux données agrégées.

Tableau d'agrégation de plusieurs enregistrements

Ajoutons maintenant quatre autres enregistrements comme suit :

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

Le tableau se présente maintenant comme suit :

Si nous appliquons les fonctions d'agrégation à la table maintenant, nous allons obtenir de bons résultats :

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

Regroupement de données agrégées

Nous pouvons regrouper les données agrégées par n'importe quelle colonne ou ensemble de colonnes pour obtenir des agrégats basés sur cette colonne.

Par exemple, si nous voulons connaître le nombre total de commandes par date, nous devons regrouper le tableau par date en utilisant la clause Group by comme suit :

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

Le résultat est le suivant :

Donc, si nous voulons voir la somme de tout le montant de la commande, nous pouvons simplement appliquer la fonction somme à la colonne du montant total sans aucun regroupement comme suit :

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

Afin d'obtenir la somme du montant des commandes par date, nous ajoutons simplement le groupe par date à l'instruction SQL ci-dessus comme suit :

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

Obtenir des totaux sans regrouper les données

Nous pouvons immédiatement obtenir des totaux tels que le total des commandes, le montant maximum de la commande, le montant minimum de la commande, la somme du montant des commandes, le montant moyen de la commande sans avoir besoin de le regrouper si l'agrégation est destinée à toutes les tables.

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

Ajouter des clients aux commandes

Ajoutons un peu de plaisir en ajoutant des clients dans notre tableau. Nous pouvons le faire en créant une autre table de clients et en transmettant l'identifiant du client à la table des commandes, mais pour rester simple et se moquer du style d'entrepôt de données (où les tables sont dénormalisées), j'ajoute la colonne du nom du client dans la table des commandes comme suit :

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

Obtenir le nombre total de commandes par client

Pouvez-vous maintenant deviner comment obtenir le nombre total de commandes par client ? Vous devez regrouper par client (CustomerName) et appliquer la fonction d'agrégation count() à tous les enregistrements comme suit :

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

Ajout de cinq enregistrements supplémentaires à la table de commande

Nous allons maintenant ajouter cinq lignes supplémentaires à la table de commande simple comme suit :

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

Jetez un œil aux données maintenant :

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

 Obtenir le nombre total de commandes par client, trié du maximum au minimum de commandes

Si vous êtes intéressé par le nombre total de commandes par client triées par commandes maximales à minimales, ce n'est pas du tout une mauvaise idée de diviser cela en étapes plus petites comme suit :

-- (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

Pour trier le nombre de commandes du maximum au minimum, nous devons utiliser la clause Order By DESC (ordre décroissant) avec count() à la fin comme suit :

-- (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

Obtenir le nombre total de commandes par date, trié d'abord par la commande la plus récente

En utilisant la méthode ci-dessus, nous pouvons maintenant connaître le nombre total de commandes par date, triées d'abord par la commande la plus récente, comme suit :

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

La fonction CAST nous aide à obtenir uniquement la partie date. Le résultat est le suivant :

Vous pouvez utiliser autant de combinaisons que possible tant qu'elles ont du sens.

Exécuter des agrégations

Maintenant que nous sommes familiarisés avec l'application de fonctions d'agrégation à nos données, passons à la forme avancée des agrégations et l'une de ces agrégations est l'agrégation en cours d'exécution.

Les agrégations en cours sont les agrégations appliquées à un sous-ensemble de données plutôt qu'à l'ensemble de données, ce qui nous aide à créer de petites fenêtres sur les données.

Jusqu'à présent, nous avons vu que toutes les fonctions d'agrégation sont appliquées à toutes les lignes de la table qui peuvent être regroupées par une colonne telle que la date de commande ou le nom du client, mais avec des agrégations en cours, nous avons la liberté d'appliquer les fonctions d'agrégation sans regrouper l'ensemble jeu de données.

Évidemment, cela signifie que nous pouvons appliquer la fonction d'agrégation sans utiliser la clause Group By, ce qui est quelque peu étrange pour les débutants en SQL (ou parfois certains développeurs l'ignorent) qui ne sont pas familiers avec les fonctions de fenêtrage et l'exécution d'agrégations.

Windows sur les données

Comme indiqué précédemment, l'agrégation en cours est appliquée à un sous-ensemble de données ou (en d'autres termes) à de petites fenêtres de données.

Considérez les fenêtres comme un ensemble (s) dans un ensemble ou une (des) table (s) dans une table. Un bon exemple de fenêtrage sur les données dans notre cas est que nous avons la table de commandes qui contient des commandes passées à des dates différentes, alors que se passe-t-il si chaque date est une fenêtre distincte, alors nous pouvons appliquer des fonctions d'agrégation sur chaque fenêtre de la même manière que nous avons appliqué à le tableau.

Si nous trions la table de commande (SimpleOrder) par date de commande (OrderDate) comme suit :

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

Les fenêtres sur les données prêtes pour l'exécution des agrégations peuvent être consultées ci-dessous :

Nous pouvons également considérer ces fenêtres ou sous-ensembles car six mini-tables basées sur la date de commande et des agrégats peuvent être appliqués sur chacune de ces mini-tables.

Utilisation de la partition par dans la clause OVER()

Les agrégations en cours d'exécution peuvent être appliquées en partitionnant la table à l'aide de "Partitionner par" dans la clause OVER().

Par exemple, si nous voulons partitionner la table de commande par dates telles que chaque date est une sous-table ou une fenêtre sur un ensemble de données, nous devons partitionner les données par date de commande et cela peut être réalisé en utilisant une fonction d'agrégation telle que COUNT( ) avec OVER() et Partition by inside OVER() comme suit :

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

Fenêtre Obtention des totaux cumulés par date (Partition)

Les agrégations en cours nous aident à limiter la portée de l'agrégation à la fenêtre définie uniquement et nous pouvons obtenir les totaux cumulés par fenêtre comme suit :

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

Obtenir les totaux cumulés par fenêtre client (partition)

Tout comme les totaux cumulés par fenêtre de date, nous pouvons également calculer les totaux cumulés par fenêtre client en partitionnant l'ensemble de commandes (table) en sous-ensembles de petits clients (partitions) comme suit :

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

Agrégations glissantes

Les agrégations glissantes sont les agrégations qui peuvent être appliquées aux cadres d'une fenêtre, ce qui signifie réduire davantage la portée dans la fenêtre (partition).

En d'autres termes, les totaux cumulés nous donnent des totaux (somme, moyenne, min, max, nombre) pour toute la fenêtre (sous-ensemble) que nous créons dans une table, tandis que les totaux glissants nous donnent des totaux (somme, moyenne, min, max, nombre) pour le cadre (sous-ensemble de sous-ensemble) dans la fenêtre (sous-ensemble) de la table.

Par exemple, si nous créons une fenêtre sur les données basée sur le client (partition par client), nous pouvons voir que le client "Pierre" a trois enregistrements dans sa fenêtre et toutes les agrégations sont appliquées à ces trois enregistrements. Maintenant, si nous voulons créer un cadre pour deux lignes seulement à la fois, cela signifie que l'agrégation est encore réduite et qu'elle est ensuite appliquée aux première et deuxième lignes, puis aux deuxième et troisième lignes et ainsi de suite.

Utilisation de ROWS PRECEEDING avec Order By dans la clause OVER()

Des agrégations glissantes peuvent être appliquées en ajoutant ROWS PRECEEDING avec Order By (après Partition By) tandis que ROWS PRECEEDING déterminent la portée de Frame dans la fenêtre.

Par exemple, si nous voulons agréger les données pour seulement deux lignes à la fois pour chaque client, nous avons besoin d'agrégations glissantes à appliquer à la table des commandes comme suit :

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

Afin de comprendre comment cela fonctionne, regardons le tableau d'origine dans le contexte des cadres et des fenêtres :

Dans la première ligne de la fenêtre du client Peter, il a passé une commande d'un montant de 30,50 car il s'agit du début du cadre dans la fenêtre du client, donc min et max sont les mêmes car il n'y a pas de ligne précédente à comparer.

Ensuite, le montant minimum reste le même mais le maximum devient 100,50 car le montant de la ligne précédente (première ligne) est de 30,50 et ce montant de ligne est de 100,50, donc le maximum des deux est de 100,50.

Ensuite, en passant à la troisième ligne, la comparaison aura lieu avec la deuxième ligne de sorte que le montant minimum des deux est de 50,00 et le montant maximum des deux lignes est de 100,50.

Fonction MDX Year to Date (YTD) et agrégations en cours

MDX est un langage d'expression multidimensionnel utilisé pour interroger des données multidimensionnelles (telles que le cube) et est utilisé dans les solutions d'informatique décisionnelle (BI).

Selon https://docs.microsoft.com/en-us/sql/mdx/ytd-mdx, la fonction Year to Date (YTD) dans MDX fonctionne de la même manière que l'exécution ou le glissement des agrégations. Par exemple, YTD souvent utilisé en combinaison sans paramètre fourni affiche un total cumulé à ce jour.

Cela signifie que si nous appliquons cette fonction sur l'année, cela donne toutes les données de l'année, mais si nous explorons jusqu'en mars, cela nous donnera tous les totaux depuis le début de l'année jusqu'en mars et ainsi de suite.

Ceci est très utile dans les rapports SSRS.

Choses à faire

C'est ça! Vous êtes prêt à effectuer une analyse de données de base après avoir parcouru cet article et vous pouvez améliorer vos compétences en procédant comme suit :

  1. Veuillez essayer d'écrire un script d'agrégation en cours d'exécution en créant des fenêtres sur d'autres colonnes telles que Montant total.
  2. Veuillez également essayer d'écrire un script d'agrégats glissants en créant des cadres sur d'autres colonnes telles que Montant total.
  3. Vous pouvez ajouter plus de colonnes et d'enregistrements au tableau (ou même plus de tableaux) pour essayer d'autres combinaisons d'agrégation.
  4. Les exemples de scripts mentionnés dans cet article peuvent être transformés en procédures stockées à utiliser dans les rapports SSRS derrière des ensembles de données.

Références :

  • Ytd (MDX)
  • dbForge Studio pour SQL Server