1

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

1)teams是一个包含有关意大利足球队的一些基本信息的表格(足球,而不是美式足球:D),它由三个字段组成:'id' ( int, primary key), 'name' (varchar, 球队名称), nickname( Varchar, 球队昵称) ;

2)attributes是一个表格,其中包含有关足球队的可能信息列表,例如city(球队主场比赛所在的城市)、captain(球队队长的全名)、f_number(球迷人数)等。该表由三个字段组成: id (int, primary key), attribute_name( varchar, 属性的标识符), attribute_desc(text, 属性含义的解释)。该表的每条记录代表一个足球队的单个可能属性;

3)teams_information是一个表格,其中包含有关表格中列出的团队的一些信息team。该表包含三个字段:id ( int, 主键), team_id( int, 标识团队的外键), attribute_id( , 标识表中int列出的属性之一的外键), ( , 属性值)。每条记录代表单个团队的单个属性。一般来说,不同的团队会有不同数量的信息,因此对于某些团队,大量的属性可用,而对于其他团队,只有少量的属性可用。attributesattribute_valuevarchar

请注意,teams和之间的关系teams_information是一对多的,并且在attributes和之间存在相同的关系teams_information

好吧,给定这个模型,我的目的是实现一个网格(可能使用 ExtJS 4.1)来向用户显示意大利足球队的列表,这个网格的每条记录将代表一个足球队并包含所有可能的属性:一些字段可能是空(因为对于考虑的团队,对应的属性是未知的),而其他将包含存储在teams_information表中的值(对于考虑的团队)。根据上面网格的字段有:id、team_name 和一些字段来表示'attributes'表中列出的所有不同的属性。

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

提前感谢您帮助我。

问候。

恩里科。

4

4 回答 4

3

您的问题的简短回答是否定的,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

对于“多值”属性,您必须分别提取属性的每个实例。(使用 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

我在这里以昵称为例,因为美国足球俱乐部经常有多个昵称,例如芝加哥火焰足球俱乐部有昵称:“The Fire”、“La Máquina Roja”、“Men in Red”、“CF97”等.)

不是您的问题的答案,但是...

我之前是否多次提到过,我有多不喜欢使用 EAV 数据库实现?IMO 应该是一个非常简单的查询变成了一个潜在的调光查询的过度复杂的野兽。

创建一个每个“属性”都是一个单独的列的表不是更简单吗?然后返回合理结果集的查询看起来更合理......

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

但真正让我不寒而栗的是,一些开发人员将决定将 LDQ 作为视图“隐藏”在数据库中,以启用“更简单”的查询。

如果你走这条路,请拒绝任何你可能不得不将此查询作为视图存储在数据库中的冲动。

于 2012-06-21T21:14:34.880 回答
1

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

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

相反,我想推荐一种对数据进行非规范化的解决方案。

将您的所有团队数据转换为一个包含许多列的表。(如果问题中没有包含玩家数据,那将是第二个表,但我现在将忽略它。)

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

您的查询变为:

SELECT * FROM Teams

而已。这会直接显示在网站上,您就完成了。您可能必须不遗余力地实现此模式,但这完全值得花时间投资。

于 2012-06-22T20:44:59.147 回答
0

我写了一个简单的 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;
?>
于 2012-06-25T08:44:04.973 回答
0

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

于 2012-06-21T20:50:37.193 回答