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 ...
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.
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á.
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.