So I have been working with SugarCRM pretty extensively lately and I need to be able to display the last date a contact was emailed as well search based on how long ago a contact was emailed.
I can easily create a logic hook to get the last email date but I cannot search based on the value of that.
So I looked in SearchFields.php and found the following code which obviously gets the email of the contact.
'email' =>
array (
'query_type' => 'default',
'operator' => 'subquery',
'subquery' => 'SELECT eabr.bean_id FROM email_addr_bean_rel eabr JOIN email_addresses ea ON (ea.id = eabr.email_address_id) WHERE eabr.deleted=0 AND ea.email_address LIKE',
'db_field' =>
array (
0 => 'id',
),
),
Using that data I decided to create a new array with the following code to get the last email date. Obviously it will need to be sorted or something to get the actual last email date if the contact was emailed multiple times but you get the point.
'last_email_c' =>
array (
'query_type' => 'default',
'operator' => 'subquery',
'subquery' => 'SELECT e.date_sent FROM emails e WHERE e.parent_type = \'Contacts\' AND e.parent_id = \'{0}\'',
'db_field' =>
array (
0 => 'id',
),
),
My issue is, I don't know what to do with this new subquery, I checked in the listviewdefs and the searchdefs and cannot find any reference to either of these custom SearchFields.
What am I missing?
Try this one. Basically when you use subquery you have to assume that the parameter from the form will be added to the end of the subquery so if you search for blah#blah.com sugar will append to your subquery 'blah#blah.com%' so LIKE operator is the only way to do this
t_email_c' => array (
'query_type' => 'default',
'operator' => 'subquery',
'subquery' => 'SELECT e.date_sent FROM emails e WHERE e.parent_type = \'Contacts\' AND e.parent_id LIKE ',
'db_field' =>
array (
0 => 'id',
),
Also you should be selecting the id of the parent record so that it could then be matched against the parent record.
Basically in your subquery you should be selecting a contact ID where the last email date is like.
If you can't write query this way you will need to do it by overwriting list.view actions.
Related
I have a table with a field called 'year'. It has many repetitions in the column so I want to find the distinct group. I have 4 different 'years' in about 20 rows and I dont get these values from the query. Instead what is returned are 4 numbers which are not the years (5,14,4,70). The same code worked fine when I used this with suburb field in another table where there were multiple values of this field. I dont get why this isnt working.
//in view
echo $this->Form->input('year', ['label' => 'Year','options' => $allyears]);
//controller
$allyears = $this->TimesheetDates->find('list')
->select(['TimesheetDates.id', 'TimesheetDates.year'])
->group(['TimesheetDates.year'])->autoFields(true)
->order(['TimesheetDates.year'=> 'ASC'])
->hydrate(false);
$this->set('allyears',$allyears);
//another controller and this code worked fine
$suburb = $this->Students->find('list')->where(['Students.address_suburb !=' => '','Students.student_inactive' => 0])
->select(['Students.id','Students.address_suburb'])
->group(['Students.address_suburb'])->autoFields(true)
->order(['Students.address_suburb' => 'ASC'])
->hydrate(false);
take a look at the documentation about how find('list') works
$allyears = $this->TimesheetDates->find('list', [
'keyField' => 'id',
'valueField' => 'year']
)
->group(['year'])
->order(['year'=> 'ASC']);
Note that it has no meaning selecting the id of the TimesheetDates Table as you are grouping by year and the id is choosen randomly between all the records that share the same year
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.
i.e : i have 2 tables
Product ( id, name )
Photo ( id, name, photo_id )
And I need to get result in array like this:
array(
'id' => 1,
'name' => product,
'photos' => array(
array('id' => 1, 'name' => 'photo1')
array('id' => 2, 'name' => 'photo2')
)
}
Is it possible in PHP using clear SQL?
I know that is possible to get 2 arrays and connect it but I have many records and I dont want to wase time to quering.
You have to add a foreign_key in your photo table "product_id".
Then create a method getPhotos() in your Product class with will collect all photos for your product.
Is it possible in PHP using clear SQL?
Not in a single SQL call. With a single call, this is the closest you can get:
array(
'id' => 1,
'name' => product,
'photo_id' => 1,
'photo_name' => 'photo1')
),
array(
'id' => 1,
'name' => product,
'photo_id' => 2,
'photo_name' => 'photo2')
)
Your only choice for the format you want is to run queries separately or to combine them into the data structure you want.
As mentioned, this is not possible with SQL. SQL is based on the relational model which is a 1-Normal-Form data model. That means, the result relation is also flat (no nested relations in a relation).
However, there are good frameworks which generate intermediary models in your corresponding target language (e.g. Python, Java, ...) that circumvent the impression of a flat data model. Check for example Django.
https://docs.djangoproject.com/en/1.8/topics/db/models/
Moo
Here I want to join two table with comma separated ids
For example my data is like:
[Restaurant] => Array
(
[RST_ID] => 171
[RST_NAME] => oneone
[RST_IMAGE] =>
[RST_CAT_ID] => 2,4,6
[RST_CT_ID] => 27
[RST_IS_TOP] => 3
[RST_QR_CODE] =>
[RST_CREATED_DATE] => 1394536725
[RST_MODIFIED_DATE] => 1394536725
[RST_STATUS] => 1
)
[Category] => Array
(
[CAT_ID] => 2
[CAT_NAME] => Vegetarian
[CAT_CREATED_DATE] => 1375175962
[CAT_MODIFIED_DATE] => 1375175962
[CAT_STATUS] => 1
)
My Model Code:
var $belongsTo = array(
'Category' => array(
'className' => 'Category',
'foreignKey' => 'RST_CAT_ID',
'conditions' => array('Category.CAT_ID IN ( Restaurant.RST_CAT_ID)')
)
);
Real Query:
SELECT
`Restaurant`.`RST_ID`, `Restaurant`.`RST_NAME`, `Restaurant`.`RST_IMAGE`,
`Restaurant`.`RST_CAT_ID`, `Restaurant`.`RST_CT_ID`, `Restaurant`.`RST_IS_TOP`,
`Restaurant`.`RST_QR_CODE`, `Restaurant`.`RST_CREATED_DATE`,
`Restaurant`.`RST_MODIFIED_DATE`, `Restaurant`.`RST_STATUS`,
`Category`.`CAT_ID`, `Category`.`CAT_NAME`, `Category`.`CAT_CREATED_DATE`,
`Category`.`CAT_MODIFIED_DATE`, `Category`.`CAT_STATUS`, `City`.`CT_ID`,
`City`.`CT_NAME`, `City`.`CT_CREATED_DATE`, `City`.`CT_MODIFIED_DATE`,
`City`.`CT_STATUS`
FROM `dailybit_dailybites`.`restaurant` AS `Restaurant`
LEFT JOIN `dailybit_dailybites`.`category` AS `Category`
ON (`Restaurant`.`RST_CAT_ID` = `Category`.`CAT_ID`
AND `Category`.`CAT_ID` IN ( `Restaurant`.`RST_CAT_ID`))
LEFT JOIN `dailybit_dailybites`.`city` AS `City`
ON (`Restaurant`.`RST_CT_ID` = `City`.`CT_ID`)
WHERE 1 = 1
So what’s the solution here?
It's giving me just one category data that for first id only.
First have a look at this question: MySQL search in comma list
As you can see the belongsTo query is just generating a join on the single id, CakePHP by default doesn't respect this special case. You will have to alter your query and pass all the ids manually, but your DB design is bad and it doesn't follow the CakePHP conventions at all.
How do you prevent duplicates (which would waste space)
How do you remove a given value (Requires custom function, leading to possibility of errors?
How do you respond to performance issues as the size of my tables increase?
Instead of changing the query you should change this awkward DB design. You want to use HABTM here and a join table: Restaurant hasAndBelongsToMany Categoryy.
restaurants <-> restaurants_categories <-> categories
If you insist on using this bad DB design you'll have to use bindModel() and set the conditions manually:
'conditions' => array('FIND_IN_SET (Category.CAT_ID, ' . $listOfIds. ')')
I haven't tested this, try it yourself, see FIND_IN_SET() vs IN()
You'll have to have another method that gets you all the ids you want here before. Like I said, this is ineffectice and bad design.
You have to set your foreign Key false and find_in_set condition
var $belongsTo = array(
'Category' => array(
'className' => 'Category',
'foreignKey' => false,
'conditions' => array('FIND_IN_SET(Category.CAT_ID,Restaurant.RST_CAT_ID)')
)
);
// you can pass an array at the place of 'Restaurant.RST_CAT_ID'
I've got a series of Post models that hasAndBelongsToMany Media models. In certain function calls inside of the Post model, I don't need to retrieve the entire list of Media models. However, when I use the following code:
$this->unbindModel( array('hasAndBelongsToMany' => array('Media')) );
// Rebind to get only the fields we need:
$this->bindModel(
array('hasAndBelongsToMany' => array(
'Media' => array(
'className' => 'Media',
'joinTable' => 'media_posts',
'foreignKey' => 'post_id',
'associationForeignKey' => 'media_id',
'limit' => 1,
'fields' => array('Media.type', 'Media.path', 'Media.title')
)
)
)
);
$this->find('all', $params);
This limit only works on one of the first retrieved Post model and all following Post models have no associated Media:
Array
(
[0] => Array
(
[Profile] => Array
(
)
[Media] => Array
(
[0] => Array
(
[type] => photo
[path] => ''
[title] => ''
)
)
)
[1] => Array
(
[Profile] => Array
(
)
[Media] => Array
(
)
)
)
Any suggestions would be great. Thanks!
why not use the containable behaviour
// you would probably want the next line in the app_model ot be able to use it with all models
$this->Post->actsAs = array('Containable')
$params['conditions'] = array(
);
$params['contain'] = array(
'Media' => array(
'fields' => array(
'type', 'path', 'title'
),
'limit' => 1
)
);
$this->Post->find('all', $params);
EDIT:
Just tried that and got this sql (Module <-> Tag):
SELECT `Module`.`id` FROM `modules` AS `Module` WHERE 1 = 1
and
SELECT `Tag`.`id`, `ModulesTag`.`module_id`, `ModulesTag`.`tag_id`
FROM `tags` AS `Tag`
JOIN `modules_tags` AS `ModulesTag`
ON (`ModulesTag`.`module_id` IN (1, 2, 3, 4) AND `ModulesTag`.`tag_id` = `Tag`.`id`)
WHERE `Tag`.`belongs_to` = 'Module'
ORDER BY `Tag`.`name` ASC
LIMIT 1
obviously that cannot return the wanted result, as you would have to do a query for each Module result (which then again would result in way too many queries).
As a conclusion I would return all Tags (in my example) as the overhead in too many result rows is better than the overhead of too many queries..
Cake fetches all the Habtm-related records in one batch query and then assembles them into the results array afterwards. Any additional conditions you specify in the association will be used as is in the query, so it'll look something like this:
SELECT … FROM Media WHERE Media.id in (1, 2, 3, …) LIMIT 1
So it'll only retrieve a single HABTM model.
There's no apparently easy solution for this. Maybe you could think about the original premise again and why the "first" (LIMIT 1) record is supposedly the right one, maybe you can find a different condition to query on.
Failing that, you could rebind your models so Media has a hasMany relationship to medias_posts, the pivot table. For hasMany and belongsTo queries, Cake automatically does JOIN queries. You could use a GROUP BY clause then, which would give you the desired result:
SELECT … FROM Media JOIN medias_posts … GROUP BY medias_posts.post_id
You might also want to experiment with passing the 'join' parameter with the query, to achieve that effect without extensive rebinding.
$this->Media->find('all', array('join' => array(…), …));
Try this:
$this->yourModel->hasAndBelongsToMany['Media'] = false; // or null
And then set your HABTM association manually
$this->yourModel->hasAndBelongsToMany['Media'] = array(........);
Or simply modify the association without nulling it:
$this->yourModel->HABTM['Media']['fields'] = array(....)
CakePHP has a very powerful tool for this containable behaviour