晚上好,
我是网络编程的新手,我需要你的帮助来解决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
(文本,属性含义的解释) 。该表的每条记录代表足球队的一个可能属性;
3)teams_information
是一个表格,其中提供了有关team
表中列出的团队的一些信息。此表包含三个字段:id(int
,主键),team_id
(int
,标识团队的外键),attribute_id
(int
,一个外键,用于标识attributes
表中列出的一个属性),attribute_value
(varchar
,属性值)。每条记录代表一个团队的单个属性。一般来说,不同的团队将拥有不同数量的信息,因此对于某些团队而言,可以获得大量属性,而对于其他团队,只有少量属性可用。
请注意,teams
与teams_information
之间的关系是一对多,attributes
与teams_information
之间存在相同的关系
嗯,鉴于这个模型,我的目的是实现一个网格(可能使用ExtJS 4.1)向用户显示意大利足球队的名单,该网格的每个记录将代表一个足球队并将包含所有可能的属性:一些字段可能为空(因为,对于考虑的团队,对应的属性是未知的),而其他字段将包含存储在teams_information
表中的值(对于所考虑的团队)。
根据上面的网格字段是:id,team_name和一些字段来表示'attributes'表中列出的所有不同属性。
我的问题是:我可以通过使用SINGLE SQL查询(可能是一个正确的SELECT
查询来实现这样的网格,从数据库表中获取所需的所有数据)吗?
任何人都可以建议我如何写一个类似的查询(如果它存在)?
先谢谢你的帮助。
问候。
恩里科。
答案 0 :(得分: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','LaMáquinaRoja','Men in Red','CF97'等人。
不是对你的问题的回答,但是......
之前我曾多次提到过,我不喜欢使用EAV数据库实施多少次? IMO应该是一个非常简单的查询,变成一个过于复杂的可能轻微调暗查询的野兽。
创建一个表中每个“属性”是一个单独的列是不是更简单?然后查询返回合理的结果集看起来会更合理......
SELECT id, name, nickname, city, captain, f_number, ... FROM team
但真正令我不寒而栗的是,有些开发人员决定将LDQ作为视图“隐藏”在数据库中,以启用“更简单”的查询。
如果您选择这条路线,请请您拒绝将此查询作为视图存储在数据库中的任何冲动。
答案 1 :(得分:1)
我将采取略有不同的路线。斯宾塞的答案太棒了,它很好地解决了这个问题,但仍存在很大的潜在问题。
您尝试在网站上显示的数据在数据库中过度标准化。我不会详细说明,因为斯宾塞的答案再次强调了这个问题。
相反,我想推荐一种能够对数据进行非规范化的解决方案。
将所有Team数据转换为包含许多列的单个表。 (如果问题中没有涉及播放器数据,那将是第二个表格,但我现在将对此进行掩饰。)
当然,你会有一堆整个列,而很多行的很多列可能都是NULL。它没有标准化,并且它不漂亮,但这是你获得的巨大优势。
您的查询变为:
SELECT * FROM Teams
就是这样。这显示在网站上,你就完成了。你可能不得不自己去实现这个架构,但是完全值得投入时间。
答案 2 :(得分:0)
我认为您所说的是您希望属性表中的行在结果记录集中显示为列。如果这是正确的,那么在SQL中你将使用PIVOT。 快速搜索SO似乎表明MySql中没有PIVOT等价物。
答案 3 :(得分: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;
?>