Activerecord,如何在连接上链接多个条件 table
Activerecord, how to chain multiple condition on a join table
我正在寻找一种方法来为我的产品数据库创建查询。
我有这些模型
class Category < DatabaseProducts::Base
has_many :category_searches
has_many :products
end
class Product < DatabaseProducts::Base
belongs_to :category
has_many :products_features
end
class Feature < DatabaseProducts::Base
has_many :products, through: :product_features
has_many :product_features
end
class ProductFeature < DatabaseProducts::Base
belongs_to :feature
belongs_to :product
end
class CategorySearch < DatabaseProducts::Base
belongs_to :category
end
基本上是一个产品数据库,每个产品都有一些特性,值存储在ProductFeature
连接table中。
这是结构,presentional_value
用于视图,raw_value
用于搜索
create_table "product_features", force: :cascade do |t|
t.string "raw_value"
t.string "presentional_value"
t.integer "product_id"
t.integer "feature_id"
t.boolean "searchable", default: false
t.index ["feature_id"], name: "index_product_features_on_feature_id"
t.index ["product_id"], name: "index_product_features_on_product_id"
end
我在这个产品数据库中有一个 Vue 前端,我有多个搜索。为了创建搜索字段,我创建了 category_searches table。
create_table "category_searches", force: :cascade do |t|
t.integer "category_id"
t.integer "feature_id"
t.string "name"
t.string "search_type"
t.string "search_options", default: [], array: true
t.index ["category_id"], name: "index_category_searches_on_category_id"
t.index ["feature_id"], name: "index_category_searches_on_feature_id"
end
每天晚上,当我将新产品导入我的数据库时,我都会创建新记录或更新此 table:对于每个可搜索的功能,我都会存储每个可能的可搜索值。
例如,对于电视类别,在这个 table 我有
category_id: 5
feature_id: 124
search_type: boolean
values: ["Yes","No"]
category_id: 5
feature_id: 235
search_type: options
values: ["OLED","LCD","QLED"]
在我的 Vue 前端中,对于每个类别,我使用此 table 中的记录来绘制搜索界面,因此当我 select 前端向我的搜索发送请求时 API 使用这些参数:
category_id: 5
search_options: {"124" => "Yes", "235" => "OLED" ...}
基本上我必须用 category_id=5
搜索每个产品,其中 search_options
。
到此为止:我不知道如何构建查询。
我知道我必须加入产品 table 和 products_features table。
而且我知道如何询问 Activerecord
"查找 raw_value == ? 的产品" 或 "查找 feature_id= ? 的产品“
这是一个简单的链式 where。但是不知道怎么问ActiveRecord:
"查找 ProductFeature
和 feature_id=124 具有 raw_value 的 "Yes" 和 feature_id=235 的产品有 "OLED" 的 raw_value 和... “
AND 子句不会真正给您想要的结果。你想要的是使用 OR 子句和 GROUP 和 HAVING:
f_id = ProductFeature.arel_table[:feature_id]
raw = ProductFeature.arel_table[:raw_value]
Product.joins(:product_features)
.where(
f_id.eq(124).and(raw.eq("Yes")).or(
f_id.eq(12345).and(raw.eq("No"))
)
)
.group("products.id")
.having(Arel.star.count.eq(2))
这导致以下查询:
SELECT "products".*
FROM "products"
INNER JOIN "product_features"
ON "product_features"."product_id" = "products"."id"
WHERE ( "product_features"."feature_id" = 123
AND "product_features"."raw_value" = 'Yes'
OR "product_features"."feature_id" = 12345
AND "product_features"."raw_value" = 'No' )
GROUP BY "products"."id"
HAVING ( count(*) = 2 )
LIMIT ?
其中 returns 在联接中至少有两个匹配项的所有产品 table。
您可能希望使用 JSON/JSONB 列而不是字符串列来存储值。这将帮助您缓解 EAV 模式的最大问题之一,即将所有内容强制转换为字符串列时令人头疼的问题。
在 Postgres(可能还有 MySQL)上,您可以使用 WHERE (columns) IN (values)
来编写一个更简单、更有效的查询:
class Product < ApplicationRecord
has_many :product_features
has_many :features, through: :product_features
def self.search_by_features(*pairs)
t = ProductFeature.arel_table
conditions = Arel::Nodes::In.new(
Arel::Nodes::Grouping.new( [t[:feature_id], t[:raw_value]] ),
pairs.map { |pair| Arel::Nodes::Grouping.new(
pair.map { |value| Arel::Nodes.build_quoted(value) }
)}
)
Product.joins(:product_features)
.where(
conditions
).group(:id)
.having(Arel.star.count.eq(pairs.length))
end
end
用法:
Product.search_by_features([1, "Yes"], [2, "No"], [3, "Maybe"])
SQL查询:
SELECT "products".*
FROM "products"
INNER JOIN "product_features"
ON "product_features"."product_id" = "products"."id"
WHERE
("product_features"."feature_id", "product_features"."raw_value")
IN
((1, 'Yes'),(2, 'No'),(3, 'Maybe'))
GROUP BY "products"."id"
HAVING ( COUNT(*) = 3) )
LIMIT
使用左连接怎么样?
Product
.left_joins(:product_features)
.left_joins(:features)
.where(product_features: {feature_id: feature_id, raw_values: "YES"})
.where(features: {feature_id: feature_id, raw_values: "OLED"})
如果还有更多的table可以连接,只需添加left joins语句和where语句即可
我认为 ActiveRecord 不支持此类查询。您可以使用普通 SQL 或 Arel, or ransack 等
目前无法检查,但 Arel 版本可能与此类似:
products = Product.arel_table
result = params[:search_options].reduce(Product.where(category_id: 5)) do |scope, (feature_id, feature_value)|
product_feature_table = ProductFeature.arel_table.alias("product_feature_#{feature_id}")
join_clause = product_feature_table[:product_id].eq(products[:id]).
merge(product_feature_table[:raw_value].eq(feature_value))
scope.joins(product_feature_table.on(join_clause)
end
我正在寻找一种方法来为我的产品数据库创建查询。
我有这些模型
class Category < DatabaseProducts::Base
has_many :category_searches
has_many :products
end
class Product < DatabaseProducts::Base
belongs_to :category
has_many :products_features
end
class Feature < DatabaseProducts::Base
has_many :products, through: :product_features
has_many :product_features
end
class ProductFeature < DatabaseProducts::Base
belongs_to :feature
belongs_to :product
end
class CategorySearch < DatabaseProducts::Base
belongs_to :category
end
基本上是一个产品数据库,每个产品都有一些特性,值存储在ProductFeature
连接table中。
这是结构,presentional_value
用于视图,raw_value
用于搜索
create_table "product_features", force: :cascade do |t|
t.string "raw_value"
t.string "presentional_value"
t.integer "product_id"
t.integer "feature_id"
t.boolean "searchable", default: false
t.index ["feature_id"], name: "index_product_features_on_feature_id"
t.index ["product_id"], name: "index_product_features_on_product_id"
end
我在这个产品数据库中有一个 Vue 前端,我有多个搜索。为了创建搜索字段,我创建了 category_searches table。
create_table "category_searches", force: :cascade do |t|
t.integer "category_id"
t.integer "feature_id"
t.string "name"
t.string "search_type"
t.string "search_options", default: [], array: true
t.index ["category_id"], name: "index_category_searches_on_category_id"
t.index ["feature_id"], name: "index_category_searches_on_feature_id"
end
每天晚上,当我将新产品导入我的数据库时,我都会创建新记录或更新此 table:对于每个可搜索的功能,我都会存储每个可能的可搜索值。
例如,对于电视类别,在这个 table 我有
category_id: 5
feature_id: 124
search_type: boolean
values: ["Yes","No"]
category_id: 5
feature_id: 235
search_type: options
values: ["OLED","LCD","QLED"]
在我的 Vue 前端中,对于每个类别,我使用此 table 中的记录来绘制搜索界面,因此当我 select 前端向我的搜索发送请求时 API 使用这些参数:
category_id: 5
search_options: {"124" => "Yes", "235" => "OLED" ...}
基本上我必须用 category_id=5
搜索每个产品,其中 search_options
。
到此为止:我不知道如何构建查询。
我知道我必须加入产品 table 和 products_features table。 而且我知道如何询问 Activerecord
"查找 raw_value == ? 的产品" 或 "查找 feature_id= ? 的产品“
这是一个简单的链式 where。但是不知道怎么问ActiveRecord:
"查找 ProductFeature
和 feature_id=124 具有 raw_value 的 "Yes" 和 feature_id=235 的产品有 "OLED" 的 raw_value 和... “
AND 子句不会真正给您想要的结果。你想要的是使用 OR 子句和 GROUP 和 HAVING:
f_id = ProductFeature.arel_table[:feature_id]
raw = ProductFeature.arel_table[:raw_value]
Product.joins(:product_features)
.where(
f_id.eq(124).and(raw.eq("Yes")).or(
f_id.eq(12345).and(raw.eq("No"))
)
)
.group("products.id")
.having(Arel.star.count.eq(2))
这导致以下查询:
SELECT "products".*
FROM "products"
INNER JOIN "product_features"
ON "product_features"."product_id" = "products"."id"
WHERE ( "product_features"."feature_id" = 123
AND "product_features"."raw_value" = 'Yes'
OR "product_features"."feature_id" = 12345
AND "product_features"."raw_value" = 'No' )
GROUP BY "products"."id"
HAVING ( count(*) = 2 )
LIMIT ?
其中 returns 在联接中至少有两个匹配项的所有产品 table。
您可能希望使用 JSON/JSONB 列而不是字符串列来存储值。这将帮助您缓解 EAV 模式的最大问题之一,即将所有内容强制转换为字符串列时令人头疼的问题。
在 Postgres(可能还有 MySQL)上,您可以使用 WHERE (columns) IN (values)
来编写一个更简单、更有效的查询:
class Product < ApplicationRecord
has_many :product_features
has_many :features, through: :product_features
def self.search_by_features(*pairs)
t = ProductFeature.arel_table
conditions = Arel::Nodes::In.new(
Arel::Nodes::Grouping.new( [t[:feature_id], t[:raw_value]] ),
pairs.map { |pair| Arel::Nodes::Grouping.new(
pair.map { |value| Arel::Nodes.build_quoted(value) }
)}
)
Product.joins(:product_features)
.where(
conditions
).group(:id)
.having(Arel.star.count.eq(pairs.length))
end
end
用法:
Product.search_by_features([1, "Yes"], [2, "No"], [3, "Maybe"])
SQL查询:
SELECT "products".*
FROM "products"
INNER JOIN "product_features"
ON "product_features"."product_id" = "products"."id"
WHERE
("product_features"."feature_id", "product_features"."raw_value")
IN
((1, 'Yes'),(2, 'No'),(3, 'Maybe'))
GROUP BY "products"."id"
HAVING ( COUNT(*) = 3) )
LIMIT
使用左连接怎么样?
Product
.left_joins(:product_features)
.left_joins(:features)
.where(product_features: {feature_id: feature_id, raw_values: "YES"})
.where(features: {feature_id: feature_id, raw_values: "OLED"})
如果还有更多的table可以连接,只需添加left joins语句和where语句即可
我认为 ActiveRecord 不支持此类查询。您可以使用普通 SQL 或 Arel, or ransack 等
目前无法检查,但 Arel 版本可能与此类似:
products = Product.arel_table
result = params[:search_options].reduce(Product.where(category_id: 5)) do |scope, (feature_id, feature_value)|
product_feature_table = ProductFeature.arel_table.alias("product_feature_#{feature_id}")
join_clause = product_feature_table[:product_id].eq(products[:id]).
merge(product_feature_table[:raw_value].eq(feature_value))
scope.joins(product_feature_table.on(join_clause)
end