Mysql SQL问题:一对多关系和EAV模型

Mysql SQL问题:一对多关系和EAV模型,mysql,sql,Mysql,Sql,各位晚上好, 我是一个网络编程新手,我需要你的帮助来解决SQL查询固有的问题。 我使用的数据库引擎是MySQL,我通过PHP访问它,这里我将解释我的数据库的简化版本,只是为了修正一些想法。 让我们假设使用一个包含三个表的数据库:teams、teams\u information和attributes。更准确地说: 1 teams是一个包含意大利足球队(而非美式足球队)的一些基本信息的表:D,它由三个字段组成:“id”int,主键,“name”varchar,team name,昵称varchar

各位晚上好, 我是一个网络编程新手,我需要你的帮助来解决SQL查询固有的问题。 我使用的数据库引擎是MySQL,我通过PHP访问它,这里我将解释我的数据库的简化版本,只是为了修正一些想法。 让我们假设使用一个包含三个表的数据库:teams、teams\u information和attributes。更准确地说:

1 teams是一个包含意大利足球队(而非美式足球队)的一些基本信息的表:D,它由三个字段组成:“id”int,主键,“name”varchar,team name,昵称varchar,team昵称

2属性是一个包含足球队可能信息列表的表格,例如球队主场比赛所在城市、队长全名、f_号码、球迷人数等。该表由三个字段组成:id int、主键、attribute_name varchar、属性标识符、attribute_desc text、属性含义解释。该表的每条记录代表一个足球队的一个可能属性

3团队信息是一个表格,在该表格中可以获得团队表格中所列团队的一些信息。此表包含三个字段:id int、主键、team_id int、标识团队的外键、attribute_id int、标识attributes表中列出的属性之一的外键、attribute_value varchar、属性值。每条记录代表单个团队的单个属性。一般来说,不同的团队将拥有不同数量的信息,因此对于某些团队,大量的属性将可用,而对于其他团队,只有少量的属性可用

请注意,团队和团队信息之间的关系是一对多的,属性和团队信息之间存在相同的关系

考虑到这个模型,我的目的是用ExtJS 4.1实现一个网格,向用户显示意大利足球队的名单,这个网格的每个记录将代表一个足球队,并将包含所有可能的属性:一些字段可能是空的,因为对于所考虑的球队,对应的属性是未知的,而其他将包含存储在所考虑的团队的团队信息表中的值。 根据上述网格,字段为:id、team_name和若干字段,用于表示“attributes”表中列出的所有不同属性

我的问题是:我能否通过使用一个SQL查询(可能是一个合适的SELECT查询)来实现这样一个网格,以便从数据库表中获取所需的所有数据? 如果存在类似的查询,有人能建议我如何编写它吗

提前谢谢你帮我

问候


Enrico.

我认为您的意思是希望属性表中的行在结果记录集中显示为列。如果这是正确的,那么在SQL中您将使用PIVOT。
在SO上快速搜索似乎表明MySql中没有PIVOT等价物。

我想您的意思是希望属性表中的行在结果记录集中显示为列。如果这是正确的,那么在SQL中您将使用PIVOT。
在SO上快速搜索似乎表明MySql中没有PIVOT等价物。

您的问题的简短答案是否定的,MySql中没有简单的构造来实现您所寻找的结果集

但我们有可能精心设计这样一个问题。这里有一个例子,我相信你能破译它。基本上,我在选择列表中使用相关子查询,用于我想要返回的每个属性

SELECT t.id
     , t.name
     , t.nickname

     , ( SELECT v1.attribute_value 
           FROM team_information v1 
           JOIN attributes a1
             ON a1.id = v1.attribute_id AND a1.attribute_name = 'city'
          WHERE v1.team_id = t.id ORDER BY 1 LIMIT 1
       ) AS city

     , ( SELECT v2.attribute_value
           FROM team_information v2 JOIN attributes a2
             ON a2.id = v2.attribute_id AND a2.attribute_name = 'captain'
          WHERE v2.team_id = t.id ORDER BY 1 LIMIT 1
       ) AS captain

     , ( SELECT v3.attribute_value
           FROM team_information v3 JOIN attributes a3
             ON a3.id = v3.attribute_id AND a3.attribute_name = 'f_number'
          WHERE v3.team_id = t.id ORDER BY 1 LIMIT 1
       ) AS f_number

  FROM teams t
 ORDER BY t.id
对于“多值”属性,必须分别提取属性的每个实例。使用限制指定是否检索第一个、第二个等

     , ( SELECT v4.attribute_value
           FROM team_information v4 JOIN attributes a4
             ON a4.id = v4.attribute_id AND a4.attribute_name = 'nickname'
          WHERE v4.team_id = t.id ORDER BY 1 LIMIT 0,1
       ) AS nickname_1st

     , ( SELECT v5.attribute_value
           FROM team_information v5 JOIN attributes a5
             ON a5.id = v5.attribute_id AND a5.attribute_name = 'nickname'
          WHERE v5.team_id = t.id ORDER BY 1 LIMIT 1,1
       ) AS nickname_2nd

     , ( SELECT v6.attribute_value
           FROM team_information v6 JOIN attributes a6
             ON a6.id = v6.attribute_id AND a6.attribute_name = 'nickname'
          WHERE v6.team_id = t.id ORDER BY 1 LIMIT 2,1
       ) AS nickname_3rd
我在这里使用昵称作为一个例子,因为美国足球俱乐部经常有不止一个昵称,例如芝加哥消防足球俱乐部有昵称:“火灾”、“拉玛奎纳罗亚”、“穿红衣服的男人”、“CF97”等

不是你问题的答案,但是

我以前多次提到过,我有多不喜欢使用EAV数据库实现?一个非常简单的查询会变成一个非常复杂的、可能会变暗的查询

在每个属性都是独立列的情况下创建一个表不是要简单得多吗?然后返回合理结果集的查询看起来更合理

SELECT id, name, nickname, city, captain, f_number, ... FROM team
但真正让我不寒而栗的是,一些开发人员可能会决定将LDQ作为视图隐藏在数据库中,以实现更简单的查询


如果你走这条路,请不要急于将此查询作为视图存储在数据库中。

对你的问题的简短回答是否定的,MySQL中没有简单的构造来实现你想要的结果集 我在等你

但我们有可能精心设计这样一个问题。这里有一个例子,我相信你能破译它。基本上,我在选择列表中使用相关子查询,用于我想要返回的每个属性

SELECT t.id
     , t.name
     , t.nickname

     , ( SELECT v1.attribute_value 
           FROM team_information v1 
           JOIN attributes a1
             ON a1.id = v1.attribute_id AND a1.attribute_name = 'city'
          WHERE v1.team_id = t.id ORDER BY 1 LIMIT 1
       ) AS city

     , ( SELECT v2.attribute_value
           FROM team_information v2 JOIN attributes a2
             ON a2.id = v2.attribute_id AND a2.attribute_name = 'captain'
          WHERE v2.team_id = t.id ORDER BY 1 LIMIT 1
       ) AS captain

     , ( SELECT v3.attribute_value
           FROM team_information v3 JOIN attributes a3
             ON a3.id = v3.attribute_id AND a3.attribute_name = 'f_number'
          WHERE v3.team_id = t.id ORDER BY 1 LIMIT 1
       ) AS f_number

  FROM teams t
 ORDER BY t.id
对于“多值”属性,必须分别提取属性的每个实例。使用限制指定是否检索第一个、第二个等

     , ( SELECT v4.attribute_value
           FROM team_information v4 JOIN attributes a4
             ON a4.id = v4.attribute_id AND a4.attribute_name = 'nickname'
          WHERE v4.team_id = t.id ORDER BY 1 LIMIT 0,1
       ) AS nickname_1st

     , ( SELECT v5.attribute_value
           FROM team_information v5 JOIN attributes a5
             ON a5.id = v5.attribute_id AND a5.attribute_name = 'nickname'
          WHERE v5.team_id = t.id ORDER BY 1 LIMIT 1,1
       ) AS nickname_2nd

     , ( SELECT v6.attribute_value
           FROM team_information v6 JOIN attributes a6
             ON a6.id = v6.attribute_id AND a6.attribute_name = 'nickname'
          WHERE v6.team_id = t.id ORDER BY 1 LIMIT 2,1
       ) AS nickname_3rd
我在这里使用昵称作为一个例子,因为美国足球俱乐部经常有不止一个昵称,例如芝加哥消防足球俱乐部有昵称:“火灾”、“拉玛奎纳罗亚”、“穿红衣服的男人”、“CF97”等

不是你问题的答案,但是

我以前多次提到过,我有多不喜欢使用EAV数据库实现?一个非常简单的查询会变成一个非常复杂的、可能会变暗的查询

在每个属性都是独立列的情况下创建一个表不是要简单得多吗?然后返回合理结果集的查询看起来更合理

SELECT id, name, nickname, city, captain, f_number, ... FROM team
但真正让我不寒而栗的是,一些开发人员可能会决定将LDQ作为视图隐藏在数据库中,以实现更简单的查询


如果您选择这条路线,请不要急于将此查询作为视图存储在数据库中。

我将选择稍微不同的路线。斯宾塞的回答非常棒,它很好地解决了这个问题,但仍然存在一个巨大的潜在问题

您试图在站点上显示的数据在数据库中过度规范化。我不会详细说明,因为斯宾塞的回答再次突出了这个问题

相反,我想推荐一种解决方案,使数据稍微非规范化一点

将所有团队数据转换为具有多个列的单个表。如果问题中没有涉及到玩家数据,那将是第二张表,但我现在将对此进行润色

当然,您将有一大堆列,并且对于许多行,许多列可能为空。它不是标准化的,也不漂亮,但这是您获得的巨大优势

您的查询变成:

SELECT * FROM Teams

就这样。这会直接显示在网站上,你就完成了。您可能需要不遗余力地实现此模式,但这完全值得投入时间。

我将采取稍微不同的路线。斯宾塞的回答非常棒,它很好地解决了这个问题,但仍然存在一个巨大的潜在问题

您试图在站点上显示的数据在数据库中过度规范化。我不会详细说明,因为斯宾塞的回答再次突出了这个问题

相反,我想推荐一种解决方案,使数据稍微非规范化一点

将所有团队数据转换为具有多个列的单个表。如果问题中没有涉及到玩家数据,那将是第二张表,但我现在将对此进行润色

当然,您将有一大堆列,并且对于许多行,许多列可能为空。它不是标准化的,也不漂亮,但这是您获得的巨大优势

您的查询变成:

SELECT * FROM Teams

就这样。这会直接显示在网站上,你就完成了。您可能需要不遗余力地实现此模式,但这完全值得投入时间。

我编写了一个简单的PHP脚本来概括spencer的想法,以解决我的问题。 代码如下:

<?php
    require_once('includes/db.config.php'); //this file performs connection to mysql

/*
 * Following function requires a table name ($table)
 * and a number of service fields ($num). Given those parameters
 * it returns the number of table fields (excluding service fields). 
 */

function get_fields_number($table,$num,$conn)
{   
    $query = "SELECT * FROM $table";
    $result = mysql_query($query,$conn);
    return mysql_num_fields($result)-$num; //remember there are $num service fields
}

/*
 * Following function requires a table name ($table) and an array
 * containing a list of service fields names. Given those parameters, 
 * it returns the list of field names. That list is contained within an array and
 * service fields are excluded.
 */

function get_fields_name($table,$service,$conn)
{
    $query = "SELECT * FROM $table";
    $result = mysql_query($query,$conn);
    $name = array(); //Array to be returned
    for ($i=0;$i<mysql_num_fields($result);$i++)
    {
        if(!in_array(mysql_field_name($result,$i),$service))
    {
       //currently selected field is not a service field
       $name[] = mysql_field_name($result,$i); 
    }
    }
    return $name;
}

//Below $conn is db connection created in 'db.config.php'

$query = "SELECT `name` FROM  `detail_arg` WHERE visibility = 0";
$res = mysql_query($query,$conn);
if($res===false)
{
    $err_msg = mysql_real_escape_string(mysql_error($conn));
    echo "{success:false,data:'".$err_msg."'}";
    die();
}
$arg = array(); //list of argument names
while($row = mysql_fetch_assoc($res))
{
    $arg[] = $row['name'];
}

//Following function writes the select subquery which is
    //necessary to build a column containing a single attribute.

function make_subquery($attribute) //$attribute contains attribute name
{
    $query = "";
    $query.="(SELECT incident_detail.arg_value ";
    $query.="FROM incident_detail ";
    $query.="INNER JOIN detail_arg ";
    $query.="ON incident_detail.arg_id = detail_arg.id AND      detail_arg.name='".$attribute."' ";
    $query.="WHERE incident.id = incident_detail.incident_id) ";
    $query.="AS $attribute";
    return $query;
}

/*

echo make_subquery("date"); //debug code

*/

$subquery = array(); //list of subqueries
for($i=0;$i<count($arg);$i++)
{
    $subquery[] = make_subquery($arg[$i]); 
}

$query = "SELECT "; //final query containing subqueries

$fields = get_fields_name("incident",array("id","visibility"),$conn); 
    //list of 'incident' table's fields

for($i=0;$i<count($fields);$i++)
{
    $query.="incident.".$fields[$i].", ";
}

//insert the subqueries

$sub = implode($subquery,", ");
$query .= $sub;

    $query.=" FROM incident ORDER BY incident.id";
echo $query;
?>

我写了一个简单的PHP脚本来概括斯宾塞的想法来解决我的问题。 代码如下:

<?php
    require_once('includes/db.config.php'); //this file performs connection to mysql

/*
 * Following function requires a table name ($table)
 * and a number of service fields ($num). Given those parameters
 * it returns the number of table fields (excluding service fields). 
 */

function get_fields_number($table,$num,$conn)
{   
    $query = "SELECT * FROM $table";
    $result = mysql_query($query,$conn);
    return mysql_num_fields($result)-$num; //remember there are $num service fields
}

/*
 * Following function requires a table name ($table) and an array
 * containing a list of service fields names. Given those parameters, 
 * it returns the list of field names. That list is contained within an array and
 * service fields are excluded.
 */

function get_fields_name($table,$service,$conn)
{
    $query = "SELECT * FROM $table";
    $result = mysql_query($query,$conn);
    $name = array(); //Array to be returned
    for ($i=0;$i<mysql_num_fields($result);$i++)
    {
        if(!in_array(mysql_field_name($result,$i),$service))
    {
       //currently selected field is not a service field
       $name[] = mysql_field_name($result,$i); 
    }
    }
    return $name;
}

//Below $conn is db connection created in 'db.config.php'

$query = "SELECT `name` FROM  `detail_arg` WHERE visibility = 0";
$res = mysql_query($query,$conn);
if($res===false)
{
    $err_msg = mysql_real_escape_string(mysql_error($conn));
    echo "{success:false,data:'".$err_msg."'}";
    die();
}
$arg = array(); //list of argument names
while($row = mysql_fetch_assoc($res))
{
    $arg[] = $row['name'];
}

//Following function writes the select subquery which is
    //necessary to build a column containing a single attribute.

function make_subquery($attribute) //$attribute contains attribute name
{
    $query = "";
    $query.="(SELECT incident_detail.arg_value ";
    $query.="FROM incident_detail ";
    $query.="INNER JOIN detail_arg ";
    $query.="ON incident_detail.arg_id = detail_arg.id AND      detail_arg.name='".$attribute."' ";
    $query.="WHERE incident.id = incident_detail.incident_id) ";
    $query.="AS $attribute";
    return $query;
}

/*

echo make_subquery("date"); //debug code

*/

$subquery = array(); //list of subqueries
for($i=0;$i<count($arg);$i++)
{
    $subquery[] = make_subquery($arg[$i]); 
}

$query = "SELECT "; //final query containing subqueries

$fields = get_fields_name("incident",array("id","visibility"),$conn); 
    //list of 'incident' table's fields

for($i=0;$i<count($fields);$i++)
{
    $query.="incident.".$fields[$i].", ";
}

//insert the subqueries

$sub = implode($subquery,", ");
$query .= $sub;

    $query.=" FROM incident ORDER BY incident.id";
echo $query;
?>

最好发布DDL,而不是表的描述。当您发布DDL时,我们可以在本地加载和测试它。如果你不这样做,我们必须对你的描述进行反向工程。我们大多数人没有时间这么做,我赞成。想象一下五到十个人从文本中逆向工程数据模型:如果提供一个可用的数据模型+dat,我个人回答这个问题是多么浪费时间啊。现在,我没有。不是因为我懒惰,而是因为我不想为懒惰的人工作。@Catcall:他描述了一个相当标准的EAV模型,我们中的一些人对此非常熟悉。@wildplasser:我认为这个问题一点也不懒惰。这个问题似乎被充分和彻底地提出了。我认为提供的表格说明足以回答这个问题,但如果没有工作,它是不可用的。他可以添加一个20页的PDF,但仍然无法使用。请发布代码+描述+意图,而不是文本。最好发布DDL而不是表的描述。当您发布DDL时,我们可以在本地加载和测试它。如果你不这样做,我们必须对你的描述进行反向工程。我们大多数人没有时间这么做,我赞成。想象一下,五到十个人从文本中逆向工程数据模型:这是多么浪费时间啊

如果提供了可用的数据模型+dat,您将亲自回答这个问题。现在,我没有。不是因为我懒惰,而是因为我不想为懒惰的人工作。@Catcall:他描述了一个相当标准的EAV模型,我们中的一些人对此非常熟悉。@wildplasser:我认为这个问题一点也不懒惰。这个问题似乎被充分和彻底地提出了。我认为提供的表格说明足以回答这个问题,但如果没有工作,它是不可用的。他可以添加一个20页的PDF,但仍然无法使用。请发布代码+描述+意图,而不是文本。这些似乎是一些尽职调查会提前确定的属性。EAV表使开发人员不必弄清楚他们应该存储哪些数据,这是一种罪恶。现在,有时不可能预先知道存储医疗测试时每个新测试可能有新的属性。在这种情况下,如果很难正确查询并且很难实施良好的数据完整性,EAV是合适的。@HLGEM:是的,我同意,EAV是一种可行的,有时是适合数据库的模型。让我伤心的是,我们有时会通过SQL扭曲将EAV模型转换回关系表示。关于强制执行数据完整性和创建返回正确结果的查询的困难,您是完全正确的。EAV数据库中的数据操作可能很复杂。当我们呈现EAV数据的关系视图时,用户希望在关系视图中插入或更新行。。。太糟糕了,这不是CTEs的功能,它允许您将属性表预加入到名称列的团队信息表中。@X-Zero:是的,但MySQL不支持CTEs。我想继续投票支持您的答案。和EAV进行了一次很好的通话,并查看了必然会出现的问题。这些似乎是需要提前进行一点尽职调查才能确定的属性。EAV表使开发人员不必弄清楚他们应该存储哪些数据,这是一种罪恶。现在,有时不可能预先知道存储医疗测试时每个新测试可能有新的属性。在这种情况下,如果很难正确查询并且很难实施良好的数据完整性,EAV是合适的。@HLGEM:是的,我同意,EAV是一种可行的,有时是适合数据库的模型。让我伤心的是,我们有时会通过SQL扭曲将EAV模型转换回关系表示。关于强制执行数据完整性和创建返回正确结果的查询的困难,您是完全正确的。EAV数据库中的数据操作可能很复杂。当我们呈现EAV数据的关系视图时,用户希望在关系视图中插入或更新行。。。太糟糕了,这不是CTEs的功能,它允许您将属性表预加入到名称列的团队信息表中。@X-Zero:是的,但MySQL不支持CTEs。我想继续投票支持您的答案。对EAV和视图的调用非常好,这必然会出现问题。我也喜欢传统的关系数据库模型,一行表示实体的实例,属性值存储在命名列中。此方案中的表为许多用户所熟悉,例如,与许多用户在Excel中的操作兼容,并且还使用子表来重复属性。但存储在EAV模型中的数据也是可行的,有时甚至是适当的,即使它有点麻烦。EAV模型确实需要另一层逻辑,用于从数据库检索,尤其是数据操作。我也喜欢传统的关系数据库模型,一行表示实体实例,属性值存储在命名列中。此方案中的表为许多用户所熟悉,例如,与许多用户在Excel中的操作兼容,并且还使用子表来重复属性。但存储在EAV模型中的数据也是可行的,有时甚至是适当的,即使它有点麻烦。EAV模型确实需要另一层逻辑,用于从数据库检索,特别是用于数据操作。