I have a problem, i am noob with cakePHP and i use cakePHP 1.3 and i have a query with a lot subquerys and try to convert this to queryBuilder of cake using find sentences or things like this:
My query is the next-one:
SELECT
`PbFeedback`.`id`,
`PbFeedback`.`createdby`,
`PbFeedback`.`created`,
`PbFeedback`.`msg`,
`Usuario`.`name`,
`PbFeedback`.`tipo`,
COUNT(IF(PbFeedback.msg = 0, 1, 0))AS totalMsg,
`Entidad`.`nombre`
,UltimoMensaje.* , #Subconsulta
MensajeNuevo.* #Subconsulta
FROM
# A A
`eon_feedback` AS `PbFeedback`
LEFT JOIN
# B B
`eon_sys_usuarios` AS `Usuario` ON(`PbFeedback`.`createdby` = `Usuario`.`id`)
LEFT JOIN `eon_entidades` AS `Entidad` ON(
`PbFeedback`.`entidad_id` = `Entidad`.`id`
)
LEFT JOIN (SELECT
`F`.`createdby`,
`F`.`created`,
`F`.`msg`,
`F`.`tipo`
FROM eon_feedback AS `F`
GROUP BY `F`.`createdby`
ORDER BY F.created DESC
) AS UltimoMensaje ON (UltimoMensaje.createdby = `PbFeedback`.`createdby`)
LEFT JOIN (
SELECT
F.createdby AS usuario_id,
COUNT(*) AS `count`
FROM
`eon_feedback` AS `F`
WHERE `F`.`status` = 0
GROUP BY F.createdby
) AS MensajeNuevo ON MensajeNuevo.usuario_id = PbFeedback.createdby
WHERE
`PbFeedback`.`usuario_id` = 0
GROUP BY
`PbFeedback`.`createdby`
ORDER BY
`PbFeedback`.`createdby` ASC
LIMIT 0,
30 ;
Thanks ;)
I would suggest you look into the ContainableBehavior:
http://book.cakephp.org/1.3/en/The-Manual/Core-Behaviors/Containable.html
Containable allows you to easily build complex queries, for example:
$this->PbFeedback->find('all', array(
'contain' => array (
'Usuario',
'UltimoMensaje ',
'eon_feedback' => array (
'fields' => array ('created','msg','tipo'),
'conditions' => array ('eon_feedback.status =' => '0')
)
),
'limit' => 30
);
Related
how to join this in single query any help to combine these two queries as one without looping,
$today_date = mktime(0, 0, 0, $mon, $day-1, $year);
SELECT * FROM (`lead_follow_up`) LEFT JOIN `leads` ON `leads`.`id` = `lead_follow_up`.`lead_id` WHERE `date` <= $today_date GROUP BY `lead_follow_up`.`lead_id` ORDER BY `lead_follow_up`.`date` DESC
from the above query i get array $previou
$previou= Array
(
[0] => stdClass Object
(
[id] => 1
[lead_id] => 75943
[date] => 1438930800
[updated_on] => 1438884890
)
[1] => stdClass Object
(
[id] => 2
[lead_id] => 75943
[date] => 1416459600
[updated_on] => 1415901523
),
[2] => stdClass Object
(
[id] => 3
[lead_id] => 75943
[date] => 1416459600
[updated_on] => 1415901523
),....etc
);
foreach($previou as $key => $p):
$q = "SELECT `id` FROM (`lead_follow_up`) WHERE `lead_id` = '".$p->id."' AND `date` > '".$p->date."' ORDER BY `updated_on` DESC ";
if(!$this->db->query($q)){
$previouData[$key] = $p;
$pCount++;
}
endforeach;
how to join this in single query any help to combine these two queries as one without looping,
Your queries don't make much sense. For a start your first query has a GROUP BY lead_follow_up.lead_id but no aggregate functions. So in MySQL that will return one row for each value of lead_id (which row it returns is not defined).
Yet your array of sample data has multiple rows per lead_id so cannot have come from the query.
You are also LEFT OUTER JOINing the leads table, yet it doesn't seem to make sense to have a lead_follow_up which doesn't relate to a lead. As such you may as well use an INNER JOIN.
I am going to assume that what you want is a list of leads / lead_follow_ups and for each one a couple of all the follow ups after that particular follow up. That would give you something like this (making loads of assumptions as I do not know your table structure):-
SELECT leads.id AS lead_id,
lead_follow_up.id
lead_follow_up.`date`,
lead_follow_up.updated_on,
COUNT(lead_follow_up_future.id) AS future_lead_count
FROM leads
INNER JOIN lead_follow_up ON leads.id = lead_follow_up.lead_id
LEFT OUTER JOIN lead_follow_up AS lead_follow_up_future ON leads.id = lead_follow_up.lead_id AND lead_follow_up_future.`date` > lead_follow_up.`date`
WHERE lead_follow_up.`date` <= $today_date
GROUP BY leads.id AS lead_id,
lead_follow_up.id
lead_follow_up.`date`,
lead_follow_up.updated_on
ORDER BY lead_follow_up.date DESC
the tables are:
units (id,...) // approx' 10,000 units
contracts(id, unit_id, active, ...) // approx 50,000 records
I want to get all the units, that have no contract attached to them (and contracts.active=true).
My ideas are:
Using NOT IN:
select * from units
where id NOT IN(select unit_id from contracts where contracts.active = true)
Or:
select * from units u
left join contracts c
on c.unit_id = u.id
where c.unit_id is null
and, if there is a native way to do it in cake, please show me the light :)
thanks
Depending on what your other joins are, the NOT IN could give you bad performance. I would suggest the following SQL query:
SELECT * FROM units AS u
LEFT JOIN contracts AS c
ON (c.unit_id = u.id AND c.active = 1)
WHERE c.id IS NULL
According to the cakephp documentation:
Cake can also check for null fields. In this example, the query will return records where the post title is not null:
array ("NOT" => array (
"Post.title" => null
)
)
So depending on how your models are setup, this may work for you:
$joins = array(('table' => 'contracts',
'alias' => 'Contracts',
'type' => 'LEFT',
'conditions' => array('Contracts.active' => 0)));
$conditions = array('Contracts.id' => NULL);
$units = $this->Units->find('all', array('joins' => $joins, 'conditions' => $conditions));
I have a SQL query that I'm trying to convert into a CakePHP find, but I'm not sure how to structure it... could someone please provide a little assistance?
SELECT texts.*, people.id, people.first_name, people.last_name FROM texts
NATURAL JOIN (
SELECT person_id, MAX(id) AS id
FROM texts
WHERE texts.status = 'received'
GROUP BY person_id
) t RIGHT JOIN people ON people.id = t.person_id
WHERE texts.body is not null
AND texts.created > '$since'
AND person.counselor_id = '2'
ORDER BY texts.created DESC
Here is what I have
$texts = $this->find('all', array(
'recursive' => -1,
'joins' => array(
array(
'table' => 'texts',
'alias' => 't',
'type' => 'NATURAL',
'conditions' => array('t.status' => 'received')
),
array(
'table' => 'people',
'alias' => 'Person',
'type' => 'RIGHT',
'conditions' => 'people.id = t.person_id'
)
),
'conditions' => array('AND' => array('Text.body IS NOT NULL', 'Text.created > 0000-00-00 00:00:00')),
'order' => 'Text.created DESC'
));
This is the SQL it writes
SELECT Text.id, Text.person_id, Text.sid, Text.to, Text.from, Text.body, Text.status,
Text.direction, Text.owner, Text.counselor_read, Text.created, Text.modified
FROM admissionsedge_penfield.texts AS Text
NATURAL JOIN admissionsedge_penfield.texts
AS t ON (t.status = 'received')
RIGHT JOIN admissionsedge_penfield.people AS Person ON (people.id = t.person_id)
WHERE ((Text.body IS NOT NULL) AND (Text.created > 0000-00-00 00:00:00))
ORDER BY Text.created DESC
Thank you!
Do you have any models defined? If so, you need to share them, along with which one you would expect to perform the find because the right join is not generally used AND a Natural Join is not used.
Assuming you do not...
In the controller, run the query above with this code. {The $recordset assignment line would be where a find method would generally go}
$some_sql = 'your sql statement';
$db = ConnectionManager::getDataSource('default');
$recordset = $db->rawQuery($some_sql);
set('recordset', $recordset);
If you want to leverage CakePHP's MVC, then I suggest this query be rewritten as left joins and inner joins
I'm pretty useless at SQL it seems and I'm trying to figure out what is the correct query to use.
I have a table Items and a table Reservations. An item can have many reservations and reservations are made between two dates.
I'm trying to create a search query which will return all the Items which don't have reservations between two user inputted dates.
the SQL I have at the minute looks like:
SELECT `Item`.`id`, `Item`.`user_id`, `Item`.`name`, `Item`.`description`, `User`.`id`, `User`.`username`, `User`.`password`, `User`.`email`
FROM `database`.`items` AS `Item`
LEFT JOIN `database`.`reservations` AS `ReservationJoin` ON (`ReservationJoin`.`item_id` = `Item`.`id` AND `ReservationJoin`.`start` >= '2013-07-17' and `ReservationJoin`.`finnish` <= '2013-07-20')
LEFT JOIN `database`.`users` AS `User` ON (`Item`.`user_id` = `User`.`id`)
WHERE ((`Item`.`name` LIKE '%projector%') OR (`Item`.`description` LIKE '%projector%')
AND `ReservationJoin`.`id` IS NULL
LIMIT 10
I'm doing this in cakephp 2.3 so the code looks like:
$this->paginate = array(
"conditions" => array(
"or" => array(
"Item.name LIKE" => '%'.$projector.'%',
"Item.description LIKE" => '%'.$projector.'%',
),
"ReservationJoin.id" => null,
),
"joins" => array(
array(
"table" => "reservations",
"alias" => "ReservationJoin",
"type" => "LEFT",
"conditions" => array(
"ReservationJoin.item_id = Item.id",
"ReservationJoin.checkin >= '{$start}' and ReservationJoin.checkout <= '{$finnish}'",
)
)
),
"limit"=>10
);
$data = $this->paginate('Item');
This isn't working and I think it's to do with the join not excluding the reservations properly. But I've not been able to figure out what the correct mysql is. Can a kind soul tell me what I should be using?
thanks
If something has a reservation between two dates, then one of the following is true:
It has a start date between the dates
It has an end date between the dates
It has a start date before the earlier date and an end date after the second one
The following query uses this logic in a having clause. The approach is to aggregate at the item level and ensure that the three above conditions are true:
SELECT i.`id`, i.`user_id`, i.`name`, i.`description`
FROM `database`.`items`i LEFT JOIN
`database`.`reservations` r
ON r.`item_id` = i.`id`
WHERE ((i.`name` LIKE '%projector%') OR (i.`description` LIKE '%projector%')
group by i.id
having max(start between '2013-07-17' and '2013-07-20') = 0 and
max(finish between '2013-07-17' and '2013-07-20') = 0 and
max(start < '2013-07-17' and finished > '2013-07-20') = 0
LIMIT 10;
Note that none matches are returned, because the conditions are treated as false when start and ned are NULL.
I think it may be easier for you in CakePHP to put all of the conditions in a WHERE clause. (This could also be done with some OUTER JOINs but it may be difficult to transcribe into CakePHP)
SELECT id, user_id, name, description
FROM items
WHERE ((name LIKE '%projector%') OR (description LIKE '%projector%'))
AND NOT EXISTS(
SELECT *
FROM reservations
WHERE items.id = reservations.item_id
AND (
'2013-07-17' BETWEEN start and finnish
OR
'2013-07-20' BETWEEN start and finnish
OR
start BETWEEN '2013-07-17' AND '2013-07-20'));
Or, using the same logic the WHERE clause can be cleaned up to be
SELECT id, user_id, name, description
FROM items
WHERE ((name LIKE '%projector%') OR (description LIKE '%projector%'))
AND NOT EXISTS(
SELECT *
FROM reservations
WHERE items.id = reservations.item_id
AND NOT(
'2013-07-17' > finnish
OR
'2013-07-20' < start
));
you can try this.
SELECT `Item`.`id`, `Item`.`user_id`, `Item`.`name`, `Item`.`description`, `User`.`id`, `User`.`username`, `User`.`password`, `User`.`email`
FROM `database`.`items` AS `Item`
LEFT JOIN `database`.`reservations` AS `ReservationJoin` ON (`ReservationJoin`.`item_id` = `Item`.`id`)
LEFT JOIN `database`.`users` AS `User` ON (`Item`.`user_id` = `User`.`id`)
WHERE ((`Item`.`name` LIKE '%projector%') OR (`Item`.`description` LIKE '%projector%'))
AND `ReservationJoin`.`start` >= '2013-07-17'
AND `ReservationJoin`.`finnish` <= '2013-07-20'
AND `ReservationJoin`.`id` IS NULL
LIMIT 10
i.e just move the date clauses into the WHERE clause as oppose to being in the JOIN CLAUSE
It's late and I have written this monstrosity of a query to get related products based on a product I have already found.
I need to fetch the products in the same category (HABTM), the parent product, products with the same parent (siblings/neighbours), and products that are direct children of the current product (there is only one level of nesting). I have the product ID and its parent_id of the current product. If it could be possible to put conditions on the product as for Product.published = 1 that would be great, but if it's going to make the query so big I can always check that after. Additionally, I need to exclude the current product.
SELECT `products`.*
FROM `products`, `categories_products`
WHERE
(
(
`categories_products`.`product_id` = `products`.`id`
AND `categories_products`.`category_id` IN (
SELECT `category_id`
FROM `categories_products`
WHERE `categories_products`.`product_id` = '$product_id'
)
)
OR `products`.`parent_id` = '$parent_id'
OR `products`.`parent_id` = '$product_id'
OR `products`.`id` = '$parent_id'
)
AND `product`.`id` <> '$product_id'
GROUP BY `products`.`id`
It might even be possible to optimize it a bit more, so far I have:
public function related($productData, $limit = 4) {
$conditions = array(
'OR' => array(array('Product.parent_id' => $productData['Product']['id'])), // Children of product),
'Product.id <>' => $productData['Product']['id']
);
if(!empty($product['parent_id'])) {
$conditions['OR'][] = array('Product.parent_id' => $productData['Product']['parent_id']); // Siblings
$conditions['OR'][] = array('Product.id' => $productData['Product']['parent_id']); // Parent of product
}
return $this->find('all', array(
'conditions' => $conditions,
'contain' => array('Category'),
'group' => 'Product.id',
'limit' => $limit
));
}
You will need to use cake's Complex Find Conditions syntax (scroll down to Sub-queries secrion).
To be honest I didn't think its the best approach to get "related" products from mysql, thats what search engines are for. especially your similar category approach is would doom to fail when dealing with big data.
After saying this much this is a a re-write of your current sql which would hopefully have you gain some performance.
SELECT * FROM products p
WHERE
p.published = 1 AND
p.id != $product_id AND
(
p.id IN
(
SELECT DISTINCT(cp2.product_id) FROM categories_product cp1
LEFT JOIN categories_product cp2 ON cp1.category_id = cp2.category_id
WHERE cp1.product_id = $product_id
UNION SELECT $parent_id
)
OR p.parent_id IN($parent_id, $product_id)
)
;
I tried to get rid of unnecessary group by statement. Hope this helps.
P.S : There can be syntax errors since I wrote this in text editor.
I would prevent the need of the recursive attempt to SELECT IN on the category. Pre build that based on the one product in question and get all its distinct categories. From that, get distinct products that match the category. Now, you have a prequery of "CommonByCategory" that will ALREADY be a single instance of IDs.
Next, do a hard join to products again "OriginalProduct" based on the SPECIFIC ID you are trying to qualify against. Since it will always exist and never change, we can use this as the pointer for the siblings to compare against, and also for a parent ID match (in case not null -- via the IFNULL() tests applied
Since each product will only be scanned ONCE and not return multiple entries due to the multiple category possibilities, no "GROUP BY" is required.
SELECT STRAIGHT_JOIN
p.*
from
products p
left join
( SELECT DISTINCT
cp2.product_id
from
( SELECT cp.Category_ID
from categories_products cp
where cp.product_id = '$product_id' ) JustCats
join categories_products cp2
ON JustCats.Category_ID = cp2.Category_ID ) as CommonByCategory
ON p.ID = CommonByCategory.product_ID
join products OriginalProduct
ON OriginalProduct.ID = '$product_id'
where
p.id <> '$product_id'
and ( IFNULL( CommonByCategory.Product_ID, -1) > 0
OR p.id = IFNULL( OriginalProduct.Parent_ID, -1 )
OR p.parent_id = OriginalProduct.id
This issue came round again, and I figured it out 100%! Here was my finished code:
// Model
public function related($product, $limit = 9) {
// Children of product
$conditions = array(
'OR' => array(array('Product.parent_id' => $product['Product']['id'])), // Children of product),
'Product.id <>' => $product['Product']['id'],
'Product.published' => 1
);
// Siblings and parent of product if applicable
if (!empty($product['Product']['parent_id'])) {
$conditions['OR'][] = array('Product.parent_id' => $product['Product']['parent_id']);
$conditions['OR'][] = array('Product.id' => $product['Product']['parent_id']);
}
// Products in the same categories
// Get category IDs in an array
$categoryIds = Set::extract($product['Category'], '{n}.id');
$conditionsSubQuery['category_id IN(?)'] = implode(',', $categoryIds);
$db = $this->getDataSource();
$subQuery = $db->buildStatement(
array(
'fields' => array('product_id'),
'table' => 'categories_products',
'joins' => array(),
'alias' => 'c_p',
'conditions' => $conditionsSubQuery,
'order' => null,
'group' => null,
'limit' => null
), $this->CategoryProduct
);
$subQuery = 'Product.id IN (' . $subQuery . ') ';
$subQueryExpression = $db->expression($subQuery);
$conditions['OR'][] = $subQueryExpression;
return $this->find('all', array(
'conditions' => $conditions,
'contain' => array('Category'),
'group' => 'Product.id',
'limit' => $limit
));