select with multiple join and conditions where column null codeigniter - php

I'm looking to get items from database where the items has options and some not.
My model to get the data is: {This return only products that product.id is in options and option_values}
$category_id = 2;
$this->db->select('category_products.*, products.*, option_values.price as prodPrice, option_values.special_price, LEAST(IFNULL(NULLIF(option_values.special_price, 0), option_values.price), option_values.price) as sort_price', false)
->from('category_products')
->join('products', 'category_products.product_id=products.id')
->join('options', 'options.product_id=attributes.product_id')
->join('option_values', 'option_values.option_id=options.id')
->where('category_products.category_id', $category_id)
->where('option_values.inventory >', '0');
->where('products.quantity >', '0');
$this->db->group_by('products.id');
$result = $this->db->get()->result();
return $result;
But i need to get also items that has no options and options_values.
category_products table:
product_id | category_id | sequence
74 | 2 | 0
75 | 2 | 0
products table:
id | code | name | type | price | saleprice | quantity
74 | 12345_ | Product with options | 1 | 0 | NULL | 1
75 | 12346_ | Product without options | 2 | 199 | NULL | 1
options table:
id | product_id | sequence | name | type | required
74 | 74 | 1 | Size | radiolist | 1
option_values table:
id | option_id | name | value | price | special_price | weight | inventory | sequence | limit
777 | 74 | 8K | 12345 | 199.00 | 159.00 | 1.00 | 0 | 17 | NUL
With the model i write above i can get only the product_id 74, and my question is how can i adapt the query to get the product_id 75 to?
Any help is appreciated.

Try changing your joins to include LEFT JOIN. I don't have a database set up so I can't test it but I would try the following:
NOTE: the addition of the left parameters in the join() functions.
$category_id = 2;
$this->db->select('category_products.*, products.*, option_values.price as prodPrice, option_values.special_price, LEAST(IFNULL(NULLIF(option_values.special_price, 0), option_values.price), option_values.price) as sort_price', false)
->from('category_products')
->join('products', 'category_products.product_id=products.id', 'left')
->join('options', 'options.product_id=attributes.product_id', 'left')
->join('option_values', 'option_values.option_id=options.id', 'left')
->where('category_products.category_id', $category_id)
->where('option_values.inventory >', '0');
->where('products.quantity >', '0');
$this->db->group_by('products.id');
$result = $this->db->get()->result();
return $result;
You might need to modify the joins to be LEFT or RIGHT joins to get the desired result. I may try to simulate it a little later when I get a minute.
This is a good article on understanding MySQL joins. I refer to it from time to time when I have to craft complex queries.

Related

Laravel Implement greatest-n-per-group

User table:
| id | name | age |
|----|------------|-----|
| 1 | Apple | 22 |
| 2 | Strawberry | 23 |
| 3 | Orange | 50 |
| 4 | Mango | 30 |
Memberships table:
| id | user_id | expire_at |
|----|---------|----------------------|
| 1 | 1 | 2019-08-17T11:19:30Z |
| 2 | 1 | 2019-08-10T11:20:10Z |
| 3 | 2 | 2019-08-29T11:20:19Z |
| 4 | 3 | 2019-08-02T11:20:30Z |
| 5 | 3 | 2019-08-28T11:20:40Z |
Problom
I want select users with the latest 'expire_at'.
After reference: https://stackoverflow.com/a/2111420/5588637,
I tried the following:
SELECT
u.*,
m1.*
FROM
users u
INNER JOIN memberships m1 ON u.id = m1.user_id
LEFT JOIN memberships m2 ON u.id = m2.user_id
AND (
m1.expire_at < m2.expire_at
OR m1.expire_at = m2.expire_at
AND m1.id < m2.id
)
WHERE
m2.id IS NULL;
Result
The id will appear twice because I used to join.
| id | name | age | id | user_id | expire_at |
|----|------------|-----|----|---------|----------------------|
| 1 | Apple | 22 | 1 | 1 | 2019-08-17T11:19:30Z |
| 2 | Strawberry | 23 | 3 | 2 | 2019-08-29T11:20:19Z |
| 3 | Orange | 50 | 5 | 3 | 2019-08-28T11:20:40Z |
After change m1.* to m1.expire_at. I got the result I want.
| id | name | age | expire_at |
|----|------------|-----|----------------------|
| 1 | Apple | 22 | 2019-08-17T11:19:30Z|
| 2 | Strawberry | 23 | 2019-08-29T11:20:19Z |
| 3 | Orange | 50 | 2019-08-28T11:20:40Z |
online try: http://sqlfiddle.com/#!9/27fa22/4
Implement in Lavavel
Laravel Framework version: 5.6.39
I am trying to convert the above SQL into Laravel using Database: Query Builder.
$users = DB::table('users as u')
->select('u.*', 'm1.*')
->join('memberships as m1','u.id','=','m1.user_id')
->leftJoin('memberships as m2', function($join){
$join->on('u.id', '=', 'm2.user_id')
->where(function ($query) {
$query->where('m1.expire_at','<','m2.expire_at')
->orWhere('m1.expire_at','=','m2.expire_at')
->where('m1.id','<','m2.id');
});
})
->whereNull('m2.id')
->toSQL();
I'm using toSql(). This will convert it to SQL first to make sure it's same of above SQL.
SELECT
`u`.*,
`m1`.*
FROM
`users` AS `u`
INNER JOIN `memberships` AS `m1` ON `u`.`id` = `m1`.`user_id`
LEFT JOIN `memberships` AS `m2` ON `u`.`id` = `m2`.`user_id`
AND (
`m1`.`expire_at` < ?
OR `m1`.`expire_at` = ?
AND `m1`.`id` < ?
)
WHERE
`m2`.`id` IS NULL
? seems to be the characteristic of laravel, I believe it is same of above SQL.
when i change toSQL() to get(), the result following:
Collection { ▼
#items: []
}
The above result is wrong, so i tried remove
whereNull('m2.id') in Laravel code (WHERE m2.id IS NULL in SQL), let’s see what happened.
Laravel result
Collection { ▼
#items: array:5 [▼
0 => { ▼
+"id": 1
+"name": "Apple"
+"age": "Eric Yiu SL"
+"user_id": 1
+"expire_at": "2019-08-10T11:20:10Z"
}
...
]
Ideal result
| id | name | age | id | user_id | expire_at |
|----|------------|-----|----|---------|----------------------|
| 1 | Apple | 22 | 2 | 1 | 2019-08-10T11:20:10Z |
| 3 | Orange | 50 | 4 | 3 | 2019-08-02T11:20:30Z |
| 1 | Apple | 22 | 1 | 1 | 2019-08-17T11:19:30Z |
| 2 | Strawberry | 23 | 3 | 2 | 2019-08-29T11:20:19Z |
| 3 | Orange | 50 | 5 | 3 | 2019-08-28T11:20:40Z |
Comparing results, Laravel result missing second id which is memberships table id, i guess this is the reason of incorrect results.
I have searched the Internet, seems is this problem.
https://github.com/laravel/framework/issues/4962
But I failed after various attempts...
You cannot select two rows with the same name in Laravel. The second one will override the first one. Use an alias instead.
$users = DB::table('users as u')
->select('u.*', 'm1.id as membership_id')
->join('memberships as m1','u.id','=','m1.user_id')
->leftJoin('memberships as m2', function($join){
$join->on('u.id', '=', 'm2.user_id')
->where(function ($query) {
$query->whereColumn('m1.expire_at','<','m2.expire_at')
->orWhere(function ($query) {
$query->whereColumn('m1.expire_at','=','m2.expire_at')
->whereColumn('m1.id','<','m2.id');
});
});
})
->whereNull('m2.id')
->get();
Note: I also encapsulated the orWhere() in the join to avoid confusion about the order of AND/OR.
What also works is using a different order in the select. You can for example use the following:
$query->select([
'm1.*',
'm1.id as membership_id',
'u.*'
])
It will return all columns of both tables plus the new membership_id column. But if there is a column on the users table which is named similarly to a column on the memberships table, only the users table column is returned (e.g. created_at). What comes last in your list is returned.
EDIT:
As #Namoshek mentioned, you should not select everything because you have a duplicate key problem in your SQL query. I modified my answer so that it would match #RaymondNijland answer. And by the way, even for the table user, you should select exactly what you need. And not only for a duplicate key problem but also for the speed of your SQL query. We don't think about it enough but it can quickly make the difference on a big set of results.
Less data to send from the database to your PHP server = faster
You should try this one :
DB::table('users as u')
->select('u.*', 'm1.id as membership_id')
->join('memberships as m1','u.id','=','m1.user_id')
->leftJoin('memberships as m2', function ($join) {
$join->on('u.id', '=', 'm2.user_id')
->on(function($join) {
$join->on('m1.id', '<', 'm2.id')
->on(function($join) {
$join->on('m1.expire_at', '<', 'm2.expire_at')
->orOn('m1.expire_at', '=', 'm2.expire_at');
});
});
})
->whereNull('m2.id')
->toSQL()
As mentioned in Laravel's documentation on this page: https://laravel.com/api/5.8/Illuminate/Database/Query/JoinClause.html#method_on
You can pass a closure to the on() method and there is the orOn() method that you can use in this closure.
I tested it and it gives the same result as your SQL query.

Join between two table don't return rows with no link between the tables

I've two tables:
___Rooms:
|--------|------------|
| ROO_Id | ROO_Status |
|--------|------------|
| 47 | active |
| 48 | active |
| 49 | active |
|--------|------------|
___Availabilities:
|--------|------------|------------|------------|
| AVA_Id | AVA_RoomId | AVA_Date | AVA_Status |
|--------|------------|------------|------------|
| 1 | 47 | 2019-02-25 | Open |
| 2 | 48 | 2019-02-26 | Open |
| 4 | 47 | 2019-02-28 | Close |
| 5 | 47 | 2019-02-25 | Open |
|--------|------------|------------|------------|
I would like to get info for both of these tables for a range of dates.
So my query is the following:
SELECT ROO_Id, AVA_Id, AVA_Date, AVA_Status
FROM ___Rooms
LEFT JOIN ___Availabilities
ON ___Rooms.ROO_Id = ___Availabilities.AVA_RoomId
WHERE ROO_Status!="inactive"
AND AVA_Date BETWEEN "2019-02-24" AND "2019-03-10"
ORDER BY ROO_Id ASC
The problem is the query don't take all the ___Rooms rows. For example, Room #49 don't have a record in ___Availabilities but I need to have it in the final result.
Do you know why ?
Thanks.
Using a left join is correct, but by filtering on the AVA_Date column in a where clause you will exclude any rows where AVA_Date is null.
Move that filter to the on clause and your left-join will work as expected
SELECT ROO_Id, AVA_Id, AVA_Date, AVA_Status
FROM ___Rooms
LEFT JOIN ___Availabilities
ON ___Rooms.ROO_Id = ___Availabilities.AVA_RoomId
AND AVA_Date BETWEEN "2019-02-24" AND "2019-03-10"
WHERE ROO_Status!="inactive"
ORDER BY ROO_Id ASC

Laravel 5.7 - multiply two columns and add to final sum

I have two tables - a students table and a products table.
When i make a list of the students in a table, i need to see the total amount (sum) of payments that has been made, unfortunately it seems like the result is the correct sum but multiplied by the amount of rows.
Students table:
+----+----------+
| id | name |
+----+----------+
| 1 | Jonathan |
| 2 | Bob |
+----+----------+
Products table:
+----+------------+-------+----------+
| id | student_id | money | quantity |
+----+------------+-------+----------+
| 1 | 1 | 1000 | 2 |
| 2 | 1 | 2000 | 1 |
| 3 | 2 | 500 | 5 |
| 4 | 2 | 3000 | 1 |
+----+------------+-------+----------+
Payments table:
+----+-------+------------+
| id | money | student_id |
+----+-------+------------+
| 1 | 5000 | 1 |
| 2 | 2000 | 1 |
| 3 | 2500 | 2 |
| 4 | 2500 | 2 |
+----+-------+------------+
In theory, the output of my query should be:
+-------------+----------+----------------+----------------+
| id | name | payments_total | products_total |
+-------------+----------+----------------+----------------+
| 1 | Jonathan | 4000 | 7000 |
| 2 | Bob | 5500 | 10000 |
+-------------+----------+----------------+----------------+
What i have tried:
$teamStudents = DB::table('students')->where('students.team', $team)->orderBy('first_name', 'ASC')
->join('products', 'students.id', '=', 'products.student_id')
->join('payments', 'students.id', '=', 'payments.student_id')
->select('students.first_name AS first_name', 'students.last_name AS last_name', 'students.created_at AS created_at', DB::raw('SUM(products.money * products.amount) AS products_total'), DB::raw('SUM(payments.money) AS payments_total'), 'students.id AS id')
->groupBy('students.id')
->get();
It returns no error except for the fact that the returned "payments_total" is inaccurate and multiplied by the amount of rows for some reason.
So my question is:
How do i get around this and what have i done wrong? I've been googling for an hour with no result.
Is my query an issue or the way i've set it up, if so, what would the correct solution be?
With your edit I was able to solve the problem that you have, but in your edit you use couple of things for which I don't have data, such as the $team, first_name and last_name of the students. But anyway, here is a solution for your problem, you have to use subqueries in order to solve this:
$teamStudents = DB::table('students')->orderBy('name', 'ASC')
->leftJoin(DB::raw('(select SUM(products.money * products.quantity) AS products_total, student_id from products group by student_id) products'), 'students.id', '=', 'products.student_id')
->leftJoin(DB::raw('(select sum(payments.money) as payments_total, student_id from payments group by student_id) payments'), 'students.id', '=',
'payments.student_id')
->select('students.name', 'payments.payments_total', 'products.products_total', 'students.id AS id')
->groupBy('students.id')
->get();
I am not sure if technically I will be correct, but the problem is because you use multiple joins, so that's why the results are doubled, if you don't use subqueries.
There's no need to join in this case, you don't use it anyways.
$teamStudents = DB::table('students')
->select('id, name')
->selectRaw('select sum(money) from payments where student_id = students.id as payments_total')
->selectRaw('select sum(money) from products where student_id = students.id as products_total')
->orderBy('name')
->get();

Laravel Query Builder - Sum Where and Sum Where

I have a table called stock_movements:
| product_id | type | qty |
|------------|------|------|
| 1 | A | 2 |
| 1 | A | 1 |
| 1 | A | 7 |
| 1 | B | -2 |
| 1 | B | -4 |
| 1 | B | -1 |
| 2 | A | 2 |
| 2 | A | 1 |
| 2 | A | 7 |
| 2 | B | -3 |
| 2 | B | -3 |
| 2 | B | -1 |
I am trying to create a collection of the products where which have the values of the sum of A and sum of B.
i.e.
Group by products, type
Sum qty for each type
The key thing is that it is a collection against the product, which is the bit I am struggling with. Does anyone have any advice?
So far I have got...
return $this->stockMovements()->groupBy('product_id')
->selectRaw('sum(qty), product_id')
->where('type', $this->type)
->get();
But this does not split out for types A and B.
You need a basic pivot query, which would look something like this in Laravel:
return $this->stockMovements()
->selectRaw("SUM(CASE WHEN type = 'A' THEN qty ELSE 0 END) AS sum_a, ".
"SUM(CASE WHEN type = 'B' THEN qty ELSE 0 END) AS sum_b, product_id")
->groupBy('product_id')
->get();
I removed the WHERE clause, because your data only seems to have two types, and there isn't much sense in restricting that. If you really want the sum of quantity for just A or B, then we can write a much simpler Laravel/MySQL query.
Using A Raw Expression
$result = DB::table('stock_movements')
->select(DB::raw('sum(qty) as sum_qty, type'))
->groupBy('type')
->get();
Rule of thumb for the use of aggregation functions in the SELECT clause.
If a select block does have a GROUP BY clause, any column
specification specified in the SELECT clause must exclusively occur as
a parameter of an aggregated function or in the list of columns given
in the GROUP BY clause, or in both.
For more details :
http://www.informit.com/articles/article.aspx?p=664143&seqNum=6

Need help with a MySQL statement

I have a table of Products that looks like so:
| id | Description | Price |
| 1 | dinglehopper | 2.99 |
| 2 | flux capacitor | 48.99 |
| 3 | thing1 | 48.99 |
And so on...
Then I have an OrderLineItem table which, as you can guess, links each item in an order to the product:
| id | productID | OrderID |
| 43 | 1 | 12 |
| 44 | 2 | 12 |
| 52 | 3 | 15 |
So, as you can see, order #12 contains a dinglehopper and flux capacitor. How can I get this information in a single query? I just want ALL the products associated with a given OrderID in the OrderLineItem table.
May be by
select p.description,p.id,o.irderId
from
`orderLineItem` o, `product` p
where
p.id = o.productId;
or
select p.description,p.id,o.irderId
from `orderLineItem` o
join `product` p
on p.id = o.productId;
LEFT JOIN :)
http://www.w3schools.com/sql/sql_join_left.asp
#Pete About "single" query part, you should make VIEW from this join, if really going to use a lot.

Categories