cakephp: find ignores DISTINCT - cakephp

i got the following cakephp find situation:
$data = $this->find('all', array(
'conditions' => array(
'Roster.league_id' => $league_id,
'Roster.season' => $season,
),
'fields' => array(
'DISTINCT Roster.player_id',
'Roster.league_id',
'Roster.fflteam_id',
'Roster.season',
'Roster.modified',
'Fflteam.name',
'Player.firstName',
'Player.lastName',
'Player.position'
),
'order' => array(
'Roster.player_id',
'Roster.modified DESC'),
'contain' => array(
'Fflteam',
'Player' => array(
'Stat' => array(
'conditions' => array(
'Stat.season' => $season),
'Scores'),
'Teamplayer')
)
));
There are more Roster-records with specific player_ids, thats why i try to use DISTINCT. I only need the most recent. Thats why i order the results by player_id and Roster.modified. But die DISTINCT command gets ignored.
e.g:
records:
id=1 player_id=1 modified=2012
id=2 player_id=1 modified=2013
id=3 player_id=1 modified=2014
id=4 player_id=2 modified=2014
id=5 player_id=2 modified=2013
result should be:
id=3 player_id=1 modified=2014
id=4 player_id=2 modified=2014
I don't see any syntax errors. Maybe there some commands are not possible together or my way of filtering is wrong. would be great if someone can help me.

DISTINCT does not give you one distinct field if there are multiple fields , if you have Distinct title it will provides distinct title .
If you have distinct id , title it will output distinct combination of rows with same id and title.
This is how DISTINCT works.
Try GroupBy
Check this page to understand what to use when http://www.mysqltutorial.org/mysql-distinct.aspx

As AgRizzo suggested i am using a query now.
Its split into 2 Parts. The first gets all entries with the one2one relations:
$this->query("SELECT *
FROM (SELECT * FROM `rosters` AS `Roster1` WHERE " . $conditions . " ORDER BY `Roster1`.`modified` DESC) AS `Roster`
LEFT JOIN `fflteams` AS `Fflteam` ON (`Roster`.`fflteam_id` = `Fflteam`.`id`)
LEFT JOIN `players` AS `Player` ON (`Roster`.`player_id` = `Player`.`id`)
GROUP BY `Player`.`id` ORDER BY `Roster`.`player_id` ASC");
The second part gets all one2many relations with its relation:
$this->Stat->find('all', array(
'conditions' => $conditions,
'contain' => array(
'Scores')
));
at the end i merge those 2 arrays

Related

Cakephp find syntax for 'OR' and 'AND' complex join conditions

I have a task planning app and I'm attempting to fetch tasks based on two criteria: either they have a due date within the next 2 weeks, or the task itself is ending within 2 weeks. Here is my non-working code:
$conditions['AND'] = array(
'OR'=>array(
'AND'=>array(
'Task.due_date >'=> $now,
'Task.due_date <'=> $twfn
),
'AND'=>array(
'Task.end_time > '=> $now,
'Task.end_time <' => $twfn
)
)
);
There are additional conditions, hence the outer $conditions['AND']
The SQL log shows:
WHERE ((((`Task`.`end_time` > '2015-06-30') AND (`Task`.`end_time` < '2015-07-14')))
Which implies that the OR isn't being evaluated. If it's relevant, every task has an end_time, but not necessarily a due_date.
Any help or pointers in the right direction would be much appreciated!
use this: the ANDs should be in array, because you are giving the same key(AND) for the array and the last one will be used
$conditions['AND'] = array(
'OR'=>array(
array(
'AND'=> array(
array(
'Task.due_date >'=> $now,
),
array(
'Task.due_date <'=> $twfn
)
),
),
array(
'AND'=>array(
array(
'Task.end_time > '=> $now,
),
array(
'Task.end_time <' => $twfn
)
)
)
)
);
more examples from docs
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#complex-find-conditions

CakePHP find joined records with conditions on each table

I want to see all the records from a join, setting a WHERE condition on each side of the join.
For example, I have LOAN and BORROWER (joined on borrower.id = loan.borrower_id). I want the records where LOAN.field = 123 and BORROWER.field = 'abc'.
The answers here (this one, for example) seem to say that I should use Containable.
I tried that. Here's my code:
$stuff = $this->Borrower->find('all', array(
'conditions' => array(
'Borrower.email LIKE' => $this->request->data['email'] // 'abc'
),
'contain'=>array(
'Loan' => array(
'conditions' => array('Loan.id' => $this->request->data['loanNumber']) // 123
)
)
));
I expected to have a single result because in my data, there is only one joined record with both of those conditions. Instead, I get two results,
Result 1 is {Borrower: {field:abc, LOAN: {field: 123} } // correct
Result 2 is {Borrower: {field:abc, LOAN: {NULL} } // incorrect
When I look at the SQL that CakePHP used, I don't see a join. What I see is two separate queries:
Query 1: SELECT * from BORROWER // (yielding 2 IDs),
Query 2: SELECT * FROM LOAN WHERE borrower_id in (IDs)
This is not what I want. I want to join the tables, then apply my conditions. I could easily write the SQL query, but am trying to do it the Cake way since we've adopted that framework.
Is it possible?
Try to do something like this:
$options['conditions'] = array(
'Borrower.email LIKE' => $this->request->data['email'] // 'abc',
'loan.field' => '123' )
$options['joins'] = array(
array('table' => 'loans',
'alias' => 'loan',
'type' => 'INNER',
'conditions' => array(
'borrower.id = loan.borrower_id')
)
);
$options['fields'] = array('borrower.email', 'loan.field');
$test = $this->Borrower->find('all', $options);
You should see a SQL statement like:
SELECT borrower.email, loan.field
FROM borrowers AS borrower
INNER JOIN loans AS loan
ON borrower.id = loan.borrower_id
AND loan.field = '123'
WHERE borrower.email = 'abc'
Your results will be in an array
{Borrower: {field:abc} LOAN: {field: 123} }
You will find more information in this document.
I think I'll accept Jose's answer because it's exactly what I want. But I did notice that I didn't need any fancy tricks -- no joins or contains -- if I used the other model as my starting point.
A Borrower hasMany Loans, and a Loan belongsTo a Borrower. Using Loan as my model, Cake will automatically join the tables, but not using Borrower.
$this->Loan->find('all', array( // Not $this->Borrower->find() !
'conditions' => array(
'Borrower.field' => 'abc',
'Loan.field' => 123
)
));

CakePHP Search between 2 Date Records

I am building a small Web App that lets users reserve Office Rooms and Equipment. For the Reservation they enter a Start and an End Date.
When a user tries to find out if any (for example) car is available on 2012-10-23, and the database holds reservation date records of Start: 2012-10-20 and End: 2012-10-25 for (lets say) all the cars, how do I include all the dates between my date entries in the search?
The $date variable gets it's value from the Date Search Form Field.
This, unfortunately does not work, and I can't figure out how to use daysAsSql for this query:
$conditions = array(
'conditions' => array(
'? BETWEEN ? AND ?' => array($date,'Equipment.date_start','Equipment.date_end'),
)));
$this->set('equipments', $this->Equipment->find('all', $conditions));
There is a simpler solution to this, but thanks for the help:
(As a condition in the find:)
array('Equipment.date_start <= ' => $date,
'Equipment.date_end >= ' => $date
),
In case if you have one date and you want to use BETWEEN
$date_start = start date;
$date_end = date_end;
$conditions = array(
'conditions' => array(
'date(Equipment.create) BETWEEN ? AND ?' => array($date_start, $date_end),
)));
$this->set('equipments', $this->Equipment->find('all', $conditions));
This is the case if you have From AND To
in your case
$date_start = start date;
$date_end = date_end;
$conditions = array(
'conditions' => array(
'Equipment.start_date >=' => array($date_start),
'Equipment.end_date <=' => array($date_end)
));
$this->set('equipments', $this->Equipment->find('all', $conditions));
Here is CakePHP BETWEEN query example.
I'm defining my arrays as variables, and then using those variables in my CakePHP find function call:
// just return these two fields
$fields = array('uri', 'page_views');
// use this "between" range
$conditions = array('Event.date BETWEEN ? and ?' => array($start_date, $end_date));
// run the "select between" query
$results = $this->Event->find('all',
array('fields'=>$fields,
'conditions'=>$conditions));
Ref from
$start = date('Y-m-d');
$end = date('Y-m-d', strtotime('+1 month'));
$conditions = array('Event.start <=' => $end, 'Event.end >=' => $start);
$this->Event->find('all', array('conditions' => $conditions));
If you are on cakephp 2.0 this is the answer http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#TimeHelper::daysAsSql
If you are on cakephp 1.3 you can use TimeHelper, just import it and use the same function as per example in documentation here http://book.cakephp.org/1.3/en/view/1471/Formatting
You cannot use database columns with this BETWEEN-syntax. If you assign strings in the array, CakePHP will quote them. Same for numeric values depending on your database setup.
CakePHP will quote the numeric values depending on the field type in your DB.
– see http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#complex-find-conditions
If you still want to use the BETWEEN, you can write your queries into the array key because CakePHP will not escape the keys, but only the values.
CakePHP only escapes the array values. You should never put user data into the keys. Doing so will make you vulnerable to SQL injections.
– see http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#complex-find-conditions
Concerning your problem:
$this->Model->find('all', array(
'conditions' => array(
'"YYYY-MM-DD" BETWEEN Model.date_start AND Model.date_end',
),
));
You can even work with MySQL date and time functions:
$this->Model->find('all', array(
'conditions' => array(
'CURDATE() BETWEEN Model.date_start AND Model.date_end',
),
));
If you use date/time functions, do not quote them. If you use a specific date put it in quotes.

convert query() to find() cakephp

$v = array(1,11.38,15.8);
$sortByPrice = $this->Product->query
(
"SELECT *,
CASE `currency`
WHEN '1' THEN $v[0]
WHEN '2' THEN $v[1]
WHEN '3' THEN $v[2]
END AS 'ratio'
FROM products
ORDER BY price*ratio DESC
"
);
i want to convert what is above to a find function
i tried something like that(but it do not work)..
$v = array(1,11.38,15.8);
$bla = $this->Product->find('all', array(
'conditions' => array(
'Product.currency',
'((
CASE WHEN
Product.currency=1 THEN $v[0]
Product.currency=2 THEN $v[1]
Product.currency=3 THEN $v[2]
END
)) AS ratio'),
'order' => 'ratio',
'limit' => 10
));
can somebody to convert query into find
You are putting into the conditions key. Move it into the fields key or make it a virtualField - that may work.
Edit: also $v[0] in single quotes will not actually replace it with the variable - it will just appear as that text.

Paginate results filtered by condition on associated model (HABTM) using Containable

I need to paginate list of Products belonging to specific Category (HABTM association).
In my Product model I have
var $actsAs = array('Containable');
var $hasAndBelongsToMany = array(
'Category' => array(
'joinTable' => 'products_categories'
)
);
And in ProductsController
$this->paginate = array(
'limit' => 20,
'order' => array('Product.name' => 'ASC'),
'contain' => array(
'Category' => array(
'conditions' => array(
'Category.id' => 3
)
)
)
);
$this->set('products', $this->paginate());
However, resulting SQL looks like this:
SELECT COUNT(*) AS `count`
FROM `products` AS `Product`
WHERE 1 = 1;
SELECT `Product`.`*`
FROM `products` AS `Product`
WHERE 1 = 1
ORDER BY `Product`.`name` ASC
LIMIT 20;
SELECT `Category`.`*`, `ProductsCategory`.`category_id`, `ProductsCategory`.`product_id`
FROM `categories` AS `Category`
JOIN `products_categories` AS `ProductsCategory` ON (`ProductsCategory`.`product_id` IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) AND `ProductsCategory`.`category_id` = `Category`.`id`)
WHERE `Category`.`id` = 3
(I.e. it selects 20 Products and then queries their Categories)
while I'd need
SELECT COUNT(*) AS `count`
FROM `products` AS `Product`
JOIN `products_categories` AS `ProductsCategory` ON `ProductsCategory`.`product_id` = `Product`.`id`
JOIN `categories` AS `Category` ON `Category`.`id` = `ProductsCategory`.`category_id`
WHERE `Category`.`id` = 3;
SELECT `Product`.*, `Category`.*
FROM `products` AS `Product`
JOIN `products_categories` AS `ProductsCategory` ON `ProductsCategory`.`product_id` = `Product`.`id`
JOIN `categories` AS `Category` ON `Category`.`id` = `ProductsCategory`.`category_id`
WHERE `Category`.`id` = 3
ORDER BY `Product`.`name` ASC
LIMIT 20;
(I.e. select top 20 Products which belong to Category with id = 3)
Note:
Possible solution without Containable would be (as Dave suggested) using joins.
This post offers a very handy helper to build $this->paginate['joins'] to paginate over HABTM association.
Note: Still looking for more elegant solution using Containable than fake hasOne binding.
Finally I found a way to do what I want, so posting it as an answer:
To force JOIN (and be able to filter via condition on associated model) in Containable - you've got to use fake hasOne association.
In my case, code in ProductsController should be:
$this->Product->bindModel(array('hasOne' => array('ProductsCategory')), false);
$this->paginate = array(
'limit' => 20,
'order' => array('Product.name' => 'ASC'),
'conditions' => array(
'ProductsCategory.category_id' => $category
),
'contain' => 'ProductsCategory'
);
$this->set('products', $this->paginate());
Note false as a second argument to bindModel - which makes binding persistent. This is needed because paginate() issues find('count') before find('all'), which would reset temporary binding. So you might want to manually unbindModel afterwards.
Also, if your condition includes multiple IDs in HABTM associated model, you might want to add 'group' => 'Product.id' into your $this->paginate[] (as Aziz has shown in his answer) to eliminate duplicate entries (will work on MySQL only).
UPDATE:
However, this approach has one serious drawback compared to joins approach (suggested by Dave): condition can apply only to intermediate model's foreign key (category_id in my case); if you want to use condition on any other field in associated model - you'd probably have to add another bindModel('hasOne'), binding intermediate model to HABTM associated model.
When you put the condition in the nested Contain, you're asking it to retrieve only the Categories with that ID. So - it's doing what you're asking, but that's not what you want.
Though it seems like it should be possible, the only luck I've had doing what you're trying to do (after MANY hours and a few stackoverflow questions) is via Joins instead of Contain.
http://book.cakephp.org/view/1047/Joining-tables
It's not the exact same problem, but you can go through some of my code where I query against HABTM conditions (I answered my question at the bottom) here: Select All Events with Event->Schedule->Date between start and end dates in CakePHP

Resources