SlideShare uma empresa Scribd logo
1 de 23
Baixar para ler offline
いまさらサブクエリ
2018.06.12 Otemachi.rb #7
Yuya Taki
self.inspect
[1] https://github.com/muramurasan/okuribito [2] http://poject.herokuapp.com/
➢ gemのロゴを書いたり…
➢ たまにQiitaの記事を書いたり…
➢ 新卒で某SIerにてスマートメータのヘッドエ
ンドシステムの開発に携わったあと、渋谷の
ベンチャー企業にてRuby on Railsを学び、
2016年10月エネチェンジに入社。
Name : Yuya Taki
GitHub : yuyasat
Qiita : yuyasat
[1]
若輩者ですので、何卒優しくご教授いた
だければと思います。
(commitはしていない)
➢ ○よ○よ風ゲームをReact.jsで実装し
たり
[2]
今回のモデル
0
*
0
*
id | name
----+------------
1 | 多部未華子
2 | 佐津川愛美
3 | 新垣結衣
4 | 堀北真希
5 | 吉高由里子
6 | 悠城早矢
id | actress_id | year | title
----+------------+------+---------------------
1 | 2 | 2005 | 蝉しぐれ
2 | 1 | 2006 | 夜のピクニック
3 | 4 | 2005 | ALWAYS 三丁目の夕日
4 | 2 | 2012 | 忍道-SHINOBIDO-
5 | 2 | 2016 | 貞子vs伽椰子
6 | 4 | 2013 | 県庁おもてなし課
7 | 5 | 2013 | 真夏の方程式
id | movie_id | key
----+----------+------------
1 | 1 | 時代劇
2 | 1 | 子役
3 | 3 | 昭和
4 | 5 | ホラー
5 | 7 | ミステリー
6 | 7 | 夏
7 | 6 | 公務員
8 | 6 | 地方活性
9 | 1 | 夏
class Actress < ApplicationRecord
has_many :movies
has_many :tags, through: :movies
end
class Movie < ApplicationRecord
has_many :tags
belongs_to :actress
end
class Tag < ApplicationRecord
belongs_to :movie
end
actresses
movies
tags
サブクエリとは
➢ クエリ内のクエリ
SELECT
"movies".*
FROM
"movies"
WHERE
"movies"."actress_id" IN (
SELECT
"actresses"."id"
FROM
"actresses"
WHERE
"actresses"."name" = '堀北真希'
)
;
0
*
0
*
id | actress_id | year | title
----+------------+------+---------------------
3 | 4 | 2005 | ALWAYS 三丁目の夕日
6 | 4 | 2013 | 県庁おもてなし課
moviesに紐づけられているactressを取得したい
➢ moviesテーブルと内部結合すればいい
Actress.joins(:movies)
SELECT
"actresses".*
FROM
"actresses"
INNER JOIN
"movies"
ON
"movies"."actress_id" = "actresses"."id"
;
id | name
----+------------
2 | 佐津川愛美
1 | 多部未華子
4 | 堀北真希
2 | 佐津川愛美
2 | 佐津川愛美
4 | 堀北真希
5 | 吉高由里子
0
*
0
*
actresses
movies
重複を排除するためにdistinctする
SELECT
DISTINCT "actresses".*
FROM
"actresses"
INNER JOIN
"movies"
ON
"movies"."actress_id" = "actresses"."id"
;
id | name
----+------------
2 | 佐津川愛美
1 | 多部未華子
4 | 堀北真希
5 | 吉高由里子
0
*
0
*
Actress.joins(:movies).distinct
よく使いそうなのでscopeにする
class Actress < ApplictionRecord
scope :having_movies, -> {
joins(:movies).distinct
}
end
0
*
0
*
scope内でjoinはあまり使いたくない
Tag.joins(movie: :actress).merge(Actress.having_movies)
SELECT
DISTINCT "tags".*
FROM
"tags"
INNER JOIN
"movies"
ON
"movies"."id" = "tags"."movie_id"
INNER JOIN
"actresses"
ON
"actresses"."id" = "movies"."actress_id"
INNER JOIN
"movies"
ON
"movies"."actress_id" = "actresses"."id"
;
ERROR: table name "movies" specified
more than once
0
*
0
*
movieに紐づいているactressに紐づいているmovieのtagを取得したい(かなり作為的。例がよくない)
回避策(サブクエリ)
SELECT
"tags".*
FROM
"tags"
INNER JOIN
"movies"
ON
"movies"."id" = "tags"."movie_id"
INNER JOIN
"actresses"
ON
"actresses"."id" = "movies"."actress_id"
WHERE
"actresses"."id" IN (
SELECT
DISTINCT "actresses"."id"
FROM
"actresses"
INNER JOIN
"movies"
ON
"movies"."actress_id" = "actresses"."id"
)
;
Tag.joins(movie: :actress).where(actresses: { id: Actress.having_movies })
0
*
0
*
➢ 突然のテーブル名
○ モデルで記述したい
➢ サブクエリの中でもJOIN
➢ なんか匂う
やっぱりmergeを使いたい
Tag.joins(movie: :actress).merge(Actress.having_movies)
SELECT
"tags".*
FROM
"tags"
INNER JOIN
"movies"
ON
"movies"."id" = "tags"."movie_id"
INNER JOIN
"actresses"
ON
"actresses"."id" = "movies"."actress_id"
WHERE
"actresses"."id" IN (
SELECT
DISTINCT "movies"."actress_id"
FROM
"movies"
)
;
➢ 実行したいSQLはこれ
class Actress < ApplictionRecord
scope :having_movies, -> {
where(id: Movie.select("actress_id").distinct)
}
end
0
*
0
*
実行コスト(EXPLAIN)
class Actress < ApplictionRecord
scope :having_movies, -> { 
joins(:movies).distinct
}
end
Tag.joins(movie: :actress).where(actresses: { id: Actress.having_movies })
class Actress < ApplictionRecord
scope :having_movies, -> {
where(id: Movie.select("actress_id").distinct)
}
end
Tag.joins(movie: :actress).merge(Actress.having_movies)
class Actress < ApplictionRecord
scope :having_movies, -> {
where(id: Movie.select("actress_id"))
}
end
Tag.joins(movie: :actress).merge(Actress.having_movies)
171.22..215.55
90.27..115.51
93.93..122.11
0
*
0
*
(補足)もう少し現実的な例
SELECT
DISTINCT "tags".*
FROM
"tags"
INNER JOIN "movies" ON "movies"."id" = "tags"."movie_id"
INNER JOIN "actresses" ON "actresses"."id" = "movies"."actress_id"
INNER JOIN "actress_song_relationships" ON "actress_song_relationships"."actress_id" = "actresses"."id"
INNER JOIN
"songs"
ON
"songs"."id" = "actress_song_relationships"."song_id"
INNER JOIN
"songs"
ON
"songs"."id" = "movies"."theme_song_id"
WHERE
"songs"."name" = '世界が終わる夜に '
ORDER BY
"tags"."id"
;
「世界が終わる夜に」がすきな曲としてもつ女優が主演かつ、主題歌が「かざぐる
ま」の映画のタグを取得したい。
ERROR: table name "songs" specified more than once
Tag.joins(movie: :actress).merge(
Actress.joins(:favorite_songs).merge(Song.where(name: '世界が終わる夜に ')).distinct
).merge(
Movie.joins(:theme_song).merge(Song.where(name: 'かざぐるま ')).distinct
)
例が非現実的だっ
たので
ここをscopeにしたとする
(補足)回避策:サブクエリ
SELECT
DISTINCT "tags".*
FROM
"tags"
INNER JOIN "movies" ON "movies"."id" = "tags"."movie_id"
INNER JOIN "actresses" ON "actresses"."id" = "movies"."actress_id"
INNER JOIN "Actress_song_relationships" ON "actress_song_relationships"."actress_id" = "actresses"."id"
INNER JOIN "songs" ON "songs"."id" = "actress_song_relationships"."song_id"
WHERE
"songs"."name" = '世界が終わる夜に '
AND
"movies"."theme_song_id" IN (
SELECT
"songs"."id"
FROM
"songs"
WHERE
"songs"."name" = 'かざぐるま '
)
Tag.joins(movie: :actress).merge(
Actress.joins(:favorite_songs).merge(Song.where(name: '世界が終わる夜に ')).distinct
).merge(
Movie.where(theme_song: Song.where(name: 'かざぐるま '))
)
「世界が終わる夜に」がすきな曲としてもつ女優が主演かつ、主題歌が「かざぐる
ま」の映画のタグを取得したい。
片方をサブクエリにする
もう少し複雑なサブクエリの例
➢ 映画公開年の降順にActressを取り出したい
id | actress_id | year | title
----+------------+------+---------------------
1 | 2 | 2005 | 蝉しぐれ
2 | 1 | 2006 | 夜のピクニック
3 | 4 | 2005 | ALWAYS 三丁目の夕日
4 | 2 | 2012 | 忍道-SHINOBIDO-
5 | 2 | 2016 | 貞子vs伽椰子
6 | 4 | 2013 | 県庁おもてなし課
7 | 5 | 2013 | 真夏の方程式
id | name
----+------------
2 | 佐津川愛美
4 | 堀北真希
5 | 吉高由里子
1 | 多部未華子
0
*
0
*
id | name
----+------------
1 | 多部未華子
2 | 佐津川愛美
3 | 新垣結衣
4 | 堀北真希
5 | 吉高由里子
6 | 悠城早矢
actresses
movies
SQLで記述
SELECT
actresses.*
FROM
actresses
INNER JOIN (
SELECT
"movies"."actress_id" AS actress_id,
MAX(year) AS max_year
FROM
"movies"
GROUP BY
"movies"."actress_id"
) AS movies_max_year
ON actresses.id = movies_max_year.actress_id
ORDER BY max_year DESC, actresses.id ASC
actress_id | max_year
------------+----------
5 | 2013
4 | 2013
2 | 2016
1 | 2006
id | name
----+------------
1 | 多部未華子
2 | 佐津川愛美
3 | 新垣結衣
4 | 堀北真希
5 | 吉高由里子
6 | 悠城早矢
id | name
----+------------
2 | 佐津川愛美
4 | 堀北真希
5 | 吉高由里子
1 | 多部未華子
0
*
0
*
actressesmovies_max_year
ActiveRecordで記述(find_by_sql)
Actress.find_by_sql(%|
SELECT
actresses.*
FROM
actresses
INNER JOIN (
SELECT
"movies"."actress_id" AS actress_id,
MAX(year) AS max_year
FROM
"movies"
GROUP BY
"movies"."actress_id"
) AS movies_max_year
ON actresses.id = movies_max_year.actress_id
ORDER BY max_year DESC, actresses.id ASC
|)
➢ Arrayで出力
➢ Kaminariが使えない
○ Kaminari.paginate_arrayを使えばArrayでも使える
が、scopeで使いたい。
[#<Actress:0x007f8978525928
id: 2,
name: "佐津川愛美 ">,
#<Actress:0x007f8972e37968
id: 4,
name: "堀北真希">,
#<Actress:0x007f897851d0c0
id: 5,
name: "吉高由里子 ">,
#<Actress:0x007f897850fd58
id: 1,
name: "多部未華子 ">]
0
*
0
*
ActiveRecordで記述(Arelを使う)
SELECT
actresses.*
FROM
actresses
INNER JOIN (
SELECT
"movies"."actress_id" AS actress_id,
MAX(year) AS max_year
FROM
"movies"
GROUP BY
"movies"."actress_id"
) AS movies_max_year
ON actresses.id = movies_max_year.actress_id
ORDER BY max_year DESC, actresses.id ASC
actress_at = Actress.arel_table
movie_at = Movie.arel_table
movies_max_year = movie_at.project(
Arel.sql(%|
"movies"."actress_id" as actress_id,
MAX(year) as max_year
|)
).group("movies.actress_id").as("movies_max_year")
join_conds = actress_at.join(
movies_max_year,
Arel::Nodes::InnerJoin
).on(
movies_max_year[:actress_id].eq(actress_at[:id])
).join_sources
Actress.joins(join_conds).order("movies_max_year.max_year DESC")
0
*
0
*
ActiveRecordで記述(joinsの中にSQL直書き)
SELECT
actresses.*
FROM
actresses
INNER JOIN (
SELECT
"movies"."actress_id" AS actress_id,
MAX(year) AS max_year
FROM
"movies"
GROUP BY
"movies"."actress_id"
) AS movies_max_year
ON actresses.id = movies_max_year.actress_id
ORDER BY max_year DESC, actresses.id ASC
Actress.joins(%|
INNER JOIN (
SELECT
"movies"."actress_id" AS actress_id,
MAX(year) AS max_year
FROM
"movies"
GROUP BY
"movies"."actress_id"
) AS movies_max_year
ON actresses.id = movies_max_year.actress_id
|).order("max_year desc, actresses.id asc")
0
*
0
*
scopeにして
class Actress < ApplicationRecord
scope :order_by_movie_year, -> {
actress_at = arel_table
movie_at = Movie.arel_table
movies_max_year = movie_at.project(
Arel.sql(%|
"movies"."actress_id" as actress_id,
      MAX(year) as max_year
|)
).group("movies.actress_id").as("movies_max_year")
join_conds = actress_at.join(
movies_max_year,
    Arel::Nodes::InnerJoin
).on(
movies_max_year[:actress_id].eq(actress_at[:id])
).join_sources
joins(join_conds).order("movies_max_year.max_year DESC")
}
end
pry> Actress.order_by_movie_year.class
=> Actress::ActiveRecord_Relation
scope :order_by_movie_year, -> {
joins(%|
INNER JOIN (
SELECT
"movies"."actress_id" AS actress_id,
MAX(year) AS max_year
FROM
"movies"
GROUP BY
"movies"."actress_id"
) AS movies_max_year
ON actresses.id = movies_max_year.actress_id
|).order("max_year DESC, actresses.id ASC")
}
0
*
0
*
まとめ
➢ サブクエリを使ったほうが内部結合するよりもうまくscopeを定義できることが
ある
➢ where句でサブクエリを使うときは、
selectを使う
○ pluckを用いるとクエリが2つに
○ SQLのパフォーマンス次第では二つに分ける
のもアリ。
➢ 結合先にサブクエリを用いる場合
○ Arelを使う
○ joinsの中にSQLを直書きする
Actress.where(id: Movie.select("actress_id").distinct)
Actress.where(id: Movie.pluck(:actress_id).uniq)
「ActiveRecord サブクエリ」で検索するとArelの例が出てくるけど
joinsの中にSQLを書けば良いんじゃないかな。。
資料
達人に学ぶDB設計 徹底指南書
Visual Representation of SQL Joins
ActiveRecordでサブクエリのJOIN
Arel でサブクエリ
ActiveRecordでサブクエリ(副問い合わせ)と内部結合
余談
ご静聴ありがとうございました。

Mais conteúdo relacionado

Destaque

2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by HubspotMarius Sescu
 
Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTExpeed Software
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsPixeldarts
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthThinkNow
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfmarketingartwork
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024Neil Kimberley
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)contently
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024Albert Qian
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsKurio // The Social Media Age(ncy)
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Search Engine Journal
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summarySpeakerHub
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next Tessa Mero
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentLily Ray
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best PracticesVit Horky
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project managementMindGenius
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...RachelPearson36
 

Destaque (20)

2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot
 
Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPT
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage Engineerings
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental Health
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
 
Skeleton Culture Code
Skeleton Culture CodeSkeleton Culture Code
Skeleton Culture Code
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie Insights
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search Intent
 
How to have difficult conversations
How to have difficult conversations How to have difficult conversations
How to have difficult conversations
 
Introduction to Data Science
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data Science
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best Practices
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project management
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
 

いまさらサブクエリ

  • 2. self.inspect [1] https://github.com/muramurasan/okuribito [2] http://poject.herokuapp.com/ ➢ gemのロゴを書いたり… ➢ たまにQiitaの記事を書いたり… ➢ 新卒で某SIerにてスマートメータのヘッドエ ンドシステムの開発に携わったあと、渋谷の ベンチャー企業にてRuby on Railsを学び、 2016年10月エネチェンジに入社。 Name : Yuya Taki GitHub : yuyasat Qiita : yuyasat [1] 若輩者ですので、何卒優しくご教授いた だければと思います。 (commitはしていない) ➢ ○よ○よ風ゲームをReact.jsで実装し たり [2]
  • 3. 今回のモデル 0 * 0 * id | name ----+------------ 1 | 多部未華子 2 | 佐津川愛美 3 | 新垣結衣 4 | 堀北真希 5 | 吉高由里子 6 | 悠城早矢 id | actress_id | year | title ----+------------+------+--------------------- 1 | 2 | 2005 | 蝉しぐれ 2 | 1 | 2006 | 夜のピクニック 3 | 4 | 2005 | ALWAYS 三丁目の夕日 4 | 2 | 2012 | 忍道-SHINOBIDO- 5 | 2 | 2016 | 貞子vs伽椰子 6 | 4 | 2013 | 県庁おもてなし課 7 | 5 | 2013 | 真夏の方程式 id | movie_id | key ----+----------+------------ 1 | 1 | 時代劇 2 | 1 | 子役 3 | 3 | 昭和 4 | 5 | ホラー 5 | 7 | ミステリー 6 | 7 | 夏 7 | 6 | 公務員 8 | 6 | 地方活性 9 | 1 | 夏 class Actress < ApplicationRecord has_many :movies has_many :tags, through: :movies end class Movie < ApplicationRecord has_many :tags belongs_to :actress end class Tag < ApplicationRecord belongs_to :movie end actresses movies tags
  • 4. サブクエリとは ➢ クエリ内のクエリ SELECT "movies".* FROM "movies" WHERE "movies"."actress_id" IN ( SELECT "actresses"."id" FROM "actresses" WHERE "actresses"."name" = '堀北真希' ) ; 0 * 0 * id | actress_id | year | title ----+------------+------+--------------------- 3 | 4 | 2005 | ALWAYS 三丁目の夕日 6 | 4 | 2013 | 県庁おもてなし課
  • 5. moviesに紐づけられているactressを取得したい ➢ moviesテーブルと内部結合すればいい Actress.joins(:movies) SELECT "actresses".* FROM "actresses" INNER JOIN "movies" ON "movies"."actress_id" = "actresses"."id" ; id | name ----+------------ 2 | 佐津川愛美 1 | 多部未華子 4 | 堀北真希 2 | 佐津川愛美 2 | 佐津川愛美 4 | 堀北真希 5 | 吉高由里子 0 * 0 * actresses movies
  • 6. 重複を排除するためにdistinctする SELECT DISTINCT "actresses".* FROM "actresses" INNER JOIN "movies" ON "movies"."actress_id" = "actresses"."id" ; id | name ----+------------ 2 | 佐津川愛美 1 | 多部未華子 4 | 堀北真希 5 | 吉高由里子 0 * 0 * Actress.joins(:movies).distinct
  • 7. よく使いそうなのでscopeにする class Actress < ApplictionRecord scope :having_movies, -> { joins(:movies).distinct } end 0 * 0 *
  • 8. scope内でjoinはあまり使いたくない Tag.joins(movie: :actress).merge(Actress.having_movies) SELECT DISTINCT "tags".* FROM "tags" INNER JOIN "movies" ON "movies"."id" = "tags"."movie_id" INNER JOIN "actresses" ON "actresses"."id" = "movies"."actress_id" INNER JOIN "movies" ON "movies"."actress_id" = "actresses"."id" ; ERROR: table name "movies" specified more than once 0 * 0 * movieに紐づいているactressに紐づいているmovieのtagを取得したい(かなり作為的。例がよくない)
  • 9. 回避策(サブクエリ) SELECT "tags".* FROM "tags" INNER JOIN "movies" ON "movies"."id" = "tags"."movie_id" INNER JOIN "actresses" ON "actresses"."id" = "movies"."actress_id" WHERE "actresses"."id" IN ( SELECT DISTINCT "actresses"."id" FROM "actresses" INNER JOIN "movies" ON "movies"."actress_id" = "actresses"."id" ) ; Tag.joins(movie: :actress).where(actresses: { id: Actress.having_movies }) 0 * 0 * ➢ 突然のテーブル名 ○ モデルで記述したい ➢ サブクエリの中でもJOIN ➢ なんか匂う
  • 10. やっぱりmergeを使いたい Tag.joins(movie: :actress).merge(Actress.having_movies) SELECT "tags".* FROM "tags" INNER JOIN "movies" ON "movies"."id" = "tags"."movie_id" INNER JOIN "actresses" ON "actresses"."id" = "movies"."actress_id" WHERE "actresses"."id" IN ( SELECT DISTINCT "movies"."actress_id" FROM "movies" ) ; ➢ 実行したいSQLはこれ class Actress < ApplictionRecord scope :having_movies, -> { where(id: Movie.select("actress_id").distinct) } end 0 * 0 *
  • 11. 実行コスト(EXPLAIN) class Actress < ApplictionRecord scope :having_movies, -> {  joins(:movies).distinct } end Tag.joins(movie: :actress).where(actresses: { id: Actress.having_movies }) class Actress < ApplictionRecord scope :having_movies, -> { where(id: Movie.select("actress_id").distinct) } end Tag.joins(movie: :actress).merge(Actress.having_movies) class Actress < ApplictionRecord scope :having_movies, -> { where(id: Movie.select("actress_id")) } end Tag.joins(movie: :actress).merge(Actress.having_movies) 171.22..215.55 90.27..115.51 93.93..122.11 0 * 0 *
  • 12. (補足)もう少し現実的な例 SELECT DISTINCT "tags".* FROM "tags" INNER JOIN "movies" ON "movies"."id" = "tags"."movie_id" INNER JOIN "actresses" ON "actresses"."id" = "movies"."actress_id" INNER JOIN "actress_song_relationships" ON "actress_song_relationships"."actress_id" = "actresses"."id" INNER JOIN "songs" ON "songs"."id" = "actress_song_relationships"."song_id" INNER JOIN "songs" ON "songs"."id" = "movies"."theme_song_id" WHERE "songs"."name" = '世界が終わる夜に ' ORDER BY "tags"."id" ; 「世界が終わる夜に」がすきな曲としてもつ女優が主演かつ、主題歌が「かざぐる ま」の映画のタグを取得したい。 ERROR: table name "songs" specified more than once Tag.joins(movie: :actress).merge( Actress.joins(:favorite_songs).merge(Song.where(name: '世界が終わる夜に ')).distinct ).merge( Movie.joins(:theme_song).merge(Song.where(name: 'かざぐるま ')).distinct ) 例が非現実的だっ たので ここをscopeにしたとする
  • 13. (補足)回避策:サブクエリ SELECT DISTINCT "tags".* FROM "tags" INNER JOIN "movies" ON "movies"."id" = "tags"."movie_id" INNER JOIN "actresses" ON "actresses"."id" = "movies"."actress_id" INNER JOIN "Actress_song_relationships" ON "actress_song_relationships"."actress_id" = "actresses"."id" INNER JOIN "songs" ON "songs"."id" = "actress_song_relationships"."song_id" WHERE "songs"."name" = '世界が終わる夜に ' AND "movies"."theme_song_id" IN ( SELECT "songs"."id" FROM "songs" WHERE "songs"."name" = 'かざぐるま ' ) Tag.joins(movie: :actress).merge( Actress.joins(:favorite_songs).merge(Song.where(name: '世界が終わる夜に ')).distinct ).merge( Movie.where(theme_song: Song.where(name: 'かざぐるま ')) ) 「世界が終わる夜に」がすきな曲としてもつ女優が主演かつ、主題歌が「かざぐる ま」の映画のタグを取得したい。 片方をサブクエリにする
  • 14. もう少し複雑なサブクエリの例 ➢ 映画公開年の降順にActressを取り出したい id | actress_id | year | title ----+------------+------+--------------------- 1 | 2 | 2005 | 蝉しぐれ 2 | 1 | 2006 | 夜のピクニック 3 | 4 | 2005 | ALWAYS 三丁目の夕日 4 | 2 | 2012 | 忍道-SHINOBIDO- 5 | 2 | 2016 | 貞子vs伽椰子 6 | 4 | 2013 | 県庁おもてなし課 7 | 5 | 2013 | 真夏の方程式 id | name ----+------------ 2 | 佐津川愛美 4 | 堀北真希 5 | 吉高由里子 1 | 多部未華子 0 * 0 * id | name ----+------------ 1 | 多部未華子 2 | 佐津川愛美 3 | 新垣結衣 4 | 堀北真希 5 | 吉高由里子 6 | 悠城早矢 actresses movies
  • 15. SQLで記述 SELECT actresses.* FROM actresses INNER JOIN ( SELECT "movies"."actress_id" AS actress_id, MAX(year) AS max_year FROM "movies" GROUP BY "movies"."actress_id" ) AS movies_max_year ON actresses.id = movies_max_year.actress_id ORDER BY max_year DESC, actresses.id ASC actress_id | max_year ------------+---------- 5 | 2013 4 | 2013 2 | 2016 1 | 2006 id | name ----+------------ 1 | 多部未華子 2 | 佐津川愛美 3 | 新垣結衣 4 | 堀北真希 5 | 吉高由里子 6 | 悠城早矢 id | name ----+------------ 2 | 佐津川愛美 4 | 堀北真希 5 | 吉高由里子 1 | 多部未華子 0 * 0 * actressesmovies_max_year
  • 16. ActiveRecordで記述(find_by_sql) Actress.find_by_sql(%| SELECT actresses.* FROM actresses INNER JOIN ( SELECT "movies"."actress_id" AS actress_id, MAX(year) AS max_year FROM "movies" GROUP BY "movies"."actress_id" ) AS movies_max_year ON actresses.id = movies_max_year.actress_id ORDER BY max_year DESC, actresses.id ASC |) ➢ Arrayで出力 ➢ Kaminariが使えない ○ Kaminari.paginate_arrayを使えばArrayでも使える が、scopeで使いたい。 [#<Actress:0x007f8978525928 id: 2, name: "佐津川愛美 ">, #<Actress:0x007f8972e37968 id: 4, name: "堀北真希">, #<Actress:0x007f897851d0c0 id: 5, name: "吉高由里子 ">, #<Actress:0x007f897850fd58 id: 1, name: "多部未華子 ">] 0 * 0 *
  • 17. ActiveRecordで記述(Arelを使う) SELECT actresses.* FROM actresses INNER JOIN ( SELECT "movies"."actress_id" AS actress_id, MAX(year) AS max_year FROM "movies" GROUP BY "movies"."actress_id" ) AS movies_max_year ON actresses.id = movies_max_year.actress_id ORDER BY max_year DESC, actresses.id ASC actress_at = Actress.arel_table movie_at = Movie.arel_table movies_max_year = movie_at.project( Arel.sql(%| "movies"."actress_id" as actress_id, MAX(year) as max_year |) ).group("movies.actress_id").as("movies_max_year") join_conds = actress_at.join( movies_max_year, Arel::Nodes::InnerJoin ).on( movies_max_year[:actress_id].eq(actress_at[:id]) ).join_sources Actress.joins(join_conds).order("movies_max_year.max_year DESC") 0 * 0 *
  • 18. ActiveRecordで記述(joinsの中にSQL直書き) SELECT actresses.* FROM actresses INNER JOIN ( SELECT "movies"."actress_id" AS actress_id, MAX(year) AS max_year FROM "movies" GROUP BY "movies"."actress_id" ) AS movies_max_year ON actresses.id = movies_max_year.actress_id ORDER BY max_year DESC, actresses.id ASC Actress.joins(%| INNER JOIN ( SELECT "movies"."actress_id" AS actress_id, MAX(year) AS max_year FROM "movies" GROUP BY "movies"."actress_id" ) AS movies_max_year ON actresses.id = movies_max_year.actress_id |).order("max_year desc, actresses.id asc") 0 * 0 *
  • 19. scopeにして class Actress < ApplicationRecord scope :order_by_movie_year, -> { actress_at = arel_table movie_at = Movie.arel_table movies_max_year = movie_at.project( Arel.sql(%| "movies"."actress_id" as actress_id,       MAX(year) as max_year |) ).group("movies.actress_id").as("movies_max_year") join_conds = actress_at.join( movies_max_year,     Arel::Nodes::InnerJoin ).on( movies_max_year[:actress_id].eq(actress_at[:id]) ).join_sources joins(join_conds).order("movies_max_year.max_year DESC") } end pry> Actress.order_by_movie_year.class => Actress::ActiveRecord_Relation scope :order_by_movie_year, -> { joins(%| INNER JOIN ( SELECT "movies"."actress_id" AS actress_id, MAX(year) AS max_year FROM "movies" GROUP BY "movies"."actress_id" ) AS movies_max_year ON actresses.id = movies_max_year.actress_id |).order("max_year DESC, actresses.id ASC") } 0 * 0 *
  • 20. まとめ ➢ サブクエリを使ったほうが内部結合するよりもうまくscopeを定義できることが ある ➢ where句でサブクエリを使うときは、 selectを使う ○ pluckを用いるとクエリが2つに ○ SQLのパフォーマンス次第では二つに分ける のもアリ。 ➢ 結合先にサブクエリを用いる場合 ○ Arelを使う ○ joinsの中にSQLを直書きする Actress.where(id: Movie.select("actress_id").distinct) Actress.where(id: Movie.pluck(:actress_id).uniq) 「ActiveRecord サブクエリ」で検索するとArelの例が出てくるけど joinsの中にSQLを書けば良いんじゃないかな。。
  • 21. 資料 達人に学ぶDB設計 徹底指南書 Visual Representation of SQL Joins ActiveRecordでサブクエリのJOIN Arel でサブクエリ ActiveRecordでサブクエリ(副問い合わせ)と内部結合