Problem With Cgridview Displaying Many-To-Many Data

Hi folks,

I REALLY hope someone can help me here! :P

I’ve been working on this problem the last couple days, and I can’t seem to find a solution.

I am working on a view using CGridView which is to display information from a database of races and the participating runners.

I am using CDbCriteria to build the criteria for the CActiveDataProvider, using CDbCriteria->with() to fetch the data form the many to many relationship. Now, I have checked the sql.log file and the SQL query that this is producing is returning the correct data - I have verified this by running directly it in my SQL program (Heidi) and it is returning the correct data (the Cartesian product). Incase it is of any use to helping solve this, the SQL query is this: -


SELECT `t`.`id` AS `t0_c0`, `xRaceRunners`.`id` AS

 `t1_c0`, `xRaceRunners`.`race_fk_nn` AS `t1_c1`,

 `xRaceRunners`.`runner_fk_nn` AS `t1_c2`,

 `xRaceRunners`.`non_runner_nn` AS `t1_c3`,

 `runnerFkNn`.`id` AS `t2_c0`,

 `runnerFkNn`.`runner_name_nn` AS `t2_c1` FROM `race`

 `t` LEFT OUTER JOIN `x_race_runner` `xRaceRunners` ON

 (`xRaceRunners`.`race_fk_nn`=`t`.`id`)  LEFT OUTER JOIN

 `runner` `runnerFkNn` ON

 (`xRaceRunners`.`runner_fk_nn`=`runnerFkNn`.`id`) 

 WHERE (`t`.`id` IN (128, 154, 155, 121, 122))

Now, the problem is that when the data is displayed using CGridView, it doesn’t display the records as many-to-many with the relational data i.e. no Cartesian product - just the records from the ‘t’ table (the venue for the race and the starting time of the race). If I am cheeky and put this in the columns array: -




array(

	'name'=> 'race_date_time_nn',

	'value' => 'CVarDumper::dump($data, 10, TRUE)',

),



When CVarDumper is run through eval(), it gives a array with the [CActiveRecord:_related] => array()… index, which does indeed have all the correct race participants in it. However, this doesn’t help me much as I need a cartesian product so I can display: -

BeachFront : 14:00 : Sandy McNeil

BeachFront : 14:00 : Tommy Jackson

BeachFront : 14:00 : Mike Walters

‘BeachFront’ and ‘14:00’ are from the primary table, ‘Sandy McNeil’ from the runnerFkNn.runner_name_nn relation in the junction table (xRaceRunners).

Has this maybe got something to do with the junction table (x_race_runners, model : xRaceRunners) having a composite index?

I REALLY hope someone can help me with this… it’s one of the final remaining issues in this project, and I can potentially get it wrapped up as soon as this is resolved.

Thanks in advance,

U4EA

Edited:

This is the relation I am using to junction table:-




'xRaceRunners' => array(self::HAS_MANY, 'XRaceRunner', 'race_fk_nn')



And in that model (XRaceRunner), I have the following relation to the runner table: -




'runnerFkNn' => array(self::BELONGS_TO, 'HorseRunner', 'runner_fk_nn'),



XRaceRunner uses a composite unique index of the columns race_fk_nn and runner_fk_nn.


  • reason for editing - additional information for more clarity.

[b]

show your cbdcriteria you are calling please!

Do you want it in one row like the following:[/b]

row 1

BeachFront : 14:00 : Sandy McNeil

BeachFront : 14:00 : Tommy Jackson

BeachFront : 14:00 : Mike Walters.

row 2

race2Location: 13:00: Joe Smo

race2Location: 13:00: Joe Joe

race2Location: 13:00: Jim Smo

[b]or

[/b]

row 1 = BeachFront : 14:00 : Sandy McNeil

row 2 = BeachFront : 14:00 : Tommy Jackson

row 3 = BeachFront : 14:00 : Mike Walters

[b]

or do you want it in multiple columns and rows like [/b]

          	column 1  	column2 	column3

row 1 = BeachFront : 14:00 : Sandy McNeil

row 2 = BeachFront : 14:00 : Tommy Jackson

row 3 = BeachFront : 14:00 : Mike Walters

Do you want them still searchable and sortable?

The easiest would be multiple rows and multiple columns!

[b]

Also, how are your names stored? [/b]

is there a first name and last name column?

[b]

or[/b]

is it just one that holds first and last name?

Please show your two tables that you are using. I want to see if your fk are just named funny or you are calling them wrong for the cbdcriteria.

You have a really common scenario that is easy to fix with more info

Thank you for you reply… really hoping I can get this fixed soon.

The CDbCriteria is quite simple…





public function search() {


	$criteria = new CDbCriteria;


	/*

	$criteria->compare('id', $this->id);

	$criteria->compare('venue_fk_nn', $this->venue_fk_nn);

	$criteria->compare('race_date_time_nn', $this->race_date_time_nn);

	*/


	$criteria->with = array('xRaceRunners');

	$criteria->together = true;


	return new CActiveDataProvider($this, array(

		'criteria' => $criteria,

		'pagination'=>array(

			'pageSize'=> 8000,

		),

	));

}




Please note the search criteria has been commented out just now as I am concentrating on getting the CGridView displaying the required data first.

The example in bold please - 1 row for each record in the Cartesian product and 1 column for each value.

Absolutely want them to be searchable and sortable (I am looking at the possibility of dozens of venues, dozens of races for each and dozens of runners for each race.

As a single name - goes against data normalisation rules but is fine for this system and this will not be remodelled.

‘race’ is the primart (‘t’) table.

‘x_race_runner’ is the junction table.

‘runner’ is the secondary table (on the other side of the JOIN).




CREATE TABLE IF NOT EXISTS `race` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `venue_fk_nn` smallint(6) NOT NULL,

  `race_date_time_nn` int(11) NOT NULL,

  PRIMARY KEY (`id`),

  UNIQUE KEY `race_venue-race_date_index_unique` (`venue_fk_nn`,`race_date_time_nn`),

  CONSTRAINT `race_venue_fk_nn` FOREIGN KEY (`venue_fk_nn`) REFERENCES `venue` (`id`) ON DELETE CASCADE

)


CREATE TABLE IF NOT EXISTS `x_race_runner` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `race_fk_nn` int(11) NOT NULL,

  `runner_fk_nn` smallint(6) NOT NULL,

  PRIMARY KEY (`id`),

  UNIQUE KEY `race_runner_race-runner_unique` (`race_fk_nn`,`runner_fk_nn`),

  KEY `race_runner_runner_fk_nn` (`runner_fk_nn`),

  CONSTRAINT `race_runner_race_fk_nn` FOREIGN KEY (`race_fk_nn`) REFERENCES `race` (`id`) ON DELETE CASCADE,

  CONSTRAINT `race_runner_runner_fk_nn` FOREIGN KEY (`runner_fk_nn`) REFERENCES `runner` (`id`) ON DELETE CASCADE

)


CREATE TABLE IF NOT EXISTS `runner` (

  `id` smallint(6) NOT NULL AUTO_INCREMENT,

  `runner_name_nn` varchar(40) NOT NULL,

  PRIMARY KEY (`id`),

  UNIQUE KEY `runner_name_nn` (`runner_name_nn`)

)




Thanks again… really hope I can get this sorted soon.

xracerunner.php (model)


	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(

			'race' => array(self::HAS_ONE, 'Race', 'id'),

			'runner' => array(self::HAS_ONE, 'Runner', 'id'),

		);

	}

venue.php (model) i made this i dont know what yours is called


	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(

			'races' => array(self::HAS_MANY, 'Race', 'venue_fk_nn'),

		);

	}

race.php (model)


	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(

			'venue' => array(self::BELONGS_TO, 'Venue', 'venue_fk_nn'),

		);

	}

The data provider needs to be the xracerunner.php (model) search it wont work any other way.


<?php $this->widget('zii.widgets.grid.CGridView', array(

	'id'=>'xrace-runner-grid',

	'dataProvider'=>$model->search(),

	'itemsCssClass'=>'table table-bordered table-hover',

	'htmlOptions'=>array('style'=>'text-align:center; vertical-align: middle; cursor:pointer;'),

	'emptyText'=>'We did not find any results with your search criteria, please try differnt search filters.',

	'template'=>'{items}{pager}',

	'pager'=> array(

		'maxButtonCount' => 5,

		'header' => '',

		'footer' => '',

		'firstPageLabel' => '',

		'prevPageLabel'  => 'Prev. Page',

		'nextPageLabel'  => 'Next Page',

		'lastPageLabel'  => '',

		'htmlOptions'=>array('class'=>'pager'),

	),

	'filter'=>$model,

	'columns'=>array(

		array(

			'header'=>'Race Location',

			'value'=>'$data->race->venue->venue_name_nn',

			'htmlOptions' => array('style' => 'vertical-align:middle;text-align:center'),

			'headerHtmlOptions' => array('style'=>'text-align:center; vertical-align: middle;'),

		),

		array(

			'header'=>'Race Time',

			'value'=>'$data->race->race_date_time_nn',

			'htmlOptions' => array('style' => 'vertical-align:middle;text-align:center'),

			'headerHtmlOptions' => array('style'=>'text-align:center; vertical-align: middle;'),

		),

		array(

			'header'=>'Runner',

			'value'=>'$data->runner->runner_name_nn',

			'htmlOptions' => array('style' => 'vertical-align:middle;text-align:center'),

			'headerHtmlOptions' => array('style'=>'text-align:center; vertical-align: middle;'),

		),

	),

)); ?>



Your search will be like this for now


	public function search()

	{

		// Warning: Please modify the following code to remove attributes that

		// should not be searched.

		$criteria=new CDbCriteria;

		$criteria->compare('id',$this->id);

		$criteria->compare('race_fk_nn',$this->race_fk_nn);

		$criteria->compare('runner_fk_nn',$this->runner_fk_nn);

		$criteria->with = array('race', 'runner');

		$criteria->together = true;

		return new CActiveDataProvider($this, array(

			'criteria'=>$criteria,

		));

	}

Your search and filters wont work out of the gate. We will need to add some variables and rules to get it to work. but it will now display how you want it. (i tested everything and it works). I have to go home now but i will show you how to do the search later tonight.

here is a screen shot of what i just made… disregard the template it was just easy for me to put it in there.

Also, id change some things with your db. There is no such thing as int(11). The proper syntax is int(4) as the numer refers to bytes not characters. All integers types in MySQL are this way. Also, I’d change your runners if to int(4) unless you expect to have less than 32k runners total. If you keep it smallint, it should be smallint(2) because it can only hold 2 bytes of data. MySql Int Types

Thank you so much for that.

It’s 02:40 AM here and although I’ve been working on this all day (or perhaps BECAUSE I’ve been working on this all day :D ), I decided to give your code a try before I signed off for the day and I seem to have it working now.

Two things: -

In order to get your code to work, I have to changed your code to this…




'race' => array(self::BELONGS_TO, 'Race', 'race_fk_nn'),

'runner' => array(self::BELONGS_TO, 'Runner', 'runner_fk_nn'),



Otherwise it wasn’t pulling the relational data over.

Additionally, I had to use this or there was no $data->race->venue->venue_name_un_nn




$criteria->with = array('race', 'runner', 'race.venue');



I am really not sure why and I my head is mush from coding all day. I will review the code tomorrow when my head is up to it and see what I can in it.

Many, many thanks for your input here - you’ve been utterly invaluable to helping me sort something that is a priority to complete ASAP.

Regards,

U4EA

I’m glad it worked here (you should hit the plus sign next to the post :rolleyes:)is the model with comments to make search and sorting work. You will have to make the changes you made before to get this to work but i kept it the same as before so people reading didnt get confused.


<?php

class XRaceRunner extends CActiveRecord

{

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	/**

 	* @return string the associated database table name

 	*/

	public function tableName()

	{

		return 'x_race_runner';

	}

	//need to declair the virtual attributes to be searched

	public $runners;

	public $venue;

	public $race_date;

	

	

	//need to add the vitural attributes to the rules

	public function rules()

	{

		// NOTE: you should only define rules for those attributes that

		// will receive user inputs.

		return array(

			array('runners, venue, race_date', 'safe', 'on'=>'search'),

		);

	}

	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(

			'race' => array(self::HAS_ONE, 'Race', 'id'),

			'runner' => array(self::HAS_ONE, 'Runner', 'id'),

			//creating a relation for the vitural attribute even though there isn't a column in the db we can still relate it.

			'race_venue' => array(self::HAS_ONE, 'Venue', 'id'),

		);

	}

	

	//add labels for the vitural attributes

	public function attributeLabels()

	{

		return array(

			'race_date'=>'Race Time',

			'runner'=>'Runners',

			'venue'=>'Race Venue',

		);

	}

	public function search()

	{

		$criteria=new CDbCriteria;

		

		//comparing the vitural attributes to our actual attributes

		$criteria->compare( 'race.race_date_time_nn', $this->race_date, true );

		$criteria->compare( 'runner.runner_name_nn', $this->runners, true );

		//using the vitrual relation to compare the virtual attributes to our actual attributes

		$criteria->compare( race_'venue.venue_name_nn', $this->venue, true );

		//lazy load our relations

		$criteria->with = array('race', 'race_venue', 'runner');

		$criteria->together = true;

		return new CActiveDataProvider($this, array(

			'criteria'=>$criteria,

			//we have to manually set our sort since it's using relations and virtual attributes. 

			'sort'=>array(

				'defaultOrder'=>'race.race_date_time_nn ASC',

				'attributes'=>array(

					'race_date'=>array(

						'asc'=>'race.race_date_time_nn',

						'desc'=>'race.race_date_time_nn DESC',

					),

					'runners'=>array(

						'asc'=>'runner.runner_name_nn',

						'desc'=>'runner.runner_name_nn DESC',

					),

					'venue'=>array(

						'asc'=>'race_venue.venue_name_nn',

						'desc'=>'race_venue.venue_name_nn DESC',

					),

				),

			),

		));

	}

}

Your search form would be something like this


<div class="wide form">


<?php $form=$this->beginWidget('CActiveForm', array(

	'id'=>'search-form',

	'action'=>Yii::app()->createUrl($this->route),

	'method'=>'get',

)); ?>


	<div class="row">

		<?php echo $form->label($model,'race_date'); ?>

		<?php echo $form->textField($model,'race_date', array('class'=>'form-control')); ?>

	</div>


	<div class="row">

		<?php echo $form->label($model,'runners'); ?>

		<?php echo $form->textField($model,'runners', array('class'=>'form-control')); ?>

	</div>


	<div class="row" style="margin-bottom:20px;">

		<?php echo $form->label($model,'venue'); ?>

		<?php echo $form->textField($model,'venue', array('class'=>'form-control')); ?>

	</div>


	<div class="row buttons">

		<?php echo CHtml::submitButton('Search', array('class'=>'btn blue')); ?>

		<?php echo CHtml::submitButton('Reset Results' , array('class'=>'btn red', 'onclick'=>'js:$("#search-form").trigger("reset")')); ?>

	</div>


<?php $this->endWidget(); ?>


</div><!-- search-form -->

I’d also look into adding a date range like this wiki explains

I clicked the little ‘+’ sign on the post :)

BTW I am using extendedGridView, is that makes any difference.

It looks like I am going to have to add a another relations - sponsors for each participant (runner). However, I will get your prototype up and running first. As a note - I will need to sort/filter by sponsor and sort (only) but amount sponsorship amount (£).

Thanks again for all the help… now stage 1 for me is getting it all working, stage 2 is examining all the code to make sure I understand it all.

Nothing like something that causes you anger and frustration to force you to learn something new.

Hi

Have a look at my extension "RelatedSearchBehavior" (http://www.yiiframework.com/extension/relatedsearchbehavior/ ) and its demo.

Your junction table does not have many-many relations, and one entry in the junction table is exactly one line in your gridview, which is good. The same thing is done in the demo for the extension where InvoiceLine is the junction table.

I’m back working on this having had to take some time away from it due to commitments to another project.

As it stands right now, I have the race venue, date/time and runner name in the CGridView grid. Now what I need to do is add in a cartesian product from the many-to-many relationship to associated a runner to one or more sponsorships for a given race.

Here is a SQL file, inclusive of dummy data…




-- --------------------------------------------------------

-- Host:                         127.0.0.1

-- Server version:               5.5.27 - MySQL Community Server (GPL)

-- Server OS:                    Win32

-- HeidiSQL Version:             8.3.0.4694

-- --------------------------------------------------------


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;

/*!40101 SET NAMES utf8 */;

/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;

/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;


-- Dumping database structure for sponsorship

CREATE DATABASE IF NOT EXISTS `sponsorship` /*!40100 DEFAULT CHARACTER SET latin1 */;

USE `sponsorship`;




-- Dumping structure for table sponsorship.race

CREATE TABLE IF NOT EXISTS `race` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `venue_fk_nn` smallint(6) NOT NULL,

  `race_date_time_nn` int(11) NOT NULL,

  PRIMARY KEY (`id`),

  UNIQUE KEY `venue-race_date_index_unique` (`venue_fk_nn`,`race_date_time_nn`),

  CONSTRAINT `race_venue_fk_nn` FOREIGN KEY (`venue_fk_nn`) REFERENCES `venue` (`id`) ON DELETE CASCADE

) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin1;


-- Dumping data for table sponsorship.race: ~9 rows (approximately)

/*!40000 ALTER TABLE `race` DISABLE KEYS */;

INSERT INTO `race` (`id`, `venue_fk_nn`, `race_date_time_nn`) VALUES

	(1, 1, 1404482400),

	(3, 1, 1404485400),

	(2, 1, 1404494700),

	(4, 2, 1404482400),

	(6, 2, 1404485400),

	(5, 2, 1404494700),

	(7, 3, 1404482400),

	(9, 3, 1404485400),

	(8, 3, 1404494700);

/*!40000 ALTER TABLE `race` ENABLE KEYS */;




-- Dumping structure for table sponsorship.runner

CREATE TABLE IF NOT EXISTS `runner` (

  `id` smallint(2) NOT NULL AUTO_INCREMENT,

  `runner_name_nn` varchar(40) NOT NULL,

  PRIMARY KEY (`id`),

  UNIQUE KEY `runner_name_nn` (`runner_name_nn`)

) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1;


-- Dumping data for table sponsorship.runner: ~10 rows (approximately)

/*!40000 ALTER TABLE `runner` DISABLE KEYS */;

INSERT INTO `runner` (`id`, `runner_name_nn`) VALUES

	(1, 'Runner1'),

	(10, 'Runner10'),

	(2, 'Runner2'),

	(3, 'Runner3'),

	(4, 'Runner4'),

	(5, 'Runner5'),

	(6, 'Runner6'),

	(7, 'Runner7'),

	(8, 'Runner8'),

	(9, 'Runner9');

/*!40000 ALTER TABLE `runner` ENABLE KEYS */;




-- Dumping structure for table sponsorship.sponsor

CREATE TABLE IF NOT EXISTS `sponsor` (

  `id` smallint(2) NOT NULL AUTO_INCREMENT,

  `sponsor_name_un_nn` varchar(40) NOT NULL,

  PRIMARY KEY (`id`),

  UNIQUE KEY `sponsor_name_un_nn` (`sponsor_name_un_nn`)

) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;


-- Dumping data for table sponsorship.sponsor: ~8 rows (approximately)

/*!40000 ALTER TABLE `sponsor` DISABLE KEYS */;

INSERT INTO `sponsor` (`id`, `sponsor_name_un_nn`) VALUES

	(1, 'SponsoringCompany1'),

	(2, 'SponsoringCompany2'),

	(3, 'SponsoringCompany3'),

	(4, 'SponsoringCompany4'),

	(5, 'SponsoringCompany5'),

	(6, 'SponsoringCompany6'),

	(7, 'SponsoringCompany7'),

	(8, 'SponsoringCompany8');

/*!40000 ALTER TABLE `sponsor` ENABLE KEYS */;




-- Dumping structure for table sponsorship.sponsorship

CREATE TABLE IF NOT EXISTS `sponsorship` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `runner_sponsorship_jnc_fk_nn` int(11) NOT NULL,

  `sponsor_fk_nn` smallint(6) NOT NULL,

  `amount` float(7,3) DEFAULT NULL,

  PRIMARY KEY (`id`),

  KEY `sponsorship_sponsor_fk_nn` (`sponsor_fk_nn`),

  CONSTRAINT `sponsorship_sponsor_fk_nn` FOREIGN KEY (`sponsor_fk_nn`) REFERENCES `sponsor` (`id`) ON DELETE CASCADE

) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=latin1;


-- Dumping data for table sponsorship.sponsorship: ~30 rows (approximately)

/*!40000 ALTER TABLE `sponsorship` DISABLE KEYS */;

INSERT INTO `sponsorship` (`id`, `runner_sponsorship_jnc_fk_nn`, `sponsor_fk_nn`, `amount`) VALUES

	(1, 1, 1, 5.000),

	(2, 2, 2, 5.000),

	(3, 3, 3, 5.600),

	(4, 4, 4, 8.000),

	(5, 5, 5, 6.900),

	(6, 6, 6, 6.400),

	(7, 7, 7, 5.000),

	(8, 8, 8, 10.000),

	(9, 9, 1, 2.000),

	(10, 10, 2, 3.000),

	(11, 11, 3, 71.000),

	(12, 12, 4, 56.200),

	(13, 13, 5, 23.200),

	(14, 14, 6, 51.200),

	(15, 15, 7, 7.200),

	(16, 16, 8, 2.300),

	(17, 17, 1, 6.200),

	(18, 18, 2, 4.700),

	(19, 19, 3, 6.500),

	(20, 20, 4, 4.400),

	(21, 21, 5, 3.600),

	(22, 22, 6, 4.500),

	(23, 23, 7, 45.300),

	(24, 24, 8, 63.200),

	(25, 25, 1, 14.500),

	(26, 26, 2, 2.300),

	(27, 27, 3, 1.900),

	(28, 28, 4, 20.000),

	(29, 29, 5, 25.000),

	(30, 30, 6, 36.300);

/*!40000 ALTER TABLE `sponsorship` ENABLE KEYS */;




-- Dumping structure for table sponsorship.tbl_migration

CREATE TABLE IF NOT EXISTS `tbl_migration` (

  `version` varchar(255) NOT NULL,

  `apply_time` int(11) DEFAULT NULL,

  PRIMARY KEY (`version`)

) ENGINE=InnoDB DEFAULT CHARSET=latin1;


-- Dumping data for table sponsorship.tbl_migration: ~8 rows (approximately)

/*!40000 ALTER TABLE `tbl_migration` DISABLE KEYS */;

INSERT INTO `tbl_migration` (`version`, `apply_time`) VALUES

	('m000000_000000_base', 1404517233),

	('m130207_231295_create_venue_table', 1404517234),

	('m130207_231296_create_race_table', 1404517238),

	('m130207_231300_create_runner_table', 1404517241),

	('m130207_231310_create_x_race_runner_table', 1404593228),

	('m130207_231315_create_sponsor_table', 1404593233),

	('m130207_231370_create_sponsorship_table', 1404593233),

	('m130207_231375_create_x_race_runner_sponsorship_table', 1404593234);

/*!40000 ALTER TABLE `tbl_migration` ENABLE KEYS */;




-- Dumping structure for table sponsorship.venue

CREATE TABLE IF NOT EXISTS `venue` (

  `id` smallint(2) NOT NULL AUTO_INCREMENT,

  `venue_name_un_nn` varchar(40) NOT NULL,

  PRIMARY KEY (`id`),

  UNIQUE KEY `venue_name_un_nn` (`venue_name_un_nn`)

) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;


-- Dumping data for table sponsorship.venue: ~3 rows (approximately)

/*!40000 ALTER TABLE `venue` DISABLE KEYS */;

INSERT INTO `venue` (`id`, `venue_name_un_nn`) VALUES

	(1, 'RaceVenue1'),

	(2, 'RaceVenue2'),

	(3, 'RaceVenue3');

/*!40000 ALTER TABLE `venue` ENABLE KEYS */;




-- Dumping structure for table sponsorship.x_race_runner

CREATE TABLE IF NOT EXISTS `x_race_runner` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `race_fk_nn` int(11) NOT NULL,

  `runner_fk_nn` smallint(6) NOT NULL,

  PRIMARY KEY (`id`),

  UNIQUE KEY `x_race_runner_x_race_runner_race-runner_unique` (`race_fk_nn`,`runner_fk_nn`),

  KEY `x_race_runner_x_race_runner_runner_fk_nn` (`runner_fk_nn`),

  CONSTRAINT `x_race_runner_x_race_runner_runner_fk_nn` FOREIGN KEY (`runner_fk_nn`) REFERENCES `runner` (`id`) ON DELETE CASCADE,

  CONSTRAINT `x_race_runner_race_fk_nn` FOREIGN KEY (`race_fk_nn`) REFERENCES `race` (`id`) ON DELETE CASCADE

) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=latin1;


-- Dumping data for table sponsorship.x_race_runner: ~30 rows (approximately)

/*!40000 ALTER TABLE `x_race_runner` DISABLE KEYS */;

INSERT INTO `x_race_runner` (`id`, `race_fk_nn`, `runner_fk_nn`) VALUES

	(1, 1, 1),

	(3, 1, 3),

	(4, 1, 4),

	(6, 2, 6),

	(7, 2, 7),

	(9, 2, 9),

	(10, 2, 10),

	(11, 3, 1),

	(12, 3, 2),

	(13, 3, 3),

	(14, 3, 4),

	(15, 3, 5),

	(16, 3, 6),

	(21, 4, 1),

	(20, 4, 10),

	(22, 5, 2),

	(23, 5, 3),

	(26, 5, 6),

	(17, 5, 7),

	(18, 5, <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/cool.gif' class='bbc_emoticon' alt='8)' />,

	(19, 5, 9),

	(27, 6, 7),

	(28, 6, <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/cool.gif' class='bbc_emoticon' alt='8)' />,

	(29, 6, 9),

	(30, 6, 10),

	(2, 7, 2),

	(5, 7, 5),

	(8, 7, <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/cool.gif' class='bbc_emoticon' alt='8)' />,

	(24, 8, 4),

	(25, 8, 5);

/*!40000 ALTER TABLE `x_race_runner` ENABLE KEYS */;




-- Dumping structure for table sponsorship.x_race_runner_sponsorship

CREATE TABLE IF NOT EXISTS `x_race_runner_sponsorship` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `x_race_runner_fk_nn` int(11) NOT NULL,

  `sponsorship_fk_nn` int(11) NOT NULL,

  PRIMARY KEY (`id`),

  UNIQUE KEY `x_race_runner_sponsorship_runner-sponsor_unique` (`x_race_runner_fk_nn`,`sponsorship_fk_nn`),

  KEY `x_race_runner_sponsorship_sponsorship_fn_nn` (`sponsorship_fk_nn`),

  CONSTRAINT `x_race_runner_sponsorship_sponsorship_fn_nn` FOREIGN KEY (`sponsorship_fk_nn`) REFERENCES `sponsorship` (`id`) ON DELETE CASCADE,

  CONSTRAINT `x_race_runner_sponsorship_race_runner_fk_nn` FOREIGN KEY (`x_race_runner_fk_nn`) REFERENCES `x_race_runner` (`id`) ON DELETE CASCADE

) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=latin1;


-- Dumping data for table sponsorship.x_race_runner_sponsorship: ~30 rows (approximately)

/*!40000 ALTER TABLE `x_race_runner_sponsorship` DISABLE KEYS */;

INSERT INTO `x_race_runner_sponsorship` (`id`, `x_race_runner_fk_nn`, `sponsorship_fk_nn`) VALUES

	(1, 1, 1),

	(2, 2, 2),

	(3, 3, 3),

	(4, 4, 4),

	(5, 5, 5),

	(6, 6, 6),

	(7, 7, 7),

	(8, 8, <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/cool.gif' class='bbc_emoticon' alt='8)' />,

	(9, 9, 1),

	(10, 10, 2),

	(11, 11, 3),

	(12, 12, 4),

	(13, 13, 5),

	(14, 14, 6),

	(15, 15, 7),

	(16, 16, <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/cool.gif' class='bbc_emoticon' alt='8)' />,

	(17, 17, 1),

	(18, 18, 2),

	(19, 19, 3),

	(20, 20, 4),

	(21, 21, 5),

	(22, 22, 6),

	(23, 23, 7),

	(24, 24, <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/cool.gif' class='bbc_emoticon' alt='8)' />,

	(25, 25, 1),

	(26, 26, 2),

	(27, 27, 3),

	(28, 28, 4),

	(29, 29, 5),

	(30, 30, 6);

/*!40000 ALTER TABLE `x_race_runner_sponsorship` ENABLE KEYS */;

/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;

/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;




In the XRaceRunner model, I currently have…




    public function relations() {

        return array(

            'sponsorships' => array(self::HAS_MANY, 'XRaceRunnerSponsorship', 'x_race_runner_fk_nn'),

            'runner' => array(self::BELONGS_TO, 'Runner', 'runner_fk_nn'),

            'race' => array(self::BELONGS_TO, 'Race', 'race_fk_nn'),

        );

    }


    public function search() {


        $criteria = new CDbCriteria;


        $criteria->compare('id', $this->id);

        $criteria->compare('race_fk_nn', $this->race_fk_nn);

        $criteria->compare('runner_fk_nn', $this->runner_fk_nn);

        $criteria->with = array('race', 'race.venue', 'runner');


        $criteria->together = true;


        return new CActiveDataProvider($this, array(

            'criteria' => $criteria,

        ));

    }




In the XRaceRunnerSponsorship model…




    public function relations() {

        return array(

            'sponsorship' => array(self::BELONGS_TO, 'Sponsorship', 'sponsorship_fk_nn'),

            'xRaceRunnerFkNn' => array(self::BELONGS_TO, 'XRaceRunner', 'x_race_runner_fk_nn'),

        );

    }



And in the Sponsorship model…




    public function relations() {

        return array(

            'sponsorFkNn' => array(self::BELONGS_TO, 'Sponsor', 'sponsor_fk_nn'),

            'xRaceRunnerSponsorships' => array(self::HAS_MANY, 'XRaceRunnerSponsorship', 'sponsorship_fk_nn'),

        );

    }



As mentioned in earlier posts, sorting/filtering is a requirement for the CGridView. However, no data needs to be added or changed in the frontend, so I therefore only need an admin view that will give a grid that can be sorted/filtered, and that is all (the data will actually be fed in via the backend).

I’ve tried to many different ways of creating the correct criteria for the CActiveDataProvider but I just cant seem to get it to work. I am checking the SQL queries created by using CWebLogRoute in the log component and sometimes it actually creates a query that would provide the required data (the Cartesian product) but the data from this is not what is displayed in the CGridView and it’s driving me crazy!! :(

I would be so appreciative is someone could help me here. I would have assumed there would be a simple way to display a Cartesian product from a many-to-many relationship but I have dug through all the threads and examples I can find and I still cant seem to be able to solve it.

I would be ever so grateful if someone could help me :rolleyes:

Thanks in advance,

U4EA

BTW, as a footnote - my main aim right now is to get the Cartesian product displaying properly, and I will work on sorting/filtering once that is done. I would prefer to work it that way as, while I am ever so grateful for the help this forums provides, I would rather try to work on the sorting/filtering myself rather than have others spend their time doing it for me… I am only asking about the Cartesian product issue as a last resort as I have all but run out of ideas! :angry:

Did you have a good look at my extension, especially at the RelatedSearchBehavior demo ?

In the demo, ‘InvoiceLine’ is the table/model used for the search with CActiveDataProvider. In your case it might need to be x_race_runner_sponsorship or x_race_runner.

As you can see in that demo, all columns are sortable and searcheable despite the number of tables involved. All of that is quite easy with the RelatedSearchBehavior extension.

Hi,

Thanks for pointing me to this again. I haven’t actually looked at it yet as I just posted on the thread after completely exhausting my attempts at producing it myself… perhaps that time would have been better spent looking over your work, which I will do first thing tomorrow when my brain is fully charged again!

Thanks mate :)

Hi le_top,

I finally got around to having a look at your extension today and it INCREDIBLY useful to say the least… saves so much time and hassle. I REALLY wish I had known about this before :rolleyes: Really, really looking forward to using this extension going forward.

I was wondering if you could give your opinion on the db schema I have proposed? The schema given in my post above uses a junction table to link the runner to their to their sponsorships ( x_race_runner->x_race_runner_sponsorship<-sponsorship ). Would you suggest going with this schema or would you suggest I go with a schema where there is a single table with x_race_runner.id, sponsor.id as FKs and then the sponsorship amount? This would allow me to create a true composite index of x_race_runner.id+sponsor.id, simplifying the schema and also reduces code. I assume I could then use the CActiveRecord model of this table as the base model for the CGridView, which means the dataset being worked with is already the Cartesian product.

Any thoughts?

HI

It looks like you have understood what the extension does :wink: and I fully agree with your statements about it. It is my preferred extension.

Regarding your schema - analysing it precisely takes some time and from a quick look I am not sure what the difference is because x_race_runner_sponsorship already seems to use foreign keys to the user and to the sponsor.

As far as I understand, the sponsor can pay the runner one or more times. This payment can be related or not related to a race. Hence I would indeed suggest a table representing a single sponsorship referring to the runner, the sponsor, and the race (which may be "NULL"). That same table kind of corresponds to the invoiceline in the demo of the relatedsearchbehavoir extension: the runner is a product that can be bought by a customer (the sponsor) in a given store (the race). This action can have several other attributes like: date, amount, duration, … .

Hi,

Thanks for your input.

The data will be held in such a manner that each sponsorship will be linked to a single runner in a race (x_race_runner) and that race runner can only be sponsored a single time by one sponsor, so we are effectively looking at a schema where there should be a unique index on the x_race_runner.id and the sponsor.id, so the InvoiceLine table model is not quite right for this scenario.

So I think create a single table for this and link it to the x_race_runner table with x_race_runner.id as a FK? Then I assume I would model this table, use this as the model for the CGridView, incorporate RelatedSearchBehavior into the model and build the CGridView from there. Does that sound right?

I cant tell you how much I like your extension… once I finish this part of the project, I will be using your extension for many other models to simply things… a true lightbulb moment! :D

I think that your table should have its own key. It could still be that in some future a sponsor can sponsor more than once. When the table has its own key, it is also easier to retrieve the record based on the key and to make updates to it (keys with multiple columns are harder to manage).

The analogy with the InvoiceLine model is simply that an invoice line corresponds to one sponsorship for a given runner. The buyer is of course defined in the invoice itself (not in the invoiceline).

Thanks.

I won’t go into reasons right now as they are not really important, but a sponsor can only sponsor 1 runner in a race a single time, so I am working with that data model.

Suggestions? Is a many-to-many really required or can I just create a table with the compound index and the x_race_runner.id as the FK as suggested above?