具有指向多个表的一列的表的模式设计

时间:2016-07-28 11:14:37

标签: mysql sql mysql-workbench

考虑以下架构:

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映射的表中选择(名称属性)?

1 个答案:

答案 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