web-dev-qa-db-ja.com

PostgreSQLインターバル除算

これは、たとえば postgresqlニュースグループwiki などで数回発生しています。一般的に、異なるintervals間の関係は明確に定義されていない可能性があります。1か月は、考慮されている月(および年)に応じて、異なる日数になる場合があります。ただし、2つの時点の間に発生する間隔の数を計算する必要がある場合もあります。 (簡略化した例):

CREATE TABLE recordings(tstart timestamp, tend timestamp, interval ticklength);

SELECT (tend - tstart) / ticklength AS numticks
FROM recordings;

これはPostgreSQLでは許可されていません。これは、2つの間隔の間の除算であり、上記の理由により一般的な動作が明確に定義されていないためです。 回避策 は、間隔を秒に変換できる場合に存在しますが、これが当てはまらない場合、たとえば間隔がミリ秒のオーダーである場合に最適な方法は何ですか?

3
beldaz

@a_horse_with_no_nameがコメントで言及しているように、除算はミリ秒に変換することで実現できます。

SELECT 
  (1000*EXTRACT(Epoch FROM tend - tstart) 
    + EXTRACT(MILLISECOND FROM tend - tstart))
  / (1000*EXTRACT(Epoch FROM ticklength) 
    + EXTRACT(MILLISECOND FROM ticklength)) AS numticks
FROM recordings;
1
beldaz

リンクする回避策(間隔からエポックを抽出し、それらで割る)は機能しますfine秒とミリ秒。つまり、それのユースケースであれば、それを使用してください。

しかし、より一般的になり、答えが得られないときにNULLを返したい場合は、さらに複雑にする必要があります。

-- An interval consists of a number of years, months, days, hours,
-- minutes, and seconds.  The problem of dividing two intervals lies
-- in the fact that "one month" is not a fixed number of days.
-- Therefore, dividing, say, P1M with P1D would not make sense.

CREATE OR REPLACE FUNCTION interval_months_part(INTERVAL)
        RETURNS INTEGER AS $$
        SELECT EXTRACT(years FROM $1)::INTEGER * 12
                + EXTRACT(months FROM $1)::INTEGER
$$ LANGUAGE SQL IMMUTABLE STRICT;
COMMENT ON FUNCTION interval_months_part(INTERVAL) IS
'Years plus months as a whole number of months';

CREATE OR REPLACE FUNCTION interval_seconds_part(INTERVAL)
        RETURNS DOUBLE PRECISION AS $$
        SELECT EXTRACT(days FROM $1) * 24 * 60 * 60
                + EXTRACT(hours FROM $1) * 60 * 60
                + EXTRACT(mins FROM $1) * 60
                + EXTRACT(secs FROM $1);
$$ LANGUAGE SQL IMMUTABLE STRICT;
COMMENT ON FUNCTION interval_months_part(INTERVAL) IS
'Days, hours, minutes, and seconds as seconds';

-- If we can divide exactly, do so.  Otherwise return NULL, unless
-- fudging is requested, in which case all months are implicitly
-- assumed to be 30 days, (except for 12 months, which become 365.25
-- days), by extracting and dividing the epochs.
CREATE OR REPLACE FUNCTION interval_divide(dividend INTERVAL,
                                                divisor INTERVAL,
                                                fudge BOOL DEFAULT false)
        RETURNS DOUBLE PRECISION AS $$
        SELECT CASE WHEN interval_months_part($1) = 0
                                AND interval_months_part($2) = 0
                        THEN interval_seconds_part($1)
                                / interval_seconds_part($2)
                        WHEN interval_seconds_part($1) = 0
                                AND interval_seconds_part($2) = 0
                        THEN CAST(interval_months_part($1)
                                        AS DOUBLE PRECISION)
                                / interval_months_part($2)
                        WHEN fudge
                                THEN EXTRACT(Epoch FROM $1)
                                        / EXTRACT(Epoch FROM $2)
                        ELSE NULL
                        END;
$$ LANGUAGE SQL IMMUTABLE STRICT;
1
Teddy