i used this query:
$brands = TblBrand::find(array("id In (Select p.brand_id From EShop\\Models\\TblProduct as p Where p.id In (Select cp.product_id From EShop\\Models\\TblProductCategory as cp Where cp.group_id_1='$id'))", "order" => "title_fa asc"));
if($brands != null and count($brands) > 0)
{
foreach($brands as $brand)
{
$brandInProductCategory[$id][] = array
(
"id" => $brand->getId(),
"title_fa" => $brand->getTitleFa(),
"title_en" => $brand->getTitleEn()
);
}
}
TblBrand => 110 records
TblProduct => 2000 records
TblProductCategory => 2500 records
when i used this code, my site donot show and loading page very long time ...
but when i remove this code, my site show.
how to solve this problem?
The issue is your query. You are using the IN statement in a nested format, and that is always going to be slower than anything else. MySQL will need to first evaluate what is in the IN statement, return that and then do it all over again for the next level of records.
Try simplifying your query. Something like this:
SELECT *
FROM Brands
INNER JOIN Products ON Brand.id = Products.brand_id
INNER JOIN ProductCategory ON ProductCategory.product_id = Products.id
WHERE ProductCategory.group_id_1 = $id
To achieve the above, you can either use the Query Builder and get the results that way
https://docs.phalconphp.com/en/latest/api/Phalcon_Mvc_Model_Query_Builder.html
or if you have set up relationships in your models between brands, products and product categories, you can use that.
https://docs.phalconphp.com/en/latest/reference/model-relationships.html
example:
$Brands = Brands::query()
->innerJoin("Products", "Products.brand_id = Brand.id")
->innerJoin("ProductCategory", "ProductCategory.product_id = Products.id")
->where("ProductCategory.group_id_1 = :group_id:")
->bind(["group_id" => $id])
->cache(["key" => __METHOD__.$id] // if defined modelCache as DI service
->execute();
$brandInProductCategory[$id] = [];
foreach($Brands AS $Brand) {
array_push($brandInProductCategory[$id], [
"id" => $Brand->getId(),
"title_fa" => $Brand->getTitleFa(),
"title_en" => $Brand->getTitleEn()
]);
}
I am getting stuck on using SQL functions queries made in CakePHP 3 in combinations with associations.
The situation is as follows: I have three tables, a 'products' table, an 'orders' table and a join table called 'orders_products'.
In the index of OrdersController I would like to add the total price (= sum of relevant product prices) to the table of orders. In SQL this exactly can be done with the following query:
SELECT orders.id, SUM(products.price)
FROM orders
LEFT JOIN orders_products
ON orders.id = orders_products.order_id
LEFT JOIN products
ON orders_products.product_id = products.id
GROUP BY orders.id;
I figured to following controller code should do the trick:
$orders = $this->Orders->find('all')->contain(['Products']);
$orders
->select(['total_price' => $orders->func()->sum('Products.price')])
->group('Orders.id');
However, when the query object is executed, I get an error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column
'Products.price' in 'field list'
...Even though the association between orders and products is defined.
Calling only $orders = $this->Orders->find('all')->contain(['Products'])->all(); does return an array of orders with each order a number of products, the model has to be set up correctly. Any ideas what might be wrong? Thanks in advance!
From OrdersTable:
$this->belongsToMany('Products', [
'foreignKey' => 'order_id',
'targetForeignKey' => 'product_id',
'joinTable' => 'orders_products'
]);
And from ProductsTable:
$this->belongsToMany('Orders', [
'foreignKey' => 'product_id',
'targetForeignKey' => 'order_id',
'joinTable' => 'orders_products'
]);
One way to do it:
$orders = $this->Orders->find()
->select([
'order_id' =>'orders.id',
'price_sum' => 'SUM(products.price)'
])
->leftJoin('orders_products', 'orders.id = orders_products.order_id'),
->leftJoin('products', 'orders_products.product_id = products.id')
->group('orders.id');
I have two tables - parties, bills. In parties table the fiels are parties_partyname,parties_district. And in the bills table it has field bills_partyname, billamount,billdate.
I want to see the query as
SELECT parties_district,parties_partyname, COALESCE(sum(bills.billamount),0) as sale FROM `parties` left join bills on parties.parties_partyname = bills.bills_partyname group by parties.parties_partyname
My Search Model looks like -
$query = Parties::find()
->select('parties.parties_district, parties.parties_partyname, bills.billamount as sale')
->from('parties')
->leftJoin('bills',['bills.bills_partyname' => 'parties.parties_partyname'])
->groupBy('parties.parties_partyname');
Parties index.php
'parties_partyname',
'parties_district',
[
'attribute' => 'sale',
'value' => 'sale'
],
I've added public $sale in the parties model
If you want use the activeQuery like you shown you should use
$query = Parties::find()
->select('parties.parties_district as parties_district,
parties.parties_partyname as parties_partyname,
COALESCE(sum(bills.billamount),0) as sale ')
->from('parties')
->leftJoin('bills',['bills.bills_partyname' => 'parties.parties_partyname'])
->groupBy('parties.parties_partyname');
otherwise you can use a simple (activeRecord) findBySql() (this give you the query for dataProvider)
$sql = 'SELECT
parties.parties_district as parties_district,
parties.parties_partyname as parties_partyname,
COALESCE(sum(bills.billamount),0) as sale
FROM `parties`
left join bills on parties.parties_partyname = bills.bills_partyname
group by parties.parties_partyname'
$query = Parties::findBySql($sql);
if you use instead
$models = Parties::findBySql($sql)>all();
you get all the models
for grid view if you value is the same of the attribute value use
'parties_partyname',
'parties_district',
'sale',
without value =>
I have the following criteria for a findAll statement
$with=array(
'tumor'=>array(
'select'=>false,
'joinType'=>'INNER JOIN',
),
'tumorLibraryType'=>array(
'select'=>false,
'joinType'=>'INNER JOIN',
'condition'=>'tumorLibraryType.id = 1 OR tumorLibraryType.id = 6',
),
'tumorPatient'=>array(
'select'=>false,
'joinType'=>'INNER JOIN',
)
);
$libPairs=LibraryPairs::model()->with($with)->findAll();
These are the relevant model relations:
'tumor' => array(self::BELONGS_TO, 'Libraries', array('tumor_library'=>'id')),
'normal' => array(self::BELONGS_TO, 'Libraries', array('normal_library'=>'id')),
// making separate AR routes for tumor and normal. only tumor used currently
'tumorLibraryType'=>array(self::HAS_ONE,'LibraryTypes','','on'=>'tumor.library_type_id = tumorLibraryType.id'),
'tumorLibrariesIsolates'=>array(self::HAS_MANY,'LibrariesIsolates',array('id'=>'library_id'),'through'=>'tumor'),
'tumorSamplesIsolates'=>array(self::HAS_MANY,'SamplesIsolates',array('isolate_id'=>'isolate_id'),'through'=>'tumorLibrariesIsolates'),
'tumorSamples'=>array(self::HAS_MANY,'Samples',array('sample_id'=>'id'),'through'=>'tumorSamplesIsolates'),
'tumorPatient'=>array(self::HAS_ONE,'Patients',array('patient_id'=>'id'),'through'=>'tumorSamples'),
This code generates the following sql:
SELECT `t`.`tumor_library` AS `t0_c0`, `t`.`normal_library` AS `t0_c1`, `t`.`created` AS `t0_c2`, `t`.`created_by` AS `t0_c3`, `t`.`last_modified` AS `t0_c4`, `t`.`last_modified_by` AS `t0_c5`, `tumor`.`library_type_id` AS `t1_c2`, `tumor`.`id` AS `t1_c0`
FROM `library_tumor_normal_pairs` `t`
INNER JOIN `library_types` `tumorLibraryType` ON (tumor.library_type_id = tumorLibraryType.id)
INNER JOIN `libraries` `tumor` ON (`t`.`tumor_library`=`tumor`.`id`)
LEFT OUTER JOIN `libraries_isolates` `tumorLibrariesIsolates` ON (`tumor`.`id`=`tumorLibrariesIsolates`.`library_id`)
LEFT OUTER JOIN `samples_isolates` `tumorSamplesIsolates` ON (`tumorLibrariesIsolates`.`isolate_id`=`tumorSamplesIsolates`.`isolate_id`)
LEFT OUTER JOIN `samples` `tumorSamples` ON (`tumorSamplesIsolates`.`sample_id`=`tumorSamples`.`id`)
INNER JOIN `patients` `tumorPatient` ON (`tumorSamples`.`patient_id`=`tumorPatient`.`id`)
WHERE (tumorLibraryType.id = 1 OR tumorLibraryType.id = 6)
But that sql throws an error:
"Column not found: 1054 Unknown column 'tumor.library_type_id' in 'on clause'. "
However if I simply move the tumor line in the sql query up to be the first join statement, and run the query manually, then the query works.
SELECT `t`.`tumor_library` AS `t0_c0`, `t`.`normal_library` AS `t0_c1`, `t`.`created` AS `t0_c2`, `t`.`created_by` AS `t0_c3`, `t`.`last_modified` AS `t0_c4`, `t`.`last_modified_by` AS `t0_c5`, `tumor`.`library_type_id` AS `t1_c2`, `tumor`.`id` AS `t1_c0`
FROM `library_tumor_normal_pairs` `t`
INNER JOIN `libraries` `tumor` ON (`t`.`tumor_library`=`tumor`.`id`)
INNER JOIN `library_types` `tumorLibraryType` ON (tumor.library_type_id = tumorLibraryType.id)
LEFT OUTER JOIN `libraries_isolates` `tumorLibrariesIsolates` ON (`tumor`.`id`=`tumorLibrariesIsolates`.`library_id`)
LEFT OUTER JOIN `samples_isolates` `tumorSamplesIsolates` ON (`tumorLibrariesIsolates`.`isolate_id`=`tumorSamplesIsolates`.`isolate_id`)
LEFT OUTER JOIN `samples` `tumorSamples` ON (`tumorSamplesIsolates`.`sample_id`=`tumorSamples`.`id`)
INNER JOIN `patients` `tumorPatient` ON (`tumorSamples`.`patient_id`=`tumorPatient`.`id`)
WHERE (tumorLibraryType.id = 1 OR tumorLibraryType.id = 6)
So my question is, how can I control the sql join order of "with" criteria in Yii? Is it possible? As you can see my 'with' array and relations have 'tumor' before the others, but the join order is not preserved.
I encountered a similar problem: Yii generates joins in such order that makes SQL statement invalid. This situation occurs, for example, when you try to write custom $CDBCriteria->join which relies on tables specified in relations passed by $CDBCriteria->with. This happens because join is processed in CJoinQuery::__constructor whereas all "standard" joins (from with) are generated by Yii in CJoinQuery::join, that is after the constructor.
Unfortunately there is no solution other than a patch. Here is an example of how I did it my copy of Yii (the code is provided "as is" - please, check if it's applicable for your case).
First, we need to add a field into CDbCriteria, which will switch on/off a new option.
CDbCriteria.php
class CDbCriteria extends CComponent
{
...
/**
* #var string how to join with other tables. This refers to the JOIN clause in an SQL statement.
* For example, <code>'LEFT JOIN users ON users.id=authorID'</code>.
*/
public $join='';
/**
* Patch begins:
*/
public $joinreorder = false; // new option
...
Second, we need to extend CJoinQuery (please, note, it's in CActiveFinder.php):
CActiveFinder.php
class CJoinQuery
{
...
/**
* #var array list of join element IDs (id=>true)
*/
public $elements=array();
/**
* Patch begins:
*/
private $joinreorder = false; // the same new option
private $postjoins; // the variable to store our custom joins
...
Now we can alter the CJoinQuery constructor:
CActiveFinder.php (continuation)
public function __construct($joinElement,$criteria=null)
{
if($criteria!==null)
{
$this->joinreorder = $criteria->joinreorder;
$this->selects[]=$joinElement->getColumnSelect($criteria->select);
$this->joins[]=$joinElement->getTableNameWithAlias();
if($this->joinreorder) //
{ //
$this->postjoins=$criteria->join; // new lines
} //
else //
{ //
$this->joins[]=$criteria->join;
} //
$this->conditions[]=$criteria->condition;
$this->orders[]=$criteria->order;
If joinreorder is true we store custom joins in our new member variable postjoins. Otherwise, all should work as before.
And now the actual fix in CJoinQuery::createCommand:
CActiveFinder.php (continuation)
public function createCommand($builder)
{
$sql=($this->distinct ? 'SELECT DISTINCT ':'SELECT ') . implode(', ',$this->selects);
$sql.=' FROM ' . implode(' ',$this->joins);
if($this->joinreorder) //
{ //
$sql .= $this->postjoins; // new lines
} //
...
Finally we add the custom joins into SQL statement, and they are appended (not prepended as in standard implementation) to other joins generated from Yii's relations.
Now we can use it like so:
$criteria = new CDbCriteria;
$criteria->joinreorder = true;
$criteria->with = array('product', 'shop');
$criteria->join = 'LEFT OUTER JOIN `shop2address` `s2a` ON (`shop`.`id` = `s2a`.`shop_id`)';
Without joinreorder = true this generates the error stating that shop.id is unknown column in ON clause, bacause the 'shop' table is not yet added into SQL-statement. With joinreorder = true it works as expected.
As for the cases when only with is used, and incorrect sequence of joins is generated, one should apply more complicated patch. It involves CJoinQuery::join method. It should, possibly, have an optional parameter $priority, which can be again populated via corresponding member added into CDbCriteria. Then in CJoinQuery::join change these lines:
$this->joins[$element->priority]=$element->getJoinCondition();
$this->joins[$element->priority]=$element->relation->join;
This allows for re-ordering joins in arbitrary manner according to specified priorities.
This line doesn't look correct:
'tumorLibraryType'=>array(self::HAS_ONE,'LibraryTypes','','on'=>'tumor.library_type_id = tumorLibraryType.id'),
Maybe it should be
'tumorLibraryType'=>array(self::HAS_ONE,'LibraryTypes',array('id'=>'library_type_id'),'through'=>'tumor'),
guys, I believe I'm late to the party
I had similar problem
I've criteria with merges:
$criteria = new CDbCriteria();
$criteria->with = [
'codebaseName' => [
'alias' => 'cn'
],
'codebaseProducer' => [
'alias' => 'cp'
],
'registrationDocumentLast' => [
'alias' =>'rdl'
]
];
It ended up in such order by statement:
ORDER BY COALESCE(cn.name_our,cn.name_supplier), id DESC LIMIT 50
I didn't specify ordering by id DESC explicitly!
After playing for around, I discovered that it came from relation registrationDocumentLast , which was defined as
'registrationDocumentLast' => [
self::HAS_ONE, RegistrationDocument::class, 'codebase_product_pharm_id',
'joinType' => 'LEFT JOIN',
'order' => 'id DESC'
],
Look at order key. Changing it from
'order' => 'id DESC'
to
'order' => 't.id DESC'
solved the problem
If I have a company entity, with a one to many association to user entities, how can I get a list of company entities, sorted by the number of users?
Here is my solution but it is maybe not the best way:
SELECT
c, COUNT(u.id) as num_users
FROM
Acme\Model\Company c
LEFT JOIN c.users u
GROUP BY
c.id
ORDER BY
num_users DESC
After hydrating the results, you will get an array like:
array(
array(
0 => /* entity instance */,
'num_users' => 123
),
array(
0 => /* entity instance */,
'num_users' => 111
),
// ...
)
So you have to filter the results:
return array_map(
function ($result) { return $result[0]; },
$results
);
And that's it!
Maybe you can avoid the filtering part tweaking the hydration part.