web-dev-qa-db-ja.com

SELECTクエリでフィールド名としてテーブル列の値を使用するにはどうすればよいですか?

シナリオ:

testtableという名前のTable1のフィールド名

iD、名前、サイズ、幅、高さ

エラーテーブルと名付けられたTable2のフィールド名

id、desc、field1、field2、operator

エラーテーブルの値

+----+-------------------------------------+--------+--------+----------+
| id |                desc                 | field1 | field2 | operator |
+----+-------------------------------------+--------+--------+----------+
|  1 | size should not greater than width  | size   | width  | >        |
|  2 | size should not greater than height | size   | height | >        |
|  3 | with should be equal to height      | width  | height | <>       |
+----+-------------------------------------+--------+--------+----------+

testtableから確認したい:

  1. サイズ>幅であるすべてのレコードをカウントします。
  2. サイズ>高さのすべてのレコードをカウントします。
  3. 幅<>高さのすべてのレコードをカウントします。

必要な出力

+-------------------------------------+-------+
|              errorname              | count |
+-------------------------------------+-------+
| size should not greater than width  |     6 |
| size should not greater than height |     2 |
| with should be equal to height      |     3 |
+-------------------------------------+-------+

このようにすることは可能ですか?

現在のクエリ:

select desc,
  (select count(*) as "Total Errors" 
   from testtable 
   where errortable.field1 errortable.operator errortable.field2 ) 
from errortable group by id;
2
pakistanimoon

前提と説明

  • Postgres 9.4
  • 同じ特定のテーブルからさまざまなカウントが必要です。

desc SQLの予約語です。識別子として使用しないでください。代わりにdescrを使用します。

1.基本的なクエリ

同じテーブルから複数の部分カウントを取得する最も速くて最もエレガントな方法は、SELECT句を使用して複数の集約関数を含む単一のFILTERステートメントです。

次のようなステートメント:

_SELECT count(*) FILTER (WHERE size > width)    AS ct1
     , count(*) FILTER (WHERE size > height)   AS ct2
     , count(*) FILTER (WHERE width <> height) AS ct3
FROM   testtable;
_

または、Postgres 9.3の場合:

_SELECT count(size > width OR NULL)    AS ct1
     , ...
FROM   testtable;
_

2.高度なクエリ

希望する形式で結果を取得するには、VALUES結合でLATERAL式を使用してカウンターピボットし、プロセスにIDと説明_id, descr_を追加します。

_SELECT x.*
FROM  (
   SELECT count(*) FILTER (WHERE size > width)    AS ct1
        , count(*) FILTER (WHERE size > height)   AS ct2
        , count(*) FILTER (WHERE width <> height) AS ct3
   FROM   testtable
   ) t
, LATERAL (
   VALUES (1, 'size should not greater than width' , ct1)
        , (2, 'size should not greater than height', ct2)
        , (3, 'with should be equal to height'     , ct3)
   ) x(id, descr, ct);_

太字ステートメントは、ステートメントを動的に作成するときにerrortableから取得されます。

3.動的クエリ

上記のステートメントをerrortableで提供された値から動的に連結するには:

_CREATE OR REPLACE FUNCTION
SELECT 'SELECT x.* FROM (SELECT '
    || string_agg(
         format('count(*) FILTER (WHERE %I %s %I) AS c%s'
              , field1, operator, field2, id)
       , ', ')
    || ' FROM testtable) t, LATERAL (VALUES ('
    || string_agg(format('%s, %L, c%s', id, descr, id), '), (')
    || ')) x(id, dscr, ct)'
FROM   errortable;
_

format() with _%I_は、必要に応じて列名を引用し、ステートメントをSQLインジェクションに対して安全にします-operator、そのまま連結されます。 OPERATOR()構文 ...を使用して、それを安全にすることもできます。

信頼できないユーザーにerrortableへの書き込みアクセス権がない場合は、そのコンテンツを制御でき、心配する必要はありません。

4.完全自動化

戻り値の型は均一でよく知られているので、関数にカプセル化できます。

_CREATE OR REPLACE FUNCTION f_error_count()
  RETURNS TABLE (id int, descr text, ct bigint) AS
$func$
BEGIN

RETURN QUERY EXECUTE (
   SELECT format('SELECT x.* FROM (SELECT %s FROM testtable) t
                        , LATERAL (VALUES (%s)) x(id, dscr, ct)'
               , string_agg(format('count(*) FILTER (WHERE %I %s %I) AS c%s'
                                 , e.field1, e.operator, e.field2, e.id), ', ')
               , string_agg(format('%s, %L, c%s', e.id, e.descr, e.id), '), ('))
   FROM   errortable e
   );

END
$func$ LANGUAGE plpgsql;
_

コール:

_SELECT * FROM f_error_count();
_

外側のformat()を使用して、連結をもう1ステップ簡略化します。また、競合を回避するために、すべての列がテーブル修飾されていることに注意してください。
ここでも、動的SQLが関係しているため、SQLインジェクションのために悪用されないようにしてください。

SQL Fiddle Postgres 9.3の場合、9.4は使用できません。

5