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
我想做什么:找到所有支持Model 2的产品(例如产品2).然后,对于每个产品,显示产品支持的model_ids列表(产品2 => [m1,m2])
这是我的第一次尝试.我需要N个查询来搜索每个产品的model_ids.
# 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 }
我找了一种方法来只使用一个查询来获得结果.用预取替换连接不起作用.
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` ) }
接下来,我尝试了“两次预取同一个表”:
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; }
但是我不太确定这是正确的(?)方式.这是正确的方法吗?
例如,如果我使用连接而不是预取,则产品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
为什么在使用预取时,产品2只出现一次?
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'
解决方法
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'),'m2') ; CREATE TABLE model (`id` VARCHAR(2) PRIMARY KEY,`name` VARCHAR(7)) ; INSERT INTO model (`id`,`name`) VALUES ('m1','Model 1'),('m2','Model 2') ;
这基本上是你的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
现在我们有了这个,我们可以专注于查询.起初这看起来很吓人,但我会试着解释一下.
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; } }
预取意味着加入这种关系,并保留数据以供日后使用.因此,要获得产品的所有型号,我们必须写
# 1 2 { prefetch => { product_models => 'product_id' } }
其中product_models是N:M表,product_id是Models表的关系名称.箭头=> 1表示从Product到ProductModel的第一次连接. 2用于ProductModel返回到每个具有m2模型的产品.有关说明,请参阅ER模型的图纸.
现在我们想要拥有此产品具有的所有ProductModel.那是箭头3.
# 1 2 3 { prefetch => { product_models => { product_id => 'product_models' } } }
最后,为了获得N:M关系的模型,我们必须使用带有箭头4的model_id关系.
{ 'prefetch' => { # 1 product_models => { # 2 product_id => { # 3 product_models => 'model_id' # 4 } } } },
看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,@models; } }
首先,我们获取所有ProductModel条目(1).对于每一个,我们采取产品(2).每一行中始终只有一个产品,因为这样我们就有了1:N的关系,所以我们可以直接访问它.该产品又具有ProductModel关系.这是3.因为这是N方面,我们需要采取所有这些并进行迭代.然后,我们将每个Model(4)的id推送到我们的产品模型列表中.在那之后,它只是打印.
这是另一种看待它的方式:
我们可以在预取中消除最后一个model_id,但是我们必须使用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
如果我们对我们的数据库运行这个,我们有这些行:
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
当然,如果我们手动完成它就没用了,但DBIC的魔力确实对我们有帮助,因为所有奇怪的连接和组合都被完全抽象掉了,我们只需要一个查询来获取所有数据.