Yii2 querybuilder sum from a one-many relation field - mysql

Help me understand how the sum function works, when I want to count from a relational table field.
The query is pretty simple. I have unitsOrdered table and unitsSent table. The relation is 1-n. So i want to count all sent units of the specific order. In this example order id=2;
In my db I have data.
unitsOrdered:
id
1
2
unitsSent:
id | order_id | units
1 | 1 | 5
2 | 2 | 2
My query is:
query = OrderedUnits::find()
->select([
'ou.*',
'sum(us.units) as alreadySent'
])
->joinWith('unitsSent us')
->where(['ou.id' => 2])
->orderBy('ou.id desc')
->groupBy(['ou.id'])
->all();
The result should be pretty clear:
id 2 = 2 units
However, my query returns:
id 2 = 10 units.
As I understand what i does is 5 * 2 = 10;
What is wrong with my query?
The relation is defined like this:
/**
* #return \yii\db\ActiveQuery
*/
public function getUnitsSent()
{
return $this->hasMany(UnitsSent::className(), ['order_id' => 'id'])
->from(['us' => UnitsSent::tableName()]);
}
Actual query:
'SELECT `ou`.*, sum(us.sent_units) as alreadySent FROM `ordered_units` `ou`
LEFT JOIN `units_sent` `us` ON `ou`.`id` = `us`.`order_id`
WHERE (`ou`.`id`= 2)
GROUP BY `ou`.`id`
ORDER BY `ou`.`id` DESC'

You should use the hash filter format this way ->where(['us.id' => 2])
and for get result 2 you need innerJoin (your raw sql show you have a left join) otherwise also the rows that not match are joined
query = OrderedUnits::find()
->select([
'ou.*',
'sum(us.units) as alreadySent'
])
->innerJoinWith('unitsSent us')
->where(['us.id' => 2])
->orderBy('ou.id desc')
->groupBy(['ou.id'])
->all();
and try check the real query executed using
echo $query->createCommand()->getRawSql();
and remeber that the selection of columns not aggregated and not mentioned in group by is not allowed in mysql starting for 5.7 ( and anyway is not a correct way to use group by in SQL)

Related

How to search from comma separated string in comma separated column mysql

I am working on a project where I have to code for job filters.
In this when I filters for salaries it sends salary id's in array like:
job_salary = array(
[0]=>3,
[1]=>4,
[2]=>5,
[3]=>6,
[4]=>7,
[5]=>8
)
and job in my jobs table contains multiple salaries using comma string.
id salary_ids
1 2,4,6
2 1,3,5
3 4,5
4 9,1
So if i search for value 3, and 5 in array form like
job_salary = array(
[0]=>3,
[1]=>5
);
It should return me the second and third row because these rows contain value 3 and 5.
Use it like below let me know if it works
$values=array("1","2","3");
foreach($values as $val)
{
$query="Select * from table_name where FIND_IN_SET('".$val."',column_name)";
$result=mysql_query($query);
$data=mysql_fetch_array($result);
$dbval[]=$data['column_name'];
}
print_r($dbval);
please try again like this
1).For Postgres MySQL :
SELECT FIND_IN_SET(5,userid) as result FROM department
2).For Postgres SQL :
select *
from TABLENAME f
where 'searchvalue' = ANY (string_to_array(COLOUMNNAME,','))
Example
select *
from customer f
where '11' = ANY (string_to_array(customerids,','))

MySql NOT IN failing to return empty set

I am currently having a problem when trying to select where a job is listed in the tbl_jobs table and has not been assigned to a delivery item in the tbl_delivery_items table by using a NOT IN subquery.
The sub query should return supplier_job_job_id 1 (which it does when you run this as a seperate query), with the NOT IN excluding the job with an id of 1. Alas, it is not working and causing me a headache by returningthe job with a job_id of 1 when I was expecting an empty set. Here is the codeigniter code generating the query:
$this->db->join("tbl_jobs", "tbl_jobs.job_id = tbl_supplier_jobs.supplier_job_job_id");
$this->db->where_not_in("supplier_job_job_id", "SELECT delivery_item_job_id FROM tbl_delivery_items");
$result = $query->result_array();
echo $this->db->last_query();
return $result;
Here is the query it generates:
SELECT * FROM (`tbl_supplier_jobs`) JOIN `tbl_jobs` ON `tbl_jobs`.`job_id` = `tbl_supplier_jobs`.`supplier_job_job_id` WHERE `supplier_job_job_id` NOT IN ('SELECT delivery_item_job_id FROM tbl_delivery_items') AND `supplier_job_supplier_id` = '1' ORDER BY `tbl_jobs`.`job_number` DESC
And here is the data:
tbl_supplier_jobs
supplier_job_id | supplier_job_job_id | supplier_job_supplier_id
1 1 1
2 2 2
tbl_jobs
job_id | job_number | job_description | job_delivered
1 1024 aaaaa 0
2 2048 bbbbb 0
tbl_delivery_items
delivery_item_id | delivery_item_delivery_id | delivery_item_job_id | delivery_item_toa | delivery_item_pallet_quantity | delivery_item_box_quantity
1 1 1 2014-08-18 16:23:04 2 1
Any ideas?
The problem is that the subquery is rendered as a string. You can see this clearly in the generated query that you supplied.
This seems to be a limitation in the where_not_in method of CodeIgniter. A possible solution, change the code to call the where method and render a slightly larger part of the query yourself:
$this->db->where("supplier_job_job_id NOT IN (SELECT delivery_item_job_id FROM tbl_delivery_items)");
The query isn't executing the subquery it is using the string value:
`supplier_job_job_id` NOT IN (
'SELECT delivery_item_job_id FROM tbl_delivery_items'
)
Will check if supplier_job_job_id equals the string 'SELECT delivery_item_job_id FROM tbl_delivery_items'.
You should consider a LEFT JOIN to tbl_delivery_items and a WHERE condition of delivery_item_job_id IS NULL.. which should be fairly easy in your framework.
Your subselect is being output as a string. Note that it is in single quotes in your resulting query. That of course will not work.
I would actually question your intended approach here. As your tbl_delivery_items table gets bigger and bigger your query will get slower and slower. This is not a scalable approach. You should revisit your table schema and get a more direct way of flagging completed deliveries.

Mysql SELECT where 2 column match set of values

I am doing product filter the point is the more specific the user select the products the less results should appear. At he moment I am writing multiple queries and storing in arrays and checking for array intersect, but the result is opposite, which means when user apply more filters, i will show more products.
So i am thinking there could be a SQL command which I don't know!
simplified example:
------------
table "filter"
------------
product
Spec
value
------------
Sample data
------------
book1,page,200
book1,cover,leather
book1,language,en
book2,page,300
book2,cover,paper
book2,language,de
book3,page,150
book3,cover,hard
book3,language,en
SELECT `product` FROM `filter` where ...
how do I select (page=200 and langauge=en)?
If understand correctly you are probably looking for something like this
SELECT product
FROM filter
WHERE (spec = 'page' AND value = '200')
OR (spec = 'language' AND value = 'en')
GROUP BY product
HAVING COUNT(*) = 2 -- 2 here represents number of spec-value pairs
Output:
| PRODUCT |
-----------
| book1 |
SQLFiddle
Another alternative, but less elegant. I just wanted to show another way of doing it.
SELECT DISTINCT product
FROM filter f
WHERE
EXISTS (SELECT 1 FROM filter WHERE spec = 'language' AND value = 'en' AND product = f.product)
AND EXISTS (SELECT 1 FROM filter WHERE spec = 'page' AND value = 200 AND product = f.product);

MySQL GROUP_CONCAT as multiple fields and custom order

Here is a structure of my tables:
Here is what I want as a result of a query (considering that user provides me with 2|4|1 pattern):
Here is what I tried:
SELECT parcel.TrackCode, parcelType.Name, GROUP_CONCAT(track.DateTime SEPARATOR '|') AS dt
FROM track
JOIN parcel ON track.ParcelID = parcel.ID
JOIN parcelType ON parcel.ParcelTypeID = parcelType.ID
JOIN event ON track.EventID = event.ID
GROUP BY parcel.ID;
The result is:
So the problem is that I need GROUP_CONCAT() to divide data to several fields (date where track.eventID = 3, date where track.eventID = 1, date where track.eventID = 5, date where track.eventID = 7 # considering that the pattern is 3|1|5|7). Any ideas?
I would suggest running two queries. First this one, to get the relevant events:
SELECT ParcelID, EventID, DateTime
FROM track
WHERE EventID IN(1, 2, 4)
Store the results of this query in a map of parcel ids to arrays of events where the key is the parcel ID and the value is another array. In that inner array, the key is the event ID and the value is the event date.
array(1 => array(
2 => '2012-05-15 15:33:00',
4 => '2012-05-22 11:35:41',
1 => '2012-05-04 18:58:30'
),
2 => array(
2 => '2012-07-01 09:05:56',
4 => '2012-07-14 13:32:00',
1 => '2012-06-27 12:44:32'
)
);
Then, use a query like this next one to get the list of parcels, and for each one, you can easily look in memory at the results of the previous query to find out the dates of each of those events, for any given parcel ID.
SELECT parcel.ID, parcel.TrackCode, parceltype.Name
FROM parcel
JOIN parceltype ON parceltype.ID = parcel.ParcelTypeID
Note: this answer is a trimmed version of the conversation that took place in the MySQL chat room
If you want the IDs also, you should GROUP_CONCAT the ids from track, they will be given in the same order too.
Or set an ORDER BY at the end of the query and it will manipulate that data as well.

MySQL Inner Query with a Where and Group by conditions in cakephp

i am having a table named Reports with
id report_id user_id
1 1 5
2 1 5
3 1 5
4 2 5
5 2 5
6 3 6
7 3 6
8 4 1
9 4 1
10 4 1
i am trying to write a Query such that user_id = 5 and to find how many reports he has created.(Answer should be of 2 )
i have a Wrote a Mysql Query as
select count(distinct report_id) from Reports where user_id=5
i m trying the same MYSQl sub Query inside the Foreach users loop where my 5 is from $user['User']['id'];
how to write the MYSQL Query above inside this for loop in cakephp Framework....
foreach($users as & $user):
echo "User id ".$user['User']['id'];
$user['User']['report_count'] = $this->Report->find('count',
array('conditions'=>array('Report.user_id'=>$user['User']['id'])));
endforeach;
$this->set('users', $users);
Please suggest me.......HOw to write the above Mysql Query in cakephp
You want to use the following functions GROUP BY and COUNT
Your query could look somewhat like this
select count(distinct report_id) from Reports where user_id=5
If this is a list of users you are showing in your application... you could significantly reduce the number of queries you are running.
eg. for 100 users you will be running 100 queries instead you can run a single single query to extract the user_id and count of reports by each user
select count(distinct report_id) as count,user_id from Reports where user_id IN (1,2) GROUP BY user_id;
OR if you want to run seperate queries for each user
select count(distinct report_id) as count,user_id from Report where user_id=5;
Try this:
$user['User']['report_count'] = $this->Report->find('count',
array( 'conditions' => array('Report.user_id' => $user['User']['id']),
'fields' => 'DISTINCT Report.report_id'
)
);
It should fetch all distinct report_ids for a given user_id, then count them. Basically, it should run the query:
SELECT DISTINCT report_id FROM Reports WHERE user_id=$user['User']['id']
(after substituting the value of $user['User']['id']), then count the number of rows in the result. Caveat: I don't use CakePHP in real life, I just read the documentation; your mileage may vary. As halocursed mentions, running a single SQL query on your own would be more efficient than calling find(...) for each user ID. You could also try:
$report_counts = $this->Report->find('list',
array( 'conditions' => array('Report.user_id' => array_map(create_function('$user', 'return $user["User"]["id"];'), $users)),
'group' => array('Report.user_id'),
'fields' => array('Report.user_id', 'COUNT(DISTINCT Report.report_id) AS report_count')
)
);
foreach ($users as &$user) {
$user['User']['report_count'] = $report_counts[$user['User']['id']];
}
However, I don't know if CakePHP will accept aggregate functions in the 'fields' parameter, and I don't know as though find('list', ...) will pick Report.user_id as the array index. If you're having problems with the latter, you could switch to a [find('all', ...)][3] call and loop over $report_counts rather than $users. I didn't take this approach because I don't know the structure of $users, such as how it's indexed.