web-dev-qa-db-fra.com

Comment décomposer ctid en numéros de page et de ligne?

Chaque ligne d'une table a un colonne systèmectid de type tid qui représente l'emplacement physique de la ligne:

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 --- (ici

Quelle est la meilleure façon d'obtenir uniquement le numéro de page à partir de ctid dans le type le plus approprié (par exemple integer, bigint ou numeric(1000,0))?

Le seule façon dont je peux penser est très moche.

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

Votre violon avec ma solution.

@bma a déjà fait allusion à quelque chose de similaire dans un commentaire. Voici une ...

Justification du type

ctid est de type tid (identificateur de tuple), appelé ItemPointer dans le code C. Par documentation:

Il s'agit du type de données de la colonne système ctid. Un ID de tuple est une paire ( numéro de bloc , Index de tuple dans le bloc ) qui identifie l'emplacement physique de la ligne dans sa table.

Accentuation sur moi. Et:

(ItemPointer, également appelé CTID)

Un bloc fait 8 Ko dans les installations standard. La taille maximale de la table est 32 To . Il s'ensuit logiquement que les numéros de bloc doivent prendre en compte au moins = un maximum de (calcul fixé selon le commentaire de @Daniel):

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

Ce qui rentrerait dans un integer non signé. Après une enquête plus approfondie, j'ai trouvé dans le code source que ...

les blocs sont numérotés séquentiellement, 0 à 0xFFFFFFFE .

Accentuation sur moi. Ce qui confirme le premier calcul:

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

Postgres utilise un entier signé et est donc un peu court. Je n'ai pas encore pu déterminer si la représentation du texte est décalée pour s'adapter à un entier signé. Jusqu'à ce que quelqu'un puisse clarifier cela, je retomberais sur bigint, qui fonctionne dans tous les cas.

Jeter

Il y a pas de cast enregistré pour le type tid dans Postgres 9.3:

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

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

Vous pouvez toujours caster sur text. Il y a représentation textuelle pour tout dans Postgres :

Une autre exception importante est que les "conversions d'E/S automatiques", celles effectuées à l'aide des propres fonctions d'E/S d'un type de données pour convertir vers ou à partir de texte ou d'autres types de chaînes, ne sont pas explicitement représentées dans pg_cast.

La représentation textuelle correspond à celle d'un point, qui se compose de deux float8 nombres, cette distribution est sans perte.

Vous pouvez accéder au premier numéro d'un point avec l'index 0. Cast à bigint. Voilá.

Performance

J'ai effectué un test rapide sur une table avec 30k lignes (le meilleur de 5) sur quelques expressions alternatives qui me sont venues à l'esprit, y compris votre original:

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;

int au lieu de bigint ici, la plupart du temps sans objet aux fins du test. Je n'ai pas répété pour bigint.
Le casting vers t_tid s'appuie sur un type composite défini par l'utilisateur, comme @Jake a commenté.
L'essentiel: le casting a tendance à être plus rapide que la manipulation de cordes. Les expressions régulières coûtent cher. La solution ci-dessus est la plus courte et la plus rapide.

21