Background:
Mediawiki's PHP framework provides functions for creating SQL statements to be executed against the application. Creating a simple SELECT FROM WHERE statement is easy and well documented, but I'm trying to create a JOIN statement and I don't understand the documentation. The examples are fragmented and disjointed.
Current attempt:
The SQL I'm trying to recreate is as follows. Using phpmyadmin I've tested this code and it returns the results I need:
SELECT user.user_id, user.user_name, user.user_real_name, user.user_email
FROM user
LEFT JOIN ipblocks ON user.user_id = ipblocks.ipb_user
WHERE ipblocks.ipb_id IS NULL
Translating this into Mediawiki's framework looks something like this, but this code doesn't work. The function documentation is here.
$result = $this->dbr->select(
array( $this->dbr->tableName( 'user' )),
array( 'user_name', 'user_id', 'user_email' ),
array( 'ipb_id' => 'NULL'),
__METHOD__,
array( 'GROUP_BY' => 'user_id DSC' ),
array( 'ipblocks' => array( 'LEFT JOIN', 'user_id=ipb_user' ) )
);
The SQL generated by this code can be checked by calling selectSQLText() instead. This function returns the generated SQL rather than executing it. The calling convention is the same. THis results in the following SQL:
SELECT user_name,user_id,user_email
FROM `user`
WHERE ipb_id = 'NULL'
I can see why the function has returned this, but I don't understand why the last two parameters have been ignored. The GROUP_BY and JOIN parts have been ignored. Why is this and how do I correct the code?
Thanks.
I'm not an user of Mediawiki, I've just glossed over the function documentation. As for grouping, I believe you should use GROUP BY array key, not GROUP_BY. As for joins, I think you must include ipblocks table in $table parameter in order to use it in $join_conds.
So, try this:
$result = $this->dbr->select(
array( 'user', 'ipblocks' ),
array( 'user_name', 'user_id', 'user_email' ),
array( 'ipb_id' => null),
__METHOD__,
array( 'GROUP BY' => 'user_id DSC' ),
array( 'ipblocks' => array( 'LEFT JOIN', 'user_id=ipb_user' ) )
);
Related
I'm using CakePHP and trying to retrieve FOUND_ROWS() for a query that runs several times in a loop (each time with a different WHERE section). The strangest thing is happening: it returns the correct result for the first query - and then the same exact result for subsequent queries.
This is the code:
$article = new Article();
$query = $db->buildStatement(
array(
'table' => $db->fullTableName($article)
, 'alias' => 'Article'
, 'order' => '`Article`.`publish_date` desc'
, 'offset' => $startIndex
, 'joins' => array(
array(
'table' => 'articles_categories',
'alias' => 'ArticlesCategories',
'type' => 'inner',
'conditions' => array(
'ArticlesCategories.article_id = Article.id',
),
)
)
, 'limit' => $this->maxNumArticlesToLoadPerPage
, 'conditions' => "ArticlesCategories.category_id = $categoryId and publish_date <= now()"
, 'fields' => array('Article.id', 'Article.name', 'Article.search_engine_teaser', 'Article.image_file_name', 'Article.content_modified', 'ArticlesCategories.category_id')),
$article
);
$query = str_replace('SELECT', 'SELECT SQL_CALC_FOUND_ROWS', $query);
$articles = $article->query($query);
// also return how many total articles are in this category
$numArticlesInCategory = $article->query('select found_rows()')[0][0]['found_rows()'];
Now, where real curiosity here is that I used ET GLOBAL general_log = 'ON' in the mysql database to track down what's going on, and it appears that the first query is indeed run several times in a loop, but the query select found_rows() is only run once!
I also tried to run select SQL_NO_CACHE found_rows(), but that didn't help.
Has anyone encountered this?
Check your models $cacheQueries property setting, it might be true, causing the query to be cached on the CakePHP side. Use the second query() parameter to disable caching per query:
query('sql', false)
Btw., ArticlesCategories.category_id = $categoryId looks like a possible SQL injection vulnerability, you should never insert data in queries that way, always use the key => value syntax for conditions, or use prepared statements!
Also model classes shouldn't be instantiated directly, instead ClassRegistry::init() should be used.
See also
Cookbook > Models > Retrieving Your Data > Model::query()
Cookbook > Models > Model Attributes > cacheQueries
Hellow , I want to use group by within contain in cakephp. In the following case i want to take only distinct organization within organizationUser array..
$options = array(
'conditions' => array('User.' .$this->User->primaryKey => $userId),
'contain' => array(
'OrganizationUser'=>array(
'conditions'=>['status'=>3],
'group'=> array( 'OrganizationUser.organization_id')),
'OrganizationUser.Organization',
'OrganizationUser.Organization.Noticeboard',
'OrganizationUser.Organization.Newsboard',
'OrganizationUser.Organization.Noticeboard.Branch',
),
'page'=>$page,
'limit'=>$limit
);
$org = $this->User->find('all', $options);
But this is throwing error like 'Column not found', and 'conditions' is working fine within OrganizationUser but 'group' not working.I am using cakephp version 2.Thanks in advance.
I don't think cakephp 2+ offer something like you are doing to make field distinct within contain. So better to try following..
Replace :
'group'=> array( 'OrganizationUser.organization_id')
By
'fields'=> array( 'DISTINCT OrganizationUser.organization_id')
that might work for you.
In my case, I'm using cake version 4+.
In my table the relation I've made
$this->belongsTo('CreatedOperator')
->setClassName(USERS_PLUGIN . '.Users')
->setForeignKey('created_by')
->setJoinType('INNER')
;
and I'm calling the relation like
$query
->disableHydration()
->select([
'CreatedOperator.id',
'CreatedOperator.first_name',
'CreatedOperator.last_name',
'full_name' => $query->func()->concat(['CreatedOperator.first_name' => 'identifier',' ','CreatedOperator.last_name' => 'identifier']),
'total' => $query->func()->count('CreatedOperator.id')])
->contain(['CreatedOperator'])
->group(['CreatedOperator.id'])
;
return $query->toList();
I have a standard find query on my user model that looks as followed:
$user = $this->User->find( 'all', array( 'conditions' => array( 'User.id' => $user_id ) ) );
I also have some extensions I would like to make to the where clause of this function call like so:
$query_extension = 'AND users.id IN ( complex join between a few tables )';
I want to add this complex WHERE clause on to the end of that user find condition, but I'm not sure how to do this. I'm looking into the ConnectionManager class, but I'm still not sure how to append this extra clause:
http://api.cakephp.org/2.5/class-ConnectionManager.html#_getDataSource
Check sample code I used, you can also set dynamic content like joins Will have array What you built Or
will have blank array.
$courseNames = $this->UsersCourse->Course->find('list',
array('fields'=> array('Course.course_name'),
'order'=> array('Course.course_name'),
'conditions'=>array('Course.is_active'=>1 ,
'CourseCategory.is_active'=>1
),
"joins" => array(
array(
"table" => "course_categories",
"alias" => "CourseCategory",
"type" => "INNER",
"conditions" => array(
"CourseCategory.id = Course.course_category_id"
)
)
)
)
);
This is how this operation can be preformed. Notice that the clause being added to the main query includes an AND This means that it is actually part of the where clause and can be added as a string to a find as followed.
// Note here that including the $complex_subselect_string as an un-keyed term in the conditions automatically ANDs this extra query clause
$complete_user_list = $this->find( 'all', array( 'conditions' => array( $complex_subselect_string ), 'limit' => $limit, 'contain' => array() ) );
Basically, is it possible, and if so, how do you do the following code without using the raw query() method in CakePHP 2.0.6. I am using PostgreSQL (which generate_series() is a function of). So, how do you do this query the CakePHP way?
$sql = "
SELECT new_rank FROM generate_series(1,999) AS new_rank
LEFT JOIN tasks ON tasks.rank = new_rank
WHERE tasks.rank IS NULL LIMIT 1
";
$data = $this->Task->query($sql);
EDIT
One user said I could try to use the find call on Task and right join to generate_series(). Here is my attempt at that. This code throws an error, in that CakePHP is putting double quotes around the function arguments for generate_series. I wonder how I can get it to not do that?
$data = $this->Task->find('all', array(
'conditions' => array('Task.rank' => null),
'limit' => 1,
'recursive' => -1,
'fields' => 'new_rank',
'joins' => array(
array(
'table'=>'generate_series(1,999)',
'alias'=>'new_rank',
'type'=>'RIGHT',
'conditions' => array(
'tasks.rank'=>'new_rank'
)
)
)
));
Which products the following SQL:
SELECT "Task"."new_rank" AS "Task__new_rank"
FROM "tasks" AS "Task"
RIGHT JOIN generate_series("1,999") AS "new_rank" ON ("tasks"."rank" = 'new_rank')
WHERE "Task"."rank" IS NULL LIMIT 1
What you're probably looking for is in DboSource::expression(). It basically allows you to do a SQL expression without any of Cake's escaping. Make sure to sanitize inputs.
To have it show up as a field, you can try adding it to the fields key in your options array:
$ds = $this->Task->getDataSource();
$this->Task->find('all', array(
'fields' => $ds->expression('generate_series(1,999) AS new_rank')
));
If that doesn't work, you can always try DboSource::rawQuery() which doesn't escape anything, afaik.
Tables
restaurants
cuisines
cuisines_restaurants
Both restaurant and cuisine model are set up to HABTM each other.
I'm trying to get a paginated list of restaurants where Cuisine.name = 'italian' (example), but keep getting this error:
1054: Unknown column 'Cuisine.name' in 'where clause'
Actual query it's building:
SELECT `Restaurant`.`id`, `Restaurant`.`type` .....
`Restaurant`.`modified`, `Restaurant`.`user_id`, `User`.`display_name`,
`User`.`username`, `User`.`id`, `City`.`id`,`City`.`lat` .....
FROM `restaurants` AS `Restaurant` LEFT JOIN `users` AS `User` ON
(`Restaurant`.`user_id` = `User`.`id`) LEFT JOIN `cities` AS `City` ON
(`Restaurant`.`city_id` = `City`.`id`) WHERE `Cuisine`.`name` = 'italian'
LIMIT 10
The "....." parts are just additional fields I removed to shorten the query to show you.
I'm no CakePHP pro, so hopefully there's some glaring error. I'm calling the paginate like this:
$this->paginate = array(
'conditions' => $opts,
'limit' => 10,
);
$data = $this->paginate('Restaurant');
$this->set('data', $data);
$opts is an array of options, one of which is 'Cuisine.name' => 'italian'
I also tried setting $this->Restaurant->recursive = 2; but that didn't seem to do anything (and I assume I shouldn't have to do that?)
Any help or direction is greatly appreciated.
EDIT
models/cuisine.php
var $hasAndBelongsToMany = array('Restaurant');
models/restaurant.php
var $hasAndBelongsToMany = array(
'Cuisine' => array(
'order' => 'Cuisine.name ASC'
),
'Feature' => array(
'order' => 'Feature.name ASC'
),
'Event' => array(
'order' => 'Event.start_date ASC'
)
);
As explained in this blogpost by me you have to put the condition of the related model in the contain option of your pagination array.
So something like this should work
# in your restaurant_controller.php
var $paginate = array(
'contain' => array(
'Cuisine' => array(
'conditions' => array('Cuisine.name' => 'italian')
)
),
'limit' => 10
);
# then, in your method (ie. index.php)
$this->set('restaurants', $this->paginate('Restaurant'));
This fails because Cake is actually using 2 different queries to generate your result set. As you've noticed, the first query doesn't even contain a reference to Cuisine.
As #vindia explained here, using the Containable behavior will usually fix this problem, but it doesn't work with Paginate.
Basically, you need a way to force Cake to look at Cuisine during the first query. This is not the way the framework usually does things, so it does, unfortunately, require constructing the join manually
. paginate takes the same options as Model->find('all'). Here, we need to use the joins option.
var $joins = array(
array(
'table' => '(SELECT cuisines.id, cuisines.name, cuisines_restaurants.restaurant_id
FROM cuisines_restaurants
JOIN cuisines ON cuisines_restaurants.cuisines_id = cuisines.id)',
'alias' => 'Cuisine',
'conditions' => array(
'Cuisine.restaurant_id = Restaurant.id',
'Cuisine.name = "italian"'
)
)
);
$this->paginate = array(
'conditions' => $opts,
'limit' => 10,
'joins' => $joins
);
This solution is a lot clunkier than the others, but has the advantage of working.
a few ideas on the top of my mind:
have you checked the model to see if the HABTM is well declared?
try using the containable behavior
in none of those work.. then you could always construct the joins for the paginator manually
good luck!
Cuisine must be a table (or alias) on the FROM clausule of your SELECT.
so the error:
1054: Unknown column 'Cuisine.name' in 'where clause'
Is just because it isn't referenced on the FROM clausule
If you remove the Feature and Event part of your HABTM link in the Restaurant model, does it work then?
Sounds to me like you've failed to define the right primary and foreing keys for the Cuisine model, as the HABTM model is not even including the Cuisine tabel in the query you posted here.