Chaque table contient plusieurs colonnes système implicitement définies par le système. De ce fait, leurs noms ne peuvent pas être utilisés comme noms de colonnes utilisateur (ces restrictions sont distinctes de celles sur l'utlisation de mot-clés ; mettre le nom entre guillemets ne permet pas d'échapper à cette règle). Il n'est pas vraiment utile de se préoccuper de ces colonnes, mais au minimum de savoir qu'elles existent.
L'identifiant objet (object ID) d'une ligne. Cette colonne n'est présente que si la table a été créée en précisant WITH OIDS ou si la variable de configuration default_with_oids était activée à ce moment-là. Cette colonne est de type oid (même nom que la colonne) ; voir la Section 8.16, « Types identifiant d'objet » pour obtenir plus d'informations sur ce type.
L' OID de la table contenant la ligne. Cette colonne est particulièrement utile pour les requêtes qui utilisent des hiérarchies d'héritage (voir Section 5.8, « L'héritage »). Il est, en effet, difficile, en son absence, de savoir de quelle table provient une ligne. tableoid peut être joint à la colonne oid de pg_class pour obtenir le nom de la table.
L'identifiant (ID de transaction) de la transaction qui a inséré cette version de la ligne. (Une version de ligne est un état individuel de la ligne ; toute mise à jour d'une ligne crée une nouvelle version de ligne pour la même ligne logique.)
L'identifiant de commande (à partir de zéro) au sein de la transaction d'insertion.
L'identifiant (ID de transaction) de la transaction de suppression, ou zéro pour une version de ligne non effacée. Il est possible que la colonne ne soit pas nulle pour une version de ligne visible ; cela indique habituellement que la transaction de suppression n'a pas été effectuée, ou qu'une tentative de suppression a été annulée.
L'identifiant de commande au sein de la transaction de suppression, ou zéro.
La localisation physique de la version de ligne au sein de sa table. Bien que le ctid puisse être utilisé pour trouver la version de ligne très rapidement, le ctid d'une ligne change si la ligne est actualisée ou déplacée par un VACUUM FULL. ctid est donc inutilisable comme identifiant de ligne sur le long terme. Il est préférable d'utiliser l'OID, ou, mieux encore, un numéro de série utilisateur, pour identifier les lignes logiques.
Les OID sont des nombres de 32 bits et sont attribués à partir d'un compteur unique sur le cluster. Dans une base de données volumineuse ou agée, il est possible que le compteur boucle. Il est de ce fait peu pertinent de considérer que les OID puissent être uniques ; pour identifier les lignes d'une table, il est fortement recommandé d'utiliser un générateur de séquence. Néanmoins, les OID peuvent également être utilisés sous réserve que quelques précautions soient prises :
une contrainte d'unicité doit être ajoutée sur la colonne OID de chaque table dont l'OID est utilisé pour identifier les lignes. Dans ce cas (ou dans celui d'un index d'unicité), le système n'engendre pas d'OID qui puisse correspondre à celui d'une ligne déjà présente. Cela n'est évidemment possible que si la table contient moins de 232 (4 milliards) lignes ; en pratique, la taille de la table a tout intérêt à être bien plus petite que ça, dans un souci de performance ;
l'unicité inter-tables des OID ne doit jamais être envisagée ; pour obtenir un identifiant unique sur l'ensemble de la base, il faut utiliser la combinaison du tableoid et de l'OID de ligne ;
les tables en question doivent être créées avec l'option WITH OIDS. Depuis PostgreSQL™ 8.1, WITHOUT OIDS est l'option par défaut.
Les identifiants de transaction sont aussi des nombres de 32 bits. Dans une base de données agée, il est possible que les ID de transaction bouclent. Cela n'est pas un problème fatal avec des procédures de maintenance appropriées ; voir le Chapitre 23, Planifier les tâches de maintenance pour les détails. Il est, en revanche, imprudent de considérer l'unicité des ID de transaction sur le long terme (plus d'un milliard de transactions).
Les identifiants de commande sont aussi des nombres de 32 bits. Cela crée une limite dure de 232 (4 milliards) commandes SQL au sein d'une unique transaction. En pratique, cette limite n'est pas un problème -- la limite est sur le nombre de commandes SQL, pas sur le nombre de lignes traitées. De plus, à partir de PostgreSQL™ 8.3, seules les commandes qui modifient réellement le contenu de la base de données consomment un identifiant de commande.