I am currently working on a YII application, which I must admit is my first attempt. I am having difficulty mastering the active record component of YII. In particular I would like to join three tables using with().
My mysql tables are as follows:
video_specific
- id (primary key)
- random_id
- user_id
- video_link
- quality (enum, high,low)
video_details
- video_id
- upvote_count
- downvote_count
- timestamp
ladder_videos
- ladder_id
- video_id
ladder_specific
- id
- random_id
- name
- description
- ladder_type
- status
- video_count
Thus after using the gii tool I was give models with the following relationships. Please note that I did not create a model for the ladder_videos table.
In videoSpecific model
'ladderSpecifics' => array(self::MANY_MANY, 'LadderSpecific',
'ladder_videos(video_id, ladder_id)'),
'videoDetails' => array(self::HAS_ONE, 'VideoDetails', 'video_id')
In ladderSpecific model
'videoSpecifics' => array(self::MANY_MANY, 'VideoSpecific',
'ladder_videos(ladder_id, video_id)')
With these relations I thought I could right the following query
$ladders = LadderSpecific::model()->with(
array('videoSpecifics'=>array('select'=>'id,video_link,random_id',
'join'=>'videoDetails')))->findAll();
But I get the following error
CDbCommand failed to execute the SQL statement: SQLSTATE[42000]: Syntax error or
access violation: 1064 You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use near 'videoDetails'
at line 1. The SQL statement executed was: SELECT `t`.`id` AS `t0_c0`, `t`.`random_id` AS
`t0_c1`, `t`.`name` AS `t0_c2`, `t`.`description` AS `t0_c3`, `t`.`ladder_type` AS
`t0_c4`, `t`.`status` AS `t0_c5`, `t`.`video_count` AS `t0_c6`, `videoSpecifics`.`id` AS
`t1_c0`, `videoSpecifics`.`video_link` AS `t1_c3`, `videoSpecifics`.`random_id` AS `t1_c1`
FROM `ladder_specific` `t` LEFT OUTER JOIN `ladder_videos` `videoSpecifics_videoSpecifics`
ON (`t`.`id`=`videoSpecifics_videoSpecifics`.`ladder_id`) LEFT OUTER JOIN `video_specific`
`videoSpecifics` ON (`videoSpecifics`.`id`=`videoSpecifics_videoSpecifics`.`video_id`)
videoDetails
Any ideas why?? Please limit your answers to active record, and not DAO or query builder. Thanks
It would help to review how relations work in Yii.
http://www.yiiframework.com/doc/guide/1.1/en/database.arr
When querying the model, Yii automatically generates the query to fetch the relational data, so you don't have to supply it.
You must therefore supply the name given to your relation.
$ladders = LadderSpecific::model()->with('videoSpecifics')->findAll();
SO the answer was to update the ladderSpecific relations. So once you update them they should like this:
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(
'ladderDetails' => array(self::HAS_ONE, 'LadderDetails', 'ladder_id'),
'videoSpecifics' => array(self::MANY_MANY, 'VideoSpecific', 'ladder_videos(ladder_id, video_id)'),
array(self::HAS_ONE, 'VideoDetails', 'video_id'),
);
}
Once you have updated this relation you can do this:
$ladders = LadderSpecific::model()->with(
array('videoSpecifics.videoDetails'=>
array(
'order'=>'videoDetails.upvote_count - videoDetails.downvote_count DESC',
'limit'=>'1'
)))->findAll(array('order'=>'name',));
The only issue is for some reason I can not limit the number of records returned. Once I figure that out I will update my answer. If you have a better way of doing it let me know.
Related
I'm annoyed because of a big query that goes a little in all directions.
She will use several tables:
User
Package
User Type
PackageDDLExternal (with foreign key User.id and Package.id to which I added a date attribute)
The purpose of this query is to be able to return a list that will contain:
The different existing packages with the number of times they were downloaded, for a certain period, with a given order (ASC or DESC) on the number of downloads, and all this depending on the user types whose ID table has passed as parameter.
So, I've :
Paquet :
Type Utilisateur :
User:
The manyToMany relation between TypeUtilisateur and Paquet:
And the PackageDDLExternal:
So I have already tried to create this query, but there seems to be synthax errors on the one hand (at the level of adding the order) and other things that seem to block...
public function getPackagesDDLBetween($debut, $fin,$typesUser,$ordre)
{
$qb = $this->createQueryBuilder('p');
$queryBuilder = $qb
->select("pa.titre, count(p.package)")
->join("p.package","pa")
->join("p.user","u")
->where("p.date between :debut and :fin")
->andWhere($qb->expr()->in("u.typeUser", $typesUser[0]))
->groupBy("pa.titre")
->orderBy("count(p.package) :ordre")
->setParameter('debut',$debut)
->setParameter('fin',$fin)
->setParameter('ordre', $ordre);
return $queryBuilder->getQuery()->getResult();
}
Using the $order, I get this error:
[Syntax Error] line 0, col 213: Error: Expected known function, got
'count'
But without it, my result is just null
Someone can help me please ?
EDIT: The query is almost good. The problem remains at:
->andWhere($qb->expr()->in("u.typeUser", $typesUser))
My $ tabUser is worth this:
Use an alias:
->select("pa.titre, count(p.package) as total")
Order by alias:
->orderBy("total", $ordre)
I have converted an sql into laravel query so I came with this below.
DB::table('posts')
->select('posts.*', DB::raw('GROUP_CONCAT(l.name ORDER BY l.id) as location_name'))
->join('locations as l', "posts.location_ids", "=", "l.id")
->whereRaw('FIND_IN_SET(l.id, posts.location_ids) > 0')
->where("posts.status", 'active')
->groupBy('posts.id')
->get();
But it gives me an error.
SQLSTATE[42000]: Syntax error or access violation: 1055 'laraveltest.posts.title' isn't in GROUP BY (SQL: select `posts`.*, GROUP_CONCAT(l.name ORDER BY l.id) as location_name from `posts` inner join `locations` as `l` on `posts`.`location_ids` = `l`.`id` where FIND_IN_SET(l.id, posts.location_ids) > 0 and `posts`.`status` = active group by `posts`.`id`)
When I run this query into any mysql tool, it returns row without any fail. why laravel giving me errors?
This is a setting in laravel/mysql for laravel look in config/database.php.
Set strict mode to false
'connections' => [
'mysql' => [
'strict' => false,
]
Laravel is not giving you the error it's just actually enforcing MYSQL rules. PHPMYADMIN and other tools don't force it so that's why you don't get the error.
The reason laravel is so good, is because you can get rid of writing huge database queries like this.
You should take some time out to really site down and read the documentation for laravel and how eloquent works. You will be surprised how quickly you pick it up, i promise.
Now for a situation such as your self, you have two models.
Post Model
Location Model
In your Posts model you would create a relationship like so
public function location()
{
return $this->belongsTo(Location::class);
}
and in your Location model you would now define a relationship like so
public function post()
{
return $this->hasMany(Post::class);
}
Now in your posts_table you need to ensure you have a location_id integer column.
Now what you can do is get the Location instance you want for example:
//This will give you the row where id = 1
$location = Location::find(1);
Now you can easily do this to access all the posts with that location:
foreach($location->posts as $post){
//Do whatever you want here $post is the object
echo $post->title
}
And the relationship works on the inverse, example:
$post = Post::find(1);
Now because this post belongs to only 1 location you can call it like so
echo $post->location->column_name_in_location_table
Hope this helps kick start your journey to learning laravel!
I have a view like this:
CREATE VIEW orders_sales AS
SELECT code, SUM(quantity) * SUM(sale_price) as product_total, shop_id
FROM `orders`
GROUP BY code, product_id
I am trying to query it like this:
$data = DB::table('orders_sales')->get();
I am getting the following error:
SQLSTATE[42000]: Syntax error or access violation: 1055
'pakishops2.orders.shop_id' isn't in GROUP BY (SQL: select * from
orders_sales)
I am able to query it in phpmyadmin like select * from orders_sales but it wont query with Laravel.
enter image description here
EDIT:
I have changed view name to view_saleorders, still no luck
Go to the config/database.php file and check if the option strict is set to true.
Set it to false and you query will work.
Try alternative now,
Create model with that name as laravel standard,
like
class ViewOrders extends Eloquent
Add namespaces required inside model if any.
then create class variable as
protected $table = 'your-view-name';
And now check data by getting like,
ViewOrders::all();
And check if you are getting data.
add strict => false in config/database.php.
If this is set to true then it'll add the ONLY_FULL_GROUP_BY when querying.
Alternative to be more specific with modes,
'modes' => [
'NO_ZERO_DATE',
'ONLY_FULL_GROUP_BY',
],
Sorry to revive an old post, but your view is wrong. You need to match your columns to your GROUP BY clause.
Either change shop_id to product_id, or product_id to shop_id.
-- This is wrong.
CREATE VIEW orders_sales AS
SELECT code, SUM(quantity) * SUM(sale_price) as product_total, <!shop_id!>
FROM `orders`
GROUP BY code, <!product_id!>
This is a very odd issue that I have not been able to figure out for a very long time.
I have a model Upload, with several hasMany all being ignored by UploadModel.
For example. Upload hasMany Face.
On comments table I have a field called foreign_key that hosts the Upload.id.
So the relationship in UploadModel looks like this:
$hasMany = ['Face' => ['foreignKey' => 'Face.upload_id']];
When performing a find in Upload with contain Face + conditions [Face.celebrity_id = 4018] I get the following error because the query is missing the Left Join to Face:
$media = $this->Upload->find('all',array(
'conditions' => array(
'Face.celebrity_id' => 4018
),
'contain' => array(
'Face'
)
));
SQL Query:
SELECT `Upload`.`id`, `Upload`.`user_id`, `Upload`.`filename`, `Upload`.`created`
FROM `uploads` AS `Upload`
WHERE `Face`.`celebrity_id` = 4018;
SQL Error:
Unknown column 'Face.celebrity_id' in 'where clause'
As you can see the query is missing the left joing to Face, that's all the problem
If instead of in the $hasMany, I add the Face relationship in $belongsTo, it adds the relationship to the query and it works!
But an Upload can have many Faces, and Face.upload_id has the foreignKey to Upload, so IT NEEDS TO BE A motherf***** HASMANY... lol
As you can see, this is terrible, I am already desperate, I have bypassed the issue by adding a bindModel() before each query, but it is totally unnecessary, the hasMany should work!!!! f****!!!!!
The query that I want Cake to perform for me is this:
SELECT `Upload`.`id`, `Upload`.`filename` FROM `uploads` AS `Upload`
LEFT JOIN `upload_faces` AS `Face` ON (`Upload`.id = `Face`.`upload_id`)
WHERE `Face`.`celebrity_id` = 4018
I appreciate any help, thanks.
ok, so basically, it is not ignoring the hasMany, but I can not use conditions on a hasMany relationship, can only be done if it is a hasOne
In your test() function, you run query on upload table. So it can not match Face.celebrity_id field from your query. Two things you have know first:
Condition you are writing in conditions clause is applied on the table matching with your model. In your case, upload is the table on which query is executed and your table contains no field Face.celebrity_id.
$hasMany creates application level relations(associations). So by doing query like you have written in test() function, it doesn't join the query results.
What you can do is,
$this->find('all', array(
'contain' => array(
'Face' => array('conditions' => 'Face.celebrity_id = 4018'))))
This will return all rows of upload table with associated row of face table if celebrity_id is 4018, otherwise null array.
Hope this will help you and if you want that only those many rows will be returned which are associated with Face.celebrity_id = 4018, first you have to run query on face table and then another on first query result.
For more detail of second case you can refer this.
I am using PHP Yii framework's Active Records to model a relation between two tables. The join involves a column and a literal, and could match 2+ rows but must be limited to only ever return 1 row.
I'm using Yii version 1.1.13, and MySQL 5.1.something.
My problem isn't the SQL, but how to configure the Yii model classes to work in all cases. I can get the classes to work sometimes (simple eager loading) but not always (never for lazy loading).
First I will describe the database. Then the goal. Then I will include examples of code I've tried and why it failed.
Sorry for the length, this is complex and examples are necessary.
The database:
TABLE sites
columns:
id INT
name VARCHAR
type VARCHAR
rows:
id name type
-- ------- -----
1 Site A foo
2 Site B bar
3 Site C bar
TABLE field_options
columns:
id INT
field VARCHAR
option_value VARCHAR
option_label VARCHAR
rows:
id field option_value option_label
-- ----------- ------------- -------------
1 sites.type foo Foo Style Site
2 sites.type bar Bar-Like Site
3 sites.type bar Bar Site
So sites has an informal a reference to field_options where:
field_options.field = 'sites.type' and
field_options.option_value = sites.type
The goal:
The goal is for sites to look up the relevant field_options.option_label to go with its type value. If there happens to be more than one matching row, pick only one (any one, doesn't matter which).
Using SQL this is easy, I can do it 2 ways:
I can join using a subquery:
SELECT
sites.id,
f1.option_label AS type_label
FROM sites
LEFT JOIN field_options AS f1 ON f1.id = (
SELECT id FROM field_options
WHERE
field_options.field = 'sites.type'
AND field_options.option_value = sites.type
LIMIT 1
)
Or I can use a subquery as a column reference in the select clause:
SELECT
sites.id,
(
SELECT id FROM field_options
WHERE
field_options.field = 'sites.type'
AND field_options.option_value = sites.type
LIMIT 1
) AS type_label
FROM sites
Either way works great. So how do I model this in Yii??
What I've tried so far:
1. Use "on" array key in relation
I can get a simple eager lookup to work with this code:
class Sites extends CActiveRecord
{
...
public function relations()
{
return array(
'type_option' => array(
self::BELONGS_TO,
'FieldOptions', // that's the class for field_options
'', // no normal foreign key
'on' => "type_option.id = (SELECT id FROM field_options WHERE field = 'sites.type' AND option_value = t.type LIMIT 1)",
),
);
}
}
This works when I load a set of Sites objects and force it to eager load type_label, e.g. Sites::model()->with('type_label')->findByPk(1).
It does not work if type_label is lazy-loaded.
$site = Sites::model()->findByPk(1);
$label = $site->type_option->option_label; // ERROR: column t.type doesn't exist
2. Force eager loading always
Building on #1 above, I tried forcing Yii to always to eager loading, never lazy loading:
class Sites extends CActiveRecord
{
public function relations()
{
....
}
public function defaultScope()
{
return array(
'with' => array( 'type_option' ),
);
}
}
Now everything always works when I load Sites, but it's no good because there are other models (not pictured here) that have relations that point to Sites, and those result in errors:
$site = Sites::model()->findByPk(1);
$label = $site->type_option->option_label; // works now
$other = OtherModel::model()->with('site_relation')->findByPk(1); // ERROR: column t.type doesn't exist, because 't' refers to OtherModel now
3. Make the reference to the base table somehow relative
If there was a way that I could refer to the base table, other than "t", that was guaranteed to point to the correct alias, that would work, e.g.
'on' => "type_option.id = (SELECT id FROM field_options WHERE field = 'sites.type' AND option_value = %%BASE_TABLE%%.type LIMIT 1)",
where %%BASE_TABLE%% always refers to the correct alias for table sites. But I know of no such token.
4. Add a true virtual database column
This way would be the best, if I could convince Yii that the table has an extra column, which should be loaded just like every other column, except the SQL is a subquery -- that would be awesome. But again, I don't see any way to mess with the column list, it's all done automatically.
So, after all that... does anyone have any ideas?
EDIT Mar 21/15: I just spent a long time investigating the possibility of subclassing parts of Yii to get the job done. No luck.
I tried creating a new type of relation based on BELONGS_TO (class CBelongsToRelation), to see if I could somehow add in context sensitivity so it could react differently depending on whether it was being lazy-loaded or not. But Yii isn't built that way. There is no place where I can hook in code during query buiding from inside a relation object. And there is also no way I can tell even what the base class is, relation objects have no link back to the parent model.
All of the code that assembles these queries for active records and their relations is locked up in a separate set of classes (CActiveFinder, CJoinQuery, etc.) that cannot be extended or replaced without replacing the entire AR system pretty much. So that's out.
I then tried to see if I can create "fake" database column entries that would actually be a subquery. Answer: no. I figured out how I could add additional columns to Yii's automatically generated schema data. But,
a) there's no way to define a column in such a way that it can be a derived value, Yii assumes it's a column name in way too many places for that; and
b) there also doesn't appear to be any way to avoid having it try to insert/update to those columns on save.
So it really is looking like Yii (1.x) just does not have any way to make this happen.
Limited solution provided by #eggyal in comments: #eggyal has a suggestion that will meet my needs. He suggests creating a MySQL view table to add extra columns for each label, using a subquery to look up the value. To allow editing, the view would have to be tied to a separate Yii class, so the downside is everywhere in my code I need to be aware of whether I'm loading a record for reading only (must use the view's class) or read/write (must use the base table's class, does not have the extra columns). That said, it is a workable solution for my particular case, maybe even the only solution -- although not an answer to this question as written, so I'm not going to put it in as an answer.
OK, after a lot of attempts, I have found a solution. Thanks to #eggyal for making me think about database views.
As a quick recap, my goal was:
link one Yii model (CActiveRecord) to another using a relation()
the table join is complex and could match more than one row
the relation must never join more than one row (i.e. LIMIT 1)
I got it to work by:
creating a view from the field_options base table, using SQL GROUP BY to eliminate duplicate rows
creating a separate Yii model (CActiveRecord class) for the view
using the new model/view for the relation(), not the original table
Even then there were some wrinkles (maybe a Yii bug?) I had to work around.
Here are all the details:
The SQL view:
CREATE VIEW field_options_distinct AS
SELECT
field,
option_value,
option_label
FROM
field_options
GROUP BY
field,
option_value
;
This view contains only the columns I care about, and only ever one row per field/option_value pair.
The Yii model class:
class FieldOptionsDistinct extends CActiveRecord
{
public function tableName()
{
return 'field_options_distinct'; // the view
}
/*
I found I needed the following to override Yii's default table data.
The view doesn't have a primary key, and that confused Yii's AR finding system
and resulted in a PHP "invalid foreach()" error.
So the code below works around it by diving into the Yii table metadata object
and manually setting the primary key column list.
*/
private $bMetaDataSet = FALSE;
public function getMetaData()
{
$oMetaData = parent::getMetaData();
if (!$this->bMetaDataSet) {
$oMetaData->tableSchema->primaryKey = array( 'field', 'option_value' );
$this->bMetaDataSet = TRUE;
}
return $oMetaData;
}
}
The Yii relation():
class Sites extends CActiveRecord
{
// ...
public function relations()
{
return (
'type_option' => array(
self::BELONGS_TO,
'FieldOptionsDistinct',
array(
'type' => 'option_value',
),
'on' => "type_option.field = 'sites.type'",
),
);
}
}
And all that does the trick. Easy, right?!?