web-dev-qa-db-ja.com

MySQLエンティティ属性値スキーマをピボットする方法

ファイルのすべてのメタデータ(つまり、ファイル名、作成者、タイトル、作成日)とカスタムメタデータ(ユーザーによってファイルに追加されたもの、たとえばCustUseBy、CustSendBy)を格納するテーブルを設計する必要があります。カスタムメタデータフィールドの数は事前に設定できません。実際、ファイルに追加されたカスタムタグの数と数を判断する唯一の方法は、テーブルに何が存在するかを調べることです。

これを保存するために、ベーステーブル(ファイルのすべての一般的なメタデータを含む)、Attributesテーブル(ファイルに設定できる追加のオプションの属性を保持する)、およびFileAttributesテーブル(これは、ファイルの属性に値を割り当てます)。

CREAT TABLE FileBase (
    id VARCHAR(32) PRIMARY KEY,
    name VARCHAR(255) UNIQUE NOT NULL,
    title VARCHAR(255),
    author VARCHAR(255),
    created DATETIME NOT NULL,
) Engine=InnoDB;

CREATE TABLE Attributes (
    id VARCHAR(32) PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    type VARCHAR(255) NOT NULL
) Engine=InnoDB;

CREATE TABLE FileAttributes (
    sNo INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    fileId VARCHAR(32) NOT NULL,
    attributeId VARCHAR(32) NOT NULL,
    attributeValue VARCHAR(255) NOT NULL,
    FOREIGN KEY fileId REFERENCES FileBase (id),
    FOREIGN KEY attributeId REFERENCES Attributes (id)
 ) Engine=InnoDB;

サンプルデータ:

INSERT INTO FileBase
(id,      title,  author,  name,        created)
  VALUES
('F001', 'Dox',   'vinay', 'story.dox', '2009/01/02 15:04:05'),
('F002', 'Excel', 'Ajay',  'data.xls',  '2009/02/03 01:02:03');

INSERT INTO Attributes
(id,      name,            type)
  VALUES
('A001', 'CustomeAttt1',  'Varchar(40)'),
('A002', 'CustomUseDate', 'Datetime');

INSERT INTO FileAttributes 
(fileId, attributeId, attributeValue)
  VALUES
('F001', 'A001',      'Akash'),
('F001', 'A002',      '2009/03/02');

ここで問題となるのは、次のような方法でデータを表示したいということです。

FileId, Title, Author, CustomAttri1, CustomAttr2, ...
F001    Dox    vinay   Akash         2009/03/02   ...
F002    Excel  Ajay     

どのクエリがこの結果を生成しますか?

25
Ashok

質問はMySQLに言及しており、実際、このDBMSにはこの種の問題のための特別な関数GROUP_CONCAT(expr)があります。 group-by-functionsに関するMySQLリファレンスマニュアル をご覧ください。この関数はMySQLバージョン4.1で追加されました。クエリではGROUP BY FileIDを使用します。

結果をどのように表示するかはよくわかりません。すべてのアイテムにすべての属性をリストしたい場合(設定されていない場合でも)、それは難しくなります。しかし、これはそれを行う方法についての私の提案です:

SELECT bt.FileID, Title, Author, 
 GROUP_CONCAT(
  CONCAT_WS(':', at.AttributeName, at.AttributeType, avt.AttributeValue) 
  ORDER BY at.AttributeName SEPARATOR ', ') 
FROM BaseTable bt JOIN AttributeValueTable avt ON avt.FileID=bt.FileID 
 JOIN AttributeTable at ON avt.AttributeId=at.AttributeId 
GROUP BY bt.FileID;

これにより、すべての属性が同じ順序で表示されるため、便利な場合があります。出力は次のようになります。

'F001', 'Dox', 'vinay', 'CustomAttr1:varchar(40):Akash, CustomUseDate:Datetime:2009/03/02'

このように、必要なDBクエリは1つだけで、出力は簡単に解析できます。属性を実際の日時などとしてDBに格納する場合は、動的SQLを使用する必要がありますが、それは避けて、値をvarcharsに格納します。

21
nawroth

このようなクエリの一般的な形式は次のようになります。

SELECT file.*,
   attr1.value AS 'Attribute 1 Name', 
   attr2.value AS 'Attribute 2 Name', 
   ...
FROM
   file 
   LEFT JOIN attr AS attr1 
      ON(file.FileId=attr1.FileId and attr1.AttributeId=1)
   LEFT JOIN attr AS attr2 
      ON(file.FileId=attr2.FileId and attr2.AttributeId=2)
   ...

したがって、必要な属性からクエリを動的に構築する必要があります。 php風の擬似コード

$cols="file";
$joins="";

$rows=$db->GetAll("select * from Attributes");
foreach($rows as $idx=>$row)
{
   $alias="attr{$idx}";
   $cols.=", {$alias}.value as '".mysql_escape_string($row['AttributeName'])."'";   
   $joins.="LEFT JOIN attr as {$alias} on ".
       "(file.FileId={$alias}.FileId and ".
       "{$alias}.AttributeId={$row['AttributeId']}) ";
}

 $pivotsql="select $cols from file $joins";
9
Paul Dixon

グループ連結の結果よりも使いやすい(そして参加可能な)ものを探している場合は、以下のこのソリューションを試してください。これを理解するために、あなたの例に非常によく似たテーブルをいくつか作成しました。

これは次の場合に機能します。

  • 純粋なSQLソリューション(コードやループなし)が必要です
  • 予測可能な属性のセットがあります(動的ではないなど)
  • 新しい属性タイプを追加する必要があるときにクエリを更新しても問題ありません
  • 副選択として結合、結合、またはネストできる結果をお勧めします

表A(ファイル)

FileID, Title, Author, CreatedOn

表B(属性)

AttrID, AttrName, AttrType [not sure how you use type...]

表C(Files_Attributes)

FileID, AttrID, AttrValue

従来のクエリでは、多くの冗長行がプルされていました。

SELECT * FROM 
Files F 
LEFT JOIN Files_Attributes FA USING (FileID)
LEFT JOIN Attributes A USING (AttributeID);
 AttrID FileID Title Author CreatedOn AttrValue AttrName AttrType 
 50 1 TestFile Joe 2011-01-01 true ReadOnly bool 
 60 1 TestFile Joe 2011-01-01 xls FileFormat text 
 70 1 TestFile Joe 2011-01-01 false Private bool 
 80 1 TestFile Joe 2011-01-01 2011-10-03 LastModified date 
 60 2 LongNovel Mary 2011-02-01 json FileFormat text 
 80 2 LongNovel Mary 2011-02-01 2011-10-04 LastModified date 
 70 2 LongNovel Mary 2011-02-01 true Private bool 
 50 2 LongNovel Mary 2011 -02-01 true ReadOnly bool 
 50 3 ShortStory Susan 2011-03-01 false ReadOnly bool 
 60 3 ShortStory Susan 2011-03-01 ascii FileForm at text 
 70 3 ShortStory Susan 2011-03-01 false Private bool 
 80 3 ShortStory Susan 2011-03-01 2011-10-01 LastModified date 
 50 4 ProfitLoss Bill 2011 -04-01 false ReadOnly bool 
 70 4 ProfitLoss Bill 2011-04-01 true Private bool 
 80 4 ProfitLoss Bill 2011-04-01 2011-10-02 LastModified date 
 60 4 ProfitLoss Bill 2011-04-01 text FileFormat text 
 50 5 MonthlyBudget George 2011-05-01 false ReadOnly bool 
 60 5 MonthlyBudget George2011-05-01バイナリFileFormattext 
 70 5 MonthlyBudget George 2011-05-01falseプライベートブール値
 80 5 MonthlyBudget George 2011-05-01 2011-10-20 LastModified date 

この合体クエリ(MAXを使用したアプローチ)は、行をマージできます。

SELECT
F.*,
MAX( IF(A.AttrName = 'ReadOnly', FA.AttrValue, NULL) ) as 'ReadOnly',
MAX( IF(A.AttrName = 'FileFormat', FA.AttrValue, NULL) ) as 'FileFormat',
MAX( IF(A.AttrName = 'Private', FA.AttrValue, NULL) ) as 'Private',
MAX( IF(A.AttrName = 'LastModified', FA.AttrValue, NULL) ) as 'LastModified'
FROM 
Files F 
LEFT JOIN Files_Attributes FA USING (FileID)
LEFT JOIN Attributes A USING (AttributeID)
GROUP BY
F.FileID;
 FileID Title Author CreatedOn ReadOnly FileFormat Private LastModified 
 1 TestFile Joe 2011-01-01 true xls false 2011-10-03 
 2 LongNovel Mary 2011-02-01 true json true 2011-10-04 
 3 ShortStory Susan 2011-03-01 false ascii false 2011-10-01 
 4 ProfitLoss Bill 2011-04-01 false text true 2011-10-02 
 5 MonthlyBudget George 2011-05-01 false binary false 2011-10-20 
8
methai

これは、SQLの標準的な「行から列へ」の問題です。

これはSQLの外部で最も簡単に実行できます。

アプリケーションで、次の手順を実行します。

  1. ファイル、システム属性、およびユーザー属性のコレクションを含む単純なクラスを定義します。この顧客属性のコレクションには、リストが適しています。このクラスをFileDescriptionと呼びましょう。

  2. ファイルとファイルのすべての顧客属性の間で単純な結合を実行します。

  3. クエリ結果からFileDescriptionsをアセンブルするループを記述します。

    • 最初の行をフェッチし、FileDescriptionを作成して、最初の顧客属性を設定します。

    • フェッチする行はまだありますが、次のようになります。

      • 行をフェッチします
      • この行のファイル名が作成中のFileDescriptionと一致しない場合は、次のようにします。FileDescriptionの作成を終了します。これをファイル記述の結果コレクションに追加します。指定された名前と最初の顧客属性を使用して、新しい空のFileDescriptionを作成します。
      • この行のファイル名が作成中のFileDescriptionと一致する場合:現在のFileDescriptionに別の顧客属性を追加します
6
S.Lott

私はさまざまな答えを試してきましたが、Methaiの答えが私にとって最も便利でした。私の現在のプロジェクトは、MySQLでDoctrineを使用していますが、かなりの数のルーズテーブルがあります。

以下は、Methaiのソリューションでの私の経験の結果です。

エンティティテーブルの作成

DROP TABLE IF EXISTS entity;
CREATE TABLE entity (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(255),
    author VARCHAR(255),
    createdOn DATETIME NOT NULL
) Engine = InnoDB;

属性テーブルの作成

DROP TABLE IF EXISTS attribute;
CREATE TABLE attribute (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    type VARCHAR(255) NOT NULL
) Engine = InnoDB;

属性値テーブルを作成

DROP TABLE IF EXISTS attributevalue;
CREATE TABLE attributevalue (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    value VARCHAR(255) NOT NULL,
    attribute_id INT UNSIGNED NOT NULL,
    FOREIGN KEY(attribute_id) REFERENCES attribute(id)
 ) Engine = InnoDB;

entity_attributevalue結合テーブルを作成

DROP TABLE IF EXISTS entity_attributevalue;
CREATE TABLE entity_attributevalue (
    entity_id INT UNSIGNED NOT NULL,
    attributevalue_id INT UNSIGNED NOT NULL,
    FOREIGN KEY(entity_id) REFERENCES entity(id),
    FOREIGN KEY(attributevalue_id) REFERENCES attributevalue(id)
) Engine = InnoDB;

エンティティテーブルにデータを入力

INSERT INTO entity
    (title, author, createdOn)
VALUES
    ('TestFile', 'Joe', '2011-01-01'),
    ('LongNovel', 'Mary', '2011-02-01'),
    ('ShortStory', 'Susan', '2011-03-01'),
    ('ProfitLoss', 'Bill', '2011-04-01'),
    ('MonthlyBudget', 'George', '2011-05-01'),
    ('Paper', 'Jane', '2012-04-01'),
    ('Essay', 'John', '2012-03-01'),
    ('Article', 'Dan', '2012-12-01');

属性テーブルにデータを入力

INSERT INTO attribute
    (name, type)
VALUES
    ('ReadOnly', 'bool'),
    ('FileFormat', 'text'),
    ('Private', 'bool'),
    ('LastModified', 'date');

属性値テーブルにデータを入力

INSERT INTO attributevalue 
    (value, attribute_id)
VALUES
    ('true', '1'),
    ('xls', '2'),
    ('false', '3'),
    ('2011-10-03', '4'),
    ('true', '1'),
    ('json', '2'),
    ('true', '3'),
    ('2011-10-04', '4'),
    ('false', '1'),
    ('ascii', '2'),
    ('false', '3'),
    ('2011-10-01', '4'),
    ('false', '1'),
    ('text', '2'),
    ('true', '3'),
    ('2011-10-02', '4'),
    ('false', '1'),
    ('binary', '2'),
    ('false', '3'),
    ('2011-10-20', '4'),
    ('doc', '2'),
    ('false', '3'),
    ('2011-10-20', '4'),
    ('rtf', '2'),
    ('2011-10-20', '4');

entity_attributevalueテーブルにデータを入力

INSERT INTO entity_attributevalue 
    (entity_id, attributevalue_id)
VALUES
    ('1', '1'),
    ('1', '2'),
    ('1', '3'),
    ('1', '4'),
    ('2', '5'),
    ('2', '6'),
    ('2', '7'),
    ('2', '8'),
    ('3', '9'),
    ('3', '10'),
    ('3', '11'),
    ('3', '12'),
    ('4', '13'),
    ('4', '14'),
    ('4', '15'),
    ('4', '16'),
    ('5', '17'),
    ('5', '18'),
    ('5', '19'),
    ('5', '20'),
    ('6', '21'),
    ('6', '22'),
    ('6', '23'),
    ('7', '24'),
    ('7', '25');

すべてのレコードを表示

SELECT * 
FROM `entity` e
LEFT JOIN `entity_attributevalue` ea ON ea.entity_id = e.id
LEFT JOIN `attributevalue` av ON ea.attributevalue_id = av.id
LEFT JOIN `attribute` a ON av.attribute_id = a.id;
 id title author createdOn entity_id attributevalue_id id value attribute_id id name type 
 1 TestFile Joe 2011-01-01 00:00:00 1 1 1 true 1 1 ReadOnly bool 
 1 TestFile Joe 2011-01-01 00:00:00 1 2 2 xls 2 2 FileFormat text 
 1 TestFile Joe 2011-01-01 00:00:00 1 3 3 false 3 3 Private bool 
 1 TestFile Joe 2011-01-01 00:00:00 1 4 4 2011-10-03 4 4 LastModified date 
 2 LongNovel Mary 2011-02-01 00:00:00 2 5 5 true 1 1 ReadOnly bool 
 2 LongNovel Mary 2011-02-01 00:00:00 2 6 6 json 2 2 FileFormat text 
 2 LongNovel Mary 2011-02-01 00:00:00 2 7 7 true 33プライベートbool 
 2 LongNovel Mary 2011-02-01 00:00:00 2 8 8 2011-10-04 4 4 LastModified date 
 3 ShortStory Susan 2011-03-01 00:00:00 3 9 9 false 1 1 ReadOnly bool 
 3 ShortStory Susan 2011-03- 01 00:00:00 3 10 10 ascii 2 2 FileFormat text 
 3 ShortStory Susan 2011-03-01 00:00:00 3 11 11 false 3 3 Private bool 
 3 ShortStory Susan 2011- 03-01 00:00:00 3 12 12 2011-10-01 4 4 LastModified date 
 4 ProfitLoss Bill 2011-04-01 00:00:00 4 13 13 false 1 1 ReadOnly bool 
 4 ProfitLoss Bill 2011-04-01 00:00:00 4 14 14 text 2 2 FileFormat text 
 4 ProfitLoss Bill 2011-04-01 00:00:00 4 15 15 true 33プライベートブール値
 4ProfitLoss Bill 2011-04-01 00:00:00 4 16 16 2011-10-02 4 4 LastModified date 
 5 MonthlyBudget George 2011-05-01 00:00:00 5 17 17 false 1 1 ReadOnly bool 
 5 MonthlyBudget George 2011-05-01 00:00:00 5 1818バイナリ2 2 FileFormat text 
 5 MonthlyBudget George 2011-05-01 00:00:00 5 19 19 false 3 3 Private bool 
 5 MonthlyBudget George 2011-05-01 00:00:00 5 20 20 2011-10-20 4 4 LastModified date 
 6 Paper Jane 2012-04-01 00:00: 00 6 2121バイナリ22 FileFormat text 
 6 Paper Jane 2012-04-01 00:00:00 6 22 22 false 3 3 Private bool 
 6 Paper Jane 2012-04-01 00: 00:00 6 23 23 2011-10-20 4 4 LastModified date 
 7 Essay John 2012-03-01 00:00:00 7 24 24 binary 2 2 FileFormat text 
 7 Essay John 2012 -03-01 00:00:00 7 25 25 2011-10-20 4 4 LastModified date 
 8 Article Dan 2012-12-01 0 0:00:00 NULL NULL NULL NULL NULL NULL NULL NULL 

ピボットテーブル

SELECT e.*,
    MAX( IF(a.name = 'ReadOnly', av.value, NULL) ) as 'ReadOnly',
    MAX( IF(a.name = 'FileFormat', av.value, NULL) ) as 'FileFormat',
    MAX( IF(a.name = 'Private', av.value, NULL) ) as 'Private',
    MAX( IF(a.name = 'LastModified', av.value, NULL) ) as 'LastModified'
FROM `entity` e
LEFT JOIN `entity_attributevalue` ea ON ea.entity_id = e.id
LEFT JOIN `attributevalue` av ON ea.attributevalue_id = av.id
LEFT JOIN `attribute` a ON av.attribute_id = a.id
GROUP BY e.id;
 id title author createdOn ReadOnly FileFormat Private LastModified 
 1 TestFile Joe 2011-01-01 00:00:00 true xls false 2011-10-03 
 2 LongNovel Mary 2011-02 -01 00:00:00 true json true 2011-10-04 
 3 ShortStory Susan 2011-03-01 00:00:00 false ascii false 2011-10-01 
 4 ProfitLoss Bill 2011 -04-01 00:00:00 false text true 2011-10-02 
 5 MonthlyBudget George 2011-05-01 00:00:00 false binary false 2011-10-20 
 6論文Jane 2012-04-01 00:00:00NULLバイナリfalse2011-10-20 
 7エッセイJohn2012-03-01 00:00:00NULLバイナリNULL2011-10-20 
 8記事Dan2012-12-01 00:00:00 NULL NULL NULL NULL 
3
thoroc

ただし、行を列として使用する、つまりデータを転置するソリューションがあります。純粋なSQLでそれを行うにはクエリのトリックが必要です。そうでない場合は、ピボットテーブル(またはクロステーブル)を使用して、特定のデータベースでのみ使用可能な特定の機能に依存する必要があります。

例として、Oracle(11g)でこれを行う方法を確認できます。

プログラミングバージョンは、保守と作成が簡単になり、さらにどのデータベースでも機能します。

0
MarmouCorp