簡體   English   中英

SQL問題:一對多關系和EAV模型

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

晚上好,我是網絡編程的新手,我需要你的幫助才能解決SQL查詢固有的問題。 我正在使用的數據庫引擎是MySQL,我通過PHP訪問它,在這里我將解釋我的數據庫的簡化版本,只是為了解決想法。 讓我們假設使用包含三個表的數據庫: teamsteams_informationattributes 更確切地說:

1) teams是一個包含意大利足球隊基本信息的表(足球,而不是美式足球:D),它由三個領域組成:'id'( int, primary key) ,'name'(varchar,球隊名稱) , nicknameVarchar ,團隊昵稱);

2) attributes是一個表格,其中包含有關足球隊的可能信息列表,例如city (球隊主場比賽的城市), captain (隊長的全名), f_number (球迷數量)等等。 該表由三個字段組成:id (int, primary key)attribute_namevarcharattribute_name的標識符), attribute_desc (text,屬性含義的解釋)。 該表的每條記錄代表足球隊的一個可能屬性;

3) teams_information是一個表格,其中提供了有關team表中列出的team一些信息。 該表包含三個字段:id( int ,主鍵), team_idint ,標識組的外鍵), attribute_idint ,標識attributes表中列出的attributes之一的外鍵), attribute_valuevarchar ,屬性的值)。 每條記錄代表一個團隊的單個屬性。 一般來說,不同的團隊將擁有不同數量的信息,因此對於某些團隊而言,可以獲得大量屬性,而對於其他團隊,只有少量屬性可用。

請注意, teamsteams_information之間的關系是一對多, attributesteams_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM