Checking if selected rows are favourite by user in cakephp - php

I have a Venue table with 20+ columns.
I also have a favorite_venues table with columns
id | userId | venueId
Here is my code to get venues
$this->Venue->virtualFields = array(
'bookedCount' => "SELECT count(*) FROM bookings WHERE venueId = Venue.id"
);
$result = $this->Venue->find('all', array('conditions'=>$conditions,
'order' => array('Venue.bookedCount DESC'),
'limit' => 20,
'offset' => $offset * 20
));
i want to add a condition if i send userId, it should check every venue if its added to favourite list or not and set
$venue['isFavorite'] = yes/no
I dont want a for loop and check every venue i get. is there any way i can incorporate in same Mysql query in cakephp. I am not sure how to put yes/no as virtual field

You can LEFT join in your favorite_venues table, and then for example use a CASE statement in a virtual field that checks whether there is a linked row.
Here's an untested example to illustrate what I mean. I don't know how the user ID is involved, so you got to figure that on your own.
$this->Venue->virtualFields = array(
'bookedCount' => "SELECT count(*) FROM bookings WHERE venueId = Venue.id",
'isFavorite' => 'CASE WHEN FavoriteVenues.id IS NOT NULL THEN "yes" ELSE "no" END'
);
$result = $this->Venue->find('all', array(
'conditions' => $conditions,
'order' => array('Venue.bookedCount DESC'),
'limit' => 20,
'offset' => $offset * 20,
'joins' => array(
array(
'table' => 'favorite_venues',
'alias' => 'FavoriteVenues',
'type' => 'LEFT',
'conditions' => array(
'FavoriteVenues.venueId = Venue.id',
)
)
),
// don't forget to group to prevent duplicate results
'group' => 'Venue.id'
));
See also
Cookbook > Models > Virtual Fields > Virtual fields set in controller with JOINS
Cookbook > Models > Associations: Linking Models Together > Joining Tables
MySQL 5.7 Reference Manual / SQL Statement Syntax / ... / CASE Syntax

Related

CakePHP - MySQL Order by CAST to INT not working

With CakePHP 2.x and MySQL 5.6.40 i'm creating the following pagination:
$options = array(
'contain' => array(
'Category',
'ProductImage'
),
'limit' => 10,
'order' => array(
'Product.year' => 'DESC',
'Product.month' => 'DESC'
)
);
But i forgot my products.month column is varchar so i have to do a cast to convert the Product.month to integer:
$options = array(
'contain' => array(
'Category',
'ProductImage'
),
'limit' => 10,
'order' => array(
'Product.year' => 'DESC',
'CAST(Product.month as INT)' => 'DESC'
)
);
But on the Query output, it removes the Order By CAST as INT
SELECT
...
FROM
`web_quality`.`products` AS `Product`
LEFT JOIN `web_quality`.`categories` AS `Category` ON (
`Product`.`category_id` = `Category`.`id`
)
WHERE
`Product`.`category_id` IN (
SELECT
id
FROM
categories
WHERE
tag = 'revistas'
AND company_id IN (
SELECT
id
FROM
companies
WHERE
tag = 'dr'
)
)
AND NOT (
(
(`Product`.`year` = '2022')
AND (`Product`.`month` = '2')
)
)
AND `Product`.`enabled` = '1'
ORDER BY
`Product`.`year` DESC,
LIMIT 10
Also it doesn't generate any kind of error, if i try manually the query with the CAST as INT it works, but i have to do it using the CakePHP Model, what could be wrong ?
Thank you so much !
From what I remember you should use a virtual field for that, as the paginator checks whether the given field exists on the model, and whitelisting an SQL snippet doesn't seem like an overly good idea.
So in your model you'd do something like:
public $virtualFields = array(
'month_number' => 'CAST(Product.month as INT)',
);
and then you order by Product.month_number.
See also
Cookbook > Models > Virtual fields
Cookbook > Core Libraries > Components > Pagination > Control which fields used for ordering

data escaping remove for specific filed in cakephp

I am using subquery for id field.
$db = $this->AccountRequest->getDataSource();
$subQuery = $db->buildStatement(
array(
'fields' => array('MAX(id)'),
'table' => $db->fullTableName($this->AccountRequest),
'alias' => 'MaxRecord',
'limit' => null,
'offset' => null,
'order' => null,
'group' => array("user_id")
),
$this->AccountRequest
);
$searching_parameters = array(
#"AccountRequest.id IN " => "(SELECT MAX( id ) FROM `account_requests` GROUP BY user_id)"
"AccountRequest.id IN " => "(".$subQuery.")"
);
$this->Paginator->settings = array(
#'fields' => array('AccountRequest.*'),
'conditions' => $searching_parameters,
'limit' => $limit,
'page' => $page_number,
#'group' => array("AccountRequest.user_id"),
'order' => array(
'AccountRequest.id' => 'DESC'
)
);
$data = $this->Paginator->paginate('AccountRequest');
This structure is producing a query is:
SELECT
`AccountRequest`.`id`,
`AccountRequest`.`user_id`,
`AccountRequest`.`email`,
`AccountRequest`.`emailchange`,
`AccountRequest`.`email_previously_changed`,
`AccountRequest`.`first_name`,
`AccountRequest`.`first_namechange`,
`AccountRequest`.`f_name_previously_changed`,
`AccountRequest`.`last_name`,
`AccountRequest`.`last_namechange`,
`AccountRequest`.`l_name_previously_changed`,
`AccountRequest`.`reason`,
`AccountRequest`.`status`,
`AccountRequest`.`created`,
`AccountRequest`.`modified`
FROM
`syonserv_meetauto`.`account_requests` AS `AccountRequest`
WHERE
`AccountRequest`.`id` IN '(SELECT MAX(id) FROM `syonserv_meetauto`.`account_requests` AS `MaxRecord` WHERE 1 = 1 GROUP BY user_id)'
ORDER BY
`AccountRequest`.`id` DESC
LIMIT 25
In the subquery, its add an extra single quote so it's producing an error.
So, How can I remove these single quotes from this subquery?
Thanks
What are you trying to achieve with the sub query?
The MAX(id) just means it will pull the id with the largest value AKA the most recent insert. The sub query is completely redundant when you can just ORDER BY id DESC.
using MAX() will return only one record, if this is what you want to achieve you can replicate by adding LIMIT 1
If the sub query is just an example and is meant to be from another table I would just run the query that gets the most recent id before running the main query. Getting the last inserted id in a separate query is very quick and I cant see much of a performance loss. I think it will result in cleaner code that`s easier to follow to.
edit 1: From the comments it sounds like all your trying to get is a particular users latest account_requests.
You dont need the sub query at all. My query below will get the most recent account record for the user id you choose.
$this->Paginator->settings = array(
'fields' => array('AccountRequest.*'),
'conditions' => array(
'AccountRequest.user_id' => $userID // you need to set the $userID
)
'page' => $page_number,
'order' => array(
'AccountRequest.id DESC' //shows most recent first
),
'limit' => 1 // set however many you want the maximum to be
);
The other thing you cold be meaning is to get multiple entries from multiple users and display them in order of user first and then the order of recent to old for that user. MYSQL lets you order by more than one field, in that case try:
$this->Paginator->settings = array(
'conditions' => array(
'AccountRequest.user_id' => $userID // you need to set the $userID
)
'page' => $page_number,
'order' => array(
'AccountRequest.user_id', //order by the users first
'AccountRequest.id DESC' //then order there requests by recent to old
)
);
If the example data you have added into the question is irrelevant and you are only concerned about how to do nested subqueries it has already been answered here
CakePHP nesting two select queries
However I still think based on the data in the question you can avoid using a nested query.

Include single item from related table

I have a table called items and a table called item_pics.
item_pics has an item_id, file_name and a rank field (among others).
What I'm looking for is for each item my index page's $items array to contain the file_name from the item_pics matching the item's item_id with the lowest rank. So I can access like (or something like) this in my Items/index.ctp:
foreach ($items as $item):
$img = $item['Item']['ItemPic']['file_name'];
...
I'm pretty new to CakePHP, this is my first project. I thought that this within the Item model would cause item_pics data to be pulled (although I figured all related item_pics for each item would get pulled rather than just the one with the lowest rank):
public $hasMany = array(
'ItemPic' => array(
'className' => 'ItemPic',
'foreignKey' => 'item_id',
'dependent' => false
)
}
but I can see that no item_pics data is loaded (at the bottom of items/index):
SELECT `Item`.`id`, `Item`.`title`, `Item`.`description`, `Item`.`created`, `Item`.`modified`, `Item`.`type`, `Project`.`id`, `Project`.`item_id`, `Project`.`title`, `Project`.`description`, `Project`.`rank`, `Project`.`created`, `Project`.`modified`
FROM `laurensabc`.`items` AS `Item`
LEFT JOIN `laurensabc`.`projects`
AS `Project`
ON (`Project`.`item_id` = `Item`.`id`)
WHERE `Item`.`type` IN (1, 2)
LIMIT 20
also, while I would like projects to be joined in the view pages, I don't really need them in the index page.
I've done some searching and haven't been able to find exactly what I'm looking for. I suppose I could do a query within the index view item loop, but I'm trying to make sure I do things the right way... the CakePHP way. I assume I need to change something about my model relationships but I haven't had any luck.
CakePHP - Associations - HasMany, this makes it seem like I could order by rank and limit 1. But this didn't work... and even if it did, I wouldn't want that to affect the view pages but rather just the index page.
My Controller looks like this:
public function index($type = null) {
$this->Item->recursive = 0;
$conditions = array();
if ($type == "sale") {
$conditions = array(
"Item.type" => array(self::FOR_SALE, self::FOR_SALE_OR_RENT)
);
} else if ($type == "rent" ) {
$conditions = array(
"Item.type" => array(self::FOR_RENT, self::FOR_SALE_OR_RENT)
);
} else {
$conditions = array("Item.type !=" => self::HIDDEN);
}
$paginated = $this->Paginator->paginate($conditions);
debug($paginated);
$this->set('items', $paginated);
$this->set('title', ($type == null ? "Items for Sale or Rent" : "Items for " . ucwords($type)));
}
I have also tried this on my controller, but it doesn't seem to do anything either:
$this->paginate = array(
'conditions' => $conditions,
'joins' => array(
array(
'alias' => 'ItemPic',
'table' => 'item_pics',
'type' => 'left',
'conditions' => array('ItemPic.item_id' => 'Item.id'),
'order' => array('ItemPic.rank' => 'asc'),
'limit' => 1
)
)
);
$paginated = $this->paginate($this->Item);
First, set containable behavior in AppModel (or if you don't want it on each model, put it on Item model):
public $actsAs = array('Containable');
Then, on your find query:
$items = $this->Item->find('all', array(
'contain' => array(
'ItemPic' => array(
'fields' => array('file_name'),
'order' => 'rank',
'limit' => 1
)
)
));
Then the result array you can access it like:
foreach ($items as $item):
$img = $item['ItemPic']['file_name'];
Edit: Then you should put it on the paginate query:
$this->paginate = array(
'conditions' => $conditions,
'contain' => array(
'ItemPic' => array(
'fields' => array('file_name'),
'order' => 'rank',
'limit' => 1
)
)
);
In this case, I would probably order by rank and limit 1 as you said, and make that a dynamic association just for the index page (See http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#creating-and-destroying-associations-on-the-fly). So use $this->Item->bindModel(array('hasMany' => array('ItemPic' => $options))); (which I believe should replace your current settings for HasMany ItemPic, but you may have to unbindmodel first)
Associations created through bindModel will go through for the next query only, then it'll revert to your normal settings, unless you specifically set an option to keep using the new association.
As for why it's not getting ItemPics with Items, or why trying to order by rank and limit 1 didn't work for you, I can't really say without seeing more of your code.

CakePHP not joining the correct tables in find query

OK, another cake question from me.
I have rather a complex table structure with quite a few joins. Here's the jist of it:
So, in summary:
RiskCategory $hasMany Client [Client $belongsTo RiskCategory]
Client $hasMany Account [Account $belongsTo Client]
Account $hasMany Holding [Holding $belongsTo Account]
In my RiskCategory model I want to run a query that basically returns the sum of the holdings in each riskCategory id, grouped by date. There's a few other conditions but here is what I've put together:
$findParams = array(
'recursive' => 4,
'fields' => array(
'Holding.holding_date',
'SUM(Holding.value) AS risk_category_value'
),
'group' => array('Holding.holding_date'),
'order' => 'Holding.holding_date ASC'
);
$findParams['conditions'] = array(
'Client.active' => true,
'Client.model' => true,
'Client.currency' => 'GBP',
'OR' => array(
'Holding.holding_date' => $this->end_date,
array(
'Holding.holding_date = LAST_DAY(Holding.holding_date)',
'MONTH(Holding.holding_date)' => array(3,6,9,12)
)
)
);
$valuations = $this->Client->Account->Holding->find( 'all', $findParams );
When I run the above cake is giving me an error saying various fields are not present in the query. The raw query created is as follows:
SELECT `Holding`.`holding_date`,
Sum(`Holding`.`value`) AS risk_category_value
FROM `ips_client_db`.`holdings` AS `Holding`
LEFT JOIN `ips_client_db`.`accounts` AS `Account`
ON ( `Holding`.`account_id` = `Account`.`id` )
LEFT JOIN `ips_client_db`.`sedols` AS `Sedol`
ON ( `Holding`.`sedols_id` = `Sedol`.`id` )
WHERE `client`.`active` = '1'
AND `client`.`model` = '1'
AND `client`.`currency` = 'GBP'
AND ( ( `Holding`.`holding_date` = '2013-10-01' )
OR (( ( `Holding`.`holding_date` = Last_day(
`Holding`.`holding_date`) )
AND ( Month(`Holding`.`holding_date`) IN ( 3, 6, 9, 12 ) ) )
) )
GROUP BY `Holding`.`holding_date`
ORDER BY `Holding`.`holding_date` ASC
It looks as though cake is not doing all the joins. It is only joining Account to Holding and then Holding to Sedol (which is another joined table in the database but is not needed for this query so I've omitted it from the diagram)
Why are the joins not being made properly and how to acheive this? I'd like to avoid writing a raw statement if possible.
EDIT: The joins should be as follows:
...
FROM risk_categories
LEFT JOIN ((clients
LEFT JOIN accounts
ON clients.id = accounts.client_id)
LEFT JOIN holdings
ON accounts.id = holdings.account_id)
ON risk_categories.id = clients.risk_category_id
...
CakePHP does not perform deep joins for associations that are more than 1 level deep. That is why you're getting an error for references to the Client table in the conditions.
For these types of problems it's easier to use the Containable behavior. I usually add this behavior to my AppModel as the default handler for associations.
Containable allows you to define the associations (only those already defined) with their fields and conditions as part of the find operation. This is done by adding a new contain key in the find parameters. Below I've taking a guess at what you might need.
$findParams = array(
'fields' => array(
'Holding.holding_date',
'SUM(Holding.value) AS risk_category_value'
),
'conditions'=>array(
'OR' => array(
'Holding.holding_date' => $this->end_date,
array(
'Holding.holding_date = LAST_DAY(Holding.holding_date)',
'MONTH(Holding.holding_date)' => array(3,6,9,12)
)
)
),
'group' => array('Holding.holding_date'),
'order' => 'Holding.holding_date ASC',
'contain'=>array(
'Account'=>array(
'Client'=>array(
'RiskCategory',
'conditions'=>array(
'Client.active' => true,
'Client.model' => true,
'Client.currency' => 'GBP',
)
)
)
)
);
$valuations = $this->Client->Account->Holding->find( 'all', $findParams );

Cakephp joining tables via find option not working

I'm trying to join a Users table to my curent hasMany Through table which has Interest and User model id's.
Below is the find query with options:
$params = array(
'fields' => array('*', 'COUNT(DISTINCT(InterestsUser.interest_id)) as interest_count'),
'limit' => 15,
'recursive' => -1,
'offset' => $offset,
'conditions' => array('InterestsUser.interest_id' => $conditions),
'group' => array('InterestsUser.user_id'),
'order' => array('interest_count DESC', 'InterestsUser.user_id ASC', 'InterestsUser.interest_id ASC'),
'joins' => array(
array('table' => 'users',
'alias' => 'User',
'type' => 'LEFT',
'conditions' => array(
'User.id' => 'InterestsUser.user_id',
)
)
)
);
$results = $this->InterestsUser->find('all', $params);
This returns InterestsUser table fine but does not return any values for Users table. It only returns field names.
What could be wrong?
UPDATE:
OK, above is generating below SQL which I got from Cake's datasources sql log:
SELECT *, COUNT(DISTINCT(InterestsUser.interest_id)) as interest_count
FROM `interests_users` AS `InterestsUser`
LEFT JOIN users AS `User` ON (`User`.`id` = 'InterestsUser.user_id')
WHERE `InterestsUser`.`interest_id` IN (3, 2, 1)
GROUP BY `InterestsUser`.`user_id`
ORDER BY `interest_count` DESC, `InterestsUser`.`user_id` ASC, `InterestsUser`.`interest_id` ASC
LIMIT 15
Why is users table values returning NULL only for all fields?
UPDATE:
OK I tried below but this is working fine...What am I missing here!!??
SELECT * , COUNT( DISTINCT (
interests_users.interest_id
) ) AS interest_count
FROM interests_users
LEFT JOIN users ON ( users.id = interests_users.user_id )
WHERE interests_users.interest_id
IN ( 1, 2, 3 )
GROUP BY interests_users.user_id
ORDER BY interest_count DESC
LIMIT 15
The array syntax for join conditions should be like the following
array('User.id = InterestsUser.user_id')
as opposed to array('User.id' => 'InterestsUser.user_id'). For more, see http://book.cakephp.org/view/1047/Joining-tables.

Categories