當您不能使用 AND 關鍵字時,如何為查找表編寫“和”查詢?
我正在建立一個實踐網站,允許使用者根據酒店提供的便利設施搜尋酒店。從 FE 中,使用者將選擇他們想要的許多便利設施的複選框,並且便利設施
key
將被發送到後端。在後端,我有這三個表:hotels | id | name | vacancies | ---| ------------ | -------- | | 1| Marriott | 0 | | 2| Best Western | 10 | | 3| Sheraton | 3 | ------------------------------ amenities | id | name | key | | ---| --------------------- | --------------------- | | 1| Cafe | cafe | | 2| Wheelchair Accessible | wheelchair_accessible | | 3| Wifi | wifi | ---------------------------------------------------- hotels_amenities_lookup | id | amenity_id | hotel_id | | ---| ---------- | -------- | | 1| 1 | 3 | | 2| 2 | 1 | | 3| 2 | 2 | | 4| 2 | 3 | | 5| 3 | 2 | | 6| 3 | 1 | ----------------------------
要搜尋一種便利設施,例如
wheelchair_accessible
,我會這樣做:WITH hotels_with_amenity as ( SELECT ha.hotel_id FROM hotels_amenities_lookup ha JOIN ( SELECT id from amenities a WHERE a.key = 'wheelchair_accessible' ) amenity ON ha.amenity_id = amenity.id ) SELECT h.name, h.vacancies FROM hotels h, hotels_with_amenity hwa WHERE h.id = hwa.hotel_id;
返回所有三個酒店。
問題是:如果使用者選擇了多個設施,
wheelchair_accessible
例如wifi
,我將如何查詢同時擁有這兩個設施的酒店?用這個目前的設置,我做不到WHERE a.key = 'wheelchair_accessible AND a.key = 'wifi'
有沒有更好的方法來設置這些表以使查詢更容易?
我是關係數據庫的新手,很可能我在這裡遺漏了一些明顯的東西。
**關係劃分**的經典案例!
最有效的查詢技術之一:
SELECT h.name, h.vacancies FROM hotels_amenities_lookup ha1 JOIN hotels_amenities_lookup ha2 USING (hotel_id) -- more? JOIN hotels h ON h.id = ha1.hotel_id WHERE ha1.amenity_id = (SELECT id FROM amenities WHERE key = 'wheelchair_accessible') AND ha2.amenity_id = (SELECT id FROM amenities WHERE key = 'wifi') -- more?
db<>fiddle here(重用 McNets fiddle,謝謝!)
這些指數有助於:
amenity(key, id)
– ①hotel_amenity(amenity_id, hotel_id)
hotel_amenity(hotel_id, amenity_id)
– ②hotel(id)
– PK① 可能的形式
UNIQUE (key) INCLUDE (id)
。有關的:② 是的,另一個具有反轉列的多列索引。看:
但是還有很多其他的方法:
旁白1:上面是 using
AND
,但實際上並不是要避免使用關鍵字,是嗎?旁白2:只是
hotels_amenities
代替hotels_amenities_lookup
?數據庫設計
但是,正如 Laurenz 所評論的,第一個問題是關於正確的數據庫設計。這在很大程度上取決於確切的(未公開的)規格和要求。
您完全規範化的多對多實現是一個“包羅萬象”的解決方案。您可以根據需要組合任意數量的酒店和設施,輕鬆重命名、添加、刪除酒店和設施,在每個表中添加任意數量的附加資訊,並且使用普通 btree 索引進行索引很簡單。對任何或所有表中的許多更新具有強大的並發寫入負載。但是磁碟佔用空間很大。使用許多便利設施優化您的查詢需要一些工作。
為了組合一個充滿便利的手,呈現的查詢是快速的,並且由查詢計劃器自動優化(使用列統計中的 MCV 列表)。對於更多的便利設施(更多
join_collapse_limit
),您必須切換到更智能的解決方案,如您找到的相關答案中概述的:解決方案的關鍵是首先過濾稀有的便利設施。
關於
join_collapse_limit
:OTOH,對於一組小而穩定的便利設施,像 Laurenz 建議的帶有布爾標誌的簡單設計具有更小的磁碟佔用空間。也許為每個標誌添加一個部分索引。
或者一系列
"char"[]
帶有 GIN 索引的選項(使用 ? 優化)的便利設施。甚至可以以MATERIALIZED VIEW
.每個都有匹配的查詢樣式。
如果儲存空間很重要(它總是以某種方式):
一如既往:這取決於。
一種解決方案可能是使用 GROUP BY + HAVING COUNT。
select hal.hotel_id, count(*) as num_amenities from hotels_amenities_lookup hal join amenities am on am.id = hal.amenity_id where am.key = 'wifi' or am.key = 'wheelchair_accessible' group by hal.hotel_id having count(*) = 2; hotel_id | num_amenities -------: | ------------: 1 | 2 2 | 2
此模型的缺點是條件在您的查詢中是硬編碼的,對於通用表單,您可以使用帶有所選設施的新(臨時)表。
創建表 selected_amenities(id int);
插入 selected_amenities 值 (2),(3);
現在您可以通過這種方式將此表添加到上一個查詢中:
select h.id, h.name, h.vacancies, count(*) as num_amenities from hotels_amenities_lookup hal join hotels h on h.id = hal.hotel_id join amenities am on am.id = hal.amenity_id join selected_amenities sa on sa.id = am.id group by h.id, h.name, h.vacancies having count(*) = (select count(*) from selected_amenities);
編號 | 姓名 | 職位空缺 | num_amenities -: | :----------- | --------: | ------------: 2 | 最佳西方 | 10 | 2 1 | 萬豪 | 0 | 2
db<>在這裡擺弄