CREATE TABLE module_prefix_snapshot_row ( tenant_id UUID NOT NULL, module_id UUID NOT NULL, ord INTEGER NOT NULL, prefix TEXT NOT NULL, community_id UUID, source TEXT NOT NULL DEFAULT '', PRIMARY KEY (tenant_id, module_id, ord), FOREIGN KEY (tenant_id, module_id) REFERENCES module_prefix_snapshot (tenant_id, module_id) ON DELETE CASCADE ); -- prefixes_json from Go json.Marshal(PrefixRow) used "Prefix"/"CommunityID"/"Source" before json tags. INSERT INTO module_prefix_snapshot_row (tenant_id, module_id, ord, prefix, community_id, source) SELECT tenant_id, module_id, (row_number() OVER (PARTITION BY tenant_id, module_id ORDER BY ordinality) - 1)::int, prefix, NULLIF(community_id, '')::uuid, COALESCE(source, '') FROM ( SELECT mps.tenant_id, mps.module_id, t.ordinality, COALESCE( NULLIF(trim(t.elem->>'prefix'), ''), NULLIF(trim(t.elem->>'Prefix'), '') ) AS prefix, COALESCE( NULLIF(trim(t.elem->>'community_id'), ''), NULLIF(trim(t.elem->>'CommunityID'), '') ) AS community_id, COALESCE( NULLIF(trim(t.elem->>'source'), ''), NULLIF(trim(t.elem->>'Source'), ''), '' ) AS source FROM module_prefix_snapshot mps CROSS JOIN LATERAL jsonb_array_elements(mps.prefixes_json) WITH ORDINALITY AS t(elem, ordinality) WHERE jsonb_typeof(mps.prefixes_json) = 'array' AND jsonb_array_length(mps.prefixes_json) > 0 ) parsed WHERE parsed.prefix IS NOT NULL AND trim(parsed.prefix) <> ''; ALTER TABLE module_prefix_snapshot DROP COLUMN prefixes_json;