2017-06-23 1 views
1

J'essaie de trouver une méthode pour retourner un ensemble d'enregistrements avec un nombre dynamique de colonnes. Je peux écrire une requête qui produira la liste des noms de colonnes que j'ai besoin en tant que tel:Requête PostgreSQL avec nombre dynamique de colonnes

SELECT DISTINCT name FROM tests WHERE group = 'basic'; 

Ceci renvoie une courte liste comme « piquez », « prod », « frappé », « drop », etc. Ensuite, je veux une table produite montrant une série de tests où chacun de ces tests ont été exécutés. Chaque matin, nous regardons ce que les développeurs ont fait et poussez et aiguillons afin que chaque test soit effectué pour chaque jour. Cette requête, je peux écrire statiquement:

SELECT (SELECT success FROM test_results AS i 
     WHERE i.name = 'poke' 
     AND i.date = o.date) AS 'poke', 
     (SELECT success FROM test_results AS i 
     WHERE i.name = 'prod' 
     AND i.date = o.date) AS 'prod', 
... 
FROM test_results AS o GROUP BY date 
HAVING date > now() - '1 week'::interval; 

Cependant, ceci est codé en dur pour les tests que nous courons chaque jour. Si nous devons maintenant commencer à envoyer l'appareil chaque jour, nous devons mettre à jour la requête. Si nous décidons que le test de chute n'est plus nécessaire, après une semaine, la colonne de test de chute doit déposer le rapport car il ne se produit plus dans les résultats. Renvoyer la valeur NULL pour les tests manquants lorsque seules certaines dates ont une entrée de résultat est parfaitement acceptable.

Existe-t-il une méthode pour créer une liste dynamique de colonnes à partir des résultats en utilisant simplement du SQL normal dans une requête?

Je tentais de générer les données dont j'avais besoin en partie en utilisant une requête WITH, mais je n'arrive pas à trouver un moyen de construire correctement la dernière ligne à partir d'informations dynamiques.

Edit: est ici quelques exemples de données des deux derniers jours:

CREATE TABLE test_results (
    name TEXT NOT NULL, 
    date DATE default now() NOT NULL, 
    success BOOLEAN NOT NULL 
); 

INSERT INTO test_results (name, date, success) VALUES ('hit', '2017-06-20', TRUE); 
INSERT INTO test_results (name, date, success) VALUES ('poke', '2017-06-20', TRUE); 
INSERT INTO test_results (name, date, success) VALUES ('prod', '2017-06-20', TRUE); 

INSERT INTO test_results (name, date, success) VALUES ('poke', '2017-06-21', TRUE); 
INSERT INTO test_results (name, date, success) VALUES ('prod', '2017-06-21', TRUE); 

INSERT INTO test_results (name, date, success) VALUES ('poke', '2017-06-22', TRUE); 
INSERT INTO test_results (name, date, success) VALUES ('prod', '2017-06-22', FALSE); 

INSERT INTO test_results (name, date, success) VALUES ('poke', '2017-06-23', TRUE); 
INSERT INTO test_results (name, date, success) VALUES ('prod', '2017-06-23', TRUE); 
INSERT INTO test_results (name, date, success) VALUES ('drop', '2017-06-23', TRUE); 

Si je lance ma requête sur la plage de données de 21/06/2017 à 23/06/2017, je comme pour obtenir des résultats comme celui-ci comprenant une matrice de tous les tests qui ont été exécuté en ce moment:

date  | poke | prod | drop 
------------+--------+--------+----- 
2017-06-21 | TRUE | TRUE | NULL 
2017-06-22 | TRUE | FALSE | NULL 
2017-06-23 | TRUE | TRUE | TRUE 

les noms Poke, prod, déposer tous les noms ont été trouvés dans le champ nom d'une ligne au cours de cette période . NULL est renvoyé pour la requête détaillée pour tous les tests qui n'ont pas d'enregistrement pour cette date.

+1

Modifier votre question et de fournir des exemples de données et les résultats souhaités. –

+0

Vous devez construire votre instruction dynamiquement dans une procédure, puis l'exécuter. J'ai fait des choses comme ça dans le passé, mais il faudrait que je consulte mon disque. Si personne ne vous donne la bonne réponse, je la regarderai demain. –

Répondre

1

Il existe différentes méthodes utilisées, dont certaines sont déjà mentionnées ici, comme le tableau croisé. En outre, vous pouvez créer une propre fonction qui génère dynamiquement la requête et renvoie TABLE et quelques autres méthodes. Mais tous nécessitent que vous prédéfinissiez un nombre exact de sorties et leurs types de données.

Si je comprends bien votre cas qui est quelque chose que vous ne voudriez pas que vous avez mentionné:

Si nous devons maintenant commencer à avoir tapé dans le dispositif chaque jour, nous avons besoin de mettre à jour la requête.

Ce qui est à peu près le même inconvénient en utilisant le tableau croisé et d'autres moyens.

Il existe donc un moyen d'utiliser Cursors. Ce n'est probablement pas la meilleure façon d'y aller et si vous pouvez utiliser crosstab alors c'est probablement mieux.
Mais au moins c'est une option que j'ajouterai avec des commentaires dans le code.

Solution:

-- Function for opening cursor 
CREATE OR REPLACE 
FUNCTION test_stats(
       c REFCURSOR, -- cursor name 
       sdate date,  -- start date of period wanted (included) 
       edate date,  -- end date of period wanted (included) 
       gtype text  -- you had in your 'tests' table some group type which I included just in case 
      ) 
RETURNS  REFCURSOR 
LANGUAGE PLPGSQL 
AS 
$main$ 
BEGIN 
    OPEN c 
    FOR 
    -- Following dynamic query building can be 
    -- used also if want to go with function that RETURNS TABLE 
    EXECUTE format(
      ' SELECT r.date, 
         %s 
       FROM test_results r 
       WHERE r.date BETWEEN %L AND %L 
       GROUP BY 1 
      ', 
       -- Here we build for each 'name' own statement and 
       -- aggregate together with comma separator to feed 
       -- into main query. 
       -- P.S. We need to double check result unfortunately 
       --  against test_results table once to get pre-filter 
       --  for names in specified date range. 
       --  With this we eliminate tests that for sure will 
       --  not be presented in the range. In given test data 
       --  this means eliminating 'hit'. 
      (
       SELECT string_agg(
          DISTINCT format(
           '( SELECT success 
            FROM test_results i 
            WHERE i.name = %1$L 
            AND  i.date = r.date) AS "%1$s"', 
           t.name 
          ), 
          ',' 
         ) 
       FROM tests t, 
       LATERAL ( SELECT array_agg(DISTINCT r.name) 
          FROM test_results r 
          WHERE r.date BETWEEN sdate AND edate 
         ) a(lst) 
       WHERE t.group = gtype  -- the group type is used here 
       AND  t.name = ANY (a.lst::text[]) 
      ), 
      sdate,  -- start date for between statement 
      edate  -- end date for between statement 
     ); 
    RETURN c; 
END; 
$main$; 

-- Usage example: 
BEGIN; 
SELECT test_stats('teststats1', '2017-06-21'::date, '2017-06-23'::date, 'basic'); 
FETCH ALL IN teststats1; 
COMMIT; 

-- Result (from your given test data set): 
    date | drop | poke | prod 
------------+------+------+------ 
2017-06-22 |  | t | f 
2017-06-21 |  | t | t 
2017-06-23 | t | t | t 
(3 rows) 

Comme je l'ai mentionné, ce n'est pas la meilleure façon, mais il fait le travail :)

+0

Comme la réalisation a maintenant établi que SQL lui-même est un langage de type statique, tout comme C ou Java, ce que je demande n'est pas possible. Je pense en termes de langues dynamiquement-types comme Python ou JavaScript. Alors que diverses fonctions peuvent changer leur signature pour correspondre à leur contexte, en fin de compte, les types sont décidés lors de la compilation (préparation?) De l'instruction SQL d'origine. – penguin359

0

Je décris une requête comme ceci:

SELECT tr.name, tr.date, tr.success 
FROM tests t JOIN 
    test_results tr 
    ON t.testid = tr.testid 
WHERE t.group = 'basic' AND tr.date > now() - '1 week'::interval; 

Vous êtes probablement mieux faire pivoter les données au niveau de l'application.

+0

La raison même pour laquelle j'essaye de faire ceci en SQL est parce que je ne contrôle pas la couche d'application. Il présente simplement les résultats d'une instruction SQL en tant que table/matrice dans l'application. – penguin359

1

Activez l'extension 'tablefunc', puis utilisez la fonction 'crosstab'; voir les documents PG: https://www.postgresql.org/docs/current/static/tablefunc.html. L'argument de la fonction de tableau croisé doit être le texte d'une requête qui produit trois colonnes: la date, le nom du test et le succès du test, dans cet ordre.

+0

Le principal inconvénient de 'crosstab': il est toujours nécessaire de coder en dur la liste des champs dans la requête principale. – Abelisto