Given the following diagram:
With the code below I have the Donations grouped for each organization now I am trying to calculate the total amount a given member has donated to a given organization.
Something like:
With this code it correctly groups that organizations as I need but the problem I have here is that for the 'Amount Donated to Organization' column all values equal the total of the Organization with the highest Id. Therefore all rows in that column are showing $90
Yii Code:
// member view
<?php
$dataProvider=new CActiveDataProvider(Donation::model(), array(
'criteria'=>array(
'with' => array(
'member' => array(
'on'=>'member.MemberId='.$model->MemberId,
'group' => 't.MemberId, t.OrganizationId',
'joinType'=>'INNER JOIN',
),
),
'together'=> true,
),
));
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider'=>$dataProvider,
'columns' => array(
array(
'name'=>'OrganizationId',
'value' => '$data->OrganizationId',
),
array(
'name'=>'Amount',
'value' => '$data->memberOrgBalance;',
),
),
));
?>
// member model
'memberOrgBalance' => array(self::STAT, 'Donation', 'MemberId',
'select'=>'MemberId, OrganizationId, SUM(Amount)',
'group' => 'OrganizationId'),
// donation model
'member' => array(self::BELONGS_TO, 'Member', 'MemberId'),
EDIT: See also response to LDG
Using the advice from LDG I tried adding 'having' to my dataprovider, when that did not seem to affect the query I tried to add it to the relation memberOrgBalance where I am trying to pull the data. This seems to affect the query but it is still not right. I switched to:
'memberOrgBalance' => array(self::STAT, 'Donation', 'MemberId',
'select'=>'MemberId, OrganizationId, SUM(Amount)',
'group' => 'OrganizationId',
'having'=> 'MemberId=member.MemberId',
),
which gives this error:
CDbCommand failed to execute the SQL statement: SQLSTATE[42S22]:
Column not found: 1054 Unknown column 'member.MemberId' in 'having clause'.
The SQL statement executed was: SELECT `MemberId ` AS `c`, MemberId, OrganizationId,
SUM(Amount) AS `s` FROM `donation` `t` WHERE (`t`.`MemberId `='2')
GROUP BY `MemberId `, OrganizationId HAVING (MemberId=member.MemberId)
This makes no sense since from the donation table I have the relation defined as originally posted above. The query seems to be going the direction needed to get the SUM per organization as I want though. Any other ideas?
If I understand what you are trying to it would seem like you need to add a "having" attribute, something like
'on'=>'member.MemberId = t.MemberId',
'group' => 't.MemberId, t.OrganizationId',
'having'=> 't.MemberId=' . $model->MemberId
This is the SQL query needed..
select donation_org_id , sum(donation_amount) as donated_amount, count(d.donation_id) as members_count
from donations d
group by d.donation_org_id
Ok after running around in circles with this someone was able to push me over the top to a solution on Yii forums.
The end result is
$criteria->condition='member.MemberId="'.$model->MemberId.'"';
$criteria->with='member';
$criteria->select='MemberId,OrganizationId,sum(Amount) as Amount';
$criteria->group='t.MemberId,OrganizationId';
$dataProvider=new CActiveDataProvider(Donations::model(),
array(
'criteria'=>$criteria,
Thanks ldg for the help with this.
Related
Cakephp informs me that it cannot find the table or does not recognize the alias what am i supposed to use?
Hello i am new to cake php ORM can any one tell me how to preform a left join on a subquery I'm really interested to know how to use in working the join
Here is sub query and left join so far please ignore any syntax error
$scl = TableRegistry::get('School');
$subquery = $scl->find();
$subquery->select([
'UID',
'SID',
'Total' => $subquery->func()->sum('numberOfStudents')
])->group(['UID, SID']);
$q->select([
'TeacherID',
'ClassID',
'StudentTotal' => 'sq.Total'
])->join([
'table' => $subquery,
'alias' => 'sq',
'type' => 'LEFT',
'conditions' => ['sq.UID = TeacherID', 'sq.SID = ClassID']
]);
here is the error:
[PDOException] SQLSTATE[42S22]: Column not found: 1054 Unknown column 'sq.UID' in 'on clause'
The problem is that ORM based queries automatically alias the selected fields, so you don't get
SELECT UID, SID ...
but
SELECT UID as School__UID, SID as School__SID
hence referring to sq.UID will fail, as no such field name was selected.
To avoid this problem you can either use an alias that matches the original field name:
->select([
'UID' => 'UID',
'SID' => 'SID',
// ...
])
use the lower level database query that doesn't automatically create aliases:
$subquery = $scl
->getConnection() // connection() in older CakePHP versions
->newQuery()
->from($scl->getTable()); // table() in older CakePHP versions
or refer to the aliased fields in the main query:
'conditions' => [
'sq.' . $scl->getAlias() . '__UID = TeacherID', // alias() in older CakePHP versions
'sq.' . $scl->getAlias() . '__SID = ClassID',
]
I am trying to pull record from a table using the following code
$userId = Yii::$app->user->id;
$lists = PromoLists::findAll(['user_id' => $userId, 'list_type' => 'custom']);
which outputs a query like below
select * from promo_lists where user_id ='$userId' and list_type='custom'
But i am unable to find any thing in the documentation that would help me achieve it with the following condition.
select * from promo_lists where user_id ='$userId' and list_type='custom' and status!='deleted'
as the status is an ENUM field and there are 4 different status
'active','pending','rejected','deleted'
currently i used the following approach
PromoLists::findAll(['user_id' => $userId, 'list_type' => 'custom', 'status'=>['active','pending','rejected']]);
which outputsthe following query
select * from promo_lists where user_id ='$userId' and list_type='custom' and status in ('active','pending','rejected')
which somehow achieves the same thing but this query would need to be edited every time when there is a new status type added to the table column status.
i know i can do this by using PromoLists::find()->where()->andWhere()->all()
but how to check with != / <> operator using findAll().
Simply like this:
PromoLists::find()->where(['and',
[
'user_id' => $userId,
'list_type' => 'custom',
],
['<>', 'status', 'deleted'],
])->all();
Using operator format in condition
http://www.yiiframework.com/doc-2.0/guide-db-query-builder.html#operator-format
PromoLists::find()
->andWhere([
'user_id' => $userId,
'list_type' => 'custom',
['!=', 'status', 'deleted']
])
->all();
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 a Listings table with lat/long fields. I'm using the Haversine Formula to calculate the distance (as an alias/virtual field) between an origin point (33.987339, -81.036819) and the lat/long of each Listing and returning the listings with a distance within 10 miles of the origin point.
The following SQL query in phpMyAdmin returns exactly what I expect:
SELECT *, round(3959 * acos(cos(radians(33.987339)) * cos(radians(Listing.lat)) * cos(radians(Listing.long) - radians(-81.036819)) + sin( radians(33.987339)) * sin(radians(Listing.lat))))
AS distance, `Listing`.`id`
FROM `preview_site`.`listings` AS `Listing`
LEFT JOIN `preview_site`.`users` AS `User` ON (`Listing`.`user_id` = `User`.`id`)
LEFT JOIN `preview_site`.`categories` AS `Category` ON (`Listing`.`category_id` = `Category`.`id`)
LEFT JOIN `preview_site`.`states` AS `State` ON (`Listing`.`state_id` = `State`.`id`)
WHERE `Listing`.`status` = 'Active'
HAVING distance < 10
ORDER BY `distance` ASC LIMIT 20
After attempting (and failing several ways) to get the CakePHP code to correctly generate the above SQL, I used this tool to generate the following CakePHP controller code (it gave both Model and Controller options) from the SQL:
$this->Paginator->virtualFields = array(
'distance' => 'round(3959 * acos(cos(radians(33.987339)) * cos(radians(Listing.lat )) * cos(radians(Listing.long) - radians(-81.036819)) + sin(radians(33.987339)) * sin(radians(Listing.lat))))');
$this->Paginator->settings = array(
'fields' => array(
'Listing.*',
'Listing.distance',
'Listing.id',
'Category.*',
'State.*',
'User.*',
),
'joins' => array(
array(
'conditions' => array(
'Listing.user_id = UserJoin.id',
),
'table' => 'users',
'alias' => 'UserJoin',
'type' => 'left',
),
array(
'conditions' => array(
'Listing.category_id = CatJoin.id',
),
'table' => 'categories',
'alias' => 'CatJoin',
'type' => 'left',
),
array(
'conditions' => array(
'Listing.state_id = StateJoin.id',
),
'table' => 'states',
'alias' => 'StateJoin',
'type' => 'left',
),
),
'conditions' => array(
'Listing.status' => 'Active',
),
'order' => array(
'distance' => 'asc',
),
'limit' => '5',
'having' => array(
'distance <' => '10',
),
'contain' => array(
'User',
'Category',
'State',
),
);
$data = $this->Paginator->paginate('Listing');
$this->set('listings', $data);
If I use this code, I get the following error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Listing.distance' in 'field list'
If I change $this->Paginator->virtualFields to $this->Listing->virtualFields (as I could not find any documentation on Paginator actually using the virtualFields method), I don't get any errors and the pagination works fine, but the returned results are not limited by the distance (all Listing records are returned). Here's a snippet of the generated SQL with the distance alias:
SELECT `Listing`.*, `Listing`.`id`, `Category`.*, `State`.*, `User`.*, (round(3959 * acos(cos(radians(33.987339)) * cos(radians(`Listing`.`lat` )) * cos(radians(`Listing`.`long`) - radians(-81.036819)) + sin(radians(33.987339)) * sin(radians(`Listing`.`lat`)))))
AS `Listing__distance`
FROM `preview_site`.`listings` AS `Listing`
Does anyone have any suggestions for how to make this work correctly? ANY help would be greatly appreciated.
I think where your problem is coming from is CakePHP does not recognize "Having", I believe. Since you don't seem to have a Group By, you can just use a regular WHERE and get the same results, in this case, array('conditions' => array('distance <' => 10)) If you do have a Group By though, see the below:
CakePHP: How can I use a "HAVING" operation when building queries with find method?
There are quite a few open tickets regarding virtual fields.
This might well be one of them.
Even though your initial binding to paginator looks off.
You should add virtual fields to the current model, so Listing.
$this->Listing->virtualFields['distance'] = ...
For me, in those scenarios where I could not easily use the virtual field, it helped to manually use the aliased field, so Listing__distance ASC in your order or more importantly in your having clause.
It will also reuse the already calculated field instead of doing it again (even though I don't know if there is a speed improvement here this way). See this.
Also note that it might be cleaner to leverage a behavior to avoid repeating that for other queries (and to keep it DRY):
$this->Listing->setDistanceAsVirtualField($lag, $lng);
And I usually use conditions to limit the distance (no need for having, is there?).
I am retrieving data:
$mydata = $this->ProductList->find('all', array('order' => 'rand()', 'conditions' => array('name' => 'we love')));
I have set up a HABTM relationship to the Product model. As you can see, I am fetching all products in the 'we love'-list. Now, I want those Products I am retrieving to be randomised. But they are not, instead the MySQL is randomised on the ProductList model as you can see in the SQL. Why is that? How can I get the random fetch on the Products instead?
Resulting MySQL query:
SELECT `ProductList`.`id`, `ProductList`.`name` FROM `database`.`product_lists` AS `ProductList` WHERE `name` = 'we love' ORDER BY rand() ASC
SELECT `Product`.`id`, `Product`.`category_id`, `Product`.`name`, `Product`.`price`, `Product`.`description`, `ProductListsProduct`.`product_list_id`, `ProductListsProduct`.`product_id` FROM `database`.`products` AS `Product` JOIN `database`.`product_lists_products` AS `ProductListsProduct` ON (`ProductListsProduct`.`product_list_id` = 3 AND `ProductListsProduct`.`product_id` = `Product`.`id`)
EDIT:
There are so many different ways to approach this; to get a random product from a user's product list. You could do it with PHP - just find all of the products and then use rand() to pick on from the returned array. You could set a Model query condition. The list goes on...
I would probably create an alias to the Product model in ProductList called RandomProduct. You could set the query for the retrieved product when you set the relationship:
public $hasMany = array(
'RandomProduct' => array(
'className' => 'Product',
'foreignKey' => 'product_list_id',
'order' => 'Rand()',
'limit' => '1',
'dependent' => true
)
);
You can then use the containable behavior so that this model is only retrieved when you need it. (You wouldn't need to do this if recursive finds are greater than -1, but I usually do that as best practice so that my models only query for the data that they need.) The following would return any ProductList called 'we love' and a "random" product associated with that list.
$mydata = $this->ProductList->find(
'all',
array(
'conditions' => array(
'name' => 'we love'
)
),
'contain' => array(
'RandomProduct'
)
);