質問には実際には2つの部分があります。1 つは、テーブルのデータを正しく配置することに対応します。もう 1 つは、数え切れないほどの日付形式の処理です。
ここでは、2 つの SQL 関数 begin_time() と end_time() を想定します。以下でそれらについて説明します。
データを整列するには、テーブルをそれ自体で 2 回左結合します。
select t.col1, t.col2, t.col3, t.col4,
parse_begin_time(bt.col4) as timeBegin,
parse_end_time(et.col4) as timeEnd
from yourtable t
left join yourtable as bt on begin_t.col2 = t.col1 and bt.col3 = 'timeBegin'
left join yourtable as et on end_t.col2 = t.col1 and et.col3 = 'timeEnd'
where t.col3 not in ('timeBegin', 'timeEnd');
複数のエントリが存在するために複数のエントリが生成される場合は、集計を使用します。
select t.col1, t.col2, t.col3, t.col4,
min(parse_begin_time(bt.col4)) as timeBegin,
max(parse_end_time(et.col4)) as timeEnd
from yourtable t
left join yourtable as bt on begin_t.col2 = t.col1 and bt.col3 = 'timeBegin'
left join yourtable as et on end_t.col2 = t.col1 and et.col3 = 'timeEnd'
where t.col3 not in ('timeBegin', 'timeEnd')
group by t.col1, t.col2, t.col3, t.col4;
注: 大量のデータがある場合、上記は特にうまく機能しないことが予想されます。create table as ... ステートメントでそれらを 1 回実行し、元のスキーマを削除するか、将来使用するためにマテリアライズド ビューを作成します。
次に、乱雑な timeBegin フィールドと timeEnd フィールドの書式設定について心配する必要があります。これらは、テキスト フィールドに格納されていると想定しています。次のようになります。
create or replace function parse_begin_time(text) returns date as $$
declare
_input text := $1;
_output text;
_bc boolean := false;
_y text;
_m text;
_d text;
_tmp text;
_i int;
begin
_input := trim(both from _input);
-- PG is fine with '200-01-01 BC' as a date, but not with '-200-01-01'
if left(_input, 1) = '-'
then
_bc := true;
_input := right(_input, -1);
end if;
-- Extract year, month and day
_tmp := _input;
_i := position(_tmp for '-');
_y := substring(_tmp from 1 for i - 1);
_tmp := substring(_tmp from i);
_i := position(_tmp for '-');
_m := substring(_tmp from 1 for i - 1);
_tmp := substring(_tmp from i);
_i := position(_tmp for '-');
_d := substring(_tmp from 1 for i - 1);
if _tmp <> '' or left(trim(left '0' from _y), 1) = 'X'
then
raise exception 'invalid date input: %', _input;
end if;
-- Prevent locale-specific text to date conversion issues with one or two digit years
-- e.g. rewrite 1-2-3 as 0001-02-03.
if length(_y) < 4
then
_y := lpad(_y, 4, '0');
end if;
if length(_m) < 2
then
_m := lpad(_m, 2, '0');
end if;
if length(_d) < 2
then
_d := lpad(_m, 2, '0');
end if;
-- Process year, month, day
-- Add suitable logic here per your specs, using string and date functions
-- http://www.postgresql.org/docs/current/static/functions-string.html
-- http://www.postgresql.org/docs/current/static/functions-formatting.html
-- http://www.postgresql.org/docs/current/static/functions-datetime.html
-- for end-of-months, use the built-in arithmetics, e.g.:
-- _date := _date + interval '1 month' - interval '1 day'
-- Build _output
_output := _y || '-' || _m || '-' || _d;
if _bc
_output := _output || ' BC';
end if;
return _output::date;
end;
$$ language plpgsql strict stable;
言語に慣れている場合は、代わりに plpython または plpythonu を使用できます。あなたは私よりもこれらの 2 つについてよく知っていると思います。また、必要なコードを作成するのに十分な Python の知識があることも確かです。ローレンスのコードは、plpgsql に何かを入れたい場合のもう 1 つの良い出発点です。
このstrict
ステートメントは、Postgres に、null 入力でわざわざ関数を呼び出さず、すぐに null を返すように指示します。おそらく end_time 関数には必要ありません。