2014-12-05 55 views
1

我正在使用RAILS 4和MySQL 服務通過服務場所有很多地方。我是否在AREL查詢中使用別名引用子查詢?

我想創建一個AREL相當於下面的SQL查詢:

SELECT DISTINCT 
    services . * 
FROM 
    services 
     INNER JOIN 
    (SELECT DISTINCT 
     `services` . * 
    FROM 
     `services` 
    LEFT OUTER JOIN `service_places` ON `service_places`.`service_id` = `services`.`id` 
    WHERE 
     `service_places`.`place_id` IN (SELECT DISTINCT 
       `places`.`id` 
      FROM 
       `places` 
      WHERE 
       (`places`.`place_name` LIKE '%war%'))) s1 ON s1.id = services.id 
     INNER JOIN 
    (SELECT DISTINCT 
     `services` . * 
    FROM 
     `services` 
    LEFT OUTER JOIN `service_places` ON `service_places`.`service_id` = `services`.`id` 
    WHERE 
     `service_places`.`place_id` IN (SELECT DISTINCT 
       `places`.`id` 
      FROM 
       `places` 
      WHERE 
       (`places`.`place_name` LIKE '%leam%'))) s2 ON s2.id = services.id; 

理想的情況下,這將通過insersect查詢來完成,但已經做了一些閱讀中,我發現,雖然AREL提供了一個交叉,MySQL的不支持它。所以我使用連接創建了SQL,它返回了我期望的數據。

我已經得到了一些AREL代碼來創建兩個子查詢和工作正常:

s = Service.arel_table 
sp = ServicePlace.arel_table 
p = Place.arel_table 

search_from = "leam" 
search_to = "war" 

############ 
# From QUERY 
############ 
from_subquery = Place.select(p[:id]).where(p[:place_name].matches("%#{search_from}%")).uniq 

from_query = Service.joins(
    s.join(sp, Arel::Nodes::OuterJoin).on(sp[:service_id].eq(s[:id])) 
.join_sql 
).uniq 

from_query = from_query.where(sp[:place_id].in(from_subquery.ast)) 

########## 
# To Query 
########## 
to_subquery = Place.select(p[:id]).where(p[:place_name].matches("%#{search_to}%")).uniq 

to_query = Service.joins(
    s.join(sp, Arel::Nodes::OuterJoin).on(sp[:service_id].eq(s[:id])) 
.join_sql 
).uniq 

to_query = to_query.where(sp[:place_id].in(to_subquery.ast)) 

我的問題進來試圖創建AREL查詢具有以下兩個子查詢。我試過,但它失敗:

query = Service.joins(from_subquery.as('s1')).on(s1[:id].eq(s[:id])) 
       .join(to_subquery.as('s2')).on(s2[:id].eq(s[:place_id])) 
       .join_sql 
       ).uniq 

NameError: undefined local variable or method `s1' for main:Object 

我很高興,我建這個方法是可以的,因爲我有一個類似的語法查詢的運行和工作沒有問題(見下文):

query = Service.joins(
      s.join(sp, Arel::Nodes::OuterJoin).on(sp[:service_id].eq(s[:id])) 
      .join(p, Arel::Nodes::OuterJoin).on(p[:id].eq(sp[:place_id])) 
      .join_sql 
     ).uniq 

我知道問題在於我如何引用關係中的子查詢,但不知道這是什麼。我已經看到了「create_table_alias」,「別名」和「as」方法的引用,但找不到任何幫助我的例子。這可能是非常明顯的事情,因爲我總是看不到它。

如果有人能幫助,我將不勝感激。

在此先感謝。

UPDATE 01:

我發現一個AREL語法創建人http://www.scuttle.io/,可以是用於生成AREL語法是有用的。它並不適用於我的整體,但對於它的小塊。您需要將「喜歡」更改爲「=」,因爲它不包含它們。不過,您可以將「eq」更改回「匹配」。

UPDATE 02:使用CTE與該代碼創建的阿雷爾:: SelectManager對象

試過。但是,進一步的研究表明你不能在MYSQL中使用CTE。下面顯示了我使用的代碼作爲參考。

cte_table = Arel::Table.new(:cte_table) 
composed_cte = Arel::Nodes::As.new(cte_table, from_query) 
s.join(cte_table).on(s[:id].eq(cte_table[:id])).project(s[Arel.star]).with(composed_cte) 

回答

1

這是一種替代方法。它有使用兩個SQL查詢而不是一個的缺點,但我會建議它是更可維護的代碼:

places = Place.arel_table 
leam = Service.joins(:places).where(places[:place_name].matches('%leam%') 
wark = Service.joins(:places).where(places[:place_name].matches('%wark%') 
leam.where(id: wark.pluck(:id).uniq) 
+0

非常感謝,下週我見到你時必須讓你品嚐一杯。 – 2014-12-12 16:47:07

7

在阿雷爾as主要是用來指定在SELECT -clause特定字段名稱(粗略地說,在阿雷爾呈現爲project),表別名以不同的方式典型地處理。

問題是,在您的工作示例中,您使用不包含別名的表對象構建子查詢。但是要在SQL中爲該表別名,而不是表對象,則需要從表對象生成一個別名表對象。這可能聽起來更復雜。

給定一個混淆表你只需要使用它而不是表本身。

p = Place.arel_table # Actual table 
pa = p.alias   # Table alias 

# Use an actual table object just as an entry point to Arel, not sure if necessary 
from_subquery = p. 
    # Now use an alias (the name `pa` will likely be different, as it is chosen by Arel, 
    # but should be the same for the same alias object): 
    # SELECT pa.id FROM pa 
    project(pa[:id]).from(pa). 
    where(
    pa[:place_name].matches("%#{search_from}%") # Use alias for condition 
).uniq 

一旦做到這一點,你可以引用別名子查詢中與同一個對象,pa

這是一個基本的例子,鬆散地基於arel README,但它顯示了你需要擺脫錯誤的方式:這樣你就可以將別名表對象放入你的範圍並且能夠自由使用它。

+0

感謝您抽出時間來響應D端。我使用上面的示例,但必須刪除.uniq。它產生一個AREL :: SelectManager類,而我的產生一個ActiveRelation類。我仍然無法解決如何在我的主查詢中使用from_subquery。我會繼續堵塞,我相信我會最終得到它。 – 2014-12-10 16:07:43

+1

+1,應該是被接受的答案IMO(使用arel構建子查詢,而不是像在接受的答案中那樣執行兩個查詢) – 2015-05-21 08:40:03

0

假設服務相關聯通過的has_many到的地方:通過關係「的地方」,你嘗試過:

places = Place.arel_table 
Service.joins(:places).where(
    places[:place_name].matches('%leam%').or(
    places[:place_name].matches('%war%')) 
) 
+0

返回一個錯誤:SyntaxError:(irb):596:語法錯誤,意外的'\ n',期待tASSOC – 2014-12-11 11:36:15

+0

是的關係是服務有很多地方通過服務的地方。 – 2014-12-11 11:37:32

+0

我修改了我的解決方案。我不認爲你需要在名稱散列中包含匹配。這個簡化版本應該可以工作 – ReggieB 2014-12-11 13:23:11