qr/cannot vacuum all databases and a specific one at the same time/,
    'cannot use option --all and a dbname as argument at the same time');
 
+$node->safe_psql('postgres',
+   'CREATE TABLE regression_vacuumdb_test AS select generate_series(1, 10) a, generate_series(2, 11) b;');
+$node->issues_sql_like(
+   [ 'vacuumdb', '--analyze-only', '--missing-stats-only', '-t', 'regression_vacuumdb_test', 'postgres' ],
+   qr/statement:\ ANALYZE/sx,
+   '--missing-stats-only with missing stats');
+$node->issues_sql_unlike(
+   [ 'vacuumdb', '--analyze-only', '--missing-stats-only', '-t', 'regression_vacuumdb_test', 'postgres' ],
+   qr/statement:\ ANALYZE/sx,
+   '--missing-stats-only with no missing stats');
+
+$node->safe_psql('postgres',
+   'CREATE INDEX regression_vacuumdb_test_idx ON regression_vacuumdb_test (mod(a, 2));');
+$node->issues_sql_like(
+   [ 'vacuumdb', '--analyze-in-stages', '--missing-stats-only', '-t', 'regression_vacuumdb_test', 'postgres' ],
+   qr/statement:\ ANALYZE/sx,
+   '--missing-stats-only with missing index expression stats');
+$node->issues_sql_unlike(
+   [ 'vacuumdb', '--analyze-in-stages', '--missing-stats-only', '-t', 'regression_vacuumdb_test', 'postgres' ],
+   qr/statement:\ ANALYZE/sx,
+   '--missing-stats-only with no missing index expression stats');
+
+$node->safe_psql('postgres',
+   'CREATE STATISTICS regression_vacuumdb_test_stat ON a, b FROM regression_vacuumdb_test;');
+$node->issues_sql_like(
+   [ 'vacuumdb', '--analyze-only', '--missing-stats-only', '-t', 'regression_vacuumdb_test', 'postgres' ],
+   qr/statement:\ ANALYZE/sx,
+   '--missing-stats-only with missing extended stats');
+$node->issues_sql_unlike(
+   [ 'vacuumdb', '--analyze-only', '--missing-stats-only', '-t', 'regression_vacuumdb_test', 'postgres' ],
+   qr/statement:\ ANALYZE/sx,
+   '--missing-stats-only with no missing extended stats');
+
+$node->safe_psql('postgres',
+   "CREATE TABLE regression_vacuumdb_child (a INT) INHERITS (regression_vacuumdb_test);\n"
+   . "INSERT INTO regression_vacuumdb_child VALUES (1, 2);\n"
+   . "ANALYZE regression_vacuumdb_child;\n");
+$node->issues_sql_like(
+   [ 'vacuumdb', '--analyze-in-stages', '--missing-stats-only', '-t', 'regression_vacuumdb_test', 'postgres' ],
+   qr/statement:\ ANALYZE/sx,
+   '--missing-stats-only with missing inherited stats');
+$node->issues_sql_unlike(
+   [ 'vacuumdb', '--analyze-in-stages', '--missing-stats-only', '-t', 'regression_vacuumdb_test', 'postgres' ],
+   qr/statement:\ ANALYZE/sx,
+   '--missing-stats-only with no missing inherited stats');
+
+$node->safe_psql('postgres',
+   "CREATE TABLE regression_vacuumdb_parted (a INT) PARTITION BY LIST (a);\n"
+   . "CREATE TABLE regression_vacuumdb_part1 PARTITION OF regression_vacuumdb_parted FOR VALUES IN (1);\n"
+   . "INSERT INTO regression_vacuumdb_parted VALUES (1);\n"
+   . "ANALYZE regression_vacuumdb_part1;\n");
+$node->issues_sql_like(
+   [ 'vacuumdb', '--analyze-only', '--missing-stats-only', '-t', 'regression_vacuumdb_parted', 'postgres' ],
+   qr/statement:\ ANALYZE/sx,
+   '--missing-stats-only with missing partition stats');
+$node->issues_sql_unlike(
+   [ 'vacuumdb', '--analyze-only', '--missing-stats-only', '-t', 'regression_vacuumdb_parted', 'postgres' ],
+   qr/statement:\ ANALYZE/sx,
+   '--missing-stats-only with no missing partition stats');
+
 done_testing();
 
    bool        process_toast;
    bool        skip_database_stats;
    char       *buffer_usage_limit;
+   bool        missing_stats_only;
 } vacuumingOptions;
 
 /* object filter options */
        {"no-process-toast", no_argument, NULL, 11},
        {"no-process-main", no_argument, NULL, 12},
        {"buffer-usage-limit", required_argument, NULL, 13},
+       {"missing-stats-only", no_argument, NULL, 14},
        {NULL, 0, NULL, 0}
    };
 
            case 13:
                vacopts.buffer_usage_limit = escape_quotes(optarg);
                break;
+           case 14:
+               vacopts.missing_stats_only = true;
+               break;
            default:
                /* getopt_long already emitted a complaint */
                pg_log_error_hint("Try \"%s --help\" for more information.", progname);
        pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
                 "buffer-usage-limit", "full");
 
+   /*
+    * Prohibit --missing-stats-only without --analyze-only or
+    * --analyze-in-stages.
+    */
+   if (vacopts.missing_stats_only && !vacopts.analyze_only)
+       pg_fatal("cannot use the \"%s\" option without \"%s\" or \"%s\"",
+                "missing-stats-only", "analyze-only", "analyze-in-stages");
+
    /* fill cparams except for dbname, which is set below */
    cparams.pghost = host;
    cparams.pgport = port;
        if (analyze_in_stages)
        {
            int         stage;
+           SimpleStringList *found_objs = NULL;
 
            for (stage = 0; stage < ANALYZE_NUM_STAGES; stage++)
            {
                vacuum_one_database(&cparams, &vacopts,
                                    stage,
-                                   &objects, NULL,
+                                   &objects,
+                                   vacopts.missing_stats_only ? &found_objs : NULL,
                                    concurrentCons,
                                    progname, echo, quiet);
            }
                 "--buffer-usage-limit", "16");
    }
 
+   if (vacopts->missing_stats_only && PQserverVersion(conn) < 150000)
+   {
+       PQfinish(conn);
+       pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
+                "--missing-stats-only", "15");
+   }
+
    /* skip_database_stats is used automatically if server supports it */
    vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
 
                         " FROM pg_catalog.pg_class c\n"
                         " JOIN pg_catalog.pg_namespace ns"
                         " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
+                        " CROSS JOIN LATERAL (SELECT c.relkind IN ("
+                        CppAsString2(RELKIND_PARTITIONED_TABLE) ", "
+                        CppAsString2(RELKIND_PARTITIONED_INDEX) ")) as p (inherited)\n"
                         " LEFT JOIN pg_catalog.pg_class t"
                         " ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n");
 
                          vacopts->min_mxid_age);
    }
 
+   if (vacopts->missing_stats_only)
+   {
+       appendPQExpBufferStr(&catalog_query, " AND (\n");
+
+       /* regular stats */
+       appendPQExpBufferStr(&catalog_query,
+                            " EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
+                            " WHERE a.attrelid OPERATOR(pg_catalog.=) c.oid\n"
+                            " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
+                            " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
+                            " AND NOT a.attisdropped\n"
+                            " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
+                            " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
+                            " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
+                            " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
+                            " AND s.stainherit OPERATOR(pg_catalog.=) p.inherited))\n");
+
+       /* extended stats */
+       appendPQExpBufferStr(&catalog_query,
+                            " OR EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext e\n"
+                            " WHERE e.stxrelid OPERATOR(pg_catalog.=) c.oid\n"
+                            " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
+                            " AND e.stxstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
+                            " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext_data d\n"
+                            " WHERE d.stxoid OPERATOR(pg_catalog.=) e.oid\n"
+                            " AND d.stxdinherit OPERATOR(pg_catalog.=) p.inherited))\n");
+
+       /* expression indexes */
+       appendPQExpBufferStr(&catalog_query,
+                            " OR EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
+                            " JOIN pg_catalog.pg_index i"
+                            " ON i.indexrelid OPERATOR(pg_catalog.=) a.attrelid\n"
+                            " WHERE i.indrelid OPERATOR(pg_catalog.=) c.oid\n"
+                            " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
+                            " AND i.indkey[a.attnum OPERATOR(pg_catalog.-) 1::pg_catalog.int2]"
+                            " OPERATOR(pg_catalog.=) 0::pg_catalog.int2\n"
+                            " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
+                            " AND NOT a.attisdropped\n"
+                            " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
+                            " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
+                            " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
+                            " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
+                            " AND s.stainherit OPERATOR(pg_catalog.=) p.inherited))\n");
+
+       /* inheritance and regular stats */
+       appendPQExpBufferStr(&catalog_query,
+                            " OR EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
+                            " WHERE a.attrelid OPERATOR(pg_catalog.=) c.oid\n"
+                            " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
+                            " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
+                            " AND NOT a.attisdropped\n"
+                            " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
+                            " AND c.relhassubclass\n"
+                            " AND NOT p.inherited\n"
+                            " AND EXISTS (SELECT NULL FROM pg_catalog.pg_inherits h\n"
+                            " WHERE h.inhparent OPERATOR(pg_catalog.=) c.oid)\n"
+                            " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
+                            " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
+                            " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
+                            " AND s.stainherit))\n");
+
+       /* inheritance and extended stats */
+       appendPQExpBufferStr(&catalog_query,
+                            " OR EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext e\n"
+                            " WHERE e.stxrelid OPERATOR(pg_catalog.=) c.oid\n"
+                            " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
+                            " AND e.stxstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
+                            " AND c.relhassubclass\n"
+                            " AND NOT p.inherited\n"
+                            " AND EXISTS (SELECT NULL FROM pg_catalog.pg_inherits h\n"
+                            " WHERE h.inhparent OPERATOR(pg_catalog.=) c.oid)\n"
+                            " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext_data d\n"
+                            " WHERE d.stxoid OPERATOR(pg_catalog.=) e.oid\n"
+                            " AND d.stxdinherit))\n");
+
+       appendPQExpBufferStr(&catalog_query, " )\n");
+   }
+
    /*
     * Execute the catalog query.  We use the default search_path for this
     * query for consistency with table lookups done elsewhere by the user.
 
    if (analyze_in_stages)
    {
+       SimpleStringList **found_objs = NULL;
+
+       if (vacopts->missing_stats_only)
+           found_objs = palloc0(PQntuples(result) * sizeof(SimpleStringList *));
+
        /*
         * When analyzing all databases in stages, we analyze them all in the
         * fastest stage first, so that initial statistics become available
 
                vacuum_one_database(cparams, vacopts,
                                    stage,
-                                   objects, NULL,
+                                   objects,
+                                   vacopts->missing_stats_only ? &found_objs[i] : NULL,
                                    concurrentCons,
                                    progname, echo, quiet);
            }
    printf(_("  -j, --jobs=NUM                  use this many concurrent connections to vacuum\n"));
    printf(_("      --min-mxid-age=MXID_AGE     minimum multixact ID age of tables to vacuum\n"));
    printf(_("      --min-xid-age=XID_AGE       minimum transaction ID age of tables to vacuum\n"));
+   printf(_("      --missing-stats-only        only analyze relations with missing statistics\n"));
    printf(_("      --no-index-cleanup          don't remove index entries that point to dead tuples\n"));
    printf(_("      --no-process-main           skip the main relation\n"));
    printf(_("      --no-process-toast          skip the TOAST table associated with the table to vacuum\n"));