Using limit and offset in subquery in Zend framework 2 - mysql

I have two tables: users and usermeta. The table usermetahas 4 columns: id, user_id, meta_key and meta_value. A user can have many meta data.
I want to get the groups of the first 10 users. So I create a SQL query in Zend framework 2:
$coreSelect = $sql->select()
->from('users')
->limit(10)
->offset(0);
$select = $sql->select()
->from(array('u' => $coreSelect))
->join('usermeta',
'u.user_id = usermeta.user_id',
array(
'meta_key' => 'meta_key',
'meta_value' => 'meta_value',
),
Select::JOIN_LEFT
);
When execute this query, it show up a SQL syntax error. The SQL query string manipulated by Zend framework is like this:
SELECT u.*
FROM (
SELECT * FROM users LIMIT '10' OFFSET '0') AS u
...
It means that Zend framework has added quotes into offset and limit value and it makes the syntax error.
Can anyone please help me to resolve this problem?

This should help you:
http://www.maltblue.com/tutorial/zend-db-sql-creating-joins-and-unions-with-ease
Something that interests you, towards the end, but read all of it is useful.

Related

Zend 2 subquery columns

I want to create a SQL(MySQL) query in Zend Framework 2 like:
SELECT a.id,
a.name,
a.age,
(SELECT MAX(score)
FROM scores AS s
WHERE s.user_id = a.id) AS max_score,
(SELECT SUM(time)
FROM games_played_time AS gpt
WHERE gpt.user_id = a.id) AS time_played
FROM users AS a
ORDER BY last_visited DESC
LIMIT 0, 100
Mind that this is an artificial example of existing query.
I tried creating sub-queries and then creating main select query where when I use:
$select->columns(
array(
'id',
'name',
'age',
'max_score' => new Expression('?', array($sub1),
'time_played' => new Expression('?', array($sub2)
)
I also tried using:
$subquery = new \Zend\Db\Sql\Expression("({$sub->getSqlString()})")
And even lambda functions like suggested here: http://circlical.com/blog/2014/1/27/zend-framework-2-subqueries-subselect-and-table-gateway
Still no luck because all the time I keep getting errors like:
No data supplied for parameters in prepared statement
And when I succeed in making the query work, it ends up that column contains the text of sub-queries. It starts to look that it is not possible to make multiple expressions in columns method. Any ideas?
SOLVED:
I rewrote query by query as #Tim Klever proposed. Everythin worked except one query. It turns out there is some kind of issue when using limit in subquery and in main query. In my case one of the subqueries returns multiple rows, so I ussed limit(1) to force return of a single value. But using that turned out to produce error:
No data supplied for parameters in prepared statement
I changed the query to use MAX instead of limit and now it works. Later will try to debug why this is happening.. Thank you!
The following worked for me to produce the query you listed
$maxScoreSelect = new Select();
$maxScoreSelect->from(array('s' => 'scores'));
$maxScoreSelect->columns(array(new Expression('MAX(score)')));
$maxScoreSelect->where->addPredicates('s.user_id = a.id');
$sumTimeSelect = new Select();
$sumTimeSelect->from(array('gpt' => 'games_played_time'));
$sumTimeSelect->columns(array(new Expression('SUM(time)')));
$sumTimeSelect->where->addPredicates('gpt.user_id = a.id');
$select = new Select();
$select->from(array('a' => 'users'));
$select->columns(array(
'id',
'name',
'age',
'max_score' => new Expression('?', array($maxScoreSelect)),
'time_played' => new Expression('?', array($sumTimeSelect))
));
$select->order('last_visited DESC');
$select->limit(100);
$select->offset(0);

Selecting records from DB with Zend using distinct but two fields

I need to select two fields from a table (with Zend 1.12) -- id and sender name -- from a database, but I want to select only the unique sender names as many of them are duplicated. Here is my code:
$objSelect = $db->select()
->distinct()
->from('tbl_sc_invites', array('id', 'sender_name'), 'sender_name')
->order('sender_name ASC')
;
But I get the error Mysqli prepare error: Table 'sender_name.tbl_sc_invites' doesn't exist
How do I do that? I need to have both id and sender_name returned from the database so I can create a dropdown.
Something like this?
$objSelect = $db->select()
->distinct()
->from('tbl_sc_invites', array('id', 'sender_name'))
->order('sender_name ASC');
Otherwise if you don't care about which id you get back you could use GROUP BY
$objSelect = $db->select()
->distinct()
->from('tbl_sc_invites', array('id', 'sender_name'))
->order('sender_name ASC')
->group('sender_name');

CakePHP 2.2.4: Why does this Union $this->Model->query not work?

I'm in the view action of my PhotosController.php. What I want to do is given the id of the current photo I am viewing, create a carousel of photos containing the two photos before and two photos after the current photo with the current photo in the middle (5 in total).
I was pointed to this solution but I can't seem to convert it to CakePHP using $this->Photo->query.
My controller
$this->set('photos', $this->Photo->query("
SELECT id, file FROM photos WHERE id <= $id AND page_id = $page_id ORDER BY id DESC LIMIT 3
UNION ALL
SELECT id, file FROM photos WHERE id > $id AND page_id = $page_id ORDER BY id ASC LIMIT 2
"));
Unfortunately, when I don't see anything when I turn debugging on. id, file, and page_id are all columns in the photos table. Both #id and $page_id are passed to the action from the router. Is my syntax wrong?
EDIT: If I remove the UNION ALL and the second SELECT statement, then the query works fine so it's not an issue with the model not being loaded because it is.
EDIT (workaround): For now I'm doing two queries which is not ideal.
$this->set('photos_before', $this->Photo->find('all', array(
'conditions' => array(
'Photo.page_id' => $page_id,
'Photo.id <' => $id
),
'order' => array('Photo.id ASC'),
'limit' => 2
)));
$this->set('photos_after', $this->Photo->find('all', array(
'conditions' => array(
'Photo.page_id' => $page_id,
'Photo.id >' => $id
),
'order' => array('Photo.id ASC'),
'limit' => 2
)));
I have a contain before hand to only return the fields and associated models I need.
Below is what I want to be displayed and it currently works using the two queries above but I am hoping this can be achieved with a single, Cake-friendly query
My guess is that your original query is invalid SQL. Afaik UNIONS cannot contain multiple 'order by' clauses. As a workaround you may consider to rewrite it to use subqueries like this:
SELECT * FROM (SELECT id, file FROM photos WHERE id <= $id AND page_id = $page_id ORDER BY id DESC LIMIT 3) AS suba
UNION ALL
SELECT * FROM (SELECT id, file FROM photos WHERE id > $id AND page_id = $page_id ORDER BY id ASC LIMIT 2) AS subb
Although I serious think a query like this is far from optimal. Of course, I don't know the way your application works, but it seems that a standard pagination query, with a OFFSET/LIMIT is a more logical approach.
Please take my comment below your question into account; using model->query does NOT automatically handle sanitisation/escaping to prevent SQL injections!
You have to load model as
$this->loadModel('Photo');
Before executing query.
You should create a VIEW in MySQL and then use that as a model, and do a traditional CakePHP find on that.
Read up on creating views in MySQL and then create a model based on that view name.

Zend framework order before group

Any one have a example how to order before group with leftjoin in zend paginator adapter ?
new Zend_Paginator_Adapter_DbSelect($this->db->select()
->from(array( 'a' => $this->prefix.'contest' ), array('id'))
->joinLeft(array( 'b' => $this->prefix.'logo_to_contest'),'a.id=b.contest_id', array('img'))
->group('a.id')
->order('a.end','a.date_start DESC','b.id RAND()')
)
From mysql manuel
In general, clauses used must be given in exactly the order shown in
the syntax description. For example, a HAVING clause must come after
any GROUP BY clause and before any ORDER BY clause. The exception is
that the INTO clause can appear either as shown in the syntax
description or immediately following the select_expr list.
and in the syntax description group comes before order so it has nothing to do with zend
it's mysql that requires that you put group before order.
However to get around this issue and group after ordering you can select with a subquery with order then group on a new select like :
$subselect = $db->select()
->from(array( 'a' => $this->prefix.'contest' ), array('id'))
->joinLeft(array( 'b' => $this->prefix.'logo_to_contest'),'a.id=b.contest_id', array())
->order('a.end','a.date_start DESC','b.id RAND()');
$select = $db->select()->from(a(array( 'a' => $this->prefix.'contest' ), array('id'))
->joinLeft(array( 'b' => $this->prefix.'logo_to_contest'),'a.id=b.contest_id', array('img'))
->where("a.id in ($subselect)")
->group('a.id');

CakePHP: How do I count the number of hasMany records in a find?

I have two models, Post hasMany Comment. How do I select all Post that have less than two Comment?
I tried using a find with 'fields'=>array('COUNT(Comment.id) as numComments','Post.*'), (and then doing a numComments < 2 in 'conditions'). But, I get a Unknown column 'Comment.id' in 'field list' error.
Thanks!
EDIT: I've gotten CakePHP to generate this query:
SELECT `Post`.*, FROM `posts` AS `Post`
LEFT JOIN comments AS `Comment` ON (`Post`.`id` = `Comment`.`text_request_id`)
WHERE COUNT(`Comment`.`id`) < 2
GROUP BY `Comment`.`post_id`
LIMIT 10
But I get an error #1111 - Invalid use of group function on the COUNT function.
EDIT:
Resolved, use the HAVING COUNT instead of WHERE COUNT.
class Post extends AppModel
{
var $name = "Post";
var $hasMany = array('Comment'=>array('counterCache'=>true));
}
add comment_count fields into posts
an that's all :-)
In raw SQL, the query would look something like this:
SELECT Post.*
FROM Post LEFT JOIN Comment ON Post.id = Comment.post_id
GROUP BY Comment.post_id
HAVING COUNT(Comment.id) < 2
Most of these are easily translated to Cake:
array(
'having' => array('COUNT(Comment.id) <' => 2),
'group' => array('Comment.post_id')
)
Cake does not automatically join hasMany tables though, this is something you'll need to do manually. Have a look at the documentation for the details.
Edit:
You can do a having clause as follows:
array(
'group' => 'Comment.post_id HAVING COUNT(Comment.id) < 2'
)
The limitations are string only for the group and cant be done without the group by. Cake 3 will probably include more SQL syntax such as HAVING
If anyone interested:
here's the doc about counterCache http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#belongsto
and here's more about adding multiple counterCache fields http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#multiple-countercache