2017-06-23 40 views
1

動的な列数のレコードセットを返すメソッドを見つけようとしています。 I Iのような必要なカラム名のリストを生成します1つのクエリ書くことができます。これは「突く」などの短いリストを返します動的な列数のPostgreSQLクエリ

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

を、「PRODは」、「ヒット」、「ドロップ」など次に、それぞれのテストが実行された一連のテストを示す表を作成します。毎朝、開発者が何をしているかを突き止めて調べるので、毎日テストが実行されます。このクエリは静的に書くことができます。

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; 

ただし、これは私たちが毎日実行しているテストにハードコードされています。毎日デバイスを起動する必要がある場合は、クエリを更新する必要があります。落下テストがもう必要なくなったと判断した場合、1週間後に落下テストの列が結果に表示されなくなります。特定の日付のみが結果エントリを持つ場合、テストが見つからない場合にNULLを返すことは完全に受け入れられます。

クエリで正規のSQLを使用するだけで、結果から列の動的リストを作成する方法はありますか?

WITHクエリを使用して必要なデータを構築しようとしましたが、動的情報から最終行を正しく構築する方法が見つかりません。

編集:ここでは、最後の2日間からいくつかのサンプルデータです:

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

私は2017年6月23日に2017年6月21日のデータ範囲に対する私のクエリを実行すると、私は思いますその時に実行されたすべてのテストのマトリックスを含む、以下のような結果を取得したい:

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

名が突く、PRODを、ドロップはすべての名前は、その期間の間、行の名前フィールドで発見されました。その日付のレコードを持たないテストの詳細クエリではNULLが返されます。

+1

編集ご質問や、サンプルデータと望ましい結果を提供します。 –

+0

プロシージャ内でステートメントを動的に構築し、それを実行する必要があります。私は過去にそのようなことをやったことがありますが、私はドライブでそれを調べる必要があります。誰もあなたに正しい答えを与えないなら、私は明日それを見るでしょう。 –

答えて

1

クロス集計のように既に使用されているいくつかの方法があります。また、クエリを動的に構築し、TABLEなどのメソッドを返す独自の関数を作成することもできます。

しかし、すべての出力とデータ型の正確な数を事前に定義する必要があります。

私はあなたが述べたように、あなたが望んでいないと何かであるあなたのケースを理解している場合:私たちは今、毎日のデバイスを蹴っ起動する必要がある場合

を、我々は クエリを更新する必要があります。

クロス集計やその他の方法を使用することは、ほとんど同じ欠点です。

従って、Cursorsを使用する方法があります。おそらく、あなたがcrosstabを使用することができれば、これはおそらく最良の方法ではないでしょう。
少なくともコードでコメントを追加するオプションです。

ソリューション:

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

私が述べたように、それは完璧な方法ではありませんが、それは仕事をしていません:)

+0

実現は、SQL自体が静的型言語CやJavaのように設定されているので、私が求めていることは不可能です。私は、PythonやJavaScriptのような動的型言語に関して考えています。さまざまな関数がその文脈に合致するように署名を変更することができますが、最終的には、元のSQL文をコンパイル(準備)する際に型が決定されます。 – penguin359

0

私はこのようなクエリを絵になります。

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; 

あなたはおそらく、アプリケーションレベルでデータをピボットしたほうが良いです。

+0

私がSQLでこれをやろうとしている理由は、アプリケーション層を制御しないためです。これは、SQL文の結果をアプリケーションのテーブル/マトリックスとして提示するだけです。 – penguin359

1

'tablefunc'拡張機能を有効にしてから、 'crosstab'関数を使用します。 PGドキュメント:https://www.postgresql.org/docs/current/static/tablefunc.htmlを参照してください。クロスタブ関数への引数は、日付、テスト名、テスト成功の順に3つの列を生成するクエリのテキストである必要があります。

+0

'crosstab'の主な欠点:メインクエリのフィールドリストをハードコードする必要があります。 – Abelisto