Let's say I have 10 books in a database. When I sort by the Name, I want the first book to have a 'rank' of 1. And so on and so forth, within the query.
I cannot use the auto incremented id.
[1] A
[2] B
[3] C
Etc.
My scenario is coins and using Yii2.
$coins = Coins::find()
->select(['id', 'name', 'icon', 'volume_24_hours', 'market_cap', 'price', 'change_24_hours', 'circulating_supply', 'abbreviation'])
->where(['is_fiat' => false])
->orWhere(['is_fiat' => null])
->orderBy(['volume_24_hours' => 'DESC']);
In Oracle, Postgre and MSSQL you can use some form of row_number function. In MySql you can emulate it with the help of variables.
Hope this works for you.. (:
SELECT ROW_NUMBER()OVER(ORDER BY BookName ASC) AS RowNo,
BookName
FROM TableName
Related
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);
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.
So I have a model FeaturedListing that has a field date which is a mysql date field. There will be multiple FeaturedListings that have the same date value.
I want to find all dates that have N or more FeaturedListings on it. I think I'd have to group by date and then find which ones have N or more in there group and get the value that was grouped on. Could any one give me any pointers to accomplish that. A raw sql query may be required.
Edit: Thanks to the answers below it got me going on the right track and I finally have a solution I like. This has some extra conditions specific to my application but I think its pretty clear. This snippet finds all dates after today that have at least N featured listings on them.
$dates = $this->find('list', array(
'fields' => array('FeaturedListing.date'),
'conditions' => array('FeaturedListing.date >' => date('Y-m-d') ),
'group' => array('FeaturedListing.date HAVING COUNT(*) >= $N')
)
);
I then make a call to array_values() to remove the index from the returned list and flatten it to an array of date strings.
$dates = array_values($dates);
No need to go to raw SQL, you can achieve this easily in cake ($n is the variable that holds N):
$featuredListings = $this->FeaturedListing->find('all', array(
'fields' => array('FeaturedListing.date'),
'group' => array('FeaturedListing.date HAVING COUNT(*)' => $n),
));
In "raw" SQL you would use group by and having:
select `date`
from FeaturedListings fl
group by `date`
having count(*) >= N;
If you want the listings on these dates, you need to join this back to the original data. Here is one method:
select fl.*
from FeaturedListings fl join
(select `date`
from FeaturedListings fl
group by `date`
having count(*) >= N
) fld
on fl.`date` = fld.`date`
How could I create a sub-query in cakePHP with find method? For example:
SELECT *, (SELECT COUNT(*) FROM table2 WHERE table2.field1 = table1.id) AS count
FROM table1
WHERE table1.field1 = 'value'
!!! table2.field1 = table1.id !!!
Just for addition, you can build subquery using Cake's ORM. It will be more easy. Please read CakePHP retrieving data doc
In general you can use buildStatement() method of DataSource object. You can save all logic, including pagination etc.
You can do this one of two ways:
1. Use $this->Model->query(...)
This allows you execute SQL directly using the query you posted above but be aware that it can make your application quite brittle if the database schema were to change. Probably the fastest.(Documentation)
Example
$this->Model1->query("SELECT * FROM model;");
2. Separate the Calls
This is probably not as fast as option 1 but it does give you the ability to break your query down into a number of steps. You're also unlikely to get SQL injection which is potential risk with option 1.
Example
$model1s = $this->Model1->find
(
'all',
array
(
'conditions' => array('Model1.field1' => 'value')
)
);
foreach($model1s as $model1)
{
$model1['model2_count'] = $this->Model2->find
(
'count',
array
(
'conditions' => array
(
'Model2.field1' => $model1['id']
)
)
);
}
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.