8. Aのクエリ
parent_ids =
Parent.where("schedule_at <= :process_hhmm AND
parent_id IS NULL", process_hhmm:
process_hhmm.to_i).pluck(:id)
children_ids =
Child.where(parent_id: parent_ids)
.where(is_disabled: false).pluck(:id)
9. Aのクエリ
parent_ids =
Parent.where("schedule_at <= :process_hhmm AND
parent_id IS NULL", process_hhmm:
process_hhmm.to_i).pluck(:id)
children_ids =
Child.where(parent_id: parent_ids)
.where(is_disabled: false).pluck(:id)
ここが数万件ある。しかも日々増殖している
その結果、parent_id IN (xxxxxx, xxxxxx, xxxxxx, …)
と数万件の文字列が並ぶことになり、SQL文が4MBを超える
10. どうしたか?
• A + B - C自体も、数が少ないとはいえ数百件の
IDを返すため1クエリで表現するSQL文を考える
。
• A + BをUNIONで、- CをNOT INの副問合せで表
現する。
11. 最終的なSQL
SELECT m1.id from A m1
LEFT JOIN A m2 on m1.parent_id = m2.id
AND m2.is_disabled = 0
AND m2.deleted_at IS NULL
AND m2.type = "xxxxxxx"
WHERE m2.schedule_at <= 21180
AND m1.type = "X"
AND m1.`is_disabled` = 0
AND m1.deleted_at IS NULL
AND m1.id NOT IN (
SELECT m4.A_id from C m4
WHERE m4.`deleted_at` IS NULL AND m4.`process_on` = 1531407600)
UNION (
SELECT m3.id from A m3
WHERE m3.`deleted_at` IS NULL
AND ((m3.type = 'yyyyyyy'
AND m3.schedule_at <= 1531461195
AND truncate((m3.schedule_at + 32400) / 86400, 0) = 17725
AND m3.parent_id IS NOT NULL)
OR (m3.type = 'zzzzzzz'
AND m3.schedule_at <= 21180
AND m3.parent_id IS NULL)
OR (m3.type = 'xxxxxxxx'
AND m3.schedule_at <= 21180
AND m3.parent_id IS NOT NULL)
)
AND m3.is_disabled = 0
AND m3.id NOT IN (
SELECT m4.X_id from X m4
WHERE m4.`deleted_at` IS NULL AND m4.`process_on` = 1531407600)
)
Aのクエリ
Bのクエリ
Cのクエリ
Cのクエリ
13. 今回はArelで書きました
SELECT m1.id from A m1
LEFT JOIN A m2 on m1.parent_id = m2.id
AND m2.is_disabled = 0
AND m2.deleted_at IS NULL
AND m2.type = "xxxxxxx"
WHERE m2.schedule_at <= 21180
AND m1.type = "X"
AND m1.`is_disabled` = 0
AND m1.deleted_at IS NULL
AND m1.id NOT IN (
SELECT m4.A_id from C m4
WHERE m4.`deleted_at` IS NULL AND m4.`process_on` = 1531407600)
UNION (
SELECT m3.id from A m3
WHERE m3.`deleted_at` IS NULL
AND ((m3.type = 'yyyyyyy'
AND m3.schedule_at <= 1531461195
AND truncate((m3.schedule_at + 32400) / 86400, 0) = 17725
AND m3.parent_id IS NOT NULL)
OR (m3.type = 'zzzzzzz'
AND m3.schedule_at <= 21180
AND m3.parent_id IS NULL)
OR (m3.type = 'xxxxxxxx'
AND m3.schedule_at <= 21180
AND m3.parent_id IS NOT NULL)
)
AND m3.is_disabled = 0
AND m3.id NOT IN (
SELECT m4.X_id from X m4
WHERE m4.`deleted_at` IS NULL AND m4.`process_on` = 1531407600)
)
結合条件が複雑なので、Arel::Tableで各テーブルにエイリアスを用意
m1 = Arel::Table.new("A", as: "m1")
m2 = Arel::Table.new("A", as: "m2")
m3 = Arel::Table.new(“A", as: "m3")
m4 = Arel::Table.new("C", as: “m4”)
m1_m2 = m1.join(m2, Arel::InnerJoin).on(m1[:parent_id].eq(m2[:id]))
14. 今回はArelで書きました
SELECT m1.id from A m1
LEFT JOIN A m2 on m1.parent_id = m2.id
AND m2.is_disabled = 0
AND m2.deleted_at IS NULL
AND m2.type = "xxxxxxx"
WHERE m2.schedule_at <= 21180
AND m1.type = "X"
AND m1.`is_disabled` = 0
AND m1.deleted_at IS NULL
AND m1.id NOT IN (
SELECT m4.A_id from C m4
WHERE m4.`deleted_at` IS NULL AND m4.`process_on` = 1531407600)
UNION (
SELECT m3.id from A m3
WHERE m3.`deleted_at` IS NULL
AND ((m3.type = 'yyyyyyy'
AND m3.schedule_at <= 1531461195
AND truncate((m3.schedule_at + 32400) / 86400, 0) = 17725
AND m3.parent_id IS NOT NULL)
OR (m3.type = 'zzzzzzz'
AND m3.schedule_at <= 21180
AND m3.parent_id IS NULL)
OR (m3.type = 'xxxxxxxx'
AND m3.schedule_at <= 21180
AND m3.parent_id IS NOT NULL)
)
AND m3.is_disabled = 0
AND m3.id NOT IN (
SELECT m4.X_id from X m4
WHERE m4.`deleted_at` IS NULL AND m4.`process_on` = 1531407600)
)
結合条件が複雑なので、Arel::Tableで各テーブルにエイリアスを用意
m1 = Arel::Table.new("A", as: "m1")
m2 = Arel::Table.new("A", as: "m2")
m3 = Arel::Table.new(“A", as: "m3")
m4 = Arel::Table.new("C", as: “m4”)
m1_m2 = m1.join(m2, Arel::InnerJoin).on(m1[:parent_id].eq(m2[:id]))
SQLの関数はArel::Nodes::NamedFunctionで、SQL内での演算は
Arel::Nodes::InfixOperationで実現可能。
op1 = Arel::Nodes::InfixOperation.new(‘+’, m3[:schedule_at], 32400)
op2 = Arel::Nodes::InfixOperation.new(‘/’,
Arel::Nodes::Grouping.new((op1), 86400)
Arel::Nodes::NamedFunction.new(‘truncate’, op2, 0)
15. 今回はArelで書きました
SELECT m1.id from A m1
LEFT JOIN A m2 on m1.parent_id = m2.id
AND m2.is_disabled = 0
AND m2.deleted_at IS NULL
AND m2.type = "xxxxxxx"
WHERE m2.schedule_at <= 21180
AND m1.type = "X"
AND m1.`is_disabled` = 0
AND m1.deleted_at IS NULL
AND m1.id NOT IN (
SELECT m4.A_id from C m4
WHERE m4.`deleted_at` IS NULL AND m4.`process_on` = 1531407600)
UNION (
SELECT m3.id from A m3
WHERE m3.`deleted_at` IS NULL
AND ((m3.type = 'yyyyyyy'
AND m3.schedule_at <= 1531461195
AND truncate((m3.schedule_at + 32400) / 86400, 0) = 17725
AND m3.parent_id IS NOT NULL)
OR (m3.type = 'zzzzzzz'
AND m3.schedule_at <= 21180
AND m3.parent_id IS NULL)
OR (m3.type = 'xxxxxxxx'
AND m3.schedule_at <= 21180
AND m3.parent_id IS NOT NULL)
)
AND m3.is_disabled = 0
AND m3.id NOT IN (
SELECT m4.X_id from X m4
WHERE m4.`deleted_at` IS NULL AND m4.`process_on` = 1531407600)
)
結合条件が複雑なので、Arel::Tableで各テーブルにエイリアスを用意
m1 = Arel::Table.new("A", as: "m1")
m2 = Arel::Table.new("A", as: "m2")
m3 = Arel::Table.new(“A", as: "m3")
m4 = Arel::Table.new("C", as: “m4”)
m1_m2 = m1.join(m2, Arel::InnerJoin).on(m1[:parent_id].eq(m2[:id]))
SQLの関数はArel::Nodes::NamedFunctionで、SQL内での演算は
Arel::Nodes::InfixOperationで実現可能。
op1 = Arel::Nodes::InfixOperation.new(‘+’, m3[:schedule_at], 32400)
op2 = Arel::Nodes::InfixOperation.new(‘/’,
Arel::Nodes::Grouping.new((op1), 86400)
Arel::Nodes::NamedFunction.new(‘truncate’, op2, 0)
最後にActiveRecord::Relation#joinsでArelオブジェクトを渡して結合
original_table = OriginalTable.arel_table
picked_ids = Arel::Nodes::TableAlias.new(m1_m2.union(m3_m4), "picked_ids")
joins(original_table.join(picked_ids, Arel::Nodes::InnerJoin).
on(original_table[:id].eq(picked_uuids[:id])).join_sources)