2017-09-15 14 views
0

J'ai des tables qui contiennent le même type de données pour chaque année, mais les données collectées varient légèrement en ce qu'elles n'ont pas les mêmes champs.Essayer de créer des chaînes de requête dynamiques avec PL/PgSQL pour créer des fonctions DRY dans PostgreSQL 9.6

d_abc_2016 
d_def_2016 
d_ghi_2016 
d_jkl_2016 

Il y a certaines constantes pour chaque table: company_id, employee_id, salary. Cependant, chacun peut ou non avoir ces champs qui sont utilisés pour calculer les incitations totales: bonus, commission, cash_incentives Il y en a beaucoup plus, mais juste en les utilisant comme exemple. Tous numeric

Je dois souligner à ce stade, les utilisateurs ont seulement la possibilité d'exécuter SELECT déclarations.

Ce que je voudrais être en mesure de faire est la suivante:

  1. donner à l'utilisateur la possibilité d'appeler en SELECT et préciser leurs propres champs, en plus de l'appel
  2. passer le nom de la table utilisée dans la fonction à utiliser en logique conditionnelle pour déterminer comment la chaîne de requête doit être construite pour le calcul total_incentives en plus de passer la table entière afin qu'une tonne d'arguments ne doive pas être passée dans la fonction

Fondamentalement ceci:

SELECT employee_id, salary, total_incentives(t, 'd_abc_2016') 
FROM d_abc_2016 t; 

Ainsi, la fonction appelée calculera total_incentives qui est numeric pour cette employee_id et montrent aussi leur salary. Mais l'utilisateur peut choisir d'ajouter d'autres champs à regarder.

Pour la fonction, parce que les champs utilisés dans la fonction total_incentives varient d'une table à l'autre, j'ai besoin de créer une logique pour construire la chaîne de requête dynamiquement.

CREATE OR REPLACE FUNCTION total_incentives(ANYELEMENT, t text) 
    RETURNS numeric AS 
$$ 
DECLARE 
    -- table name lower case in case user typed wrong 
    tbl   varchar(255) := lower($2; 

    -- parse out the table code to use in conditional logic 
    tbl_code  varchar(255) := split_part(survey, '_', 2); 

    -- the starting point if the query string 
    base_calc varchar(255) := 'salary + ' 

    -- query string 
    query_string varchar(255); 

    -- have to declare this to put computation INTO 
    total_incentives_calc numeric; 
BEGIN 
    IF tbl_code = 'abc' THEN 
     query_string := base_calc || 'bonus'; 
    ELSIF tbl_code = 'def' THEN 
     query_string := base_calc || 'bonus + commission'; 
    ELSIF tbl_code = 'ghi' THEN 
     -- etc... 
    END IF; 

    EXECUTE format('SELECT $1 FROM %I', tbl) 
    INTO total_incentives_calc 
    USING query_string; 

    RETURN total_incentives_calc; 
END; 
$$ 
LANGUAGE plpgsql; 

Il en résulte une:

ERROR: invalid input syntax for type numeric: "salary + bonus" 
CONTEXT: PL/pgSQL function total_incentives(anyelement,text) line 16 at EXECUTE 

Comme il devrait être de retour un ensemble de valeurs numeric. Remplacez-le par le suivant:

CREATE OR REPLACE FUNCTION total_incentives(ANYELEMENT, t text) 
    RETURNS SETOF numeric AS 
$$ 
... 
    RETURN; 

Obtenez la même erreur.

Figure bien, c'est peut-être une table qu'il essaie de retourner.

CREATE OR REPLACE FUNCTION total_incentives(ANYELEMENT, t text) 
    RETURNS TABLE(tot_inc numeric) AS 
$$ 
... 

Obtenez la même erreur. Vraiment, toute variation produit ce résultat. Donc vraiment pas sûr de savoir comment faire fonctionner ça.

Regardez RESULT QUERY ou .

https://www.postgresql.org/docs/9.6/static/plpgsql-control-structures.html

RESULT QUERY ne fonctionnera pas, car il faut une requête codée en dur de ce que je peux dire, qui ne prendra pas des variables.

RESULT NEXTRESULT NEXT itère à travers chaque enregistrement, ce qui, je ne pense pas, sera adapté à mes besoins et semble être très lent ... et il faut une requête codée dur de ce que je peux dire.

RESULT QUERY EXECUTE semble prometteur.

-- EXECUTE format('SELECT $1 FROM %I', tbl) 
-- INTO total_incentives_calc 
-- USING query_string; 

RETURN QUERY 
    EXECUTE format('SELECT $1 FROM %I', tbl) 
    USING query_string; 

et obtenez:

ERROR: structure of query does not match function result type 
DETAIL: Returned type character varying does not match expected type numeric in column 1. 
CONTEXT: PL/pgSQL function total_incentives(anyelement,text) line 20 at RETURN QUERY 

Il devrait être de retour numeric. Enfin, je peux obtenir ceci pour fonctionner, mais ce ne sera pas DRY. Je préfère ne pas faire un tas de fonctions séparées pour chaque table avec un code duplicatif. La plupart des exemples de travail que j'ai vu que toute requête dans la fonction et sont appelés comme tels:

SELECT total_incentives(d_abc_2016, 'd_abc_2016'); 

donc des colonnes supplémentaires devraient être spécifiées dans la fonction:

EXECUTE format('SELECT employee_id...) 

Compte tenu les utilisateurs ne seront en mesure d'exécuter SELECT dans la requête ce n'est vraiment pas une option. Ils doivent spécifier toutes les colonnes supplémentaires qu'ils souhaitent voir dans une requête.

J'ai posté une question similaire mais on m'a dit que ce n'était pas clair, alors j'espère que cette version plus longue expliquera plus clairement ce que j'essaie de faire.

Répondre

1

Les noms de colonnes et les noms de tables ne doivent pas être utilisés comme paramètres de requête transmis par la clause USING.

Probablement lignes:

RETURN QUERY 
    EXECUTE format('SELECT $1 FROM %I', tbl) 
    USING query_string; 

devrait être:

RETURN QUERY 
    EXECUTE format('SELECT %s FROM %I', query_string, tbl); 

Cette affaire est par exemple pourquoi principe trop sec est parfois problématique. Si vous l'écrivez directement, votre code sera plus simple, plus propre et probablement plus court. Dynamic SQL est l'un de la dernière solution - pas d'abord. N'utilisez le SQL dynamique que lorsque votre code sera significativement plus court avec SQL dynamique qu'avec SQL dynamique.

+0

Cool, la requête a été exécutée. Merci pour l'aide! Maintenant, pour trier et l'empêcher de multiplier la quantité d'enregistrements retournés par le nombre total d'enregistrements. – sockpuppet