テーブルが多すぎます。 MySQLは、結合で61のテーブルのみを使用できます
-
18-09-2019 - |
質問
MySQLの複数のテーブルからデータをエクスポートする最良の方法は何ですか。私は基本的に製品の詳細を扱っています。製品には150の属性があるとします。それを単一の行でエクスポートしてから、CSVまたはTabdelimited形式のフラットファイルにエクスポートするにはどうすればよいですか。
テーブルが多すぎるエラーが発生します。 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"];
解決
EAVデザインを使用しており、可変数の属性から単一の行を再構築しようとしています。これは、EAVデザインを使用して遭遇する多くの地雷の1つを指摘しています。単一のSQLクエリでできる結合の数には実際的な制限があります。
特にMySQLでは、あなたが見つけたように、厳しい制限があります。しかし、他のRDBMSブランドでさえ、参加のコストはテーブルの数に関して幾何学的であるため、効果的な制限があります。
EAVを使用する場合、 SQLで行を再構築しようとしないでください 従来のデータベース設計があるかのように。代わりに、エンティティIDでソートされた属性を行として取得します。次に、アプリケーションコードでそれらを郵便処理します。これは、データを1つのステップでダンプできないことを意味します。属性の行をループするためにコードを記述し、出力する前に各データの行を改革する必要があります。
EAVは便利なデータベース設計ではありません。それを使用することには多くの高価な欠点があり、あなたはそれらの1つを打ったばかりです。
見る http://www.simple-talk.com/opinion/opinion-ces/bad-carma/ EAVの使用方法についての素晴らしいストーリーについては、1つのビジネスを運命づけています。
また、ご覧ください http://en.wikipedia.org/wiki/inner-platform_effect EAVはこのアンチパターンの例だからです。
カタログ内の製品ごとの動的な属性セットをサポートする必要性を理解しています。しかし、EAVはあなたのアプリケーションを殺すつもりです。動的属性をサポートするために私がしていることは次のとおりです。
すべての製品タイプに共通する各属性のベーステーブルの実際の列を定義します。製品名、価格、在庫の量など。標準を想像するために一生懸命働く 製品 エンティティでは、このセットにできるだけ多くの属性を含めることができます。
タイプのもう1つの列を定義します
TEXT
指定された各製品タイプのすべての追加属性について。このコラムに保管してください シリアル化されたロブ 属性のうち、どんな形式でもあなたに合った形式:XML、JSON、YAML、あなた自身の自家製DSLなど。これをSQLクエリの単一の列として扱います。これらの属性に基づいて行う必要がある検索、並べ替え、または表示には、全体を取得する必要があります
TEXT
アプリケーションにBlobを脱水し、アプリケーションコードを使用して属性を分析します。
他のヒント
この多くの属性がある場合、それはまばらなデータベースであると予想しているので、たくさんの無駄なスペースがあります。
可能であれば、代わりにエンティティアトリブと価値のデータベースを使用することを検討することができます。
http://en.wikipedia.org/wiki/entity-attribute-value_model
これがあなたを買うのは、データベースをリファクタリングする方法ですが、より拡張可能になり、必要なテーブルの数を減らします。 4-6テーブル(属性を備えた2-3エンティティテーブル)に下がることができるはずです。すべてのクエリが動的になるため、クエリを作成するのはもう少し困難ですが、エクスポートを簡素化し、データベースのメンテナンスがより簡単になるはずです。
このスキーマを使用する必要がある場合は、いくつかのトリガーを作成し、いくつかのテーブルに参加しているトリガーを呼び出してからクエリを作成できますが、大きなパフォーマンスヒットが行われます。
アップデート:
EAVテーブルが使用されており、MySQLがピボット関数を実行していないため、この質問に対する答えを読みたいかもしれません。MySQLEntity-Attribute-Valueスキーマをピボットする方法MySQL Entity-Attribute-Valueスキーマをピボットする方法
EAVを使用していて、一度に多数の属性をエクスポートしたい場合、最良の方法は実際に複数の一時テーブルを使用することです。
各一時テーブルには、同じプライマリキー列があります。次に、それらすべてに参加して、CSVにエクスポートします。
完全に肉付けされた例をやりたいかどうかはわかりませんが、うまくいけば物事をより明確にするアウトラインをしようとします。
1.)エクスポートする属性のリストを取得します。 EAV属性_Valuesテーブルへの結合で属性_IDSを使用します。
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未満の一時テーブルがあると仮定すると(不条理になります)、これらすべてに参加して1つのテーブルを作成できます。
私のシステムでは、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を使用してユーザーに送信します。
それが役立つことを願っています。
また、上記のプロセスは私にとってかなり迅速に実行されることに注意してください。一時的なテーブルを使用する理由は、使用後に自動的にドロップされるため、複数のユーザーが互いに干渉することなく同じタイプのプロセスを実行できるためです。
Spring Bootでは、Fetch Typeを怠け者として使用していない場合にこれが発生します。この種の状況では、テーブルの1つが61以上のサブ関係を持っていますEx -Table Names A、B、C、D
- a relate b
- b関連c
- c関連d
この状況では、Aには3つのサブ関係があります。 Aからデータを取得した場合、関係に基づいてDのデータにアクセスできます。
したがって、フェッチタイプを怠zyとして使用してください