ctidをページ番号と行番号に分解するにはどうすればよいですか?


16

テーブルの各行には、行の物理的な場所を表すタイプのシステム列が ctidありますtid

create table t(id serial);
insert into t default values;
insert into t default values;
select ctid
     , id
from t;
ctid | id
:---- | -:
(0,1)| 1
(0,2)| 2

ここに dbfiddle

ctid最も適切なタイプ(例えばintegerbigintまたはnumeric(1000,0))からページ番号だけを取得する最良の方法は何ですか?

私は考えることができる唯一の方法は非常に醜いです。


1
IIRCはベクトル型であり、これらのアクセサメソッドはありません。C関数から実行できるかどうかはわかりません。クレイグは確かに言うでしょう:)
dezso

2
ポイントとしてキャストできますか?例えば。select ct[0], ct[1] from (select ctid::text::point as ct from pg_class where ...) y;
bma

1
タイトルは、ページ番号タプルインデックスの両方の後であることを示しています。後でページ番号に絞り込みます。本体のバージョンを使用しましたが、タプルインデックスは簡単な拡張機能です。
アーウィンブランドステッター

回答:


21
SELECT (ctid::text::point)[0]::bigint AS page_number FROM t;

私の解決策とあなたのフィドル

@bmaはすでにコメントで似たようなことをほのめかしています。がここにあります ...

タイプの根拠

ctidCコードtidで呼び出されるタイプ(タプル識別子)ItemPointerです。ドキュメントごと:

これはシステム列のデータ型ですctid。タプルIDは、テーブル内の行の物理的な場所を識別するペア(ブロック番号ブロック内のタプルインデックス)です。

大胆な強調鉱山。そして:

ItemPointer、別名CTID

標準インストールでは、ブロックは8 KBです。最大テーブルサイズは32 TBです。論理的には、ブロック番号は少なくとも最大(@Danielのコメントに従って修正された計算)に対応する必要があります。

SELECT (2^45 / 2^13)::int      -- = 2^32 = 4294967294

これは符号なしに収まりintegerます。さらに調査すると、ソースコードで次のことがわかりました ...

ブロックには、0〜0xFFFFFFFEの順に番号が付けられます。

大胆な強調鉱山。最初の計算を確認します:

SELECT 'xFFFFFFFE'::bit(32)::int8 -- max page number: 4294967294

Postgresは符号付き整数を使用するため、1ビット短いです。符号付き整数に対応するためにテキスト表現がシフトされるかどうかは、まだ特定できませんでした。誰かがこれをクリアできるまで、私はに戻りbigint、どの場合でも機能します。

キャスト

Postgres 9.3のタイプに登録されているキャストはありませんtid

SELECT *
FROM   pg_cast
WHERE  castsource = 'tid'::regtype
OR     casttarget = 'tid'::regtype;

 castsource | casttarget | castfunc | castcontext | castmethod
------------+------------+----------+-------------+------------
(0 rows)

それでもにキャストできtextます。Postgresにはすべてのテキスト表現があります

もう1つの重要な例外は、「自動I / O変換キャスト」、データ型の独自のI / O関数を使用してテキストまたは他の文字列型との間で変換を実行するものは、で明示的に表されないこと pg_castです。

テキスト表現は、2つのfloat8数字で構成されるポイントの表現と一致し、キャストはロスレスです。

インデックス0でポイントの最初の番号にアクセスできますbigint。にキャストします。ボイラ。

性能

私は、あなたのオリジナルを含め、思いついたいくつかの代替式で、3万行(5のベスト)を持つテーブルで簡単なテストを実行しました。

SELECT (ctid::text::point)[0]::int                              --  25 ms
      ,right(split_part(ctid::text, ',', 1), -1)::int           --  28 ms
      ,ltrim(split_part(ctid::text, ',', 1), '(')::int          --  29 ms
      ,(ctid::text::t_tid).page_number                          --  31 ms
      ,(translate(ctid::text,'()', '{}')::int[])[1]             --  45 ms
      ,(replace(replace(ctid::text,'(','{'),')','}')::int[])[1] --  51 ms
      ,substring(right(ctid::text, -1), '^\d+')::int            --  52 ms
      ,substring(ctid::text, '^\((\d+),')::int                  -- 143 ms
FROM tbl;

intbigintここではなく、テストの目的にはほとんど関係ありません。繰り返しませんでしたbigint。@Jakeがコメントしたように、ユーザー定義の複合型
t_tid基づいてビルドするキャスト。
要点:キャストは文字列操作よりも高速になる傾向があります。正規表現は高価です。上記のソリューションは、最短かつ最速です。


1
有用なもの、アーウィンに感謝します。ここからctidは、ページが4バイト、行が2バイトの6バイトのように見えます。にキャストすることを心配してfloatいましたが、ここであなたが言うことから私は必要がないと思います。それは次のようになり、ユーザー定義の複合はるかに遅いその後、使用しているタイプpoint、あなたもそれを見つけるのですか?
ジャックダグラス

@JackDouglas:さらに調査すると、私はに戻りましたbigint。更新を検討してください。
アーウィンブランドステッター

1
@JackDouglas:複合型へのキャストのアイデアが気に入っています。それはきれいで、非常にうまく機能します-たとえキャストへのキャストpointとバックint8がより速くても)。事前定義された型へのキャストは常に少し速くなります。比較するためにテストに追加しました。私はそれ(page_number bigint, row_number integer)を確認したいと思います。
アーウィンブランドステッター

1
2^40は32 TBではなく1 TBであり2^45、これはで割り2^13ます2^32。したがって、ページ番号には32ビット全体が必要です。
ダニエルヴェリテ

1
また、おそらく注目に値するものであるpg_freespacemapもの用途bigintblkno用
ジャック・ダグラス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.