PostgreSQL priority between CTE and real table
PostgreSQL searches the CTE namespace with scanNameSpaceForCTE as the very first thing it does in searchRangeTableForRel

if (!relation-&gt;schemaname)
{
    cte = scanNameSpaceForCTE(pstate, refname, &amp;ctelevelsup);
    if (!cte)
        isenr = scanNameSpaceForENR(pstate, refname);
}


If there is no schema


check for a CTE
check for a "Ephemeral Named Relation"


This is similar to Variable Shadowing if there is no namespace, and what the spec otherwise demands.

Everything in PostgreSQL has a namespace, if you want to address the table as compared to the CTE, consider providing (qualifying) the namespace.

CREATE TABLE foo AS VALUES (0);

WITH foo AS ( VALUES (1) )
SELECT *
FROM ( VALUES (2) ) AS foo   -- inline virtual-table
UNION TABLE foo              -- CTE
UNION TABLE public.foo;      -- explicitly qualified the namespace;