简体   繁体   English

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

[英]SQL issue: one to many relationship and EAV model

Good evening guys, I'm a newbie to web programming and I need your help to solve a problem inherent to SQL query. 晚上好,我是网络编程的新手,我需要你的帮助才能解决SQL查询固有的问题。 The database engine I'm using is MySQL and I access it via PHP, here I'll explain a simplified version of my database, just to fix ideas. 我正在使用的数据库引擎是MySQL,我通过PHP访问它,在这里我将解释我的数据库的简化版本,只是为了解决想法。 Let's suppose to work with a database containing three tables: teams , teams_information , attributes . 让我们假设使用包含三个表的数据库: teamsteams_informationattributes More precisely: 更确切地说:

1) teams is a table containing some basic information about italian football teams (soccer, not american football :D), it is formed by three fields: 'id' ( int, primary key) , 'name' (varchar, team name), nickname ( Varchar , team nickname); 1) teams是一个包含意大利足球队基本信息的表(足球,而不是美式足球:D),它由三个领域组成:'id'( int, primary key) ,'name'(varchar,球队名称) , nicknameVarchar ,团队昵称);

2) attributes is a table containing a list of possible information about a football team, such as city (the city where team plays its home match), captain (team captain's fullname), f_number (number of fans) and so on. 2) attributes是一个表格,其中包含有关足球队的可能信息列表,例如city (球队主场比赛的城市), captain (队长的全名), f_number (球迷数量)等等。 This table is formed by three fields: id (int, primary key) , attribute_name ( varchar , an identifier for the attribute), attribute_desc (text, an explanation of the meaning of attribute). 该表由三个字段组成:id (int, primary key)attribute_namevarcharattribute_name的标识符), attribute_desc (text,属性含义的解释)。 Each record of this table represents a single possible attribute of a football team; 该表的每条记录代表足球队的一个可能属性;

3) teams_information is a table where some information, about teams listed in team table, are available. 3) teams_information是一个表格,其中提供了有关team表中列出的team一些信息。 This table contains three fields: id ( int , primary key), team_id ( int , a foreign key which identifies a team), attribute_id ( int , a foreign key which identifies one of the attributes listed in attributes table), attribute_value ( varchar , the value of the attribute). 该表包含三个字段:id( int ,主键), team_idint ,标识组的外键), attribute_idint ,标识attributes表中列出的attributes之一的外键), attribute_valuevarchar ,属性的值)。 Each record represents a single attribute of a single team. 每条记录代表一个团队的单个属性。 In general, different teams will have a different number of information, so for some teams a large number of attributes will be available while for other teams only a small number of attributes will be available. 一般来说,不同的团队将拥有不同数量的信息,因此对于某些团队而言,可以获得大量属性,而对于其他团队,只有少量属性可用。

Note that relation between teams and teams_information is one to many and the same relation exists between attributes and teams_information 请注意, teamsteams_information之间的关系是一对多, attributesteams_information之间存在相同的关系

Well, given this model my purpose is to realize a grid (maybe with ExtJS 4.1) to show user the list of italian football team, each record of this grid will represent a single football team and will contain all possible attributes: some fields may be empty (because, for considered team, the correspondent attribute is unknown), while the others will contain the values stored in teams_information table (for the considered team). 好吧,鉴于这个模型我的目的是实现一个网格(可能与ExtJS 4.1)向用户显示意大利足球队的名单,该网格的每个记录将代表一个足球队并将包含所有可能的属性:某些字段可能是为空(因为,对于考虑的团队,对应的属性是未知的),而其他人将包含存储在teams_information表中的值(对于所考虑的团队)。 According to the above grid's field are: id, team_name and a number of fields to represent all the different attributes listed in 'attributes' table. 根据上面的网格字段是:id,team_name和一些字段来表示'attributes'表中列出的所有不同属性。

My question is: can I realize such a grid by using a SINGLE SQL query (maybe a proper SELECT query, to fetch all data I need from database tables) ? 我的问题是:我可以通过使用SINGLE SQL查询(可能是一个正确的SELECT查询,从数据库表中获取我需要的所有数据)来实现这样的网格吗? Can anyone suggest me how to write a similar query (if it exists) ? 任何人都可以建议我如何写一个类似的查询(如果它存在)?

Thanks in advance for helping me. 在此先感谢您的帮助。

Regards. 问候。

Enrico. 恩里科。

The short answer to your question is no, there is no simple construct in MySQL to achieve the result set you are looking for. 对你的问题的简短回答是否定的,MySQL中没有简单的构造来实现你正在寻找的结果集。

But it is possible to carefully (painstakingly) craft such a query. 但是有可能仔细(艰苦地)制定这样的查询。 Here is an example, I trust you will be able to decipher it. 这是一个例子,我相信你将能够破译它。 Basically, I'm using correlated subqueries in the select list, for each attribute I want returned. 基本上,我在选择列表中使用相关子查询,对于我想要返回的每个属性。

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

For 'multi-valued' attributes, you'd have to pull each instance of the attribute separately. 对于“多值”属性,您必须分别拉出属性的每个实例。 (Use the LIMIT to specify whether you are retrieving the first one, the second one, etc.) (使用LIMIT指定是检索第一个,第二个,等等)

     , ( 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

I use nickname as an example here, because American soccer clubs frequently have more than one nickname, eg Chicago Fire Soccer Club has nicknames: 'The Fire', 'La Máquina Roja', 'Men in Red', 'CF97', et al.) 我在这里使用昵称作为例子,因为美国足球俱乐部经常有不止一个昵称,例如芝加哥火足球俱乐部有绰号:'The Fire','LaMáquinaRoja','Men in Red','CF97',等人。)

NOT AN ANSWER TO YOUR QUESTION, BUT ... 不是对你的问题的回答,但是......

Have I mentioned numerous times before, how much I dislike working with EAV database implementations? 我以前曾多次提到过,我不喜欢使用EAV数据库实现多少次? What should IMO be a very simple query turns into an overly complicated beast of a potentially light dimming query. IMO应该是一个非常简单的查询,变成一个过于复杂的可能轻微调暗查询的野兽。

Wouldn't it be much simpler to create a table where each "attribute" is a separate column? 创建一个表格,每个“属性”是一个单独的列,这不是更简单吗? Then queries to return reasonable result sets would look more reasonable... 那么返回合理结果集的查询看起来会更合理......

SELECT id, name, nickname, city, captain, f_number, ... FROM team

But what really makes me shudder is the prospect that some developer is going to decide that the LDQ should be "hidden" in the database as a view, to enable the "simpler" query. 但真正令我不寒而栗的是,有些开发人员决定将LDQ作为视图“隐藏”在数据库中,以启用“更简单”的查询。

If you go this route, PLEASE PLEASE PLEASE resist any urge you may have to store this query in the database as a view. 如果你走这条路,请请你不要强烈要求你将这个查询作为一个视图存储在数据库中。

I'm going to take a slightly different route. 我将采取略有不同的路线。 Spencer's answer is fantastic, and it addresses the issue quite well, but there's still a large underlying problem. 斯宾塞的答案太棒了,它很好地解决了这个问题,但仍然存在很大的潜在问题。

The data that you are trying to display on the site is over-normalized in the database. 您尝试在站点上显示的数据在数据库中过度标准化。 I won't elaborate, since, again, Spencer's answer highlights the issue pretty well. 我不会详细说明,因为斯宾塞的答案再次强调了这个问题。

Rather, I'd like to recommend a solution that denormalizes the data a bit. 相反,我想推荐一种能够使数据非规范化的解决方案。

Convert all of your Team data into a single table with many columns. 将所有Team数据转换为包含许多列的单个表。 (If there is Player data that isn't covered in the question, that would be a second table, but I'll gloss over that for now.) (如果问题中没有涉及播放器数据,那将是第二个表格,但我现在要掩盖它。)

Sure, you'll have a whole bunch of columns, and a lot of the columns might be NULL for a lot of the rows. 当然,你有一大堆的字段,并且很多列可能会为很多的行为NULL。 It's not normalized, and it's not pretty, but here's the huge advantage that you gain. 它没有正常化,而且不漂亮,但这是你获得的巨大优势。

Your query becomes: 您的查询变为:

SELECT * FROM Teams

That's it. 而已。 That gets displayed right to the website and you are done. 这显示在网站上,你就完成了。 You might have to go out of your way to realize this schema, but it would be totally worth the time investment. 您可能不得不竭尽全力去实现这种架构,但这完全值得投入时间。

I think what you're saying is that you want the rows in the attributes table to appear as columns in the result recordset. 我想你所说的是你希望属性表中的行在结果记录集中显示为列。 If this is correct, then then in SQL you would use PIVOT. 如果这是正确的,那么在SQL中你将使用PIVOT。 A quick search on SO seems to indicate that there is no PIVOT equivalent in MySql. 对SO的快速搜索似乎表明MySql中没有PIVOT等价物。

I wrote a simple PHP script to generalize spencer's idea to solve my issue. 我写了一个简单的PHP脚本来概括斯宾塞的想法来解决我的问题。 Here's the code: 这是代码:

<?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;
?>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM