Substracting two Eloquent result tables - php

With Eloquent I get two results like this from my db:
$deliveries = table1::select('place_id','product_id',DB::raw("SUM(amount) as amount"))->groupBy('place_id', 'product_id');
$purchases = table2::select('place_id','product_id',DB::raw("SUM(quantity) as amount"))->groupBy('place_id', 'product_id');
I want to get a table where all the amount entries of table2 are substracted from table1 if
place_id from table1 == place_id from table2
AND
product_id from table1 == product_id from table2
I want to use Eloquent if possible but I am also fine with some raw SQL inside of it or even some fast php-array-method to do something like:
$inventory = $deliveries - $purchases;
edited/added
So right now I do this:
foreach ($deliveries as $delivery => $delivValue) {
foreach ($purchases as $purchase => $purchValue) {
if ($delivValue['place_id']==$purchValue['place_id'] && $delivValue['product_id']==$purchValue['product_id']) {
$deliveries[$delivery]['amount'] = $delivValue['amount'] - $purchValue['amount'];
}
}
}
This works as expected but I think it's very inefficient when the array sizes do increase.

I say this is as close you can get, even if you use laravel's helper method like diff(), etc they still use the same approach internally so your best bet would be to do it at the database level
Try writing this logic at the db level, like
DB::select('
SELECT table1.place_id,table1.product_id,amount,quantity, amount-quantity
FROM table1 JOIN table2
ON (table1.place_id = table2.place_id
and table1.product_id = table2.product_id)
');
http://sqlfiddle.com/#!9/2d5775/6/2
used DB::select - assuming you aren't taking any user input

Related

Yii2 query give different result with sql query

"Table1":
id
name
1
Ulrich
2
Stern
"Table2":
id
school
tid
1
A
1
2
B
1
I want to join 2 table to get all information. With SQL query like this:
SELECT Table1.id,
name,
school
FROM `Table1`
INNER JOIN `Table2`
ON Table1.id = Table2.tid
It gives me all information as I expect (I mean 2 rows with name 'Ulrich').
But when I do with Yii2 query:
$query = self::find();
$query -> alias('t1')
-> innerJoin(['t2'=>'Table2'], 't1.id=t2.tid')
$result = NULL;
if($total = $query->count()) {
$result = $query
-> select([t1.*, t2.school])
->asArray()
->all()
;
$result[0]['count'] = $total;
}
it only gives me 1 row with name 'Ulirch'.
Can anyone help me with this problem. Thank you very much.
If you use ActiveRecord::find() method to create query you will get instance of yii\db\ActiveQuery. This is query designed to load ActiveRecord models. Because of that if you do any type of join and your result set contains primary key of your main model (The model which find() method was called to create query) the ActiveQuery will remove any rows it considers duplicate.
The duplicates are recognised based on main model primary key so the rows in resultset will be considered duplicate even if the data from joined table are different. That's exactly what happened in your case.
To avoid that you have to use query builder instead of ActiveQuery.
Your query can look for example like this:
$query = (new \yii\db\Query())
->from(['t1' => self::tableName()])
->innerJoin(['t2'=>'Table2'], 't1.id=t2.tid');

How to group by aggregation of column in foreign table with many-to-many relation in Eloquent?

I am starting to get headaches over this so I thought I just post it here.
I have two tables that are related through a pivot table (as it is a many-to-many relationship). I use Laravel and Eloquent (but general help on how to achieve this with normal SQL queries is also highly appreciated).
I want to order the first table based a column of the second one but the column needs to be "aggregated" for this.
Example with Cars that are shared by many drivers and can have different colors:
Car-Table: [id, color]
Driver-Table: [id, name]
Car.Driver-Table: [car_id, driver_id]
I need a query that gets all drivers that only drive red cars and then all that don't drive red cars.
I have to use a query because I'll maybe do other things (like filtering) on this query afterwards and want to paginate in the end.
I already use queries that get either one of the two groups. They look like this:
In the Driver model:
public function redCars() {
return $this->cars()->where('color', 'red');
}
public function otherColoredCars() {
return $this->cars()->where('color', '<>', 'red');
}
And then in somewhere in a controller:
$driversWithOnlyRedCars = Driver::whereDoesntHave('otherColoredCars')->get();
$driversWithoutRedCars = Driver::whereDoesntHave('redCars')->get();
Is there a way to combine these two?
Maybe I am just thinking completely wrong here.
Update for clarification:
Basically I would need something like this (ot any other way that would lead to the same outcome)
$driversWithOnlyRedCars->addTemporaryColumn('order_column', 0); // Create temporary column with value 0
$driversWithoutRedCars->addTemporaryColumn('order_column', 1);
$combinedQuery = $driversWithOnlyRedCars->combineWith($driversWithoutRedCars); // Somehow combine both queries
$orderedQuery = $combinedQuery->orderBy('order_colum');
$results = $combinedQuery->get();
Update 2
I think, I found out how to get near my goal with raw queries.
Would be something like this:
$a = DB::table(DB::raw("(
SELECT id, 0 as ordering
FROM drivers
WHERE EXISTS (
SELECT * FROM cars
LEFT JOIN driver_car ON car.id = driver_car.car_id
WHERE driver.id = driver_car.driver_id
AND cars.color = 'red'
)
) as only_red_cars"));
$b = DB::table(DB::raw("(
SELECT id, 1 as ordering
FROM drivers
WHERE EXISTS (
SELECT * FROM cars
LEFT JOIN driver_car ON car.id = driver_car.car_id
WHERE driver.id = driver_car.driver_id
AND cars.color <> 'red'
)
) as no_red_cars"));
$orderedQuery = $a->union($b)->orderBy('ordering');
Now the problem is that I need the models ordered like this and paginated in the end so this is not really an answer to my question. I tried to convert this back to models but I didn't succeed yet. What I tried:
$queriedIds = array_column($orderedQuery->get()->toArray(), 'id');
$orderedModels = Driver::orderByRaw('(FIND_IN_SET(drivers.id, "' . implode(',', $queriedIds) . '"))');
But looks like FIND_IN_SET only allows for a column of the table as second parameter. Is there another way to get the Models in the right order out of the ordered union query?
You can use a UNION query:
$driversWithOnlyRedCars = Driver::select('*', DB::raw('0 as ordering'))
->whereDoesntHave('otherColoredCars');
$driversWithoutRedCars = Driver::select('*', DB::raw('1 as ordering'))
->whereDoesntHave('redCars');
$drivers = $driversWithOnlyRedCars->union($driversWithoutRedCars)
->orderBy('ordering')
->orderBy('') // TODO
->paginate();
How do you want drivers with the same ordering to be ordered? You should add a second ORDER BY clause to get a consistent order every time you execute the query.
This is the best I got:
$driversWithOnlyRedCars = Driver::whereHas('cars',function($q){
$q->where('color', 'red');
})->withCount('cars')->get()->where('cars_count',1);

Access the results from a previously executed query

I have a nested relationship and I would like to filter them, using the result that comes from the first query run by Eloquent.
My Eloquent query is:
$entry = Entry::with([
'products.unit',
'products.area'
])->find($id);
The query log shows me that Eloquent runs this query first:
select * from entry where entry.deleted_at is null and entry.id = ? limit 1
And I would like to use its result in this second query, also run by Eloquent:
select area.*,
area_product.product_id, area_product.quota as pivot_quota,
area_product.month as pivot_month
from area inner join area_product
on area.id = area_product.area_id
where area.deleted_at is null and area_product.product_id in (?)
I would like to put a constraint inside it using the month value that comes from the first query.
Is there a way to do it using Eloquent?
I was thinking in something like this:
$entry = Entry::with([
'products.unit',
'products.area' => function ($q) {
// using month here!
}
])->find($id);

Mysql JoinInner if not null

Hi i have following query where it's use joininner statement to get all possible businesses. But when a business is created for first time only 1 category will be updated the rest 2 will remain null
public function searchBusinessByCategoryString($str = null, $city=null,$start,$perpage)
{
$select = $this->getDbTable()->getAdapter()->select();
$select->from('business as b', array('b.business_name','b.business_url','b.reviews_num','b.cat_id','b.business_id','b.rating','b.business_phone','b.business_add1','b.business_add2','b.x','b.y','b.photo_url'))
->joinInner('business_category as bc','b.cat_id = bc.cat_id',array('bc.cat_name'))
->joinInner('business_sub_category as bsc','b.sub_cat_id = bsc.b_sub_cat_id',array('bsc.b_subcat_name','bsc.b_sub_cat_id'))
->joinInner('business_sub_category as bsc2','b.sub_cat2_id = bsc2.b_sub_cat_id',array('bsc2.b_subcat_name','bsc2.b_sub_cat_id'))
->joinInner('business_sub_category as bsc3','b.sub_cat3_id = bsc3.b_sub_cat_id',array('bsc3.b_subcat_name','bsc3.b_sub_cat_id'))
->where("bsc.b_subcat_name like '".$str."%'")
->orWhere("bsc.b_subcat_name like '%".$str."'")
->orWhere("bsc.b_subcat_name= '".$str."'")
->orWhere("bsc2.b_subcat_name like '%".$str."'")
->orWhere("bsc2.b_subcat_name = '".$str."'")
->orWhere("bsc2.b_subcat_name like '".$str."%'")
->orWhere("bsc3.b_subcat_name like '%".$str."'")
->orWhere("bsc3.b_subcat_name = '".$str."'")
->orWhere("bsc3.b_subcat_name like '".$str."%'");
$result = $this->getDbTable()->getAdapter()->fetchAll($select);
return $result;
}
Now the issues is how can i not doing joininner query if the rest 2 categories are null? My above statement return empty result event though there is businesses with one category.
use leftJoin instead of innerJoin where the joined table can contain NULL value. INNER JOIN will join table, using the condition and will not keep lines when a null value is found on the joined table. LEFT JOIN will allow you to keep this line

PHP: Display 2D arrays in a specific order and with certain conditions?

I’ve a database which has four tables, for the sake of simplicity, I will call them table1, table2, table3, table4
Each table has different information but they all share a unique id, sessionid.
I’ve exportet all four tables through my PHPMyAdmin in PHP arrays. This gives me a file called mydomain.php.
Inside this, the data structure is as such:
$table1 = array(
array('id'=>1,'sessionid'=>'12','field1_1'=>'data','field2_1'=>'data','field3_1'=>'data, 'done'=>1),
array('id'=>2,'sessionid'=>'13','field1_1'=>'data','field2_1'=>'data','field3_1'=>'data, 'done'=>0)
);
$table2 = array(
array('id'=>4,'sessionid'=>'12','field1_2'=>'data','field2_2'=>'data','field3_2'=>'data),
array('id'=>6,'sessionid'=>'13','field1_2'=>'data','field2_2'=>'data','field3_2'=>'data)
);
$table3 = array(
array('id'=>2,'sessionid'=>'12','field1_3'=>'data','field2_3'=>'data','field3_3'=>'data),
array('id'=>5,'sessionid'=>'13','field1_3'=>'data','field2_3'=>'data','field3_3'=>'data)
);
$table4 = array(
array('id'=>6,'sessionid'=>'12','field1_4'=>'data','field2_4'=>'data','field_43'=>'data),
array('id'=>1,'sessionid'=>'13','field1_4'=>'data','field2_4'=>'data','field3_4'=>'data)
);
My wish is to display them according to their session id (as you can see, the id’s of each table can vary), and I want to check if the field ‘done’ in the first table is set to 1 or 0 and skip fields which are empty or has “NULL” in them.
I’ve tried some different methods, such foreach loops nested in each other but I don’t quite seem to work.
Hope for your help on how to solve this.
Sincere
- Mestika
Why don't you use SQL? This language is designed to do things like this.
Use JOIN statement to merge the content of the four tables.
Use SELECT statement to retrieve the field [done].
Use WHERE to filter empty strings and NULL values.
And finally, use PHP and the mySQL features to retrieve the table values directly instead of using phpMyAdmin to export the data.
Sample SQL statement:
SELECT t1.sessionid, t1.done
FROM table1 AS t1
JOIN table2 AS t2 ON t1.sessionid = t2.sessionid
JOIN table3 AS t3 ON t2.sessionid = t3.sessionid
JOIN table4 AS t4 ON t3.sessionid = t4.sessionid
WHERE t1.done IS NOT NULL
PHP resources:
MySQL Improved Extension
PDO_MYSQL
Its not the most efficient solution, but it is straight forward ..
foreach ($table1 as $data) {
if ($data['done']===0 || $data['done']===1) {
print_r($data);
echo "Data table 2:";
foreach ($table2 as $data2) {
if ($data2['sessionid']===$data['sessionid']){
print_r($data2);
}
}
echo "Data table 3:";
foreach ($table3 as $data3) {
if ($data3['sessionid']===$data['sessionid']){
print_r($data3);
}
}
echo "Data table 4:";
foreach ($table4 as $data4) {
if ($data3['sessionid']===$data['sessionid']){
print_r($data4);
}
}
}
}
I did not test it I simple wrote it down, hope there are no typos ..

Categories