Fonctions de tableau
J'effectue des migrations de bases de données complexes à très grande vitesse pour gagner ma vie, en utilisant SQL à la fois comme langage client et serveur (aucun autre langage n'est utilisé), le tout fonctionnant côté serveur, où le code apparaît rarement à partir du moteur de base de données. Les fonctions de table jouent un rôle ÉNORME dans mon travail . Je n'utilise pas de "curseurs" car ils sont trop lents pour répondre à mes exigences de performance, et tout ce que je fais est orienté résultat. Les fonctions de table m'ont été d'une aide immense pour éliminer complètement l'utilisation des curseurs, atteindre une vitesse très élevée et ont contribué de manière spectaculaire à réduire le volume de code et à améliorer la simplicité.
En bref, vous utilisez une requête qui fait référence à deux (ou plusieurs) fonctions de table pour transmettre les données d'une fonction de table à la suivante. L'ensemble de résultats de la requête de sélection qui appelle les fonctions de table sert de conduit pour transmettre les données d'une fonction de table à la suivante. Sur la plate-forme / version DB2 sur laquelle je travaille, et il apparaît sur la base d'un rapide coup d'œil au manuel Postgres 9.1 que la même chose est vraie là-bas, vous ne pouvez transmettre qu'une seule ligne de valeurs de colonne en entrée à l'un des appels de fonction de table, comme vous l'avez découvert. Cependant, étant donné que l'appel de fonction de table se produit au milieu du traitement de l'ensemble de résultats d'une requête, vous obtenez le même effet en passant un ensemble de résultats complet à chaque appel de fonction de table, bien que, dans la plomberie du moteur de base de données, les données soient transmises une seule ligne à la fois pour chaque fonction de table.
Les fonctions de table acceptent une ligne de colonnes d'entrée et renvoient un seul ensemble de résultats dans la requête appelante (c'est-à-dire select) qui a appelé la fonction. Les colonnes de l'ensemble de résultats renvoyées par une fonction de table font partie de l'ensemble de résultats de la requête appelante et sont donc disponibles comme entrée pour la fonction de table suivante , référencé ultérieurement dans la même requête, généralement en tant que jointure ultérieure. Les colonnes de résultats de la première fonction de table sont transmises en entrée (une ligne à la fois) à la deuxième fonction de table, qui renvoie ses colonnes d'ensemble de résultats dans l'ensemble de résultats de la requête appelante. Les première et deuxième colonnes de l'ensemble de résultats de la fonction de table font désormais partie de l'ensemble de résultats de la requête appelante et sont désormais disponibles en tant qu'entrée (une ligne à la fois) pour une troisième fonction de table. Chaque appel de fonction de table élargit le jeu de résultats de la requête appelante via les colonnes qu'elle renvoie. Cela peut durer jusqu'à ce que vous commenciez à atteindre des limites sur la largeur d'un ensemble de résultats, qui varie probablement d'un moteur de base de données à l'autre.
Considérez cet exemple (qui peut ne pas correspondre aux exigences de syntaxe ou aux capacités de Postgres car je travaille sur DB2). C'est l'un des nombreux modèles de conception dans lesquels j'utilise des fonctions de table, c'est l'un des plus simples, je pense qu'il est très illustratif, et celui qui, je pense, aurait un large attrait si les fonctions de table étaient largement utilisées (à ma connaissance, elles ne le sont pas, mais je pense qu'elles méritent plus d'attention qu'elles n'en reçoivent).
Dans cet exemple, les fonctions de table utilisées sont :VALIDATE_TODAYS_ORDER_BATCH, POST_TODAYS_ORDER_BATCH et DATA_WAREHOUSE_TODAYS_ORDER_BATCH. Sur la version DB2 sur laquelle je travaille, vous encapsulez la fonction de table dans "TABLE (placez l'appel de la fonction de table et les paramètres ici)", mais d'après un examen rapide d'un manuel Postgres, il semble que vous omettez l'encapsuleur "TABLE()".
create table TODAYS_ORDER_PROCESSING_EXCEPTIONS as (
select TODAYS_ORDER_BATCH.*
,VALIDATION_RESULT.ROW_VALID
,POST_RESULT.ROW_POSTED
,WAREHOUSE_RESULT.ROW_WAREHOUSED
from TODAYS_ORDER_BATCH
cross join VALIDATE_TODAYS_ORDER_BATCH ( ORDER_NUMBER, [either pass the remainder of the order columns or fetch them in the function] )
as VALIDATION_RESULT ( ROW_VALID ) --example: 1/0 true/false Boolean returned
left join POST_TODAYS_ORDER_BATCH ( ORDER_NUMBER, [either pass the remainder of the order columns or fetch them in the function] )
as POST_RESULT ( ROW_POSTED ) --example: 1/0 true/false Boolean returned
on ROW_VALIDATED = '1'
left join DATA_WAREHOUSE_TODAYS_ORDER_BATCH ( ORDER_NUMBER, [either pass the remainder of the order columns or fetch them in the function] )
as WAREHOUSE_RESULT ( ROW_WAREHOUSED ) --example: 1/0 true/false Boolean returned
on ROW_POSTED = '1'
where coalesce( ROW_VALID, '0' ) = '0' --Capture only exceptions and unprocessed work.
or coalesce( ROW_POSTED, '0' ) = '0' --Or, you can flip the logic to capture only successful rows.
or coalesce( ROW_WAREHOUSED, '0' ) = '0'
) with data
- Si la table TODAYS_ORDER_BATCH contient 1 000 000 lignes, alors VALIDATE_TODAYS_ORDER_BATCH sera appelé 1 000 000 fois, une fois pour chaque ligne.
- Si 900 000 lignes réussissent la validation dans VALIDATE_TODAYS_ORDER_BATCH, alors POST_TODAYS_ORDER_BATCH sera appelé 900 000 fois.
- Si seulement 850 000 lignes sont publiées avec succès, alors VALIDATE_TODAYS_ORDER_BATCH a besoin de certaines failles fermées LOL, et DATA_WAREHOUSE_TODAYS_ORDER_BATCH sera appelé 850 000 fois.
- Si 850 000 lignes ont réussi à entrer dans l'entrepôt de données (c'est-à-dire qu'aucune exception supplémentaire n'a été générée), la table TODAYS_ORDER_PROCESSING_EXCEPTIONS sera renseignée avec 1 000 000 - 850 000 =150 000 lignes d'exception.
Les appels de fonction de table dans cet exemple ne renvoient qu'une seule colonne, mais ils peuvent renvoyer plusieurs colonnes. Par exemple, la fonction de table validant une ligne de commande peut renvoyer la raison pour laquelle une commande a échoué à la validation.
Dans cette conception, pratiquement tout le bavardage entre un HLL et la base de données est éliminé, puisque le demandeur HLL demande à la base de données de traiter l'ensemble du lot en UNE seule demande. Cela se traduit par une réduction de millions de requêtes SQL à la base de données, par une ÉNORME suppression de millions d'appels de procédure ou de méthode HLL et, par conséquent, fournit une ÉNORME amélioration de l'exécution. En revanche, le code hérité qui traite souvent une seule ligne à la fois, enverrait généralement 1 000 000 requêtes SQL de récupération, 1 pour chaque ligne dans TODAYS_ORDER_BATCH, plus au moins 1 000 000 requêtes HLL et/ou SQL à des fins de validation, plus au moins 1 000 000 requêtes HLL et /ou requêtes SQL à des fins d'affichage, plus 1 000 000 de requêtes HLL et/ou SQL pour envoyer la commande à l'entrepôt de données. Certes, en utilisant cette conception de fonction de table, à l'intérieur des fonctions de table, des requêtes SQL sont envoyées à la base de données, mais lorsque la base de données s'adresse à elle-même (c'est-à-dire depuis l'intérieur d'une fonction de table), les requêtes SQL sont traitées beaucoup plus rapidement (en particulier par rapport à un scénario hérité où le demandeur HLL effectue un traitement de ligne unique à partir d'un système distant, avec le pire des cas sur un WAN - OMG, veuillez ne pas le faire).
Vous pouvez facilement rencontrer des problèmes de performances si vous utilisez une fonction de table pour "récupérer un jeu de résultats", puis joignez ce jeu de résultats à d'autres tables. Dans ce cas, l'optimiseur SQL ne peut pas prédire quel ensemble de lignes sera renvoyé par la fonction de table et, par conséquent, il ne peut pas optimiser la jointure aux tables suivantes. Pour cette raison, je les utilise rarement pour récupérer un ensemble de résultats, à moins que je ne sache que cet ensemble de résultats comportera un très petit nombre de lignes, ne causant donc pas de problème de performances, ou je n'ai pas besoin de joindre les tables suivantes.
À mon avis, l'une des raisons pour lesquelles les fonctions de table sont sous-utilisées est qu'elles sont souvent perçues comme un simple outil pour récupérer un ensemble de résultats, qui fonctionne souvent mal, de sorte qu'elles sont considérées comme un outil "médiocre" à utiliser.
Les fonctions de table sont extrêmement utiles pour pousser plus de fonctionnalités vers le serveur, pour éliminer la plupart des bavardages entre le serveur de base de données et les programmes sur les systèmes distants, et même pour éliminer les bavardages entre le serveur de base de données et les programmes externes sur le même serveur. Même le bavardage entre les programmes sur le même serveur entraîne plus de surcharge que beaucoup de gens ne le pensent, et une grande partie est inutile. Le cœur de la puissance des fonctions de table réside dans leur utilisation pour effectuer des actions à l'intérieur du traitement de l'ensemble de résultats.
Il existe des modèles de conception plus avancés pour l'utilisation des fonctions de table qui s'appuient sur le modèle ci-dessus, où vous pouvez optimiser encore plus le traitement de l'ensemble de résultats, mais ce message est déjà beaucoup à absorber pour la plupart.