2011-08-12 4 views
2

Je n'arrive pas à décomposer les requêtes SQL simples. J'utilise PostgreSQL mais ma question est également liée à d'autres SGBDR.Décomposition de requête PostgreSQL

Considérons l'exemple suivant. Nous avons des ordres de table et nous voulons trouver la première commande après dont le montant total a dépassé une certaine limite:

drop table if exists orders cascade; 

/** 
Table with clients' orders 
*/ 
create table orders(
date timestamp, 
amount integer 
/** 
Other columns omitted 
*/ 
); 

/** 
Populate with test data 
*/ 
insert into orders(date,amount) 
values 
('2011-01-01',50), 
('2011-01-02',49), 
('2011-01-03',2), 
('2011-01-04',1000); 

/** 
Selects first order that caused exceeding of limit 
*/ 
create view first_limit_exceed 
as 
select min(date) from 
(
    select o1.date 
    from orders o1, 
     orders o2 
    where o2.date<=o1.date 
    group by o1.date 
    having sum(o2.amount) > 100 
) limit_exceed; 

/** 
returns "2011-01-03 00:00:00" 
*/ 
select * from first_limit_exceed; 

Maintenant, nous allons rendre le problème un peu plus difficile. Considérons que nous voulons trouver le montant total seulement pour les lignes qui satisfont certains prédicats. Nous avons beaucoup de tels prédicats et la création d'une version séparée de la vue first_limit_exceed serait une duplication terrible du code. Nous avons donc besoin d'un moyen de créer une vue paramétrée et de passer un ensemble de lignes filtrées ou de lui-même se prédire. Dans Postgres, nous pouvons utiliser des fonctions de langage de requête en tant que vues paramétrées. Mais Postgres ne permet pas à la fonction d'obtenir comme argument ni ensemble de ligne ni une autre fonction. Je peux toujours utiliser l'interpolation de chaîne du côté client ou de la fonction plpgsql, mais elle est sujette aux erreurs et difficile à tester et à déboguer. Un conseil?

+1

+1 pour l'affichage des scripts de table. Tout ce que fait @dvv! – Quassnoi

+0

peut-être que c'est stupide, mais vous pourriez avoir une vue dont les colonnes étaient les résultats de prédicats (donc chaque type de colonne est booléen). puis ajouter un nouveau prédicat impliquerait (1) d'étendre la vue avec un nouveau prédicat et (2) de changer le code à sélectionner sur le nouveau nom de colonne. Cela maintient la logique de requête assez simple, mais place les prédicats dans sql. –

Répondre

1

En PostgreSQL 8.4 et plus tard:

SELECT * 
FROM (
     SELECT *, 
       SUM(amount) OVER (ORDER BY date) AS psum 
     FROM orders 
     ) q 
WHERE psum > 100 
ORDER BY 
     date 
LIMIT 1 

Ajouter des prédicats que vous voulez dans la requête interne:

SELECT * 
FROM (
     SELECT *, 
       SUM(amount) OVER (ORDER BY date) AS psum 
     FROM orders 
     WHERE date >= '2011-01-03' 
     ) q 
WHERE psum > 100 
ORDER BY 
     date 
LIMIT 1 
+0

Merci pour votre réponse rapide, mais je pense que vous avez mal compris ma question. Ma principale préoccupation était la duplication de code. Considérez que vous avez 10 prédicats. Vous pouvez copier votre requête 10 fois et substituer manuellement le prédicat. Ce n'est pas si grave lorsque vos requêtes sont petites, mais que se passe-t-il si elles sont des centaines de lignes de longueur? Copypasting cent lignes dix fois vous fera coder spagetti inabordable. – dvv

+0

@dvv: ni un ensemble ni un prédicat ne sont des objets de première classe dans 'SQL'. Vous ne pouvez pas les transmettre en tant qu'arguments à une requête statique.Vous voudrez peut-être composer une requête dynamique (qui ajouterait une surcharge d'analyse), cependant, si la maintenabilité est plus importante que la vitesse, ce serait une bonne solution. – Quassnoi

+0

@dvv: Dans 'PostgreSQL', vous pouvez sérialiser des ensembles sous forme de tableaux de tuples de table ou d'enregistrements ou de chaînes définis par l'utilisateur et les transmettre entre les fonctions. Cependant, cela serait encore plus cher que l'analyse des requêtes, aussi, cela nécessiterait que vos ensembles soient dans un format prédéfini. – Quassnoi

-1

Il sonne un peu comme vous essayez de mettre trop de code dans la base de données . Si vous êtes intéressé par les lignes d'une certaine relation qui satisfont un prédicat particulier, exécutez simplement une instruction select avec une clause where appropriée dans le code client. Avoir des vues qui prennent les prédicats comme paramètres réinventent la roue que sql résout déjà bien. D'autre part, je peux voir un argument pour stocker des requêtes elles-mêmes dans la base de données, de sorte qu'ils peuvent être composés dans des rapports plus volumineux. Ce deux est encore mieux géré par le code de l'application. Je pourrais aborder un problème comme celui-là en utilisant une bibliothèque qui est bonne pour la génération SQL dynamique (par exemple sqlalchemy), puis en stockant les représentations de requête (les objets d'expression sqlalchemy sont 'pickleable') comme blobs dans la base de données.

En d'autres termes, les bases de données sont les représentants des faits, vous y stockez des connaissances. Les applications ont le devoir de agissant sur les demandes des utilisateurs, Lorsque vous vous trouvez en train de définir des transformations sur les données, il s'agit plus d'anticiper et de mettre en œuvre les demandes des utilisateurs réels que de préserver fidèlement les connaissances. Il est préférable d'utiliser les vues lorsque le schéma change inévitablement. Vous pouvez donc laisser les applications plus anciennes qui n'ont pas besoin d'être mises au courant du nouveau schéma dans un état opérationnel.