1+ -- Dropping the materialized view if it exists as we cannot update it
12DROP MATERIALIZED VIEW IF EXISTS FeedSearch;
23ALTER TABLE gtfsdataset
34ADD COLUMN agency_timezone VARCHAR (255 ),
45ALTER COLUMN service_date_range_start SET DATA TYPE TIMESTAMP WITH TIME ZONE USING service_date_range_start::TIMESTAMP WITH TIME ZONE ,
5- ALTER COLUMN service_date_range_end SET DATA TYPE TIMESTAMP WITH TIME ZONE USING service_date_range_end::TIMESTAMP WITH TIME ZONE ;
6+ ALTER COLUMN service_date_range_end SET DATA TYPE TIMESTAMP WITH TIME ZONE USING service_date_range_end::TIMESTAMP WITH TIME ZONE ;
7+
8+ CREATE MATERIALIZED VIEW FeedSearch AS
9+ SELECT
10+ -- feed
11+ Feed .stable_id AS feed_stable_id,
12+ Feed .id AS feed_id,
13+ Feed .data_type ,
14+ Feed .status ,
15+ Feed .feed_name ,
16+ Feed .note ,
17+ Feed .feed_contact_email ,
18+ -- source
19+ Feed .producer_url ,
20+ Feed .authentication_info_url ,
21+ Feed .authentication_type ,
22+ Feed .api_key_parameter_name ,
23+ Feed .license_url ,
24+ Feed .provider ,
25+ Feed .operational_status ,
26+ -- official status
27+ Latest_official_status .is_official AS official,
28+ -- latest_dataset
29+ Latest_dataset .id AS latest_dataset_id,
30+ Latest_dataset .hosted_url AS latest_dataset_hosted_url,
31+ Latest_dataset .downloaded_at AS latest_dataset_downloaded_at,
32+ Latest_dataset .bounding_box AS latest_dataset_bounding_box,
33+ Latest_dataset .hash AS latest_dataset_hash,
34+ Latest_dataset .agency_timezone AS latest_dataset_agency_timezone,
35+ Latest_dataset .service_date_range_start AS latest_dataset_service_date_range_start,
36+ Latest_dataset .service_date_range_end AS latest_dataset_service_date_range_end,
37+ -- external_ids
38+ ExternalIdJoin .external_ids ,
39+ -- redirect_ids
40+ RedirectingIdJoin .redirect_ids ,
41+ -- feed gtfs_rt references
42+ FeedReferenceJoin .feed_reference_ids ,
43+ -- feed gtfs_rt entities
44+ EntityTypeFeedJoin .entities ,
45+ -- locations
46+ FeedLocationJoin .locations ,
47+ -- translations
48+ FeedCountryTranslationJoin .translations AS country_translations,
49+ FeedSubdivisionNameTranslationJoin .translations AS subdivision_name_translations,
50+ FeedMunicipalityTranslationJoin .translations AS municipality_translations,
51+ -- full-text searchable document
52+ setweight(to_tsvector(' english' , coalesce(unaccent(Feed .feed_name ), ' ' )), ' C' ) ||
53+ setweight(to_tsvector(' english' , coalesce(unaccent(Feed .provider ), ' ' )), ' C' ) ||
54+ setweight(to_tsvector(' english' , coalesce(unaccent((
55+ SELECT string_agg(
56+ coalesce(location- >> ' country_code' , ' ' ) || ' ' ||
57+ coalesce(location- >> ' country' , ' ' ) || ' ' ||
58+ coalesce(location- >> ' subdivision_name' , ' ' ) || ' ' ||
59+ coalesce(location- >> ' municipality' , ' ' ),
60+ ' '
61+ )
62+ FROM json_array_elements(FeedLocationJoin .locations ) AS location
63+ )), ' ' )), ' A' ) ||
64+ setweight(to_tsvector(' english' , coalesce(unaccent((
65+ SELECT string_agg(
66+ coalesce(translation- >> ' value' , ' ' ),
67+ ' '
68+ )
69+ FROM json_array_elements(FeedCountryTranslationJoin .translations ) AS translation
70+ )), ' ' )), ' A' ) ||
71+ setweight(to_tsvector(' english' , coalesce(unaccent((
72+ SELECT string_agg(
73+ coalesce(translation- >> ' value' , ' ' ),
74+ ' '
75+ )
76+ FROM json_array_elements(FeedSubdivisionNameTranslationJoin .translations ) AS translation
77+ )), ' ' )), ' A' ) ||
78+ setweight(to_tsvector(' english' , coalesce(unaccent((
79+ SELECT string_agg(
80+ coalesce(translation- >> ' value' , ' ' ),
81+ ' '
82+ )
83+ FROM json_array_elements(FeedMunicipalityTranslationJoin .translations ) AS translation
84+ )), ' ' )), ' A' ) AS document
85+ FROM Feed
86+ LEFT JOIN (
87+ SELECT *
88+ FROM gtfsdataset
89+ WHERE latest = true
90+ ) AS Latest_dataset ON Latest_dataset .feed_id = Feed .id AND Feed .data_type = ' gtfs'
91+ LEFT JOIN (
92+ SELECT
93+ feed_id,
94+ json_agg(json_build_object(' external_id' , associated_id, ' source' , source)) AS external_ids
95+ FROM externalid
96+ GROUP BY feed_id
97+ ) AS ExternalIdJoin ON ExternalIdJoin .feed_id = Feed .id
98+ LEFT JOIN (
99+ SELECT
100+ gtfs_rt_feed_id,
101+ array_agg(FeedReferenceJoinInnerQuery .stable_id ) AS feed_reference_ids
102+ FROM FeedReference
103+ LEFT JOIN Feed AS FeedReferenceJoinInnerQuery ON FeedReferenceJoinInnerQuery .id = FeedReference .gtfs_feed_id
104+ GROUP BY gtfs_rt_feed_id
105+ ) AS FeedReferenceJoin ON FeedReferenceJoin .gtfs_rt_feed_id = Feed .id AND Feed .data_type = ' gtfs_rt'
106+ LEFT JOIN (
107+ SELECT
108+ target_id,
109+ json_agg(json_build_object(' target_id' , target_id, ' comment' , redirect_comment)) AS redirect_ids
110+ FROM RedirectingId
111+ GROUP BY target_id
112+ ) AS RedirectingIdJoin ON RedirectingIdJoin .target_id = Feed .id
113+ LEFT JOIN (
114+ SELECT
115+ LocationFeed .feed_id ,
116+ json_agg(json_build_object(' country' , country, ' country_code' , country_code, ' subdivision_name' ,
117+ subdivision_name, ' municipality' , municipality)) AS locations
118+ FROM Location
119+ LEFT JOIN LocationFeed ON LocationFeed .location_id = Location .id
120+ GROUP BY LocationFeed .feed_id
121+ ) AS FeedLocationJoin ON FeedLocationJoin .feed_id = Feed .id
122+ LEFT JOIN (
123+ SELECT DISTINCT ON (feed_id) *
124+ FROM officialstatushistory
125+ ORDER BY feed_id, timestamp DESC
126+ ) AS Latest_official_status ON Latest_official_status .feed_id = Feed .id
127+ LEFT JOIN (
128+ SELECT
129+ LocationFeed .feed_id ,
130+ json_agg(json_build_object(' value' , Translation .value , ' key' , Translation .key )) AS translations
131+ FROM Location
132+ LEFT JOIN Translation ON Location .country = Translation .key
133+ LEFT JOIN LocationFeed ON LocationFeed .location_id = Location .id
134+ WHERE Translation .language_code = ' en'
135+ AND Translation .type = ' country'
136+ AND Location .country IS NOT NULL
137+ GROUP BY LocationFeed .feed_id
138+ ) AS FeedCountryTranslationJoin ON FeedCountryTranslationJoin .feed_id = Feed .id
139+ LEFT JOIN (
140+ SELECT
141+ LocationFeed .feed_id ,
142+ json_agg(json_build_object(' value' , Translation .value , ' key' , Translation .key )) AS translations
143+ FROM Location
144+ LEFT JOIN Translation ON Location .subdivision_name = Translation .key
145+ LEFT JOIN LocationFeed ON LocationFeed .location_id = Location .id
146+ WHERE Translation .language_code = ' en'
147+ AND Translation .type = ' subdivision_name'
148+ AND Location .subdivision_name IS NOT NULL
149+ GROUP BY LocationFeed .feed_id
150+ ) AS FeedSubdivisionNameTranslationJoin ON FeedSubdivisionNameTranslationJoin .feed_id = Feed .id
151+ LEFT JOIN (
152+ SELECT
153+ LocationFeed .feed_id ,
154+ json_agg(json_build_object(' value' , Translation .value , ' key' , Translation .key )) AS translations
155+ FROM Location
156+ LEFT JOIN Translation ON Location .municipality = Translation .key
157+ LEFT JOIN LocationFeed ON LocationFeed .location_id = Location .id
158+ WHERE Translation .language_code = ' en'
159+ AND Translation .type = ' municipality'
160+ AND Location .municipality IS NOT NULL
161+ GROUP BY LocationFeed .feed_id
162+ ) AS FeedMunicipalityTranslationJoin ON FeedMunicipalityTranslationJoin .feed_id = Feed .id
163+ LEFT JOIN (
164+ SELECT
165+ feed_id,
166+ array_agg(entity_name) AS entities
167+ FROM EntityTypeFeed
168+ GROUP BY feed_id
169+ ) AS EntityTypeFeedJoin ON EntityTypeFeedJoin .feed_id = Feed .id AND Feed .data_type = ' gtfs_rt'
170+ ;
171+
172+
173+ -- This index allows concurrent refresh on the materialized view avoiding table locks
174+ CREATE UNIQUE INDEX idx_unique_feed_id ON FeedSearch(feed_id);
175+
176+ -- Indices for feedsearch view optimization
177+ CREATE INDEX feedsearch_document_idx ON FeedSearch USING GIN(document);
178+ CREATE INDEX feedsearch_feed_stable_id ON FeedSearch(feed_stable_id);
179+ CREATE INDEX feedsearch_data_type ON FeedSearch(data_type);
180+ CREATE INDEX feedsearch_status ON FeedSearch(status);
0 commit comments