2017-11-10 11 views
1

私のPostgresデータベースに、データの埋込み率を決定しようとしている表があります(つまり、データの頻度/ isnを理解しようとしています)。忘れる)。私は、(私が選択した数十の列のリスト内の)各列に対して、NULL以外の値を持つ列の数と割合を数える関数を作成する必要があります。PL/pgSQL関数でカウントを取得するためにカラム名を反復する

問題は、プログラマチックな方法で列のリストを反復する方法がわかりません。列の名前から列を参照する方法がわからないためです。 the EXECUTE commandを使用して動的に作成されたSQLを実行する方法については読んだことがありますが、動作させることができませんでした。

この関数は、書かれているように、すべてのフィールドを100%の塗りつぶし率で返しますが、これは偽であることがわかります。この機能を動作させるにはどうすればよいですか?

答えて

0

私は自分の質問を書いた後、私が提出する前に解決策を見つけました。私は既に質問を書いているので、答えを分かち合いましょう。問題は私のEXECUTEステートメントで、具体的にはUSING field_nameビットです。私はそれが文字列リテラルとして扱われるようになったと思います。それは、クエリが評価していたことを意味します。"a string literal" IS NOT NULLはもちろん、常に真です。

列名をパラメータ化する代わりに、クエリ文字列に直接挿入する必要があります。そこでEXECUTE行を次のように変更しました:

EXECUTE 'SELECT COUNT(*) FROM my_table WHERE ' || field_name || ' IS NOT NULL' INTO fill_count; 
+0

たぶん良いアイデアは、識別子、ワイルドカードでのフォーマットを使用しています。それはSQLインジェクションを避けるでしょう。 –

0

私はあなたがすでにそれを解決したことを知っています。しかし、私はあなたの代わりに、識別子、ワイルドカードでformatを使用することができ、動的なクエリに識別子を連結避けるために、あなたを示唆してみましょう:

CREATE OR REPLACE FUNCTION get_fill_rates() RETURNS TABLE (field_name text, fill_count integer, fill_percentage float) AS $$ 
DECLARE 
    fields text[] := array['column_a', 'column_b', 'column_c']; 
    table_name name := 'my_table'; 
    total_rows integer; 
BEGIN 
    SELECT reltuples INTO total_rows FROM pg_class WHERE relname = table_name; 

    FOREACH field_name IN ARRAY fields 
    LOOP 
     EXECUTE format('SELECT COUNT(*) FROM %I WHERE %I IS NOT NULL', table_name, field_name) INTO fill_count; 
     fill_percentage := fill_count::float/total_rows::float; 
     RETURN NEXT; 
    END LOOP; 
END; 
$$ LANGUAGE plpgsql; 

この方法を行うと、あなたは、SQLインジェクション攻撃を防ぐのに役立ちますし、クエリがオーバーヘッドビットを解析減少します。詳細情報here

0

脇のコードのいくつかの問題(下記参照)、これはプレーンなクエリで、より速く、より簡単なテーブルオーバー単一のスキャンと実質的に使用できます

SELECT v.* 
FROM (
    SELECT count(column_a) AS ct_column_a 
     , count(column_b) AS ct_column_b 
     , count(column_c) AS ct_column_c 
     , count(*)::numeric AS ct 
    FROM my_table 
    ) sub 
    , LATERAL (
     VALUES 
     (text 'column_a', ct_column_a, round(ct_column_a/ct, 3)) 
     , (text 'column_b', ct_column_b, round(ct_column_b/ct, 3)) 
     , (text 'column_c', ct_column_c, round(ct_column_c/ct, 3)) 
    ) v(field_name, fill_count, fill_percentage); 

決定的な「トリック」がここにありますそのcount()は、最初に0以外の値をカウントするだけで、トリックは必要ありません。

パーセントを3桁の小数点以下に丸めました。これはオプションです。このため私はnumericにキャストします。

VALUES式を使用して、結果をピボット解除し、フィールドごとに1行を取得します。

繰り返し使用する場合や処理する列が長い場合は、クエリを動的に生成して実行することができます。しかし、再び、各列に対して個別のカウントを実行しないでください。ただ、動的上記のクエリを構築する:

CREATE OR REPLACE FUNCTION get_fill_rates(tbl regclass, fields text[]) 
    RETURNS TABLE (field_name text, fill_count bigint, fill_percentage numeric) AS 
$func$ 
BEGIN 
RETURN QUERY EXECUTE (
-- RAISE NOTICE '%', ( -- to debug if needed 
    SELECT 
    'SELECT v.* 
     FROM (
     SELECT count(*)::numeric AS ct 
       , ' || string_agg(format('count(%I) AS %I', fld, 'ct_' || fld), ', ') || ' 
     FROM ' || tbl || ' 
     ) sub 
      , LATERAL (
      VALUES 
       (text ' || string_agg(format('%L, %2$I, round(%2$I/ ct, 3))', fld, 'ct_' || fld), ', (') || ' 
     ) v(field_name, fill_count, fill_pct) 
     ORDER BY v.fill_count DESC' 
    FROM unnest(fields) fld 
    ); 
END 
$func$ LANGUAGE plpgsql; 

コール:あなたが見ることができるように

SELECT * FROM get_fill_rates('my_table', '{column_a, column_b, column_c}'); 

が、これは今任意の与えられたテーブルと列のリストの作品を。
すべての識別子は、format()またはregclassタイプの組み込み機能によって自動的に正しく引用されます。

関連:


あなたの元のクエリを改善することができリクこれは豚の口紅です。 この非効率的なアプローチを使用しないでください。

CREATE OR REPLACE FUNCTION get_fill_rates() 
    RETURNS TABLE (field_name text, fill_count bigint, fill_percentage float) AS 
$$ 
DECLARE 
    fields text[] := '{column_a, column_b, column_c}'; -- must be legal identifiers! 
    total_rows float; -- use float right away 
BEGIN 
    SELECT reltuples INTO total_rows FROM pg_class WHERE relname = 'my_table'; 

    FOREACH field_name IN ARRAY fields -- use FOREACH 
    LOOP 
     EXECUTE 'SELECT COUNT(*) FROM big WHERE ' || field_name || ' IS NOT NULL' 
     INTO fill_count; 
     fill_percentage := fill_count/total_rows; -- already type float 
     RETURN NEXT; 
    END LOOP; 
END 
$$ LANGUAGE plpgsql; 

プラス、pg_class.reltuplesはあくまでも目安です。あなたはとにかく数えているので、実際の数を使用してください。

関連:

関連する問題