Outdated Postgres version
PostgreSQL 9.6 has reached EOL in 2021 at the point release of 9.6.24 (!) - not 9.6.7.
Upgrade to a current version at the earliest opportunity. If that's not an option, at least upgrade to the latest point release. Quoting our versioning policy:
We always recommend that all users run the latest available minor release for whatever major version is in use.
(Bolded as in the original.)
You are missing out on a host of fixes and improvements. Not least this one in Postgres 12:
Allow common table expressions (CTEs) to be inlined into the outer query (Andreas Karlsson, Andrew Gierth, David Fetter, Tom Lane)
Specifically, CTEs are automatically inlined if they have no
side-effects, are not recursive, and are referenced only once in the
query. Inlining can be prevented by specifying MATERIALIZED, or
forced for multiply-referenced CTEs by specifying NOT MATERIALIZED.
Previously, CTEs were never inlined and were always evaluated before
the rest of the query.
Meaning, the difference between your two displayed queries goes away almost completely. In older Postgres versions, the result of a CTE is always materialized. This poses as optimization barrier. Hence, a subquery is typically faster, especially when the optimizer can simplify the query - like in your case.
The CTE processes the whole table and materializes aggregated rows - before filtering the one timestamp of concern. A huge waste!
An optimized query only considers the timestamp of concern to begin with, which saves work proportional to the number of distinct timestamps in the table. This is particularly severe in the presence of an applicable index.
(There are queries where said optimization barrier helps. But this is not one of those.)
Related:
Better query
That said, your query can be simplified to:
SELECT count(*)
FROM (
SELECT id -- or even just an empty SELECT list
FROM tbl
WHERE timestamp = '2023-01-01 08:28:45'
GROUP BY id
) temp;
No need to add timestamp to GROUP BY or the SELECT list after filtering it to a single value.
And this is simpler and faster, yet - at least in modern Postgres:
SELECT count(DISTINCT id)
FROM tbl
WHERE timestamp = '2023-01-01 08:28:45';