web-dev-qa-db-ja.com

パラメータ化されたクエリでデータ型間隔の値を渡す

コンテキストは、レストサーバーからPostgres dbに接続しています。

架空の代表的な例を考えます。アカウントの作成日が任意の値より古い/新しい名前のリストを取得できるようにしたいと思います。

以下のクエリ例では、テーブル構造は単純です-nameはタイプtextで、creation_dateはタイプtimestampです。だから私が何かをするとき

server_pg_module:query("select name from new_table where 
current_timestamp - creation_date < '6 days'") 

それはうまくいきます。しかし、私が本当にやりたいことは、サーバーから6 daysの値を取得することです。だから私は何かを試します

server_pg_module:query("select name from new_table where
current_timestamp - timestamp < $1", ["6 days"]

エラーをスローします。私は'6 days'"'6 days'"と他のいくつかの調合を試みましたが、すべてエラーをスローしました。チェックするために、タイプintervalの新しい列intervalを追加し、次のようなクエリを試しました。

server_pg_module:query("insert into new_table (name, interval) values ($1, '3 day')", ["fooo"]). 

うまくいきますが

server_pg_module:query("insert into new_table (name, interval) values ($1, $2)", ["fooo", "3 days"]). 

壊れる。良い目安として、上記の"'3 days'"のような調合に加えて、$2::intervalも試しましたが(これが合法かどうかはわかりません)、機能しません。

したがって、私はそれがparamクエリで間隔を表現することと関係があるか、または私が使用しているモジュールに特有の何かを持っていると信じています。何が問題を引き起こしているのか、そしてこの種のことをどのように行うのかについてのアイデアはいただければ幸いです。または、問題がpgではなくモジュールにあることが絞り込まれている可能性がある場合は、別の場所で対処する必要があります。

Postgresバージョン:10.x

私が使用しているモジュールはpgo(Erlangプログラミング言語用) https://github.com/SpaceTime-IoT/pgo です。 (クエリパラメータとして"2 days"または"'2 days'"を渡すと)表示されるエラーメッセージは次のようになります。

{error,{pgsql_error,#{code => <<"08P01">>,file => <<"pqformat.c">>,
                          line => <<"575">>,
                          message => <<"insufficient data left in message">>,
                          routine => <<"pq_copymsgbytes">>,severity => <<"ERROR">>,
                          {unknown,86} => <<"ERROR">>}}}

また、'2 days'をパラメーターとして渡すと、badargエラーがスローされます。

5
Yogesch

TLDR:以下の「優れたクエリ」の章にスキップしてください。

使用しているモジュールを開示していませんが、問題は明らかに型キャストの1つです。パラメータがtyped値として渡されるように見えますが、textまたはvarchartext-> intervalの暗黙の型キャストはありません。

SELECT castsource::regtype, casttarget::regtype, castcontext
FROM   pg_cast
WHERE  casttarget = 'interval'::regtype;
 castsource | casttarget | castcontext 
:--------------------- | :--------- | :---------- 
 reltime |インターバル| i 
時間帯なしの時間|インターバル| i 
間隔|インターバル|私          

db <> fiddle ここ

私の仮定が正しい場合は、次のようなエラーメッセージが表示されます。

ERROR:  operator does not exist: interval < text

私はあなたの未公開のモジュールが異なるデータ型または型なし文字列リテラルを渡す方法を持っていると確信しています。 Postgresはこの機能を提供します。

あなたも述べます:

私も試しました$2::interval

明示的な型キャストshouldも機能するため、これは奇妙です。

デモ

-- interval typed value
SELECT current_timestamp - timestamp '2018-05-04 18:40' < interval '6 days'; -- works
 | ?カラム? | 
 | :------- | 
 | f | 
--untyped string literal
SELECT current_timestamp - timestamp '2018-05-04 18:40' < '6 days'; -- works
 | ?カラム? | 
 | :------- | 
 | f | 
-- text typed value
SELECT current_timestamp - timestamp '2018-05-04 18:40' < text '6 days'; -- fails!
エラー:演算子が存在しません:間隔<テキスト
行2:... current_timestamp-タイムスタンプ '2018-05-04 18:40' <テキスト '6 ... 
 ^ 
ヒント:指定された名前と引数の型に一致する演算子はありません。明示的な型キャストを追加する必要があるかもしれません。
-- text typed value, with explicit cast
SELECT current_timestamp - timestamp '2018-05-04 18:40' < ('6 days'::text::interval); -- works
 | ?カラム? | 
 | :------- | 
 | f | 

準備されたステートメントについても同様です。

PREPARE fooplan_typed_interval(interval) AS
SELECT current_timestamp - timestamp '2018-05-04 18:40' < $1;

EXECUTE fooplan_typed_interval('6 days');
 | ?カラム? | 
 | :------- | 
 | f | 
PREPARE fooplan_untyped AS
SELECT current_timestamp - timestamp '2018-05-04 18:40' < $1;

EXECUTE fooplan_untyped('6 days');
 | ?カラム? | 
 | :------- | 
 | f | 
PREPARE fooplan_typed_text(text) AS
SELECT current_timestamp - timestamp '2018-05-04 18:40' < $1;

EXECUTE fooplan_typed_text('6 days');  -- only this one fails!
エラー:演算子が存在しません:間隔<テキスト
行3:... ELECT current_timestamp-タイムスタンプ '2018-05-04 18:40' <$ 1; 
 ^ 
ヒント:指定された名前と引数タイプに一致する演算子はありません。明示的な型キャストを追加する必要があるかもしれません。
PREPARE fooplan_typed_text(text) AS
SELECT current_timestamp - timestamp '2018-05-04 18:40' < $1::interval;

EXECUTE fooplan_typed_text('6 days');
 | ?カラム? | 
 | :------- | 
 | f | 

db <> fiddle ここ

優れたクエリ

これらすべてはさておき、どのクエリもインデックスを使用できません(「検索可能」ではありません)。 代わりにこのようなものを使用してください!

server_pg_module:query("select name from new_table
where creation_date > localtimestamp - interval '1 day' * $1", [6])

intervalintegerを乗算できます。
または、型付きパラメーターの受け渡しに関する問題を理解した場合:

server_pg_module:query("select name from new_table
where creation_date > localtimestamp - $1::interval", ['6 days'])

localtimestamp を使用して、「現在時刻」がタイプtimestampの現在のタイムゾーン設定に依存することを指摘します。詳細:

3

同じ目的を達成するための回避策を見つけました。

このアプローチは、2つの日付の差が整数であることを利用しています。タイムスタンプを日付にキャストするのは簡単です。整数値は、サーバーからデータベースへのパラメーター化されたクエリで使用できます。

server_pg_module:query("select name from new_table where 
current_timestamp::date - timestamp::date < $1", [6])
1
Yogesch