2010-05-24 4 views
19

다음 SQL 문을 생성하기 위해 Arel 및/또는 Active Record in Rails 3에 SELECT 쿼리를 중첩하려고 시도했습니다.Arel의 중첩 된 쿼리

SELECT sorted.* FROM (SELECT * FROM points ORDER BY points.timestamp DESC) AS sorted GROUP BY sorted.client_id 

서브 쿼리의 별명은

points = Table(:points) 
sorted = points.order('timestamp DESC').alias 

을 수행하여 생성 할 수 있습니다하지만 나는 (아주 추악한 소리 #to_sql를 호출 짧은) 부모 쿼리로 전달하는 방법으로 붙어 .

Arel (또는 활성 레코드)의 하위 쿼리로 SELECT 문을 사용하면 어떻게됩니까? 중첩 된 쿼리를 사용하지 않는이 쿼리를 수행하는 완전히 다른 방법이 있습니까?

답변

8

"중첩 쿼리"가 필요한 이유는 무엇입니까? 우리는 관계형 대수가 아닌 SQL의 사고 방식에서 생각하고있는 "중첩 쿼리"를 사용할 필요가 없습니다. 관계 대수로 우리가 관계를 도출하고 다른 입력으로 한 관계의 출력을 사용하므로 다음과 같은 사실이 개최 것 : 우리가 절대적으로 필요하지 않는 한 arel 할 수있는 이름 바꾸기를 떠날 경우

points = Table(:points, {:as => 'sorted'}) # rename in the options hash 
final_points = points.order('timestamp DESC').group(:client_id, :timestamp).project(:client_id, :timestamp) 

그것은 좋습니다.

여기에서 관계 (예 : 정렬. *)에서 모든 도메인을 투영 할 수 없기 때문에 client_id와 timestamp의 투영은 매우 중요합니다. 관계에 대해 그룹화 작업 내에서 사용될 모든 도메인을 구체적으로 투영해야합니다. 그 이유는 그룹화 된 client_id를 뚜렷하게 대표 할 * 값이 없다는 것입니다. 예를 들어

을 값이 27 또는 69 수 중 하나 때문에 그룹이 점수 도메인에 프로젝션을 수행 할 수 없습니다하지만 당신은 합 (점수를) 프로젝트 수 있다면 당신은

client_id | score 
---------------------- 
    4  | 27 
    3  | 35 
    2  | 22 
    4  | 69 

여기에 다음과 같은 테이블이 있다고 가정 해

그룹에 고유 한 값을 가진 도메인 속성 만 프로젝션 할 수 있습니다 (일반적으로 sum, max, min과 같은 집계 함수). 귀하의 질의를 통해 포인트가 타임 스탬프별로 정렬되었는지는 중요하지 않습니다. 왜냐하면 결국에는 client_id로 그룹화 될 것이기 때문입니다. 그룹화를 나타낼 수있는 단일 타임 스탬프가 없기 때문에 타임 스탬프 순서는 관련이 없습니다.

내가 Arel을 어떻게 도울 수 있는지 알려주십시오. 또한 저는 Arel을 핵심으로 사용하는 사람들을위한 학습 시리즈를 진행해 왔습니다. 시리즈 중 첫 번째는 http://Innovative-Studios.com/#pilot입니다. ActiveRecord 모델 포인트가 아닌 테이블 (: 점)을 사용한 이후로 어떻게 시작했는지 알 수 있습니다.

+0

자세한 답변을 보내 주셔서 감사합니다. "그룹화를 나타낼 수있는 단일 타임 스탬프가 없으므로 타임 스탬프 순서가 적절하지 않습니다." 네가 옳아; 나는 네가하는 말을 본다. MySQL은 client_id 그룹의 첫 번째 행만 반환함으로써이 불일치를 해결합니다. 나는 이것이 내가 의지해야 할 행동이 아니라는 것을 알았다. 목표는 모든 client_ids에 대한 가장 최근의 지점, 즉 각 client_id 그룹별로 최대 타임 스탬프가있는 단일 지점을 반환하는 것입니다. 자주 폴링되기 때문에 하나의 쿼리에서 수행하는 것이 중요합니다. – Schrockwell

+0

일부 집계 함수를 사용해야합니다. 우리가 "우리가 무엇을하려 하는가?"라고 물으면 대답은 SQL에서 max (timestamp)를 전달할 수 있도록 가장 최근 또는 최대 날짜를 찾는 것입니다. 이것은 Arel :: Attribute :: Expression :: Maximum에 해당하며 정렬 된 [: timestamp] .maximum()과 같이 Arel :: Attribute에서 구문 설탕으로 호출 할 수 있습니다. 하나의주의 사항이 있습니다. 그룹 작업 #group ('client_id, timestamp')에 타임 스탬프를 추가했는지 확인하십시오. 그렇지 않으면 전체 그룹화 시나리오에 오류가 발생합니다. 나는 MAX 집계 함수가 Postgres의 날짜에서 작동한다는 것을 알고 있으며, MySQL에서도 그렇다고 확신한다. – Snuggs

+1

첫째, 정렬 및 순서는 관계형 대수의 일부가 아닙니다. Arel은 그것을 어쨌든 정의합니다. 두 번째로, 서브 쿼리가 관계형 대수의 일부인지 아닌지는 부적합합니다. 개념적으로 SELECT의 결과는 WHERE 절이 실행될 때까지 표시되지 않습니다. 따라서 모든 데이터베이스 (예 : Postgres)가 WHERE 절에서 열 별칭을 허용하지 않고 대신 하위 쿼리를 사용합니다. Arel이 하위 쿼리를 처리 할 수없는 경우 WHERE 절의 이름에 별칭을 지정할 수 없습니다. Arel에 의존하여 이름을 생성 할 수 없으면 지저분해질 수 있습니다. –

7

Snuggs처럼 중첩 된 쿼리가 필요하지는 않다고 생각합니다. 중첩 쿼리가 필요한 사람들을위한 것입니다. 이것은 내가 지금까지 잘 해낸 것이지만 작동하지 않습니다 :

class App < ActiveRecord::Base 
    has_many :downloads 

    def self.not_owned_by_users(user_ids) 
    where(arel_table[:id].not_in( 
     Arel::SqlLiteral.new(Download.from_users(user_ids).select(:app_id).to_sql))) 
    end 
end 

class Download < ActiveRecord::Base 
    belongs_to :app 
    belongs_to :user 

    def self.from_users(user_ids) 
    where(arel_table[:user_id].in user_ids) 
    end 

end 

class User < ActiveRecord::Base 
    has_many :downloads 
end 

App.not_owned_by_users([1,2,3]).to_sql #=> 
# SELECT `apps`.* FROM `apps` 
# WHERE (`apps`.`id` NOT IN (
# SELECT app_id FROM `downloads` WHERE (`downloads`.`user_id` IN (1, 2, 3)))) 
# 
+0

Purest 솔루션, 속임수가'Arel :: SqlLiteral'이 있습니다 –

+0

''Arel :: SqlLiteral'''을 사용하는 대신 작은 수정으로''Arel :: Nodes :: SqlLiteral''' – jonathanccalixto

23

임시 테이블과 Arel에 대한 나의 접근 방법은 다음과 같습니다. Arel # to_sql을 사용하여 내부 쿼리에서 메서드를 전달하는 Arel #을 사용합니다.

inner_query = YourModel.where(:stuff => "foo") 
outer_query = YourModel.scoped # cheating, need an ActiveRelation 
outer_query = outer_query.from(Arel.sql("(#{inner_query.to_sql}) as results")). 
          select("*") 

이제 outer_query, paginate, select, group 등으로 멋진 일을 할 수 있습니다.

inner_query ->

select * from your_models where stuff='foo' 

outer_query ->

select * from (select * from your_models where stuff='foo') as results; 
+1

당신은 할 수 있습니다 가짜 모델이나 테이블 이름을 지정하지 않고도 outer_query를 얻을 수 있습니다.위의 마지막 두 줄은 "from"이 호출하는 내용 인이 줄로 바꿀 수 있습니다. outer_query = Arel :: SelectManager.new (Arel :: Table.engine, Arel.sql (" inner_query.to_sql}) as results ")) – DSimon

+1

이것은 전설적인 Mr.Todd입니다. – beck03076

6
Point. 
from(Point.order(Point.arel_table[:timestamp].desc).as("sorted")). 
select("sorted.*"). 
group("sorted.client_id") 
1

는 "순수"Arel에서이 작업을 수행하려면이 나를 위해 일한 : 물론

points = Arel::Table.new('points') 
sorted = Arel::Table.new('points', as: 'sorted') 
query = sorted.from(points.order('timestamp desc').project('*')).project(sorted[Arel.star]).group(sorted[:client_id]) 
query.to_sql 

, 귀하의 경우, 포인트 및 정렬 검색되고 꼬리 위와 같이 제조 된 것과는 대조적으로 Points 모델에서 생성됩니다.