From de9b7bbf2b0fd6d38dbb255c8294b2f4cf82cdb8 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Thu, 18 Jun 2026 23:38:12 +0100
Subject: [PATCH v11 8/9] Add pg_temp_statistic_ext_data global temporary
 catalog table.

This has the exact same columns as pg_statistic_ext_data, but it is a
global temporary table, and is used to hold extended statistics data
for global temporary tables, allowing the data to be session-specific
while the extended statistics object definitions remain shared.
---
 doc/src/sgml/catalogs.sgml                    |  51 ++++
 doc/src/sgml/func/func-statistics.sgml        |   5 +-
 doc/src/sgml/perform.sgml                     |   8 +-
 doc/src/sgml/system-views.sgml                |  24 +-
 src/backend/catalog/genbki.pl                 |   1 +
 src/backend/catalog/global_temp.c             |  35 ++-
 src/backend/catalog/system_views.sql          |  14 +-
 src/backend/commands/statscmds.c              |  21 +-
 src/backend/optimizer/util/plancat.c          |  13 +-
 src/backend/statistics/dependencies.c         |  11 +-
 src/backend/statistics/extended_stats.c       |  27 +-
 src/backend/statistics/extended_stats_funcs.c |  48 +++-
 src/backend/statistics/mcv.c                  |  13 +-
 src/backend/statistics/mvdistinct.c           |  10 +-
 src/backend/utils/adt/selfuncs.c              |   5 +-
 src/include/catalog/Makefile                  |   3 +-
 src/include/catalog/meson.build               |   1 +
 src/include/catalog/pg_statistic_ext_data.h   |   3 +
 .../catalog/pg_temp_statistic_ext_data.h      |  62 +++++
 src/include/commands/defrem.h                 |   2 +-
 src/include/statistics/statistics.h           |   8 +-
 src/test/isolation/expected/global-temp.out   | 242 ++++++++++++------
 src/test/isolation/specs/global-temp.spec     |  10 +-
 src/test/regress/expected/global_temp.out     |  71 +++++
 src/test/regress/expected/oidjoins.out        |   1 +
 src/test/regress/expected/rules.out           |  19 +-
 src/test/regress/expected/sanity_check.out    |  24 ++
 src/test/regress/sql/global_temp.sql          |  41 +++
 src/test/regress/sql/sanity_check.sql         |  21 ++
 29 files changed, 648 insertions(+), 146 deletions(-)
 create mode 100644 src/include/catalog/pg_temp_statistic_ext_data.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 23ac8b82cdb..84fd43179a2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -330,6 +330,11 @@
       <entry>planner statistics for global temporary relations</entry>
      </row>
 
+     <row>
+      <entry><link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link></entry>
+      <entry>extended planner statistics (built statistics) for global temporary tables</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-transform"><structname>pg_transform</structname></link></entry>
       <entry>transforms (data type to procedural language conversions)</entry>
@@ -8013,6 +8018,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
    information about tables the current user owns.
   </para>
 
+  <para>
+   Extended statistics data for global temporary tables is stored in
+   <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>,
+   instead of <structname>pg_statistic_ext_data</structname>, because the
+   contents of global temporary tables can vary between different sessions,
+   and so the statistical data needs to be local to each session
+   (<structname>pg_temp_statistic_ext_data</structname> is itself a global
+   temporary table).
+  </para>
+
   <table>
    <title><structname>pg_statistic_ext_data</structname> Columns</title>
    <tgroup cols="1">
@@ -8603,6 +8618,42 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
  </sect1>
 
 
+ <sect1 id="catalog-pg-temp-statistic-ext-data">
+  <title><structname>pg_temp_statistic_ext_data</structname></title>
+
+  <indexterm zone="catalog-pg-temp-statistic-ext-data">
+   <primary>pg_temp_statistic_ext_data</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_temp_statistic_ext_data</structname> is a global
+   temporary table that holds data for extended planner statistics defined in
+   <link linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>
+   for global temporary tables.  Each row in this catalog corresponds to a
+   <firstterm>statistics object</firstterm> created with
+   <link linkend="sql-createstatistics"><command>CREATE STATISTICS</command></link>
+   on a global temporary table.
+  </para>
+
+  <para>
+   <structname>pg_temp_statistic_ext_data</structname> has the same columns as
+   <structname>pg_statistic_ext_data</structname>, and its contents are created
+   and used by the planner in exactly the same way, except that they are local
+   to the current session, and are automatically deleted when the session exits.
+   Therefore, each session should run
+   <link linkend="sql-analyze"><command>ANALYZE</command></link> to create
+   its own extended statistics data for the contents of global temporary tables
+   with extended statistics defined, used in the session.
+  </para>
+
+  <para>
+   For more information about the table structure, and how its contents are
+   interpreted, see
+   <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>.
+  </para>
+ </sect1>
+
+
  <sect1 id="catalog-pg-transform">
   <title><structname>pg_transform</structname></title>
 
diff --git a/doc/src/sgml/func/func-statistics.sgml b/doc/src/sgml/func/func-statistics.sgml
index 22dee263cc2..2373fe4faf9 100644
--- a/doc/src/sgml/func/func-statistics.sgml
+++ b/doc/src/sgml/func/func-statistics.sgml
@@ -77,8 +77,9 @@ SELECT m.* FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid),
 </programlisting>
 
     Values of the <type>pg_mcv_list</type> type can be obtained only from the
-    <structname>pg_statistic_ext_data</structname>.<structfield>stxdmcv</structfield>
-    column.
+    <structname>pg_statistic_ext_data</structname>.<structfield>stxdmcv</structfield> and
+    <structname>pg_temp_statistic_ext_data</structname>.<structfield>stxdmcv</structfield>
+    columns.
    </para>
   </sect2>
 
diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml
index 77438f386f7..c373fd62c09 100644
--- a/doc/src/sgml/perform.sgml
+++ b/doc/src/sgml/perform.sgml
@@ -1456,6 +1456,10 @@ WHERE tablename = 'road';
     <primary>pg_statistic_ext_data</primary>
    </indexterm>
 
+   <indexterm>
+    <primary>pg_temp_statistic_ext_data</primary>
+   </indexterm>
+
    <para>
     It is common to see slow queries running bad execution plans because
     multiple columns used in the query clauses are correlated.
@@ -1485,7 +1489,9 @@ WHERE tablename = 'road';
     by <command>ANALYZE</command> (either a manual command, or background
     auto-analyze).  The collected values can be examined in the
     <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
-    catalog.
+    catalog (or the
+    <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>
+    catalog, for global temporary tables).
    </para>
 
    <para>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 6b1dd82e044..5ebc3fe4847 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -4641,10 +4641,13 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
    The view <structname>pg_stats_ext</structname> provides access to
    information about each extended statistics object in the database,
    combining information stored in the <link
-   linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>
-   and <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
+   linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>,
+   <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>, and
+   <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>
    catalogs.  This view allows access only to rows of
-   <link linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link> and <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
+   <link linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>,
+   <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>, and
+   <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>
    that correspond to tables the user owns, and therefore
    it is safe to allow public read access to this view.
   </para>
@@ -4772,7 +4775,8 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>inherited</structfield> <type>bool</type>
-       (references <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>.<structfield>stxdinherit</structfield>)
+       (references <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>.<structfield>stxdinherit</structfield> or
+       <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>.<structfield>stxdinherit</structfield>)
       </para>
       <para>
        If true, the stats include values from child tables, not just the
@@ -4872,10 +4876,13 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
    The view <structname>pg_stats_ext_exprs</structname> provides access to
    information about all expressions included in extended statistics objects,
    combining information stored in the <link
-   linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>
-   and <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
+   linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>,
+   <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>, and
+   <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>
    catalogs.  This view allows access only to rows of
-   <link linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link> and <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
+   <link linkend="catalog-pg-statistic-ext"><structname>pg_statistic_ext</structname></link>,
+   <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>, and
+   <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>
    that correspond to tables the user owns, and therefore
    it is safe to allow public read access to this view.
   </para>
@@ -4984,7 +4991,8 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>inherited</structfield> <type>bool</type>
-       (references <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>.<structfield>stxdinherit</structfield>)
+       (references <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>.<structfield>stxdinherit</structfield> or
+       <link linkend="catalog-pg-temp-statistic-ext-data"><structname>pg_temp_statistic_ext_data</structname></link>.<structfield>stxdinherit</structfield>)
       </para>
       <para>
        If true, the stats include values from child tables, not just the
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index 20d4f142a75..dd2229c41f0 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -801,6 +801,7 @@ print $syscache_ids_fh "#ifndef SYSCACHE_IDS_H
 #define SYSCACHE_IDS_H
 
 #include \"catalog/pg_temp_statistic_d.h\"
+#include \"catalog/pg_temp_statistic_ext_data_d.h\"
 
 typedef enum SysCacheIdentifier
 {
diff --git a/src/backend/catalog/global_temp.c b/src/backend/catalog/global_temp.c
index 318721a84ca..08b1b359086 100644
--- a/src/backend/catalog/global_temp.c
+++ b/src/backend/catalog/global_temp.c
@@ -1245,6 +1245,8 @@ ProcessInvalidatedGlobalTempRelations(void)
 		int			orig_xact_flags = MyXactFlags;
 		bool		tuples_deleted = false;
 		Relation	statrel;
+		SysScanDesc scan;
+		HeapTuple	tuple;
 
 		/*
 		 * Delete and forget locally-created storage for dropped relations.
@@ -1287,8 +1289,6 @@ ProcessInvalidatedGlobalTempRelations(void)
 		foreach_oid(relid, gtrs_dropped)
 		{
 			ScanKeyData key[1];
-			SysScanDesc scan;
-			HeapTuple	tuple;
 
 			gtr_remove_usage(relid);
 			remove_on_commit_action(relid);
@@ -1314,6 +1314,37 @@ ProcessInvalidatedGlobalTempRelations(void)
 
 		table_close(statrel, RowExclusiveLock);
 
+		/*
+		 * Delete any orphaned extended stats data from
+		 * pg_temp_statistic_ext_data.  This requires a full table scan, since
+		 * there is no stxrelid column referring back to the relation.  That
+		 * also means that it's not strictly limited to gtrs_dropped, but
+		 * that's probably no bad thing --- it will tidy up *any* orphaned
+		 * global temporary extended stats data, though in theory, that should
+		 * be limited to gtrs_dropped.
+		 */
+		statrel = table_open(TempStatisticExtDataRelationId, RowExclusiveLock);
+
+		scan = systable_beginscan(statrel, InvalidOid, false, NULL, 0, NULL);
+
+		while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+		{
+			Oid			stxoid;
+
+			stxoid = SysCacheGetAttrNotNull(TEMPSTATEXTDATASTXOID, tuple,
+											Anum_pg_temp_statistic_ext_data_stxoid);
+
+			if (!SearchSysCacheExists1(STATEXTOID, ObjectIdGetDatum(stxoid)))
+			{
+				CatalogTupleDelete(statrel, &tuple->t_self);
+				tuples_deleted = true;
+			}
+		}
+
+		systable_endscan(scan);
+
+		table_close(statrel, RowExclusiveLock);
+
 		/* If we deleted anything, make the changes visible */
 		if (tuples_deleted)
 			CommandCounterIncrement();
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 4bd89f2feba..702c247fe83 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -284,6 +284,11 @@ REVOKE ALL ON pg_statistic FROM public;
 REVOKE ALL ON pg_temp_statistic FROM public;
 REVOKE ALL ON pg_all_statistic FROM public;
 
+CREATE VIEW pg_all_statistic_ext_data AS
+    SELECT * FROM pg_statistic_ext_data
+    UNION ALL
+    SELECT * FROM pg_temp_statistic_ext_data;
+
 CREATE VIEW pg_stats_ext WITH (security_barrier) AS
     SELECT cn.nspname AS schemaname,
            c.relname AS tablename,
@@ -307,7 +312,7 @@ CREATE VIEW pg_stats_ext WITH (security_barrier) AS
            m.most_common_freqs,
            m.most_common_base_freqs
     FROM pg_statistic_ext s JOIN pg_class c ON (c.oid = s.stxrelid)
-         JOIN pg_statistic_ext_data sd ON (s.oid = sd.stxoid)
+         JOIN pg_all_statistic_ext_data sd ON (s.oid = sd.stxoid)
          LEFT JOIN pg_namespace cn ON (cn.oid = c.relnamespace)
          LEFT JOIN pg_namespace sn ON (sn.oid = s.stxnamespace)
          LEFT JOIN LATERAL
@@ -404,7 +409,7 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
                WHEN (stat.a).stakind5 = 7 THEN (stat.a).stavalues5
                END) AS range_bounds_histogram
     FROM pg_statistic_ext s JOIN pg_class c ON (c.oid = s.stxrelid)
-         LEFT JOIN pg_statistic_ext_data sd ON (s.oid = sd.stxoid)
+         LEFT JOIN pg_all_statistic_ext_data sd ON (s.oid = sd.stxoid)
          LEFT JOIN pg_namespace cn ON (cn.oid = c.relnamespace)
          LEFT JOIN pg_namespace sn ON (sn.oid = s.stxnamespace)
          JOIN LATERAL (
@@ -414,8 +419,11 @@ CREATE VIEW pg_stats_ext_exprs WITH (security_barrier) AS
     WHERE pg_has_role(c.relowner, 'USAGE')
     AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
 
--- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
+-- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data,
+-- pg_temp_statistic_ext_data, or pg_all_statistic_ext_data
 REVOKE ALL ON pg_statistic_ext_data FROM public;
+REVOKE ALL ON pg_temp_statistic_ext_data FROM public;
+REVOKE ALL ON pg_all_statistic_ext_data FROM public;
 
 CREATE VIEW pg_publication_tables AS
     SELECT
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 545678bc35c..a51b17220ad 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -25,6 +25,7 @@
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
+#include "catalog/pg_temp_statistic_ext_data.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
 #include "miscadmin.h"
@@ -790,14 +791,24 @@ AlterStatistics(AlterStatsStmt *stmt)
  * exists, so don't error out.
  */
 void
-RemoveStatisticsDataById(Oid statsOid, bool inh)
+RemoveStatisticsDataById(Oid relid, Oid statsOid, bool inh)
 {
 	Relation	relation;
+	SysCacheIdentifier cacheId;
 	HeapTuple	tup;
 
-	relation = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+	if (rel_is_global_temp(relid))
+	{
+		relation = table_open(TempStatisticExtDataRelationId, RowExclusiveLock);
+		cacheId = TEMPSTATEXTDATASTXOID;
+	}
+	else
+	{
+		relation = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+		cacheId = STATEXTDATASTXOID;
+	}
 
-	tup = SearchSysCache2(STATEXTDATASTXOID, ObjectIdGetDatum(statsOid),
+	tup = SearchSysCache2(cacheId, ObjectIdGetDatum(statsOid),
 						  BoolGetDatum(inh));
 
 	/* We don't know if the data row for inh value exists. */
@@ -845,8 +856,8 @@ RemoveStatisticsById(Oid statsOid)
 	 */
 	rel = table_open(relid, ShareUpdateExclusiveLock);
 
-	RemoveStatisticsDataById(statsOid, true);
-	RemoveStatisticsDataById(statsOid, false);
+	RemoveStatisticsDataById(relid, statsOid, true);
+	RemoveStatisticsDataById(relid, statsOid, false);
 
 	CacheInvalidateRelcacheByRelid(relid);
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 4c9c5e9fc33..1c6bb49ef00 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1650,14 +1650,17 @@ get_relation_constraints(PlannerInfo *root,
  * The result is stored in stainfos list.
  */
 static void
-get_relation_statistics_worker(List **stainfos, RelOptInfo *rel,
+get_relation_statistics_worker(Oid relid, List **stainfos, RelOptInfo *rel,
 							   Oid statOid, bool inh,
 							   Bitmapset *keys, List *exprs)
 {
+	SysCacheIdentifier cacheId;
 	Form_pg_statistic_ext_data dataForm;
 	HeapTuple	dtup;
 
-	dtup = SearchSysCache2(STATEXTDATASTXOID,
+	cacheId = rel_is_global_temp(relid) ? TEMPSTATEXTDATASTXOID : STATEXTDATASTXOID;
+
+	dtup = SearchSysCache2(cacheId,
 						   ObjectIdGetDatum(statOid), BoolGetDatum(inh));
 	if (!HeapTupleIsValid(dtup))
 		return;
@@ -1822,9 +1825,11 @@ get_relation_statistics(PlannerInfo *root, RelOptInfo *rel,
 
 		/* extract statistics for possible values of stxdinherit flag */
 
-		get_relation_statistics_worker(&stainfos, rel, statOid, true, keys, exprs);
+		get_relation_statistics_worker(RelationGetRelid(relation), &stainfos,
+									   rel, statOid, true, keys, exprs);
 
-		get_relation_statistics_worker(&stainfos, rel, statOid, false, keys, exprs);
+		get_relation_statistics_worker(RelationGetRelid(relation), &stainfos,
+									   rel, statOid, false, keys, exprs);
 
 		ReleaseSysCache(htup);
 		bms_free(keys);
diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c
index 48d5a53c775..31478f2ff39 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -684,20 +684,23 @@ dependency_is_fully_matched(MVDependency *dependency, Bitmapset *attnums)
  *		Load the functional dependencies for the indicated pg_statistic_ext tuple
  */
 MVDependencies *
-statext_dependencies_load(Oid mvoid, bool inh)
+statext_dependencies_load(Oid relid, Oid mvoid, bool inh)
 {
+	SysCacheIdentifier cacheId;
 	MVDependencies *result;
 	bool		isnull;
 	Datum		deps;
 	HeapTuple	htup;
 
-	htup = SearchSysCache2(STATEXTDATASTXOID,
+	cacheId = rel_is_global_temp(relid) ? TEMPSTATEXTDATASTXOID : STATEXTDATASTXOID;
+
+	htup = SearchSysCache2(cacheId,
 						   ObjectIdGetDatum(mvoid),
 						   BoolGetDatum(inh));
 	if (!HeapTupleIsValid(htup))
 		elog(ERROR, "cache lookup failed for statistics object %u", mvoid);
 
-	deps = SysCacheGetAttr(STATEXTDATASTXOID, htup,
+	deps = SysCacheGetAttr(cacheId, htup,
 						   Anum_pg_statistic_ext_data_stxddependencies, &isnull);
 	if (isnull)
 		elog(ERROR,
@@ -1604,7 +1607,7 @@ dependencies_clauselist_selectivity(PlannerInfo *root,
 		if (nmatched + nexprs < 2)
 			continue;
 
-		deps = statext_dependencies_load(stat->statOid, rte->inh);
+		deps = statext_dependencies_load(rte->relid, stat->statOid, rte->inh);
 
 		/*
 		 * The expressions may be represented by different attnums in the
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 515095faa4b..0eef2752bd2 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -23,6 +23,7 @@
 #include "catalog/indexing.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
+#include "catalog/pg_temp_statistic_ext_data.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
 #include "executor/executor.h"
@@ -77,7 +78,7 @@ typedef struct StatExtEntry
 static List *fetch_statentries_for_relation(Relation pg_statext, Relation rel);
 static VacAttrStats **lookup_var_attr_stats(Bitmapset *attrs, List *exprs,
 											int nvacatts, VacAttrStats **vacatts);
-static void statext_store(Oid statOid, bool inh,
+static void statext_store(Oid relid, Oid statOid, bool inh,
 						  MVNDistinct *ndistinct, MVDependencies *dependencies,
 						  MCVList *mcv, Datum exprs, VacAttrStats **stats);
 static int	statext_compute_stattarget(int stattarget,
@@ -227,7 +228,7 @@ BuildRelationExtStatistics(Relation onerel, bool inh, double totalrows,
 		}
 
 		/* store the statistics in the catalog */
-		statext_store(stat->statOid, inh,
+		statext_store(RelationGetRelid(onerel), stat->statOid, inh,
 					  ndistinct, dependencies, mcv, exprstats, stats);
 
 		/* for reporting progress */
@@ -805,7 +806,7 @@ lookup_var_attr_stats(Bitmapset *attrs, List *exprs,
  *	tuple.
  */
 static void
-statext_store(Oid statOid, bool inh,
+statext_store(Oid relid, Oid statOid, bool inh,
 			  MVNDistinct *ndistinct, MVDependencies *dependencies,
 			  MCVList *mcv, Datum exprs, VacAttrStats **stats)
 {
@@ -814,7 +815,12 @@ statext_store(Oid statOid, bool inh,
 	Datum		values[Natts_pg_statistic_ext_data];
 	bool		nulls[Natts_pg_statistic_ext_data];
 
-	pg_stextdata = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+	if (rel_is_global_temp(relid))
+		pg_stextdata = table_open(TempStatisticExtDataRelationId,
+								  RowExclusiveLock);
+	else
+		pg_stextdata = table_open(StatisticExtDataRelationId,
+								  RowExclusiveLock);
 
 	memset(nulls, true, sizeof(nulls));
 	memset(values, 0, sizeof(values));
@@ -862,7 +868,7 @@ statext_store(Oid statOid, bool inh,
 	 * Delete the old tuple if it exists, and insert a new one. It's easier
 	 * than trying to update or insert, based on various conditions.
 	 */
-	RemoveStatisticsDataById(statOid, inh);
+	RemoveStatisticsDataById(relid, statOid, inh);
 
 	/* form and insert a new tuple */
 	stup = heap_form_tuple(RelationGetDescr(pg_stextdata), values, nulls);
@@ -1885,7 +1891,7 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
 			MCVList    *mcv_list;
 
 			/* Load the MCV list stored in the statistics object */
-			mcv_list = statext_mcv_load(stat->statOid, rte->inh);
+			mcv_list = statext_mcv_load(rte->relid, stat->statOid, rte->inh);
 
 			/*
 			 * Compute the selectivity of the ORed list of clauses covered by
@@ -2444,8 +2450,9 @@ serialize_expr_stats(AnlExprData *exprdata, int nexprs)
  * data to use.
  */
 HeapTuple
-statext_expressions_load(Oid stxoid, bool inh, int idx)
+statext_expressions_load(Oid relid, Oid stxoid, bool inh, int idx)
 {
+	SysCacheIdentifier cacheId;
 	bool		isnull;
 	Datum		value;
 	HeapTuple	htup;
@@ -2454,12 +2461,14 @@ statext_expressions_load(Oid stxoid, bool inh, int idx)
 	HeapTupleData tmptup;
 	HeapTuple	tup;
 
-	htup = SearchSysCache2(STATEXTDATASTXOID,
+	cacheId = rel_is_global_temp(relid) ? TEMPSTATEXTDATASTXOID : STATEXTDATASTXOID;
+
+	htup = SearchSysCache2(cacheId,
 						   ObjectIdGetDatum(stxoid), BoolGetDatum(inh));
 	if (!HeapTupleIsValid(htup))
 		elog(ERROR, "cache lookup failed for statistics object %u", stxoid);
 
-	value = SysCacheGetAttr(STATEXTDATASTXOID, htup,
+	value = SysCacheGetAttr(cacheId, htup,
 							Anum_pg_statistic_ext_data_stxdexpr, &isnull);
 	if (isnull)
 		elog(ERROR,
diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index cb965fdb868..edb12e7e940 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -24,6 +24,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
+#include "catalog/pg_temp_statistic_ext_data.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -125,7 +126,7 @@ static bool extended_statistics_update(FunctionCallInfo fcinfo);
 
 static HeapTuple get_pg_statistic_ext(Relation pg_stext, Oid nspoid,
 									  const char *stxname);
-static bool delete_pg_statistic_ext_data(Oid stxoid, bool inherited);
+static bool delete_pg_statistic_ext_data(Oid relid, Oid stxoid, bool inherited);
 
 /*
  * Track the extended statistics kinds expected for a pg_statistic_ext
@@ -140,7 +141,8 @@ typedef struct
 } StakindFlags;
 
 static void expand_stxkind(HeapTuple tup, StakindFlags *enabled);
-static void upsert_pg_statistic_ext_data(const Datum *values,
+static void upsert_pg_statistic_ext_data(Oid relid,
+										 const Datum *values,
 										 const bool *nulls,
 										 const bool *replaces);
 
@@ -265,16 +267,28 @@ expand_stxkind(HeapTuple tup, StakindFlags *enabled)
  * Perform the actual storage of a pg_statistic_ext_data tuple.
  */
 static void
-upsert_pg_statistic_ext_data(const Datum *values, const bool *nulls,
-							 const bool *replaces)
+upsert_pg_statistic_ext_data(Oid relid, const Datum *values,
+							 const bool *nulls, const bool *replaces)
 {
 	Relation	pg_stextdata;
+	SysCacheIdentifier cacheId;
 	HeapTuple	stxdtup;
 	HeapTuple	newtup;
 
-	pg_stextdata = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+	if (rel_is_global_temp(relid))
+	{
+		pg_stextdata = table_open(TempStatisticExtDataRelationId,
+								  RowExclusiveLock);
+		cacheId = TEMPSTATEXTDATASTXOID;
+	}
+	else
+	{
+		pg_stextdata = table_open(StatisticExtDataRelationId,
+								  RowExclusiveLock);
+		cacheId = STATEXTDATASTXOID;
+	}
 
-	stxdtup = SearchSysCache2(STATEXTDATASTXOID,
+	stxdtup = SearchSysCache2(cacheId,
 							  values[Anum_pg_statistic_ext_data_stxoid - 1],
 							  values[Anum_pg_statistic_ext_data_stxdinherit - 1]);
 
@@ -749,7 +763,7 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 			success = false;
 	}
 
-	upsert_pg_statistic_ext_data(values, nulls, replaces);
+	upsert_pg_statistic_ext_data(relid, values, nulls, replaces);
 
 cleanup:
 	if (HeapTupleIsValid(tup))
@@ -1689,14 +1703,26 @@ exprs_error:
  * row and "inherited" pair.
  */
 static bool
-delete_pg_statistic_ext_data(Oid stxoid, bool inherited)
+delete_pg_statistic_ext_data(Oid relid, Oid stxoid, bool inherited)
 {
-	Relation	sed = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+	Relation	sed;
+	SysCacheIdentifier cacheId;
 	HeapTuple	oldtup;
 	bool		result = false;
 
+	if (rel_is_global_temp(relid))
+	{
+		sed = table_open(TempStatisticExtDataRelationId, RowExclusiveLock);
+		cacheId = TEMPSTATEXTDATASTXOID;
+	}
+	else
+	{
+		sed = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+		cacheId = STATEXTDATASTXOID;
+	}
+
 	/* Is there already a pg_statistic_ext_data tuple for this attribute? */
-	oldtup = SearchSysCache2(STATEXTDATASTXOID,
+	oldtup = SearchSysCache2(cacheId,
 							 ObjectIdGetDatum(stxoid),
 							 BoolGetDatum(inherited));
 
@@ -1831,7 +1857,7 @@ pg_clear_extended_stats(PG_FUNCTION_ARGS)
 		PG_RETURN_VOID();
 	}
 
-	delete_pg_statistic_ext_data(stxform->oid, inherited);
+	delete_pg_statistic_ext_data(relid, stxform->oid, inherited);
 	heap_freetuple(tup);
 
 	table_close(pg_stext, RowExclusiveLock);
diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c
index 10a91c97aeb..6bcd3082e5c 100644
--- a/src/backend/statistics/mcv.c
+++ b/src/backend/statistics/mcv.c
@@ -553,18 +553,21 @@ build_column_frequencies(SortItem *groups, int ngroups,
  *		Load the MCV list for the indicated pg_statistic_ext_data tuple.
  */
 MCVList *
-statext_mcv_load(Oid mvoid, bool inh)
+statext_mcv_load(Oid relid, Oid mvoid, bool inh)
 {
+	SysCacheIdentifier cacheId;
 	MCVList    *result;
 	bool		isnull;
 	Datum		mcvlist;
-	HeapTuple	htup = SearchSysCache2(STATEXTDATASTXOID,
-									   ObjectIdGetDatum(mvoid), BoolGetDatum(inh));
+	HeapTuple	htup;
 
+	cacheId = rel_is_global_temp(relid) ? TEMPSTATEXTDATASTXOID : STATEXTDATASTXOID;
+
+	htup = SearchSysCache2(cacheId, ObjectIdGetDatum(mvoid), BoolGetDatum(inh));
 	if (!HeapTupleIsValid(htup))
 		elog(ERROR, "cache lookup failed for statistics object %u", mvoid);
 
-	mcvlist = SysCacheGetAttr(STATEXTDATASTXOID, htup,
+	mcvlist = SysCacheGetAttr(cacheId, htup,
 							  Anum_pg_statistic_ext_data_stxdmcv, &isnull);
 
 	if (isnull)
@@ -2054,7 +2057,7 @@ mcv_clauselist_selectivity(PlannerInfo *root, StatisticExtInfo *stat,
 	bool	   *matches = NULL;
 
 	/* load the MCV list stored in the statistics object */
-	mcv = statext_mcv_load(stat->statOid, rte->inh);
+	mcv = statext_mcv_load(rte->relid, stat->statOid, rte->inh);
 
 	/* build a match bitmap for the clauses */
 	matches = mcv_get_match_bitmap(root, clauses, stat->keys, stat->exprs,
diff --git a/src/backend/statistics/mvdistinct.c b/src/backend/statistics/mvdistinct.c
index 601f108a2b4..5e1e3c819ce 100644
--- a/src/backend/statistics/mvdistinct.c
+++ b/src/backend/statistics/mvdistinct.c
@@ -28,6 +28,7 @@
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
 #include "statistics/extended_stats_internal.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 #include "varatt.h"
@@ -142,19 +143,22 @@ statext_ndistinct_build(double totalrows, StatsBuildData *data)
  *		Load the ndistinct value for the indicated pg_statistic_ext tuple
  */
 MVNDistinct *
-statext_ndistinct_load(Oid mvoid, bool inh)
+statext_ndistinct_load(Oid relid, Oid mvoid, bool inh)
 {
+	SysCacheIdentifier cacheId;
 	MVNDistinct *result;
 	bool		isnull;
 	Datum		ndist;
 	HeapTuple	htup;
 
-	htup = SearchSysCache2(STATEXTDATASTXOID,
+	cacheId = rel_is_global_temp(relid) ? TEMPSTATEXTDATASTXOID : STATEXTDATASTXOID;
+
+	htup = SearchSysCache2(cacheId,
 						   ObjectIdGetDatum(mvoid), BoolGetDatum(inh));
 	if (!HeapTupleIsValid(htup))
 		elog(ERROR, "cache lookup failed for statistics object %u", mvoid);
 
-	ndist = SysCacheGetAttr(STATEXTDATASTXOID, htup,
+	ndist = SysCacheGetAttr(cacheId, htup,
 							Anum_pg_statistic_ext_data_stxdndistinct, &isnull);
 	if (isnull)
 		elog(ERROR,
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 47ee254f6ac..b7f581ff3cb 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4678,7 +4678,7 @@ estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel,
 
 	Assert(nmatches_vars + nmatches_exprs > 1);
 
-	stats = statext_ndistinct_load(statOid, rte->inh);
+	stats = statext_ndistinct_load(rte->relid, statOid, rte->inh);
 
 	/*
 	 * If we have a match, search it for the specific item that matches (there
@@ -5936,7 +5936,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					 * Now we just create a new copy every time.
 					 */
 					vardata->statsTuple =
-						statext_expressions_load(info->statOid, rte->inh, pos);
+						statext_expressions_load(rte->relid, info->statOid,
+												 rte->inh, pos);
 
 					/* Nothing to release if no data found */
 					if (vardata->statsTuple != NULL)
diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile
index 6bfb60af0c9..2a4bab1bf68 100644
--- a/src/include/catalog/Makefile
+++ b/src/include/catalog/Makefile
@@ -82,7 +82,8 @@ CATALOG_HEADERS := \
 	pg_publication_rel.h \
 	pg_subscription.h \
 	pg_subscription_rel.h \
-	pg_temp_statistic.h
+	pg_temp_statistic.h \
+	pg_temp_statistic_ext_data.h
 
 GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h)
 
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index be51883747a..c9c4f5c99b8 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -70,6 +70,7 @@ catalog_headers = [
   'pg_subscription.h',
   'pg_subscription_rel.h',
   'pg_temp_statistic.h',
+  'pg_temp_statistic_ext_data.h',
 ]
 
 # The .dat files we need can just be listed alphabetically.
diff --git a/src/include/catalog/pg_statistic_ext_data.h b/src/include/catalog/pg_statistic_ext_data.h
index dbc4acc7d1a..e3a6fda0030 100644
--- a/src/include/catalog/pg_statistic_ext_data.h
+++ b/src/include/catalog/pg_statistic_ext_data.h
@@ -30,6 +30,9 @@
  */
 BEGIN_CATALOG_STRUCT
 
+/*
+ * NB: Any changes made here must be reflected in pg_temp_statistic_ext_data.
+ */
 CATALOG(pg_statistic_ext_data,3429,StatisticExtDataRelationId)
 {
 	Oid			stxoid BKI_LOOKUP(pg_statistic_ext);	/* statistics object
diff --git a/src/include/catalog/pg_temp_statistic_ext_data.h b/src/include/catalog/pg_temp_statistic_ext_data.h
new file mode 100644
index 00000000000..a47c431b228
--- /dev/null
+++ b/src/include/catalog/pg_temp_statistic_ext_data.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_temp_statistic_ext_data.h
+ *	  definition of the "temporary extended statistics data" system catalog
+ *	  (pg_temp_statistic_ext_data)
+ *
+ * This is a global temporary system catalog table storing the statistical
+ * data for extended statistics objects on temporary tables.  Currently, it
+ * is only used for global temporary tables.
+ *
+ * Portions Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/pg_temp_statistic_ext_data.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_TEMP_STATISTIC_EXT_DATA_H
+#define PG_TEMP_STATISTIC_EXT_DATA_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_temp_statistic_ext_data_d.h"	/* IWYU pragma: export */
+
+/* ----------------
+ *		pg_temp_statistic_ext_data definition.  cpp turns this into
+ *		typedef struct FormData_pg_temp_statistic_ext_data
+ * ----------------
+ */
+BEGIN_CATALOG_STRUCT
+
+/*
+ * NB: The fields here must exactly match those in pg_statistic_ext_data.
+ */
+CATALOG(pg_temp_statistic_ext_data,8089,TempStatisticExtDataRelationId) BKI_TEMP_RELATION
+{
+	Oid			stxoid BKI_LOOKUP(pg_statistic_ext);	/* statistics object
+														 * this data is for */
+	bool		stxdinherit;	/* true if inheritance children are included */
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+
+	pg_ndistinct stxdndistinct; /* ndistinct coefficients (serialized) */
+	pg_dependencies stxddependencies;	/* dependencies (serialized) */
+	pg_mcv_list stxdmcv;		/* MCV (serialized) */
+	pg_statistic stxdexpr[1];	/* stats for expressions */
+
+#endif
+
+} FormData_pg_temp_statistic_ext_data;
+
+END_CATALOG_STRUCT
+
+DECLARE_TOAST(pg_temp_statistic_ext_data, 8090, 8091);
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_temp_statistic_ext_data_stxoid_inh_index, 8092, TempStatisticExtDataStxoidInhIndexId, pg_temp_statistic_ext_data, btree(stxoid oid_ops, stxdinherit bool_ops));
+
+MAKE_SYSCACHE(TEMPSTATEXTDATASTXOID, pg_temp_statistic_ext_data_stxoid_inh_index, 4);
+
+#endif							/* PG_TEMP_STATISTIC_EXT_DATA_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 574f860bdd2..c6c403ddc20 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -89,7 +89,7 @@ extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 extern ObjectAddress CreateStatistics(List *relids, CreateStatsStmt *stmt, bool check_rights);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
-extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
+extern void RemoveStatisticsDataById(Oid relid, Oid statsOid, bool inh);
 extern Oid	StatisticsGetRelation(Oid statId, bool missing_ok);
 
 /* commands/aggregatecmds.c */
diff --git a/src/include/statistics/statistics.h b/src/include/statistics/statistics.h
index 0b163103a72..d1e2ad13f5a 100644
--- a/src/include/statistics/statistics.h
+++ b/src/include/statistics/statistics.h
@@ -94,9 +94,9 @@ typedef struct MCVList
 	MCVItem		items[FLEXIBLE_ARRAY_MEMBER];	/* array of MCV items */
 } MCVList;
 
-extern MVNDistinct *statext_ndistinct_load(Oid mvoid, bool inh);
-extern MVDependencies *statext_dependencies_load(Oid mvoid, bool inh);
-extern MCVList *statext_mcv_load(Oid mvoid, bool inh);
+extern MVNDistinct *statext_ndistinct_load(Oid relid, Oid mvoid, bool inh);
+extern MVDependencies *statext_dependencies_load(Oid relid, Oid mvoid, bool inh);
+extern MCVList *statext_mcv_load(Oid relid, Oid mvoid, bool inh);
 
 extern void BuildRelationExtStatistics(Relation onerel, bool inh, double totalrows,
 									   int numrows, HeapTuple *rows,
@@ -126,7 +126,7 @@ extern StatisticExtInfo *choose_best_statistics(List *stats, char requiredkind,
 												Bitmapset **clause_attnums,
 												List **clause_exprs,
 												int nclauses);
-extern HeapTuple statext_expressions_load(Oid stxoid, bool inh, int idx);
+extern HeapTuple statext_expressions_load(Oid relid, Oid stxoid, bool inh, int idx);
 
 extern bool import_relation_statistics(Relation rel,
 									   const NullableDatum *version,
diff --git a/src/test/isolation/expected/global-temp.out b/src/test/isolation/expected/global-temp.out
index 223f3519c2d..d6b85801118 100644
--- a/src/test/isolation/expected/global-temp.out
+++ b/src/test/isolation/expected/global-temp.out
@@ -453,8 +453,9 @@ key|val|seq
 
 step reset_tblspace: ALTER TABLE tmp SET TABLESPACE pg_default;
 
-starting permutation: create1 ins1_2 analyze1 used1 drop1 used1
+starting permutation: create1 ext_stats1 ins1_2 analyze1 used1 drop1 used1
 step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
+step ext_stats1: CREATE STATISTICS tmp2_stats ON key, val FROM tmp2;
 step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
 step analyze1: ANALYZE tmp2;
 step used1: 
@@ -462,33 +463,49 @@ step used1:
     FROM pg_gtrs_in_use()
    ORDER BY 1;
   SELECT count(*) FROM pg_temp_statistic;
-
-regexp_replace                       
--------------------------------------
-pg_temp_statistic                    
-pg_temp_statistic_relid_att_inh_index
-pg_toast.pg_toast_NNN                
-pg_toast.pg_toast_NNN_index          
-tmp2                                 
-(5 rows)
+  SELECT count(*) FROM pg_temp_statistic_ext_data;
+
+regexp_replace                             
+-------------------------------------------
+pg_temp_statistic                          
+pg_temp_statistic_ext_data                 
+pg_temp_statistic_ext_data_stxoid_inh_index
+pg_temp_statistic_relid_att_inh_index      
+pg_toast.pg_toast_NNN                      
+pg_toast.pg_toast_NNN_index                
+tmp2                                       
+(7 rows)
 
 count
 -----
     4
 (1 row)
 
+count
+-----
+    1
+(1 row)
+
 step drop1: DROP TABLE tmp2;
 step used1: 
   SELECT regexp_replace(oid::regclass::text, '_(\d+)', '_NNN', 'g')
     FROM pg_gtrs_in_use()
    ORDER BY 1;
   SELECT count(*) FROM pg_temp_statistic;
+  SELECT count(*) FROM pg_temp_statistic_ext_data;
 
-regexp_replace                       
--------------------------------------
-pg_temp_statistic                    
-pg_temp_statistic_relid_att_inh_index
-(2 rows)
+regexp_replace                             
+-------------------------------------------
+pg_temp_statistic                          
+pg_temp_statistic_ext_data                 
+pg_temp_statistic_ext_data_stxoid_inh_index
+pg_temp_statistic_relid_att_inh_index      
+(4 rows)
+
+count
+-----
+    0
+(1 row)
 
 count
 -----
@@ -496,8 +513,9 @@ count
 (1 row)
 
 
-starting permutation: create1 ins1_2 analyze1 used1 drop2 used1
+starting permutation: create1 ext_stats1 ins1_2 analyze1 used1 drop2 used1
 step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
+step ext_stats1: CREATE STATISTICS tmp2_stats ON key, val FROM tmp2;
 step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
 step analyze1: ANALYZE tmp2;
 step used1: 
@@ -505,33 +523,49 @@ step used1:
     FROM pg_gtrs_in_use()
    ORDER BY 1;
   SELECT count(*) FROM pg_temp_statistic;
-
-regexp_replace                       
--------------------------------------
-pg_temp_statistic                    
-pg_temp_statistic_relid_att_inh_index
-pg_toast.pg_toast_NNN                
-pg_toast.pg_toast_NNN_index          
-tmp2                                 
-(5 rows)
+  SELECT count(*) FROM pg_temp_statistic_ext_data;
+
+regexp_replace                             
+-------------------------------------------
+pg_temp_statistic                          
+pg_temp_statistic_ext_data                 
+pg_temp_statistic_ext_data_stxoid_inh_index
+pg_temp_statistic_relid_att_inh_index      
+pg_toast.pg_toast_NNN                      
+pg_toast.pg_toast_NNN_index                
+tmp2                                       
+(7 rows)
 
 count
 -----
     4
 (1 row)
 
+count
+-----
+    1
+(1 row)
+
 step drop2: DROP TABLE tmp2;
 step used1: 
   SELECT regexp_replace(oid::regclass::text, '_(\d+)', '_NNN', 'g')
     FROM pg_gtrs_in_use()
    ORDER BY 1;
   SELECT count(*) FROM pg_temp_statistic;
+  SELECT count(*) FROM pg_temp_statistic_ext_data;
 
-regexp_replace                       
--------------------------------------
-pg_temp_statistic                    
-pg_temp_statistic_relid_att_inh_index
-(2 rows)
+regexp_replace                             
+-------------------------------------------
+pg_temp_statistic                          
+pg_temp_statistic_ext_data                 
+pg_temp_statistic_ext_data_stxoid_inh_index
+pg_temp_statistic_relid_att_inh_index      
+(4 rows)
+
+count
+-----
+    0
+(1 row)
 
 count
 -----
@@ -539,8 +573,9 @@ count
 (1 row)
 
 
-starting permutation: create1 ins1_2 analyze1 used1 b1 drop2 used1 r1 used1
+starting permutation: create1 ext_stats1 ins1_2 analyze1 used1 b1 drop2 used1 r1 used1
 step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
+step ext_stats1: CREATE STATISTICS tmp2_stats ON key, val FROM tmp2;
 step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
 step analyze1: ANALYZE tmp2;
 step used1: 
@@ -548,21 +583,29 @@ step used1:
     FROM pg_gtrs_in_use()
    ORDER BY 1;
   SELECT count(*) FROM pg_temp_statistic;
-
-regexp_replace                       
--------------------------------------
-pg_temp_statistic                    
-pg_temp_statistic_relid_att_inh_index
-pg_toast.pg_toast_NNN                
-pg_toast.pg_toast_NNN_index          
-tmp2                                 
-(5 rows)
+  SELECT count(*) FROM pg_temp_statistic_ext_data;
+
+regexp_replace                             
+-------------------------------------------
+pg_temp_statistic                          
+pg_temp_statistic_ext_data                 
+pg_temp_statistic_ext_data_stxoid_inh_index
+pg_temp_statistic_relid_att_inh_index      
+pg_toast.pg_toast_NNN                      
+pg_toast.pg_toast_NNN_index                
+tmp2                                       
+(7 rows)
 
 count
 -----
     4
 (1 row)
 
+count
+-----
+    1
+(1 row)
+
 step b1: BEGIN;
 step drop2: DROP TABLE tmp2;
 step used1: 
@@ -570,12 +613,20 @@ step used1:
     FROM pg_gtrs_in_use()
    ORDER BY 1;
   SELECT count(*) FROM pg_temp_statistic;
+  SELECT count(*) FROM pg_temp_statistic_ext_data;
 
-regexp_replace                       
--------------------------------------
-pg_temp_statistic                    
-pg_temp_statistic_relid_att_inh_index
-(2 rows)
+regexp_replace                             
+-------------------------------------------
+pg_temp_statistic                          
+pg_temp_statistic_ext_data                 
+pg_temp_statistic_ext_data_stxoid_inh_index
+pg_temp_statistic_relid_att_inh_index      
+(4 rows)
+
+count
+-----
+    0
+(1 row)
 
 count
 -----
@@ -588,12 +639,20 @@ step used1:
     FROM pg_gtrs_in_use()
    ORDER BY 1;
   SELECT count(*) FROM pg_temp_statistic;
+  SELECT count(*) FROM pg_temp_statistic_ext_data;
 
-regexp_replace                       
--------------------------------------
-pg_temp_statistic                    
-pg_temp_statistic_relid_att_inh_index
-(2 rows)
+regexp_replace                             
+-------------------------------------------
+pg_temp_statistic                          
+pg_temp_statistic_ext_data                 
+pg_temp_statistic_ext_data_stxoid_inh_index
+pg_temp_statistic_relid_att_inh_index      
+(4 rows)
+
+count
+-----
+    0
+(1 row)
 
 count
 -----
@@ -601,8 +660,9 @@ count
 (1 row)
 
 
-starting permutation: create1 ins1_2 analyze1 b1 used1 sp1 drop2 used1 rsp1 used1 r1 used1
+starting permutation: create1 ext_stats1 ins1_2 analyze1 b1 used1 sp1 drop2 used1 rsp1 used1 r1 used1
 step create1: CREATE GLOBAL TEMP TABLE tmp2 (key int, val text, icol int, bcol box);
+step ext_stats1: CREATE STATISTICS tmp2_stats ON key, val FROM tmp2;
 step ins1_2: INSERT INTO tmp2 VALUES (1, 's1');
 step analyze1: ANALYZE tmp2;
 step b1: BEGIN;
@@ -611,21 +671,29 @@ step used1:
     FROM pg_gtrs_in_use()
    ORDER BY 1;
   SELECT count(*) FROM pg_temp_statistic;
-
-regexp_replace                       
--------------------------------------
-pg_temp_statistic                    
-pg_temp_statistic_relid_att_inh_index
-pg_toast.pg_toast_NNN                
-pg_toast.pg_toast_NNN_index          
-tmp2                                 
-(5 rows)
+  SELECT count(*) FROM pg_temp_statistic_ext_data;
+
+regexp_replace                             
+-------------------------------------------
+pg_temp_statistic                          
+pg_temp_statistic_ext_data                 
+pg_temp_statistic_ext_data_stxoid_inh_index
+pg_temp_statistic_relid_att_inh_index      
+pg_toast.pg_toast_NNN                      
+pg_toast.pg_toast_NNN_index                
+tmp2                                       
+(7 rows)
 
 count
 -----
     4
 (1 row)
 
+count
+-----
+    1
+(1 row)
+
 step sp1: SAVEPOINT sp;
 step drop2: DROP TABLE tmp2;
 step used1: 
@@ -633,12 +701,20 @@ step used1:
     FROM pg_gtrs_in_use()
    ORDER BY 1;
   SELECT count(*) FROM pg_temp_statistic;
+  SELECT count(*) FROM pg_temp_statistic_ext_data;
 
-regexp_replace                       
--------------------------------------
-pg_temp_statistic                    
-pg_temp_statistic_relid_att_inh_index
-(2 rows)
+regexp_replace                             
+-------------------------------------------
+pg_temp_statistic                          
+pg_temp_statistic_ext_data                 
+pg_temp_statistic_ext_data_stxoid_inh_index
+pg_temp_statistic_relid_att_inh_index      
+(4 rows)
+
+count
+-----
+    0
+(1 row)
 
 count
 -----
@@ -651,12 +727,20 @@ step used1:
     FROM pg_gtrs_in_use()
    ORDER BY 1;
   SELECT count(*) FROM pg_temp_statistic;
+  SELECT count(*) FROM pg_temp_statistic_ext_data;
 
-regexp_replace                       
--------------------------------------
-pg_temp_statistic                    
-pg_temp_statistic_relid_att_inh_index
-(2 rows)
+regexp_replace                             
+-------------------------------------------
+pg_temp_statistic                          
+pg_temp_statistic_ext_data                 
+pg_temp_statistic_ext_data_stxoid_inh_index
+pg_temp_statistic_relid_att_inh_index      
+(4 rows)
+
+count
+-----
+    0
+(1 row)
 
 count
 -----
@@ -669,12 +753,20 @@ step used1:
     FROM pg_gtrs_in_use()
    ORDER BY 1;
   SELECT count(*) FROM pg_temp_statistic;
+  SELECT count(*) FROM pg_temp_statistic_ext_data;
 
-regexp_replace                       
--------------------------------------
-pg_temp_statistic                    
-pg_temp_statistic_relid_att_inh_index
-(2 rows)
+regexp_replace                             
+-------------------------------------------
+pg_temp_statistic                          
+pg_temp_statistic_ext_data                 
+pg_temp_statistic_ext_data_stxoid_inh_index
+pg_temp_statistic_relid_att_inh_index      
+(4 rows)
+
+count
+-----
+    0
+(1 row)
 
 count
 -----
diff --git a/src/test/isolation/specs/global-temp.spec b/src/test/isolation/specs/global-temp.spec
index 197662657b1..57adee622d1 100644
--- a/src/test/isolation/specs/global-temp.spec
+++ b/src/test/isolation/specs/global-temp.spec
@@ -75,12 +75,14 @@ step get_tblspace1 {
 step reset_tblspace { ALTER TABLE tmp SET TABLESPACE pg_default; }
 
 # Test DROP from other backend
+step ext_stats1 { CREATE STATISTICS tmp2_stats ON key, val FROM tmp2; }
 step analyze1 { ANALYZE tmp2; }
 step used1 {
   SELECT regexp_replace(oid::regclass::text, '_(\d+)', '_NNN', 'g')
     FROM pg_gtrs_in_use()
    ORDER BY 1;
   SELECT count(*) FROM pg_temp_statistic;
+  SELECT count(*) FROM pg_temp_statistic_ext_data;
 }
 
 # Test val index
@@ -190,10 +192,10 @@ permutation ins1 ins2 alt_tblspace1 get_tblspace1 get_tblspace2
             sel1 sel2 reset_tblspace
 
 # Test DROP from other backend
-permutation create1 ins1_2 analyze1 used1 drop1 used1
-permutation create1 ins1_2 analyze1 used1 drop2 used1
-permutation create1 ins1_2 analyze1 used1 b1 drop2 used1 r1 used1
-permutation create1 ins1_2 analyze1 b1 used1 sp1 drop2 used1 rsp1 used1 r1 used1
+permutation create1 ext_stats1 ins1_2 analyze1 used1 drop1 used1
+permutation create1 ext_stats1 ins1_2 analyze1 used1 drop2 used1
+permutation create1 ext_stats1 ins1_2 analyze1 used1 b1 drop2 used1 r1 used1
+permutation create1 ext_stats1 ins1_2 analyze1 b1 used1 sp1 drop2 used1 rsp1 used1 r1 used1
 
 # Test val index
 permutation ins1 idx1 sel1_idx ins2 sel2_idx
diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
index 333ca88ce89..6a9dee45819 100644
--- a/src/test/regress/expected/global_temp.out
+++ b/src/test/regress/expected/global_temp.out
@@ -1150,3 +1150,74 @@ SELECT row_estimate('SELECT * FROM tmp2 WHERE b = 8');
 (1 row)
 
 DROP TABLE tmp2;
+-- Test extended stats
+CREATE GLOBAL TEMP TABLE tmp2 (a int, b int, c int);
+INSERT INTO tmp2
+  SELECT x, floor(sqrt(x)), floor(sqrt(x)) + 100 FROM generate_series(36, 99) x;
+CREATE STATISTICS tmp2_stats ON b, c FROM tmp2;
+ANALYZE tmp2;
+SELECT stxname,
+       replace(d.stxdndistinct, '}, ', E'},\n') AS stxdndistinct,
+       replace(d.stxddependencies, '}, ', E'},\n') AS stxddependencies
+  FROM pg_statistic_ext s
+  LEFT JOIN pg_statistic_ext_data d ON d.stxoid = s.oid
+ WHERE s.stxname = 'tmp2_stats';
+  stxname   | stxdndistinct | stxddependencies 
+------------+---------------+------------------
+ tmp2_stats |               | 
+(1 row)
+
+SELECT stxname,
+       replace(d.stxdndistinct, '}, ', E'},\n') AS stxdndistinct,
+       replace(d.stxddependencies, '}, ', E'},\n') AS stxddependencies
+  FROM pg_statistic_ext s
+  LEFT JOIN pg_temp_statistic_ext_data d ON d.stxoid = s.oid
+ WHERE s.stxname = 'tmp2_stats';
+  stxname   |              stxdndistinct               |                      stxddependencies                      
+------------+------------------------------------------+------------------------------------------------------------
+ tmp2_stats | [{"attributes": [2, 3], "ndistinct": 4}] | [{"attributes": [2], "dependency": 3, "degree": 1.000000},+
+            |                                          | {"attributes": [3], "dependency": 2, "degree": 1.000000}]
+(1 row)
+
+SELECT m.*
+  FROM pg_statistic_ext s, pg_statistic_ext_data d,
+       pg_mcv_list_items(d.stxdmcv) m
+ WHERE s.stxname = 'tmp2_stats'
+   AND d.stxoid = s.oid;
+ index | values | nulls | frequency | base_frequency 
+-------+--------+-------+-----------+----------------
+(0 rows)
+
+SELECT m.*
+  FROM pg_statistic_ext s, pg_temp_statistic_ext_data d,
+       pg_mcv_list_items(d.stxdmcv) m
+ WHERE s.stxname = 'tmp2_stats'
+   AND d.stxoid = s.oid;
+ index | values  | nulls | frequency | base_frequency 
+-------+---------+-------+-----------+----------------
+     0 | {9,109} | {f,f} |  0.296875 | 0.088134765625
+     1 | {8,108} | {f,f} |  0.265625 | 0.070556640625
+     2 | {7,107} | {f,f} |  0.234375 | 0.054931640625
+     3 | {6,106} | {f,f} |  0.203125 | 0.041259765625
+(4 rows)
+
+SELECT most_common_vals FROM pg_stats_ext
+ WHERE schemaname = 'global_temp_tests' AND tablename = 'tmp2';
+         most_common_vals          
+-----------------------------------
+ {{9,109},{8,108},{7,107},{6,106}}
+(1 row)
+
+SELECT COUNT(*) FROM tmp2 WHERE b = 8 AND c = 108;
+ count 
+-------
+    17
+(1 row)
+
+SELECT row_estimate('SELECT * FROM tmp2 WHERE b = 8 AND c = 108');
+ row_estimate 
+--------------
+           17
+(1 row)
+
+DROP TABLE tmp2;
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 883ef6885c9..aa5d3af07c2 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -284,3 +284,4 @@ NOTICE:  checking pg_temp_statistic {stacoll2} => pg_collation {oid}
 NOTICE:  checking pg_temp_statistic {stacoll3} => pg_collation {oid}
 NOTICE:  checking pg_temp_statistic {stacoll4} => pg_collation {oid}
 NOTICE:  checking pg_temp_statistic {stacoll5} => pg_collation {oid}
+NOTICE:  checking pg_temp_statistic_ext_data {stxoid} => pg_statistic_ext {oid}
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 8998e5bf59c..a69e10d20c7 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1367,6 +1367,21 @@ UNION ALL
     pg_temp_statistic.stavalues4,
     pg_temp_statistic.stavalues5
    FROM pg_temp_statistic;
+pg_all_statistic_ext_data| SELECT pg_statistic_ext_data.stxoid,
+    pg_statistic_ext_data.stxdinherit,
+    pg_statistic_ext_data.stxdndistinct,
+    pg_statistic_ext_data.stxddependencies,
+    pg_statistic_ext_data.stxdmcv,
+    pg_statistic_ext_data.stxdexpr
+   FROM pg_statistic_ext_data
+UNION ALL
+ SELECT pg_temp_statistic_ext_data.stxoid,
+    pg_temp_statistic_ext_data.stxdinherit,
+    pg_temp_statistic_ext_data.stxdndistinct,
+    pg_temp_statistic_ext_data.stxddependencies,
+    pg_temp_statistic_ext_data.stxdmcv,
+    pg_temp_statistic_ext_data.stxdexpr
+   FROM pg_temp_statistic_ext_data;
 pg_available_extension_versions| SELECT e.name,
     e.version,
     (x.extname IS NOT NULL) AS installed,
@@ -2797,7 +2812,7 @@ pg_stats_ext| SELECT cn.nspname AS schemaname,
     m.most_common_base_freqs
    FROM (((((pg_statistic_ext s
      JOIN pg_class c ON ((c.oid = s.stxrelid)))
-     JOIN pg_statistic_ext_data sd ON ((s.oid = sd.stxoid)))
+     JOIN pg_all_statistic_ext_data sd ON ((s.oid = sd.stxoid)))
      LEFT JOIN pg_namespace cn ON ((cn.oid = c.relnamespace)))
      LEFT JOIN pg_namespace sn ON ((sn.oid = s.stxnamespace)))
      LEFT JOIN LATERAL ( SELECT array_agg(pg_mcv_list_items."values") AS most_common_vals,
@@ -2900,7 +2915,7 @@ pg_stats_ext_exprs| SELECT cn.nspname AS schemaname,
         END AS range_bounds_histogram
    FROM (((((pg_statistic_ext s
      JOIN pg_class c ON ((c.oid = s.stxrelid)))
-     LEFT JOIN pg_statistic_ext_data sd ON ((s.oid = sd.stxoid)))
+     LEFT JOIN pg_all_statistic_ext_data sd ON ((s.oid = sd.stxoid)))
      LEFT JOIN pg_namespace cn ON ((cn.oid = c.relnamespace)))
      LEFT JOIN pg_namespace sn ON ((sn.oid = s.stxnamespace)))
      JOIN LATERAL ( SELECT unnest(pg_get_statisticsobjdef_expressions(s.oid)) AS expr,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 5f5756d5596..3f14d8f9e8a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -48,3 +48,27 @@ UNION ALL
 ---------+----------+--------+--------+-----------+----------+----------+----------+------------+----------------+------------+-----------+---------------+-------------+--------------+--------------+------------+-------------+--------------
 (0 rows)
 
+-- check that pg_statistic_ext_data and pg_temp_statistic_ext_data have the
+-- exact same columns
+WITH t1 AS (
+  SELECT attname, atttypid, attlen, attnum, atttypmod, attndims, attbyval,
+         attalign, attstorage, attcompression, attnotnull, atthasdef,
+         atthasmissing, attidentity, attgenerated, attisdropped, attislocal,
+         attinhcount, attcollation
+    FROM pg_attribute
+   WHERE attrelid = 'pg_statistic_ext_data'::regclass
+), t2 AS (
+  SELECT attname, atttypid, attlen, attnum, atttypmod, attndims, attbyval,
+         attalign, attstorage, attcompression, attnotnull, atthasdef,
+         atthasmissing, attidentity, attgenerated, attisdropped, attislocal,
+         attinhcount, attcollation
+    FROM pg_attribute
+   WHERE attrelid = 'pg_temp_statistic_ext_data'::regclass
+)
+(SELECT * FROM t1 EXCEPT SELECT * FROM t2)
+UNION ALL
+(SELECT * FROM t2 EXCEPT SELECT * FROM t1);
+ attname | atttypid | attlen | attnum | atttypmod | attndims | attbyval | attalign | attstorage | attcompression | attnotnull | atthasdef | atthasmissing | attidentity | attgenerated | attisdropped | attislocal | attinhcount | attcollation 
+---------+----------+--------+--------+-----------+----------+----------+----------+------------+----------------+------------+-----------+---------------+-------------+--------------+--------------+------------+-------------+--------------
+(0 rows)
+
diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql
index 0813fadd915..663e34a59c5 100644
--- a/src/test/regress/sql/global_temp.sql
+++ b/src/test/regress/sql/global_temp.sql
@@ -614,3 +614,44 @@ SELECT COUNT(*) FROM tmp2 WHERE b = 8;
 SELECT row_estimate('SELECT * FROM tmp2 WHERE b = 8');
 
 DROP TABLE tmp2;
+
+-- Test extended stats
+CREATE GLOBAL TEMP TABLE tmp2 (a int, b int, c int);
+INSERT INTO tmp2
+  SELECT x, floor(sqrt(x)), floor(sqrt(x)) + 100 FROM generate_series(36, 99) x;
+CREATE STATISTICS tmp2_stats ON b, c FROM tmp2;
+ANALYZE tmp2;
+
+SELECT stxname,
+       replace(d.stxdndistinct, '}, ', E'},\n') AS stxdndistinct,
+       replace(d.stxddependencies, '}, ', E'},\n') AS stxddependencies
+  FROM pg_statistic_ext s
+  LEFT JOIN pg_statistic_ext_data d ON d.stxoid = s.oid
+ WHERE s.stxname = 'tmp2_stats';
+
+SELECT stxname,
+       replace(d.stxdndistinct, '}, ', E'},\n') AS stxdndistinct,
+       replace(d.stxddependencies, '}, ', E'},\n') AS stxddependencies
+  FROM pg_statistic_ext s
+  LEFT JOIN pg_temp_statistic_ext_data d ON d.stxoid = s.oid
+ WHERE s.stxname = 'tmp2_stats';
+
+SELECT m.*
+  FROM pg_statistic_ext s, pg_statistic_ext_data d,
+       pg_mcv_list_items(d.stxdmcv) m
+ WHERE s.stxname = 'tmp2_stats'
+   AND d.stxoid = s.oid;
+
+SELECT m.*
+  FROM pg_statistic_ext s, pg_temp_statistic_ext_data d,
+       pg_mcv_list_items(d.stxdmcv) m
+ WHERE s.stxname = 'tmp2_stats'
+   AND d.stxoid = s.oid;
+
+SELECT most_common_vals FROM pg_stats_ext
+ WHERE schemaname = 'global_temp_tests' AND tablename = 'tmp2';
+
+SELECT COUNT(*) FROM tmp2 WHERE b = 8 AND c = 108;
+SELECT row_estimate('SELECT * FROM tmp2 WHERE b = 8 AND c = 108');
+
+DROP TABLE tmp2;
diff --git a/src/test/regress/sql/sanity_check.sql b/src/test/regress/sql/sanity_check.sql
index ed0096d7823..4bf00feba2c 100644
--- a/src/test/regress/sql/sanity_check.sql
+++ b/src/test/regress/sql/sanity_check.sql
@@ -39,3 +39,24 @@ WITH t1 AS (
 (SELECT * FROM t1 EXCEPT SELECT * FROM t2)
 UNION ALL
 (SELECT * FROM t2 EXCEPT SELECT * FROM t1);
+
+-- check that pg_statistic_ext_data and pg_temp_statistic_ext_data have the
+-- exact same columns
+WITH t1 AS (
+  SELECT attname, atttypid, attlen, attnum, atttypmod, attndims, attbyval,
+         attalign, attstorage, attcompression, attnotnull, atthasdef,
+         atthasmissing, attidentity, attgenerated, attisdropped, attislocal,
+         attinhcount, attcollation
+    FROM pg_attribute
+   WHERE attrelid = 'pg_statistic_ext_data'::regclass
+), t2 AS (
+  SELECT attname, atttypid, attlen, attnum, atttypmod, attndims, attbyval,
+         attalign, attstorage, attcompression, attnotnull, atthasdef,
+         atthasmissing, attidentity, attgenerated, attisdropped, attislocal,
+         attinhcount, attcollation
+    FROM pg_attribute
+   WHERE attrelid = 'pg_temp_statistic_ext_data'::regclass
+)
+(SELECT * FROM t1 EXCEPT SELECT * FROM t2)
+UNION ALL
+(SELECT * FROM t2 EXCEPT SELECT * FROM t1);
-- 
2.51.0

