gyp*_*ark 5 perl join prefetch dbix-class
这里有三个表:product,model和product_modelN:M关系中的产品和模型.
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
Run Code Online (Sandbox Code Playgroud)
我想做什么:找到所有支持Model 2的产品(例如product 2).然后,对于每个产品,显示产品支持的model_ids列表(product 2=> [ m1,m2])
这是我的第一次尝试.我需要N个查询来搜索model_id每个产品.
# 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
}
Run Code Online (Sandbox Code Playgroud)
我找了一种方法来只使用一个查询来获得结果.更换join有prefetch没有工作.
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` )
}
Run Code Online (Sandbox Code Playgroud)
接下来,我尝试了"两次预取同一个表":
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;
}
Run Code Online (Sandbox Code Playgroud)
我似乎成功了.只执行了一个查询,我从中获取了所有模型ID.
但是我不太确定这是正确的(?)方式.这是正确的方法吗?
例如,如果我使用join而不是prefetching,则Product 2在循环中出现两次.我理解,因为连接表是这样的:
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
Run Code Online (Sandbox Code Playgroud)
为什么Product 2我使用时只出现一次prefetch?
生成的查询几乎相同,除了SELECT字段的差异:
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'
Run Code Online (Sandbox Code Playgroud)
如果您的架构中具有正确的关系,则可以使用单个查询.但这很棘手.我们假设您的数据库如下所示:
CREATE TABLE product
(`id` VARCHAR(2) PRIMARY KEY, `name` VARCHAR(9))
;
INSERT INTO product
(`id`, `name`) VALUES
('p1', 'Product 1'),
('p2', 'Product 2')
;
CREATE TABLE product_model (
`product_id` VARCHAR(2),
`model_id` VARCHAR(2),
PRIMARY KEY (product_id, model_id),
FOREIGN KEY(product_id) REFERENCES product(id),
FOREIGN KEY(model_id) REFERENCES model(id)
)
;
INSERT INTO product_model
(`product_id`, `model_id`) VALUES
('p1', 'm1'),
('p2', 'm1'),
('p2', 'm2')
;
CREATE TABLE model
(`id` VARCHAR(2) PRIMARY KEY, `name` VARCHAR(7))
;
INSERT INTO model
(`id`, `name`) VALUES
('m1', 'Model 1'),
('m2', 'Model 2')
;
Run Code Online (Sandbox Code Playgroud)
这基本上是你的DB问题.我添加了主键和外键.无论如何你可能还有那些.
我们现在可以从中创建一个模式.我写了一个简单的程序,它使用DBIx :: Class :: Schema :: Loader来做到这一点.它可以动态创建SQLite数据库.(如果没有人把它放在CPAN上,我会).
上面的SQL将在本__DATA__节中介绍.
use strict;
use warnings;
use DBIx::Class::Schema::Loader qw/ make_schema_at /;
# create db
unlink 'foo.db';
open my $fh, '|-', 'sqlite3 foo.db' or die $!;
print $fh do { local $/; <DATA> };
close $fh;
$ENV{SCHEMA_LOADER_BACKCOMPAT} = 1;
# create schema
my $dsn = 'dbi:SQLite:foo.db';
make_schema_at(
'DB',
{
# debug => 1,
},
[ $dsn, 'sqlite', '', ],
);
$ENV{DBIC_TRACE} = 1;
# connect schema
my $schema = DB->connect($dsn);
# query goes here
__DATA__
# SQL from above
Run Code Online (Sandbox Code Playgroud)
现在我们有了这个,我们可以专注于查询.起初这看起来很吓人,但我会试着解释一下.
my $rs = $schema->resultset('Product')->search(
{ 'product_models.model_id' => 'm2' },
{
'prefetch' => {
product_models => {
product_id => {
product_models => 'model_id'
}
}
}
},
);
while ( my $product = $rs->next ) {
foreach my $product_model ( $product->product_models->all ) {
my @models;
foreach my $supported_model ( $product_model->product_id->product_models->all ) {
push @models, $supported_model->model_id->id;
}
printf "%s: %s\n", $product->id, join ', ', @models;
}
}
Run Code Online (Sandbox Code Playgroud)
这prefetch意味着加入这种关系,并保留数据以供日后使用.因此,要获得产品的所有型号,我们必须写
# 1 2
{ prefetch => { product_models => 'product_id' } }
Run Code Online (Sandbox Code Playgroud)
product_modelsN:M表在哪里,是product_idModels表关系的名称.箭头=> 1是第一个从加入Product到ProductModel.该2是ProductModel回到了那个有着典型的每一件产品M2.有关说明,请参阅ER模型的图纸.
现在,我们希望拥有所有ProductModel这此S Product了.那是箭头3.
# 1 2 3
{ prefetch => { product_models => { product_id => 'product_models' } } }
Run Code Online (Sandbox Code Playgroud)
最后,为了获得ModelN:M关系的s,我们必须使用model_id带有箭头4的关系.
{
'prefetch' => { # 1
product_models => { # 2
product_id => { # 3
product_models => 'model_id' # 4
}
}
}
},
Run Code Online (Sandbox Code Playgroud)
看ER模型图应该清楚.请记住,LEFT OUTER默认情况下,每个连接都是一个连接,因此它总是会获取所有行,而不会丢失任何内容.DBIC正在为您解决这个问题.
现在要访问所有这些,我们需要迭代.DBIC为我们提供了一些工具.
while ( my $product = $rs->next ) {
# 1
foreach my $product_model ( $product->product_models->all ) {
my @models;
# 2 3
foreach my $supported_model ( $product_model->product_id->product_models->all ) {
# 4
push @models, $supported_model->model_id->id;
}
printf "%s: %s\n", $product->id, join ', ', @models;
}
}
Run Code Online (Sandbox Code Playgroud)
首先我们抓住所有ProductModel条目(1).对于每一个,我们采取Product(2).Product每行中只有一个,因为我们有一个1:N关系,所以我们可以直接访问它.这Product反过来又有ProductModel关系.那是3.因为这是N方面,我们需要采取所有这些并进行迭代.然后我们将id每个Model(4)推入我们的产品列表中.在那之后,它只是打印.
这是另一种看待它的方式:
我们可以消除过去model_id的prefetch,但后来我们不得不使用get_column('model_id')获得的ID.它会为我们节省一次加入.
现在,如果我们打开DBIC_TRACE=1,我们得到这个SQL语句:
SELECT me.id, me.name, product_models.product_id, product_models.model_id, product_id.id, product_id.name, product_models_2.product_id, product_models_2.model_id, model_id.id, model_id.name
FROM product me
LEFT JOIN product_model product_models ON product_models.product_id = me.id
LEFT JOIN product product_id ON product_id.id = product_models.product_id
LEFT JOIN product_model product_models_2 ON product_models_2.product_id = product_id.id
LEFT JOIN model model_id ON model_id.id = product_models_2.model_id
WHERE (product_models.model_id = 'm2')
ORDER BY me.id
Run Code Online (Sandbox Code Playgroud)
如果我们对我们的数据库运行这个,我们有这些行:
p2|Product 2|p2|m2|p2|Product 2|p2|m1|m1|Model 1
p2|Product 2|p2|m2|p2|Product 2|p2|m2|m2|Model 2
Run Code Online (Sandbox Code Playgroud)
当然,如果我们手动完成它就没用了,但DBIC的魔力确实对我们有帮助,因为所有奇怪的连接和组合都被完全抽象掉了,我们只需要一个查询来获取所有数据.
| 归档时间: |
|
| 查看次数: |
431 次 |
| 最近记录: |