SELECT * FROM t ORDER BY case when _parameter='a' then column_a end, case when _parameter='b' then column_b end
のようなクエリは可能ですが、これは良い方法ですか?
クエリのWHERE部分でparametersを使用し、SELECT部分にいくつかの計算列を含めることは一般的ですが、ORDERをパラメーター化することは一般的ではありませんBY句。
中古車をリストするアプリケーションがあるとします(àCraigsList)。車のリストは、価格または色で並べ替えることができます。特定の量のパラメーター(たとえば、価格範囲、色、並べ替え基準など)を指定すると、結果を含むレコードのセットを返す関数が1つあります。
具体的に説明するために、cars
がすべて次の表にあるとします。
CREATE TABLE cars
(
car_id serial NOT NULL PRIMARY KEY, /* arbitrary anonymous key */
make text NOT NULL, /* unnormalized, for the sake of simplicity */
model text NOT NULL, /* unnormalized, for the sake of simplicity */
year integer, /* may be null, meaning unknown */
euro_price numeric(12,2), /* may be null, meaning seller did not disclose */
colour text /* may be null, meaning unknown */
) ;
テーブルにはほとんどの列のインデックスがあります...
CREATE INDEX cars_colour_idx
ON cars (colour);
CREATE INDEX cars_price_idx
ON cars (price);
/* etc. */
そしていくつかの商品の列挙があります:
CREATE TYPE car_sorting_criteria AS ENUM
('price',
'colour');
...そしていくつかのサンプルデータ
INSERT INTO cars.cars (make, model, year, euro_price, colour)
VALUES
('Ford', 'Mondeo', 1990, 2000.00, 'green'),
('Audi', 'A3', 2005, 2500.00, 'golden Magenta'),
('Seat', 'Ibiza', 2012, 12500.00, 'dark blue'),
('Fiat', 'Punto', 2014, NULL, 'yellow'),
('Fiat', '500', 2010, 7500.00, 'blueish'),
('Toyota', 'Avensis', NULL, 9500.00, 'brown'),
('Lexus', 'CT200h', 2012, 12500.00, 'dark whitish'),
('Lexus', 'NX300h', 2013, 22500.00, NULL) ;
これから行うクエリの種類は次のとおりです。
SELECT
make, model, year, euro_price, colour
FROM
cars.cars
WHERE
euro_price between 7500 and 9500
ORDER BY
colour ;
このスタイルのクエリを関数に含めたいと思います:
CREATE or REPLACE FUNCTION get_car_list
(IN _colour text,
IN _min_price numeric,
IN _max_price numeric,
IN _sorting_criterium car_sorting_criteria)
RETURNS record AS
$BODY$
SELECT
make, model, year, euro_price, colour
FROM
cars
WHERE
euro_price between _min_price and _max_price
AND colour = _colour
ORDER BY
CASE WHEN _sorting_criterium = 'colour' THEN
colour
END,
CASE WHEN _sorting_criterium = 'price' THEN
euro_price
END
$BODY$
LANGUAGE SQL ;
このアプローチの代わりに、この関数のSQLは(PL/pgSQLで)文字列として動的に生成され、実行されます。
どちらのアプローチでも、いくつかの制限、長所、短所を感じることができます。
質問:
「両方の世界を最大限に活用する」方法(可能な場合)? [効率+コンパイラチェック+デバッグが簡単+最適化が簡単]
注:これはPostgreSQL 9.6で実行されます。
まず、前提のあいまいさについて説明します。
クエリのWHERE部分でパラメーターを使用し、SELECT部分にいくつかの計算列を含めることは一般的ですが、ORDER BY句をパラメーター化することは一般的ではありません。
SELECT
部分の計算された列は、クエリプランやパフォーマンスにほとんど関係がありません。しかし、"WHERE
部分]はあいまいです。
準備されたステートメントで機能するWHERE
句でvaluesをパラメーター化するのが一般的です。 (そしてPL/pgSQLは内部的に準備されたステートメントで動作します。)一般的なクエリプランは、提供されたvaluesに関係なく、多くの場合意味があります。つまり、テーブルに非常に不均一なデータ分布がない限り、ただしPostgres 9.2以降、PL/pgSQLはクエリを数回再計画して、一般的な計画が十分であるかどうかをテストします。
しかし、全体をパラメータ化することは一般的ではありません述語(識別子を含む)WHERE
句で使用します。これは、準備されたステートメントでは最初から不可能です。 EXECUTE
を使用した動的SQLが必要であるか、クライアントでクエリ文字列を組み立てます。
動的ORDER BY
式は、両方の中間にあります。あなたはcanをCASE
式で実行しますが、一般的に最適化するのは非常に困難です。 PostgresはプレーンなORDER BY
のインデックスを使用する場合がありますが、最終的な並べ替え順序を隠すCASE
式では使用しません。プランナーは賢いですが、AIではありません。クエリの残りの部分に応じて(ORDER BY
はプランに関連するかどうかにかかわらず、例では関連があります)、最終的にsub-常に最適なクエリプラン。
さらに、CASE
式のマイナーコストを追加します。また、特定の例ではmultiple役に立たないORDER BY
列も含まれます。
通常、EXECUTE
を使用した動的SQLの方が高速または高速です。
関数本体で明確で読み取り可能なコード形式を維持する場合、保守性は問題になりません。
問題の関数はbrokenです。戻り値の型は、匿名のレコードを返すように定義されています。
RETURNS record AS
しかし、クエリは実際にはset ofレコードを返すため、次のようになります。
RETURNS SETOF record AS
しかし、それでもまだ役に立ちません。すべての呼び出しで列定義リストを提供する必要があります。クエリは、既知のタイプの列を返します。それに応じて戻り値の型を宣言してください!私はここで推測している、返された列/式の実際のデータ型を使用します:
RETURNS TABLE (make text, model text, year int, euro_price int, colour text) AS
便宜上、同じ列名を使用しています。 RETURNS TABLE
句の列は、実質的にOUT
パラメータであり、本文のすべてのSQLステートメントで表示されます(EXECUTE
内では表示されません)。したがって、名前の競合の可能性を回避するために、関数本体のクエリで列をテーブル修飾します。デモ関数は次のように機能します。
CREATE or REPLACE FUNCTION get_car_list (
_colour text,
_min_price numeric,
_max_price numeric,
_sorting_criterium car_sorting_criteria)
RETURNS TABLE (make text, model text, year int, euro_price numeric, colour text) AS
$func$
SELECT c.make, c.model, c.year, c.euro_price, c.colour
FROM cars c
WHERE c.euro_price BETWEEN _min_price AND _max_price
AND c.colour = _colour
ORDER BY CASE WHEN _sorting_criterium = 'colour' THEN c.colour END
, CASE WHEN _sorting_criterium = 'price' THEN c.euro_price END;
$func$ LANGUAGE sql;
関数宣言のRETURNS
キーワードをplpgsql RETURN
コマンドと混同しないでください Evanは彼の答えで行いました 。詳細:
some列の述語(さらに悪いことにはrange述語)、_otherORDER BY
の列、これはすでに最適化が困難です。しかし コメントで言及した :
実際の結果セットは、約1.000行程度になる可能性があります(したがって、サーバー側の小さなチャンクでソートおよびページ付けされます)
したがって、これらのクエリにLIMIT
とOFFSET
を追加して、最初にn「最良の」一致を返します。または、よりスマートなページネーション手法:
あなた必要これを高速にするための一致するインデックス。これがORDER BY
のCASE
式でどのように機能するかはわかりません。
考慮してください:
私が提起する3つのポイント、
VIEW
を作成します。ユーザーにWHERE
を使用してVIEW
条件をカスタマイズしてもらいます。関数は、クエリプランナーにとってブラックボックスです。他の関数の中でそれらを使用するのは恐ろしく、インライン化されるのはSQL
だけです。また、動的関数はキャッシュされたプランを取得しません。RETURNS QUERY
ではなくRETURNS QUERY EXECUTE
(またはRETURNS SETOF
)を使用してください。ソートでRETURNS SETOF
を使用する理由はありません。とにかくそれは緩衝されなければならない、アファイク。結果セットがwork_mem
より大きい場合、いずれかで問題が発生します。今後は、完全に任意の順序を処理する PostgREST のようなサービスをラップすることも検討します。
If you care where nulls are sorted, add nullsfirst or nullslast:
GET /people?order=age.nullsfirst
GET /people?order=age.desc.nullslast
この場合、動的SQLを使用します。
動的クエリはそれほど複雑ではなく、より優れた実行計画をもたらす可能性があります。
私の経験から、プランのキャッシュまたは解析時間の短縮による利益は、その隣では無視できます。
私は「静的SQLが機能しない場合にのみ動的SQLを使用する」というルールに従っていました。今日の経験則として、私は通常、次の点で柔軟性が必要なSQLクエリの句に従ってそれを選択します。
確かに、デバッグは少し難しいですが、いくつかの優れたプラクティスを使用すると、ギャップを減らすのに役立ちます。
たとえば、plpgsqlでは、$表記+ REPLACE + RAISE NOTICEを使用できます。
-- Write the entire query using $ for replacements.
-- Don't use || operator.
-- This makes the dynamic query clearer to read and easier to maintain and debug.
v_sql:= 'SELECT <some columns> FROM tbl ORDER BY $sorting$';
v_sql:= REPLACE(v_sql,
'$sorting$',
CASE condition
WHEN value1 THEN 'colX'
WHEN value2 THEN 'colY'
END);
-- Debug the query when running
RAISE NOTICE '%', v_sql;
RETURN QUERY EXECUTE v_sql;
最初の実行で構文エラーをトラップできるはずです。
論理SQLは、静的SQLの場合と同じように簡単/簡単に見つけることができます。
インデックスは、order by句ではそのように使用されません。データベースエンジンは、各行のケース式を計算してからソートする必要があります。また、この手法は動的な結合とフィルタに拡張できません。
私は動的SQL生成で行きます...
あなたはこのようなことをすることができます:
create or replace function fn_test(car_sort_option) returns text as $$
declare
xsql text;
xresult text;
begin
xsql = 'select array_to_json(array_agg(c)) from cars';
if $1 = 'colour' then
xsql = xsql || ' order by colour';
elsif $1 = 'model' then
xsql = xsql || ' order by model';
else
raise exception 'invalid parameter: sort option=% is invalid', $1;
end if;
execute xsql into xresult;
return xresult;
end
$$ language 'plpgsql';
私は通常、PL/pgSQL内のプレゼンテーション層に固有の動的SQLを作成しません。私は通常、このSQLビルドをPHPまたはJavaに残しておきます。しかし、他の多くのことについては、PL/pgSQL内で多くの動的SQLを実行します。主にパーティション化、データベースメンテナンス、ワークフローのために実装とデータ整合性制御。
このポリシーにより、コードがよりクリーンになり、インデックスの使用が改善され、より複雑な動的SQLで使用できることがわかりました。インデックスの使用は私の世界では非常に重要です。なぜなら、私は数十億のレコードを持つデータベースで分析を行っており、高速な応答が必要だからです。
これは、ほとんどの動的SQLが必要なのは、テーブル名または列名が動的である場合のみであるためです。それ以外の場合はすべて、パラメータ化されたクエリで問題ありません。ほとんどのレポートには動的SQLが必要です。並べ替え順序の列を選択する必要があるだけでなく、ほとんどの場合、フィルターと列を動的に含める必要があります。多くの開発者は、プレゼンテーション層のレポートプログラムまたはSQLを生成するバッチでSQLを報告し続けます。その場合、動的SQLもデータベースの外部で生成されます。