[英]SQL issue: one to many relationship and EAV model
晚上好,我是網絡編程的新手,我需要你的幫助才能解決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_name
的標識符), attribute_desc
(text,屬性含義的解釋)。 該表的每條記錄代表足球隊的一個可能屬性;
3) teams_information
是一個表格,其中提供了有關team
表中列出的team
一些信息。 該表包含三個字段:id( int
,主鍵), team_id
( int
,標識組的外鍵), attribute_id
( int
,標識attributes
表中列出的attributes
之一的外鍵), attribute_value
( varchar
,屬性的值)。 每條記錄代表一個團隊的單個屬性。 一般來說,不同的團隊將擁有不同數量的信息,因此對於某些團隊而言,可以獲得大量屬性,而對於其他團隊,只有少量屬性可用。
請注意, teams
和teams_information
之間的關系是一對多, attributes
和teams_information
之間存在相同的關系
好吧,鑒於這個模型我的目的是實現一個網格(可能與ExtJS 4.1)向用戶顯示意大利足球隊的名單,該網格的每個記錄將代表一個足球隊並將包含所有可能的屬性:某些字段可能是為空(因為,對於考慮的團隊,對應的屬性是未知的),而其他人將包含存儲在teams_information
表中的值(對於所考慮的團隊)。 根據上面的網格字段是:id,team_name和一些字段來表示'attributes'表中列出的所有不同屬性。
我的問題是:我可以通過使用SINGLE SQL查詢(可能是一個正確的SELECT
查詢,從數據庫表中獲取我需要的所有數據)來實現這樣的網格嗎? 任何人都可以建議我如何寫一個類似的查詢(如果它存在)?
在此先感謝您的幫助。
問候。
恩里科。
對你的問題的簡短回答是否定的,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作為視圖“隱藏”在數據庫中,以啟用“更簡單”的查詢。
如果你走這條路,請請你不要強烈要求你將這個查詢作為一個視圖存儲在數據庫中。
我將采取略有不同的路線。 斯賓塞的答案太棒了,它很好地解決了這個問題,但仍然存在很大的潛在問題。
您嘗試在站點上顯示的數據在數據庫中過度標准化。 我不會詳細說明,因為斯賓塞的答案再次強調了這個問題。
相反,我想推薦一種能夠使數據非規范化的解決方案。
將所有Team數據轉換為包含許多列的單個表。 (如果問題中沒有涉及播放器數據,那將是第二個表格,但我現在要掩蓋它。)
當然,你有一大堆的字段,並且很多列可能會為很多的行為NULL。 它沒有正常化,而且不漂亮,但這是你獲得的巨大優勢。
您的查詢變為:
SELECT * FROM Teams
而已。 這顯示在網站上,你就完成了。 您可能不得不竭盡全力去實現這種架構,但這完全值得投入時間。
我想你所說的是你希望屬性表中的行在結果記錄集中顯示為列。 如果這是正確的,那么在SQL中你將使用PIVOT。 對SO的快速搜索似乎表明MySql中沒有PIVOT等價物。
我寫了一個簡單的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;
?>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.