考虑以下架构:
create schema testSchema;
use testSchema;
create table A (
id int not null auto_increment,
name varchar (50),
primary key(id));
create table B (
id int not null auto_increment,
name varchar (50),
primary key(id));
create table C (
id int not null auto_increment,
name varchar (50),
primary key(id));
create table main (
id int not null auto_increment,
typeId int,
type varchar(50),
tableMappingsId int,
primary key (id)
);
create table tableMappings (
id int not null auto_increment,
tableName varchar(50),
primary key (id)
);
insert into A (name) values
('usa'),
('uk'),
('uno');
insert into B (name) values
('earth'),
('mars'),
('jupiter');
insert into C (name) values
('1211'),
('12543'),
('345');
insert into main (typeId, type, tableMappingsId) values
(1,'tableA',1),
(2,'tableB',2),
(3,'tableC',3);
insert into tableMappings (tableName) values ('A'),('B'),('C');
描述: -
我有三个表A,B和C,它们都有id和name。
在主表中,type告诉我们从哪个表(A或B或C)读取name属性。 typeId告诉该表中的id(A或B或C)。为此,我创建了一个tableMames表,其中包含tableNames。在主表中,我创建了一个列tableMappingsId,它指向tableMappings。
这是正确的做法吗?以及如何在MySQL中编写如下的查询: -
从该行指向并由tableMappings映射的表中选择(名称属性)?
答案 0 :(得分:1)
关于您的设计
如果我们以面向对象的方式思考,你的基类由主表中记录的属性构成,并由具有附加属性的A,B和C派生。 您希望避免在具有NULL的单个表中具有许多属性,具体取决于记录类型。这是一个很好的方法。但是你的方法可以改进。
回答您的问题
您想要从表格(A,B或C)中进行选择,具体取决于字段的值。据我所知,这不能在没有“准备”查询的情况下完成。
“准备”查询可以多种方式完成:
准备好的陈述的例子:
SET @idToSelect = 2;
SELECT
CONCAT('SELECT name FROM ', tableMappings.tableName, ' WHERE Id = ', main.tableMappingsId)
INTO @statement
FROM main
INNER JOIN tableMappings ON tableMappings.tableName = REPLACE(main.type, 'table', '')
WHERE main.id = @idToSelect;
PREPARE stmt FROM @statement;
EXECUTE stmt;
注意:我们必须在main.type中翻译'tableA','tableB'...以匹配tableMappings.tableName中的'A','B'...,这是不理想的。 / em>的
但这不是非常方便和有效。
其他方法和评论
从多个表中选择:不一定是大不了的
基本上,你想避免从你不需要阅读的表中选择SELECT。但请记住,如果您的架构被正确编入索引,这不一定是一个大问题。 MySQL运行查询优化器。您可以简单地LEFT JOIN所有表格,并根据'type'值从右表中选择:
SET @idToSelect = 2;
SELECT
IFNULL(A.name, IFNULL(B.name, C.name)) AS name
FROM main
LEFT JOIN A ON main.type = 'tableA' AND A.id = main.tableMappingsId
LEFT JOIN B ON main.type = 'tableB' AND B.id = main.tableMappingsId
LEFT JOIN C ON main.type = 'tableC' AND C.id = main.tableMappingsId
WHERE main.id = @idToSelect;
请注意,我没有使用tableMappings表
无用的tableMappings技巧
您可以通过在“children”表中使用与主表中相同的ID来避免使用此类映射。这是ORM实现继承的数量。我将在后面的答案中举一个例子。
有点不相关的例子
在您的问题中,无论记录的类型如何,您都要选择“名称”属性。但我敢打赌,如果你有非常不同类型的记录,每种类型都拥有一组不同的属性。如果“name”是所有类型之间的公共属性,则它应该在主表中。我假设您提供了“名称”作为简化示例。 但我认为在实际情况下,无论对象的类型如何,您都很少需要选择一个字段。
其他事项:在数据示例中,您提供的A,B和C表记录与主记录不匹配
最终提议
drop schema testSchema;
create schema testSchema;
use testSchema;
create table main (
id int not null auto_increment,
typeId int,
common_data VARCHAR(50),
primary key (id)
);
create table A (
id int not null,
specific_dataA varchar (50),
primary key(id),
foreign key FK_A (id) references main (id)
);
create table B (
id int not null,
specific_dataB varchar (50),
primary key(id),
foreign key FK_B (id) references main (id)
);
create table C (
id int not null,
specific_dataC varchar (50),
primary key(id),
foreign key FK_C (id) references main (id)
);
insert into main (typeId, common_data) values
(1, 'ABC'),
(2, 'DEF'),
(3, 'GHI');
insert into A (id, specific_dataA) values
(1, 'usa');
insert into B (id, specific_dataB) values
(2, 'mars');
insert into C (id, specific_dataC) values
(3, '345');
一些意见:
主表中的typeId是optionnal,但根据您必须执行的查询,检索对象的类型会很有用。一个字段就足够了,不需要typeId整数和类型varchar。
A,B和C表中的id不是auto_increment,因为它们必须与主id的匹配
如果没有共同的属性,这个设计是无关紧要的,所以我在主表中放了一个公共数据字段
我通过定义外键来实现关系
查询示例:
我想要id 1的公共数据字段:
SELECT common_data FROM main WHERE id = 1;
我知道id 2来自B类,我想要特定的数据B:
SELECT specific_dataB FROM B WHERE id = 2;
我知道id 3来自C类,我想要公共数据和特定数据C:
SELECT common_data, specific_dataB FROM main INNER JOIN B ON B.id = main.id WHERE main.id = 2;
(最符合您的情况)我不知道对象3的类型,但我想要一个特定的数据,具体取决于它的类型:
SELECT IFNULL(
A.specific_dataA,
IFNULL(
B.specific_dataB,
C.specific_dataC
)
)
FROM main
LEFT JOIN A on A.id = main.id
LEFT JOIN B on B.id = main.id
LEFT JOIN C on C.id = main.id
WHERE main.id = 3