用 Arel 创建一个 WHERE (columns) IN (values) 子句?
Create a WHERE (columns) IN (values) clause with Arel?
有没有办法在 Arel 中以编程方式创建 where 子句,其中列和值分别指定?
SELECT users.*
WHERE (country, occupation) IN (('dk', 'nurse'), ('ch', 'doctor'), ...
假设输入是一个非常长的我们想要匹配的对列表。
我不是在问如何生成 WHERE AND OR 子句,这对于 ActiveRecord.
来说非常简单
到目前为止,我只有基本的字符串操作:
columns = [:country, :occupation]
pairs = [['dk', 'nurse'], ['ch', 'doctor']]
User.where(
"(#{columns.join(', ')}) IN (#{ pairs.map { '(?, ?)' }.join(', ')})",
*pairs
)
它不仅与查询的长度有关 WHERE (columns) IN (values)
也将 perform much better on Postgres (以及其他),因为它可以使用仅索引扫描,其中 OR
将导致位图扫描.
我只是在寻找可以证明使用 Arel 生成 WHERE (columns) IN (values)
查询的答案。没有别的。
我读过的所有关于 Arel 的文章都开始建立一个单一的专栏:
arel_table[:foo].eq...
而且我找不到任何涉及此案例的文档或文章。
我认为我们可以使用数组条件来做到这一点here
# notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value)
此外,如前所述 here 我们可以使用 SQL in
语句:
Model.where('id IN (?)', [array of values])
或者简单地说,正如 kdeisz 指出的那样(使用 Arel 创建 SQL 查询):
Model.where(id: [array of values])
我自己没有尝试过,但您可以尝试通过这些示例进行探索。
总是乐于助人!
这仍然会生成中间有 'OR' 的长查询。但我觉得这是实现你想要的东西的小 elegant/different 方法。
ut = User.arel_table
columns = [:country, :occupation]
pairs = [['dk', 'nurse'], ['ch', 'doctor']]
where_condition = pairs.map do |pair|
"(#{ut[columns[0]].eq(pair[0]).and(ut[columns[1]].eq(pair[1])).to_sql})"
end.join(' OR ')
User.where(where_condition)
我最后尝试了这种不同的方法。希望对你有用。
class User < ActiveRecord::Base
COLUMNS = %i(
country
occupation
)
PAIRS = [['dk', 'nurse'], ['ch', 'doctor']]
scope :with_country_occupation, -> (pairs = PAIRS, columns = COLUMNS) { where(filter_country_occupation(pairs, columns)) }
def self.filter_country_occupation(pairs, columns)
pairs.each_with_index.reduce(nil) do |query, (pair, index)|
column_check = arel_table[columns[0]].eq(pair[0]).and(arel_table[columns[1]].eq(pair[1]))
if query.nil?
column_check
else
query.or(column_check)
end
end.to_sql
end
end
称这个范围为 User.with_country_occupation
让我知道它是否适合你。
谢谢!
这样做的诀窍是正确构建分组,然后将它们传递给 Arel In 节点,例如:
columns = [:country, :occupation]
pairs = [['dk', 'nurse'], ['ch', 'doctor']]
User.where(
Arel::Nodes::In.new(
Arel::Nodes::Grouping.new( columns.map { |column| User.arel_table[column] } ),
pairs.map { |pair| Arel::Nodes::Grouping.new(
pair.map { |value| Arel::Nodes.build_quoted(value) }
)}
)
)
以上将生成以下 SQL 语句(对于 MySQL):
"SELECT users
.* FROM users
WHERE (users
.country
,
users
.occupation
) IN (('dk', 'nurse'), ('ch', 'doctor'))"
有没有办法在 Arel 中以编程方式创建 where 子句,其中列和值分别指定?
SELECT users.*
WHERE (country, occupation) IN (('dk', 'nurse'), ('ch', 'doctor'), ...
假设输入是一个非常长的我们想要匹配的对列表。
我不是在问如何生成 WHERE AND OR 子句,这对于 ActiveRecord.
来说非常简单到目前为止,我只有基本的字符串操作:
columns = [:country, :occupation]
pairs = [['dk', 'nurse'], ['ch', 'doctor']]
User.where(
"(#{columns.join(', ')}) IN (#{ pairs.map { '(?, ?)' }.join(', ')})",
*pairs
)
它不仅与查询的长度有关 WHERE (columns) IN (values)
也将 perform much better on Postgres (以及其他),因为它可以使用仅索引扫描,其中 OR
将导致位图扫描.
我只是在寻找可以证明使用 Arel 生成 WHERE (columns) IN (values)
查询的答案。没有别的。
我读过的所有关于 Arel 的文章都开始建立一个单一的专栏:
arel_table[:foo].eq...
而且我找不到任何涉及此案例的文档或文章。
我认为我们可以使用数组条件来做到这一点here
# notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value)
此外,如前所述 here 我们可以使用 SQL in
语句:
Model.where('id IN (?)', [array of values])
或者简单地说,正如 kdeisz 指出的那样(使用 Arel 创建 SQL 查询):
Model.where(id: [array of values])
我自己没有尝试过,但您可以尝试通过这些示例进行探索。
总是乐于助人!
这仍然会生成中间有 'OR' 的长查询。但我觉得这是实现你想要的东西的小 elegant/different 方法。
ut = User.arel_table
columns = [:country, :occupation]
pairs = [['dk', 'nurse'], ['ch', 'doctor']]
where_condition = pairs.map do |pair|
"(#{ut[columns[0]].eq(pair[0]).and(ut[columns[1]].eq(pair[1])).to_sql})"
end.join(' OR ')
User.where(where_condition)
我最后尝试了这种不同的方法。希望对你有用。
class User < ActiveRecord::Base
COLUMNS = %i(
country
occupation
)
PAIRS = [['dk', 'nurse'], ['ch', 'doctor']]
scope :with_country_occupation, -> (pairs = PAIRS, columns = COLUMNS) { where(filter_country_occupation(pairs, columns)) }
def self.filter_country_occupation(pairs, columns)
pairs.each_with_index.reduce(nil) do |query, (pair, index)|
column_check = arel_table[columns[0]].eq(pair[0]).and(arel_table[columns[1]].eq(pair[1]))
if query.nil?
column_check
else
query.or(column_check)
end
end.to_sql
end
end
称这个范围为 User.with_country_occupation
让我知道它是否适合你。
谢谢!
这样做的诀窍是正确构建分组,然后将它们传递给 Arel In 节点,例如:
columns = [:country, :occupation]
pairs = [['dk', 'nurse'], ['ch', 'doctor']]
User.where(
Arel::Nodes::In.new(
Arel::Nodes::Grouping.new( columns.map { |column| User.arel_table[column] } ),
pairs.map { |pair| Arel::Nodes::Grouping.new(
pair.map { |value| Arel::Nodes.build_quoted(value) }
)}
)
)
以上将生成以下 SQL 语句(对于 MySQL):
"SELECT
users
.* FROMusers
WHERE (users
.country
,users
.occupation
) IN (('dk', 'nurse'), ('ch', 'doctor'))"