I have the following SQL to create a materialized view and the SQL seems reasonable to me but it's unclear from Postgres docs what should happen in this case.
I am listing the columns when creating the matview and casting to the correct types in the SELECT and all columns work except one, while it's cast to numeric(9, 2) Postgres returns that column as numeric with no precision. The values actually appear to be cast correctly, although the column type is wrong. Here's an excerpt:
CREATE MATERIALIZED VIEW "plus"."quote_exposure_chars" (... "distance_to_coast_ft", ...)
AS
SELECT
...,
MAX(CASE
WHEN (qexpcf.field_name = 'distance_to_coast_ft'::text)
THEN (qexpcf.field_value)::numeric(9,2)
ELSE NULL::numeric
END) AS ...,
distance_to_coast_ft, ...
Is it something about casting the null to numeric or not specifying the precision of the new alias column name? The PG doc on matviews seems to gloss over all of this.
The case expression can't be used to dynamically type the result and a single column cannot hold different data types in different rows, or even the same type with different typemods:
The data types of all the result expressions must be convertible to a single output type.
A more extreme example:
select case when .5>random()
then 'abc'::text
else 'today'::date
end;
ERROR: CASE types date and text cannot be matched LINE 2: then 'abc'::text ^
What you're seeing is automatic data type conversion rules coercing your numeric(9,2) and plain numeric to just numeric. However, if your goal was to round your qexpcf.field_value down to 2 decimal places, this should still be working fine before they kick in, as long as you don't mind its rounding logic (and the unexpected pg_typeof()).
As hinted by @Frank Heikens, you could probably just do the same cast in both places, or move it outside the case:
MAX((CASE WHEN (qexpcf.field_name = 'distance_to_coast_ft'::text)
THEN (qexpcf.field_value)
END)::numeric(9,2)
)
Moving it inside/outside max() will affect whether they get compared before or after getting rounded/truncated. The else null is default, implied case behaviour, so it can be skipped.