Here are three tables: product
, model
, and product_model
that maps products and models in N:M relationship.
product product_model model
id name product_id model_id id name
------------ ------------------- ----------
p1 Product 1 p1 m1 m1 Model 1
p2 Product 2 p2 m1 m2 Model 2
... p2 m2
What I want to do: Find all products that support Model 2(eg. product 2
). Then, for each product, show the list of model_ids that the product supports(product 2
=> [ m1
,m2
])
This was my first try. I needed N more queries to search model_id
s for each product.
# 1 query for searching products
my @products = $schema->resultset('Product')->search(
{ 'product_models.model_id' => 'm2' },
{ 'join' => 'product_model' },
)
# N queries for searching product_models for each product
foreach my $product ( @products ) {
my @model_ids = map { $_->model_id } $product->product_models;
# @model_ids = ( 'm1', 'm2' ) for p2
}
I looked for a way to get the result using only one query. Replacing join
with prefetch
didn't work.
my @products = $schema->resultset('Product')->search(
{ 'product_models.model_id' => 'm2' },
{ 'prefetch' => 'product_model' }, # here
)
# no additional queries, but...
foreach my $product ( @products ) {
my @model_ids = map { $_->model_id } $product->product_models;
# now, @model_ids contains only ( `m2` )
}
Next, I tried "prefetch same table twice":
my @products = $schema->resultset('Product')->search(
{ 'product_models.model_id' => 'm2' },
{ 'prefetch' => [ 'product_models', 'product_models' ] },
);
foreach my $product ( @products ) {
my @model_ids = map { $_->model_id } $product->product_models;
}
It seemed that I succeeded. Only one query was executed and I got all model IDs from it.
However I wasn't so sure that this is the right(?) way. Is this a correct approach?
For example, if I used join
instead of prefetch
ing, Product 2
appears in the loop twice. I understand that, because the joined table is like:
id name p_m.p_id p_m.m_id p_m_2.p_id p_m_2.m_id
p2 Product 2 p2 m2 p2 m1
p2 Product 2 p2 m2 p2 m2 -- Product 2, one more time
Why does Product 2
appear only once when I use prefetch
?
The resulting queries are almost same, except the difference of SELECT
fields:
SELECT "me"."id", "me"."name",
"product_models"."product_id", "product_models"."model_id", -- only in prefetch
"product_models_2"."product_id", "product_models_2"."model_id" --
FROM "product" "me"
LEFT JOIN "product_model" "product_models"
ON "product_models"."product_id" = "me"."id"
LEFT JOIN "product_model" "product_models_2"
ON "product_models_2"."product_id" = "me"."id"
WHERE "product_models"."model_id" = 'm2'
DBIx::Class
is an ORM, where R stands for 'Relational'. It would expect relations in your schema, likehas_many
orbelongs_to
. Once you have those, you no longer have to do any off the looping manually. Just a query onproduct_model
that selects those rows that belongproducts
where nameeq
'Product 2' ... hope this will give you enough hints. – Bracyproduct_model
andprefetch => { product => 'product_models' }
to get all supported models. In fact I didn't because there are several more condition to apply in my real work. I'll think more and consider what you said. Thanks! – Designing