MANY - MANY - MANY Beziehung (II)

bei diesem Code handelt es sich lediglich um ein Beispiel. Es geht darum eine Funktion zu schreiben die einen zweifachen JOIN ausführt.

320

yii_many_many_many2.png

Artikel

id

baseform

Artikel_Warengruppe

artikelId

warengruppeId

Warengruppe

id

baseform

Warengruppe_Buyer

warengruppeId

buyerId

Buyer

id

name

Beispiel - Datensätze:

Artikel

  • Apfel

  • Birne

  • Gurken

  • Blumenkohl

Warengruppe

  • Obst

  • Gemüse

Buyer

  • Dieter

  • Helmut

SQL-CODE




CREATE TABLE Artikel

(

	id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,

	baseform VARCHAR(128) NOT NULL

);


CREATE TABLE Warengruppe

(

	id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,

	baseform VARCHAR(128) NOT NULL

);


CREATE TABLE Artikel_Warengruppe

(

	artikelId INTEGER NOT NULL,

	warengruppeId INTEGER NOT NULL,

	PRIMARY KEY (artikelId, warengruppeId),

	CONSTRAINT FK_artikel FOREIGN KEY (artikelId)

		REFERENCES Artikel (id),

	CONSTRAINT FK_warengruppe FOREIGN KEY (warengruppeId)

		REFERENCES Warengruppe (id)

);




CREATE TABLE Warengruppe_Buyer

(

	warengruppeId INTEGER NOT NULL,

	buyerId INTEGER NOT NULL,

	PRIMARY KEY (warengruppeId, buyerId),

	CONSTRAINT FK_warengrupep FOREIGN KEY (warengruppeId)

		REFERENCES Warengruppe (id),

	CONSTRAINT FK_buyer FOREIGN KEY (buyerId)

		REFERENCES Buyer (id)

);


CREATE TABLE buyer

(

	id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,

	`name` VARCHAR(128) NOT NULL

);




SQL-Beispieldatensätze





INSERT INTO artikel(baseform) VALUES ( 'Apfel' );

INSERT INTO artikel(baseform) VALUES ( 'Birne' );

INSERT INTO artikel(baseform) VALUES ( 'Gurken' );

INSERT INTO artikel(baseform) VALUES ( 'Blumenkohl' );


INSERT INTO warengruppe(baseform) VALUES ( 'Obst' );

INSERT INTO warengruppe(baseform) VALUES ( 'Gemüse' );


INSERT INTO artikel_warengruppe(artikelId,warengruppeId) VALUES ( '1','1');

INSERT INTO artikel_warengruppe(artikelId,warengruppeId) VALUES ( '2','1');

INSERT INTO artikel_warengruppe(artikelId,warengruppeId) VALUES ( '4','2');


INSERT INTO buyer(`name`) VALUES ( 'Dieter' );

INSERT INTO buyer(`name`) VALUES ( 'Helmut' );


INSERT INTO warengruppe_buyer(warengruppeId,buyerId) VALUES ( '1','1');

INSERT INTO warengruppe_buyer(warengruppeId,buyerId) VALUES ( '1','2');

INSERT INTO warengruppe_buyer(warengruppeId,buyerId) VALUES ( '2','1');



So einen JOIN versuche ich zu realisieren:


SELECT artikel.baseform, warengruppe.baseform, buyer.name

	FROM artikel

	INNER JOIN artikel_warengruppe ON artikel.id = artikel_warengruppe.artikelId

	INNER JOIN warengruppe ON artikel_warengruppe.warengruppeId = warengruppe.id

	INNER JOIN warengruppe_buyer ON warengruppe.id = warengruppe_buyer.buyerId

	INNER JOIN buyer ON warengruppe_buyer.buyerId = buyer.id

	WHERE artikel.baseform LIKE 'Blumenkohl'

Model-Controller-View kommen noch

Artikel - Model




<?php


class artikel extends CActiveRecord

{

	/**

	 * The followings are the available columns in table 'artikel':

	 * @var integer $id

	 * @var string $baseform

	 */


	/**

	 * Returns the static model of the specified AR class.

	 * @return CActiveRecord the static model class

	 */

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	/**

	 * @return string the associated database table name

	 */

	public function tableName()

	{

		return 'artikel';

	}


	/**

	 * @return array validation rules for model attributes.

	 */

	public function rules()

	{

		return array(

			array('baseform','length','max'=>128),

			array('baseform', 'required'),

		);

	}


	/**

	 * @return array relational rules.

	 */

	public function relations()

	{

		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

		return array(

			'warengruppe'	=> array(self::MANY_MANY, 'warengruppe', 'artikel_warengruppe(artikelId, warengruppeId)',

				'together'	=> true,

				'joinType'	=> 'INNER JOIN',

			),

		);

	}


	/**

	 * @return array customized attribute labels (name=>label)

	 */

	public function attributeLabels()

	{

		return array(

			'id' => 'Id',

			'baseform' => 'Baseform',

		);

	}


public function artikelWarengruppeBuyer( $baseform )

{       

        return Artikel::model()->with(array('warengruppe', 'buyer'))->find('artikel.baseform=?', array( $baseform ) );

}

}

Buyer-Model




<?php


class buyer extends CActiveRecord

{

	/**

	 * The followings are the available columns in table 'buyer':

	 * @var integer $id

	 * @var string $name

	 */


	/**

	 * Returns the static model of the specified AR class.

	 * @return CActiveRecord the static model class

	 */

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	/**

	 * @return string the associated database table name

	 */

	public function tableName()

	{

		return 'buyer';

	}


	/**

	 * @return array validation rules for model attributes.

	 */

	public function rules()

	{

		return array(

			array('name','length','max'=>128),

			array('name', 'required'),

		);

	}


	/**

	 * @return array relational rules.

	 */

	public function relations()

	{

		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

		return array(

			'warengruppe'   => array(self::MANY_MANY, 'warengruppe', 'artikel_warengruppe(artikelId, warengruppeId)',

				'together'      => true,

				'joinType'      => 'INNER JOIN',

				),


		);

	}


	/**

	 * @return array customized attribute labels (name=>label)

	 */

	public function attributeLabels()

	{

		return array(

			'id' => 'Id',

			'name' => 'Name',

		);

	}

}

Warengruppe - Model




<?php


class warengruppe extends CActiveRecord

{

	/**

	 * The followings are the available columns in table 'warengruppe':

	 * @var integer $id

	 * @var string $baseform

	 */


	/**

	 * Returns the static model of the specified AR class.

	 * @return CActiveRecord the static model class

	 */

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	/**

	 * @return string the associated database table name

	 */

	public function tableName()

	{

		return 'warengruppe';

	}


	/**

	 * @return array validation rules for model attributes.

	 */

	public function rules()

	{

		return array(

			array('baseform','length','max'=>128),

			array('baseform', 'required'),

		);

	}


	/**

	 * @return array relational rules.

	 */

	public function relations()

	{

		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

		return array(

			'artikel'	=> array(self::MANY_MANY, 'artikel', 'artikel_warengruppe( artikelId, warengruppeId)',

				'together'	=> true,

				'joinType'	=> 'INNER JOIN',

			),

			'buyer'	=> array(self::MANY_MANY, 'buyer', 'warengruppe_buyer( warengruppeId, buyerId)',

				'together'	=> true,

				'joinType'	=> 'INNER JOIN',

			),

		);

	}


	/**

	 * @return array customized attribute labels (name=>label)

	 */

	public function attributeLabels()

	{

		return array(

			'id' => 'Id',

			'baseform' => 'Baseform',

		);

	}

}

Die relations() - Methode werde ich später vollständig ausfüllen

Folgende Befehle müssen noch ausgeführt werden


yiic shell index.php

crud artikel

crud warengruppe

crud buyer

Diesen Join muss ich irgendwie umsetzen.




SELECT artikel.baseform, warengruppe.baseform, buyer.name

        FROM artikel

        INNER JOIN artikel_warengruppe ON artikel.id = artikel_warengruppe.artikelId

        INNER JOIN warengruppe ON artikel_warengruppe.warengruppeId = warengruppe.id

        INNER JOIN warengruppe_buyer ON warengruppe.id = warengruppe_buyer.buyerId

        INNER JOIN buyer ON warengruppe_buyer.buyerId = buyer.id

        WHERE artikel.baseform LIKE 'Blumenkohl'



So ist es zwar falsch, aber ich weiß nicht wie ich das sonst machen soll!


	

public function artikelWarengruppeBuyer( $baseform )

{	

	return Artikel::model()->with(array('warengruppe', 'buyer'))->find('artikel.baseform=?', array( $baseform ) );

}



Dann macht doch einfach findBySql()

AR dient doch nur dazu, das ganze einfach und übersichtlich zu halten.

Wenn ich findBySql() benutze, dann benutze ich SQL-Code exakt für eine MySQL-Datenbank, somit wird mein Code nicht mehr benutzbar für andere Datenbanken (für den Fall, dass ich sie in ferne Zukunft ändern wollte).

Daher suche ich eine Lösung mit AR.

Hallo yii,

entschuldige, dass ich mich erst jetzt damit befasse, ich hatte einiges um die Ohren.

Ich hab mir eine kleine Beispielanwendung und die Tabellen mit deinen Vorlagen angelegt.

Zunächst mal zu den groben Schnitzern in den Vorlagen:

1.

Diese Query funktioniert zwar, aber ich glaube nicht, dass es das ist, was du eigentlich haben möchtest.

Führst du diese Query mit den Tabellen/Daten von dir aus, erhälst du: Blumenkohl | Gemüse | Helmut

Ein Blick in die Tabellen verrät allerdings, das Helmut bisher nur Obst gekauft hat.

Folgender Fehler:

INNER JOIN warengruppe_buyer ON warengruppe.id = warengruppe_buyer.[color="#ff0000"]buyerId[/color]

->

INNER JOIN warengruppe_buyer ON warengruppe.id = warengruppe_buyer.[color="#2e8b57"]warengruppeId[/color]

2.

Immer den "eigenen" Fremdschlüssel zuerst angeben. Sprich:


'artikel' => array(self::MANY_MANY, 'artikel', 'artikel_warengruppe( warengruppeId, artikelId )',

Ansonsten würde der JOIN unter Verwendung von ‘warengruppe.id = artikel_warengruppe.artikelId’ durchgeführt, was natürlich Quatsch ergibt.

3. Bei der Relation in deinem Buyer-Model gehe ich mal von einem Copy’n’Paste-Fehler aus, richtig?! Schließlich hat der Buyer mit der Tabelle ‘artikel_warengruppe’ mal so garnichts am Hut.

So, nun zum eigentlichen Teil ;)

Du möchtest also bei Artikel anfangen und dich quasi an den Relationen “entlanghangeln” bis zum Käufer, ja?! Kein Problem! :)

Ich habe deine Funktion artikelWarengruppeBuyer() wie folgt verändert:




public function artikelWarengruppeBuyer( $baseform )

{       

    return Artikel::model()->with(array('warengruppe','warengruppe.buyer'))->findByAttributes(array('baseform'=>$baseform));

}



Sieht eigentlich genauso aus wie deine ;) Ob find() oder findByAttributes() bzw. je nach Anwendungsfall findAll() oder findAllByAttributes() ist dabei egal, der wesentlich Punkt ist ‘warengruppe.buyer’.

Du hast versucht direkt die Relation ‘buyer’ anzugeben, aber zwischen dem Artikel und dem Käufer gibt es nunmal keine direkte Relation.

Aber es gibt eine zu ‘warengruppe’ und die wiederrum hat eine zu ‘buyer’, also gehen wir einfach über diese beiden Relationen hintereinander.

Zum Aufruf der dieser Funktion und um es etwas zu verdeutlichen habe ich mal folgenden Code zusammen geschustert:




$result = Artikel::model()->artikelWarengruppeBuyer('Apfel');

if(!empty($result))

{

    echo $result->baseform.' => ('; // baseform von Artikel ausgeben

    // MANY:MANY daher ist Warengruppe ein Array

    // Z.B. kann ein 'Apfel' in der Warengruppe 'Obst' UND in 'Genfood' sein.

    foreach($result->warengruppe as $a => $warengruppe1)

    {

        echo '( '.$warengruppe1->baseform.' ) => ( ';

        

        // MANY:MANY zwischen Warengruppe und Buyer

        // Z.B. kann 'Obst' von Meier,Schmitz und Müller gekauft worden sein,

        // während 'Genfood' von Meier und Schulze gekauft wurde.

        foreach($warengruppe1->buyer as $b => $buyer1)

            echo $buyer1->name.', ';

        echo ')';

    }

    echo ')';

}

else

    echo 'Keine Treffer';



Die Ausgabe für ‘Apfel’ (mit deinen Daten) sähe so aus:


Apfel => (( Obst ) => ( Dieter, Helmut, ))

Ich hoffe ich konnte dir damit ein bisschen weiterhelfen.

Weiterhin viel Erfolg und Spaß!

Gruß,

yoshi

Hi,

ja genau so habe ich es mir vorgestellt. Auf den Punkt wäre ich nie gekommen, dieser war zugleich mein Flaschenhals in der Anwendung.




public function artikelWarengruppeBuyer( $baseform )

{       

    return Artikel::model()->with(array('warengruppe','warengruppe.buyer'))->findByAttributes(array('baseform'=>$baseform));

}



Spaßig wird es nun sein, die Admin-Oberfläche dafür zuschreiben, sprich DELETE und EDIT.

thx!!!