how to use CJoinElement on a table without primaryKey in database

=======================================

I thought this was a bug ,but it’s not.

Just another example that Yii’s document

is limit.

=======================================

I found a problem in CActiveFinder : line 737

when I use AR to fetch rows in multi-table,which doesn’t have a primary key defined in database:




	private function populateRecord($query,$row)

	{

        //var_dump($row);

		// determine the primary key value

		if(is_string($this->_pkAlias))  // single key

		{

			if(isset($row[$this->_pkAlias]))

				$pk=$row[$this->_pkAlias];

			else	// no matching related objects

				return null;

		}

		else // is_array, composite key

		{

			$pk=array();

			foreach($this->_pkAlias as $name=>$alias)

                       #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ cause error when there is no primary key in this table

			{

				if(isset($row[$alias]))

					$pk[$name]=$row[$alias];

				else	// no matching related objects

					return null;

			}

			$pk=serialize($pk);

		}



This error will happen when you use relation to find.Error message is as below:




PHP Error

Invalid argument supplied for foreach()

/var/www/framework/db/ar/CActiveFinder.php(737)


00725:     {

00726:         // determine the primary key value

00727:         if(is_string($this->_pkAlias))  // single key

00728:         {

00729:             if(isset($row[$this->_pkAlias]))

00730:                 $pk=$row[$this->_pkAlias];

00731:             else    // no matching related objects

00732:                 return null;

00733:         }

00734:         else // is_array, composite key

00735:         {

00736:             $pk=array();

00737: foreach($this->_pkAlias as $name=>$alias) 



Here ,the code supposes that each table must have a primary key,if not a string,then it must be an array.but it’s not always true.

You can modify the Tutorial Blog’s table comment for test:




alter table `comment` drop primary key,add index `x_id` (`id`);



and then access the link:"http://localhost/blog/post/4",which would show a post with all its comments before,but now error occurs just as mentioned above.

I think that not every table should have a primary key,right?

Just think about using partitions in MySQL,when you use partition , you have to drop the primary key if you want to partition by another column rather than the primary key(unique key must be included in partition expression)




CREATE TABLE `mall_trade` (

  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,

  `tid` bigint(20) unsigned NOT NULL DEFAULT '0',

  `sid` int(10) unsigned DEFAULT NULL,

  `item_id` bigint(20) unsigned DEFAULT NULL,

  `trade_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',

  `nick` varchar(20) NOT NULL DEFAULT '',

  `price` int(10) unsigned NOT NULL,

  `num` mediumint(9) NOT NULL DEFAULT '0',

  `props` varchar(100) NOT NULL DEFAULT '',

  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,

  KEY `x_id` (`id`),

  KEY `x_sid_trade` (`sid`,`trade_time`)

) ENGINE=MyISAM DEFAULT CHARSET=utf8

/*!50100 PARTITION BY RANGE (to_days(trade_time))

(PARTITION pbase VALUES LESS THAN (733773) ENGINE = MyISAM,

 PARTITION p0 VALUES LESS THAN (734046) ENGINE = MyISAM,

 PARTITION p1 VALUES LESS THAN (734077) ENGINE = MyISAM,

 PARTITION p2 VALUES LESS THAN (734107) ENGINE = MyISAM,

 PARTITION p3 VALUES LESS THAN (734138) ENGINE = MyISAM,

 PARTITION p4 VALUES LESS THAN (734169) ENGINE = MyISAM,

 PARTITION p5 VALUES LESS THAN (734197) ENGINE = MyISAM,

 PARTITION p6 VALUES LESS THAN (734228) ENGINE = MyISAM,

 PARTITION p7 VALUES LESS THAN (734258) ENGINE = MyISAM,

 PARTITION p8 VALUES LESS THAN (734289) ENGINE = MyISAM,

 PARTITION p9 VALUES LESS THAN (734319) ENGINE = MyISAM,

 PARTITION p10 VALUES LESS THAN MAXVALUE ENGINE = MyISAM) */;



I’m eager to see the Yii1.1 stable version to be released soon

Great work,Yii team!

I find this comment in CJoinElement:




/*

* @var array list of active records found by the queries. They are indexed by primary key values.

*/

public $records=array();



It seems AR is designed to do so…

maybe my problem won’t be fixed soon…

Active Records are indexed by primary key values…why should AR do this magic ?

Normally every table has a primary key. Even if you didnt provide the DBMS with one, it exists - you just missed to define it within the DBMS.

The argument with the partition is not really valid, because you need either a PK or UNIQUE key and the only difference is the a PK can not be null. Moreover it only says that it needs to be part of the key, not the key itselfs. Therefore you can just extends the primary key with the column you want your partitions to be based on, since it needs to be unique anyway.

I do not see a problem here and I do not see a problem of defining a PK for a table :)

Thank you for your fast reply, I have a column “id auto_increment” as a normal key in my table,and it’s partitioned by range(to_days(create_time)),I’m testing whether I can define the id column as the primary key in the Model.

By the way,I think it’s necessary to have a primary key on a table if you want to update it,and AR records can be saved anywhere.Maybe this is why Active Records should be indexed by a primary key.

But in my case,I just want to list the records,and these records won’t be modified anymore since they are recorded.

You can override the primaryKey() method of your AR class to specify which column should be the PK. If you don’t do this, AR will use the one defined in DB.

Thank you!qiang!

I tried to set $primaryKey for my Model , but it doesn’t work…

I should have realized that each Property of a class is a function.The code below works very well:




class HsMallTrade extends CActiveRecord{

    public function primaryKey()

    {

        return "id";

    }

}



Hi touya,

Did you resolved this problem?. I had this problem but i cant resovle it, if you fixed, could you show me your code.

Thanks :)

He is saying that the problem is caused by not have the primary key defined correctly in the database table.

So you can either set it manually using the primaryKey function, or you can do it in your database.