14

从 MySQL 中的多个表中导出数据的最佳方法是什么。我基本上是在处理产品细节。假设一个产品有 150 个数据属性。如何将其导出为单行,然后将其导出为 CSV 或制表符分隔格式的平面文件。

得到错误太多的表;MySQL 在一个连接中只能使用 61 个表

/**** Get Resultset *****/
$rs = mysql_query($sql);
/**** End of Get Resultset *****/

$objProfileHistory->addHistory($this->profile_id, "Loaded ". mysql_num_rows($rs)." records");


$this->runQuery($sql);

$this->exportToCSV();

/**
  * getAttributeDetails
  */
function getAttributeDetails(){
    global $dbObj, $profile;

    $base_table = "catalog_product_entity";
    $select  = array();
    $tables  = array();
    $i   = 0;

    $profile->showLog("Start fields mapping", "success");

   if( is_array($this->attributes_in_db) && sizeof($this->attributes_in_db) > 0 ){
    $arr = implode("','", $this->attributes_in_db);
    $sql = "select attribute_id, attribute_code, backend_type, frontend_input
        from eav_attribute 
        where attribute_code in ('".$arr."') 
        and entity_type_id = 
         (select entity_type_id 
          from eav_entity_type 
          where entity_type_code = 'catalog_product')";
    $rs = $dbObj->customqry($sql);

    if( $rs ){
     while( $row = mysql_fetch_assoc( $rs ) ){
      $backend_type  = $row["backend_type"];
      $attribut_code = $row["attribute_code"];
      $attribute_id = $row["attribute_id"];
      $frontend_input = $row["frontend_input"];
      switch( $backend_type ){
       case "text":
        $where[]  = $base_table."_".$backend_type."".$i.".attribute_id=".$attribute_id;
        $and[]  = $base_table.".entity_id=".$base_table."_".$backend_type."".$i.".entity_id";
        $select[]  = $base_table."_".$backend_type."".$i.".value as ".$attribut_code;
        $tables[]  = $base_table."_".$backend_type." as ".$base_table."_".$backend_type."".$i;
       break;

       case "decimal":
        $where[]  = $base_table."_".$backend_type."".$i.".attribute_id=".$attribute_id;
        $and[]  = $base_table.".entity_id=".$base_table."_".$backend_type."".$i.".entity_id";
        $select[]  = $base_table."_".$backend_type."".$i.".value as ".$attribut_code;
        $tables[]  = $base_table."_".$backend_type." as ".$base_table."_".$backend_type."".$i;
       break;

       case "static":
        $where[]  = $base_table."".$i.".entity_id=".$base_table.".entity_id";
        $and[]  = $base_table.".entity_id=".$base_table."".$i.".entity_id";
        $select[]  = $base_table."".$i.".".$attribut_code." as ".$attribut_code;
        $tables[]  = $base_table." as ".$base_table."".$i;
       break;

       case "int":
        if( $attribut_code == "tax_class_id" && $frontend_input == "select" ){
         $where[]  = "tax_class{$i}.class_id=(select ".$base_table."_".$backend_type."".$i.".value from ".$base_table."_".$backend_type." as ".$base_table."_".$backend_type."".$i." where  ".$base_table."_".$backend_type."".$i.".attribute_id=".$attribute_id." and ".$base_table."_".$backend_type."".$i.".entity_id=".$base_table.".entity_id limit 1))";
         $and[]  = "";
         $select[]  = "tax_class{$i}.class_name as {$attribut_code}";
         $tables[]  = "tax_class as tax_class{$i}";
         } else if( $frontend_input == "select" ){
         $where[]  = "eav_attribute_option_value{$i}.option_id=(select ".$base_table."_".$backend_type."".$i.".value from ".$base_table."_".$backend_type." as ".$base_table."_".$backend_type."".$i." where  ".$base_table."_".$backend_type."".$i.".attribute_id=".$attribute_id." and ".$base_table."_".$backend_type."".$i.".entity_id=".$base_table.".entity_id limit 1))";
         $and[]  = "";
         $select[] = "eav_attribute_option_value{$i}.value as {$attribut_code}";
         $tables[]  = "eav_attribute_option_value as eav_attribute_option_value{$i}";
        } else {
         $where[]  = $base_table."_".$backend_type."".$i.".attribute_id=".$attribute_id;
         $and[]  = $base_table.".entity_id=".$base_table."_".$backend_type."".$i.".entity_id";
         $select[]  = $base_table."_".$backend_type."".$i.".value as ".$attribut_code;
         $tables[]  = $base_table."_".$backend_type." as ".$base_table."_".$backend_type."".$i;
        }
       break;

       case "varchar":
        $where[]  = $base_table."_".$backend_type."".$i.".attribute_id=".$attribute_id;
        $and[]  = $base_table.".entity_id=".$base_table."_".$backend_type."".$i.".entity_id";
        $select[]  = $base_table."_".$backend_type."".$i.".value as ".$attribut_code;
        $tables[]  = $base_table."_".$backend_type." as ".$base_table."_".$backend_type."".$i;
       break;

       case "datetime":
        $where[]  = $base_table."_".$backend_type."".$i.".attribute_id=".$attribute_id;
        $and[]  = $base_table.".entity_id=".$base_table."_".$backend_type."".$i.".entity_id";
        $select[]  = $base_table."_".$backend_type."".$i.".value as ".$attribut_code;
        $tables[]  = $base_table."_".$backend_type." as ".$base_table."_".$backend_type."".$i;
       break;
      }//switch
      $i++;
     }//while


     $sql = "select ".implode(",", $select)." from ".$base_table;
     for($i=0; $i < sizeof($select); $i++){
      $sql .= " left join ". $tables[$i] . " on (".$where[$i];//." and ".$and[$i].")";
      if( strlen($and[$i]) > 0 ){
       $sql .= " and ".$and[$i].")";
      }
     }//for
     $sql .= " group by {$base_table}.entity_id ";
    }//if
    //echo $sql; exit;
    return $sql;
   }
   //echo $sql;
   //echo "<pre>";print_r($tables);print_r($select);print_r($where);print_r($and);
  }//end function

  /**
  * runQuery
  */
  function runQuery( $sql ){
   global $dbObj, $profile;
   if( $sql != "" ){
    $rs = $dbObj->customqry( $sql );
    $profile->showLog("Loaded ". mysql_num_rows($rs) ." records", "success");
    if( $rs ){
     $i = 0;
     while( $row = mysql_fetch_assoc( $rs ) ){
      $cnt = sizeof($this->attributes_in_db);
      for($j=0; $j < $cnt; $j++){
       $db_key  = $this->attributes_in_db[$j];
       $file_key = $this->attributes_in_file[$j];
       $this->export_data[$i][$db_key] = $row[$db_key];
      }
      $i++;
     }//while
    }
   }//if
  }//end function


  /**
  * exportToCSV
  */
  function exportToCSV(){
   global $smarty, $objProfileHistory, $profile;
   //$newFileName = $smarty->root_dir."/export/".$this->filename; //file name that you want to create
   $cnt = sizeof($this->var_array);
   for($i=0; $i < $cnt; $i++){
    extract($this->var_array[$i]);
   }//for


   if( $delimiter = "\t" ){
    $delimiter = "\t";//$delimiter;
   }

   if( strlen($filename) < 1 ){
    $filename = time().".csv";
   }

//    echo "<pre>";
//    print_r($this->action_array);
//    print_r($this->var_array);
//    print_r($this->map_array);
//    exit;
   # add amazon headers
   if( $this->action_array[0]['type'] == 'header' ){
//     $template_type  = $this->var_array[0]['template_type'];
//     $version   = $this->var_array[0]['version'];
//     $status_message = $this->var_array[0]['status_message'];
    $sStr = "TemplateType=".$template_type."{$delimiter}{$delimiter}Version=".$version."{$delimiter}{$delimiter}{$status_message}";
    $sStr .= "� ��\n"; //to seprate every record
   }





   $export_path = $path;
   $x_path = $profile->createDir( $export_path );

   $newFileName = $x_path ."/". $filename;

   $fpWrite = fopen($newFileName, "w"); // open file as writable

   # create header
   $cnt_header = sizeof($this->attributes_in_file);
   for( $i=0; $i < $cnt_header; $i++){
    $sStr .= $deli . $this->attributes_in_file[$i];
    $deli = $delimiter;
   }//for
   $sStr .= "� ��\n"; //to seprate every record

   # attach data
   $cnt_row = sizeof($this->export_data);
   for( $i=0; $i < $cnt_row; $i++ ){
    $sStr .= $saperator;
    $newdeli = "";
    for($j=0; $j < $cnt_header; $j++){
     $key  = $this->attributes_in_db[$j];
     $sku = $this->export_data[$i]["sku"];
4

4 回答 4

24

您正在使用 EAV 设计,并尝试从可变数量的属性中重新构建单行。这指出了您在使用 EAV 设计时会遇到的众多地雷之一:在单个 SQL 查询中可以执行的连接数量存在实际限制。

尤其是在 MySQL 中——正如您所发现的,有一个硬性限制。但即使在其他 RDBMS 品牌中,也存在有效限制,因为连接成本相对于表的数量呈几何级数。

如果您使用 EAV,请不要尝试在 SQL 中重新构造一行,就好像您有传统的数据库设计一样。相反,将属性作为行获取,按实体 ID 排序。然后在您的应用程序代码中对它们进行后处理。这确实意味着您不能一步转储数据——您必须编写代码来循环遍历属性行,并在输出之前重新修改每一行数据。

EAV 不是一种方便的数据库设计。使用它有许多代价高昂的缺点,而您只是遇到了其中一个。


请参阅http://www.simple-talk.com/opinion/opinion-pieces/bad-carma/了解有关使用 EAV 如何注定一项业务的精彩故事。

并且还看到http://en.wikipedia.org/wiki/Inner-platform_effect因为 EAV 是这种反模式的一个例子。


我理解需要支持目录中每个产品的动态属性集。但是 EAV 会杀死你的应用程序。这是我为支持动态属性所做的工作:

  • 在基表中为所有产品类型共有的每个属性定义一个真实列。产品名称、价格、库存数量等。努力想象规范的产品实体,以便您可以在此集合中包含尽可能多的属性。

  • 为每个给定产品类型的所有附加属性再定义一列TEXT类型。在此列中存储为属性的序列化 LOB,以适合您的任何格式:XML、JSON、YAML、您自己的自制 DSL 等。

    将其视为 SQL 查询中的单个列。您需要基于这些属性进行的任何搜索、排序或显示都需要您将整个TEXTblob 提取到应用程序中,对其进行反序列化,并使用应用程序代码分析属性。

于 2009-11-06T02:25:47.353 回答
3

如果你有这么多属性,我希望它是一个稀疏的数据库,所以你有大量的空间浪费。

如果可能的话,您可能想考虑使用实体-属性-值数据库。

http://en.wikipedia.org/wiki/Entity-attribute-value_model

这给你带来的是一种重构数据库的方法,但让它更具可扩展性,并减少你需要的表数量。您应该能够归结为 4-6 个表(2-3 个实体表及其属性)。创建查询有点困难,因为所有查询都是动态的,但它会简化您的导出,并且数据库维护应该更简单。

如果您必须使用此模式,您可能需要创建多个触发器,然后您可以调用连接多个表的触发器,然后进行查询,但您会受到巨大的性能影响。

更新:

由于正在使用 EAV 表,并且 MySQL 不执行数据透视函数,您可能需要阅读以下问题的答案:如何透视 MySQL 实体属性值模式 如何透视 MySQL 实体属性值模式

于 2009-11-06T02:17:22.300 回答
1

如果您使用 EAV 并且想一次导出大量属性,那么最好的方法实际上是使用多个临时表。

每个临时表都将具有相同的主键列。然后加入所有这些并导出到 csv。

我不知道我是否想做一个完整的例子,但我会尝试做一个大纲,希望能让事情更清楚。

1.) 获取要导出的属性列表。您将在连接到您的 EAV 属性值表时使用它们的属性 ID。

2.) 拆分属性,这样您就不会超过连接限制。您需要原始表和每个连接 1 个表,因此在此方案中每个表可以有 60 个属性。

3.) 为每组属性创建“平面”临时表。它会是这样的。

CREATE TEMPORARY TABLE temp1
[(create_definition,...)]
SELECT t1.product_id, t1.sku, t2.color, GROUP_CONCAT(t3.sizes SEPARATOR ',') as sizes,
...

#( suppose the product has multiple sizes and you want them shown comma-separated in your export)

FROM products t1
LEFT JOIN eav_attribute_values t2 ON t1.product_id = t2.product_id AND t2.attribute_id = 55
LEFT JOIN eav_attribute_values t3 ON t1.product_id = t2.product_id AND t2.attribute_id = 76
... etc for up to 60 attributes

CREATE TEMPORARY TABLE temp2 ... # repeat for next 60 attributes

4.) 现在您有了临时表 temp1、temp2、temp3 等。它们都共享相同的主键(例如 product_id 和/或 product_sku)。假设您有少于 60 个临时表(这将是荒谬的),您现在可以连接所有这些并创建一个表。

在我的系统中,我认为我没有超过 3 个临时表,而且数量很多。

CREATE TEMPORARY TABLE export_data
[(create_definition,...)]
SELECT t1.*, t2.*, t3.* FROM # though I would not actually use * here b/c it would cause repeated key fields. I would list out all the columns
temp1 t1 LEFT JOIN temp2 t2 ON t1.product_id = t2.product_id
LEFT JOIN temp3 t3 ON t1.product_id = t3.product_id # etc for more joins

5.) 出口。使用 MySQL 的文件导出功能创建 CSV。使用 PHP 将其发送给用户。

我希望这会有所帮助。

另请注意,上述过程对我来说执行得相当快。使用临时表的原因是因为它们在使用后会被自动删除,并且因为临时表只存在于创建它们的用户,所以多个用户都可以运行相同类型的进程而不会相互干扰。

于 2013-09-15T09:59:33.907 回答
0

在 Spring Boot 中,如果您没有将 fetch 类型用作 LAZY,则会发生这种情况。在这种情况下,您的一张表有超过 61 个子关系 EX - 表名 A、B、C 和 D

  • A 相关 B
  • B 关联 C
  • C 相关 D

在这种情况下,A 有 3 个子关系。如果您从 A 获取数据,您可以根据关系访问 D 中的数据。

所以使用 Fetch 类型作为 LAZY

于 2019-06-02T18:50:18.313 回答