Yii2 - left join on multiple condition - php

I have three tables with the following relations,
------- 1 0..* ------------
|Product|-------------|Availability|
------- ------------
1 |
|
1 |
--------
|MetaData|
--------
my raw sql looks like this
SELECT p.ID FROM product p
LEFT JOIN availability a ON a.productID=p.ID
AND a.start>=DATE_ADD(DATE(now()), INTERVAL 7 DAY)
LEFT JOIN meta_data m ON m.ID=p.meta_dataID
WHERE a.ID IS NULL
AND m.published_state=1;
That is, find each Product with a MetaData.published_state equal to 1 and with no Availability such that Availability.start more than 7 days from now().
I'm trying to accomplish the same using ActiveRecord methods, using something like the following,
$products = Product::find()
->joinWith('metaData')
->joinWith('availability')
->onCondition(['>=', 'availability.start', strtotime('+7 days')])
->where(['is', 'availability.ID', NULL])
->andWhere(['=', 'meta_data.published_state', 1])
->all();
however, this is returning no results. Using Connection::createCommand() to run the raw sql returns the rows I'd expect so there is no issue with the data.
I suspect the issue is being caused by the join conditions and the where conditions 'bleeding' into each other; both join and where being applied to either the joining or the where rather than separately.
How can I output the actual sql query being run? this is in an action being called from a console controller.
How can I alter my code to return the desired Products?

I believe this one is better solution. Instead of using Raw queries like leftJoin you should complement your joinWith relations with andOnCondition (which adds needed where conditions into your join statement).
$products = Product::find()
->joinWith(['metaData' => function (ActiveQuery $query) {
return $query
->andWhere(['=', 'meta_data.published_state', 1]);
}])
->joinWith(['availability' => function (ActiveQuery $query) {
return $query
->andOnCondition(['>=', 'availability.start', strtotime('+7 days')])
->andWhere(['IS', 'availability.ID', NULL]);
}])
->all();
In addition it looks cleaner when you write where clauses inside relations. It works the same as writing it outside (if I'm not wrong), but when refactoring your query, you can easily delete the whole relation without forgetting relation conditions outside.

Just use like below condition.
$query = Product::find()
-> leftJoin('availability', 'availability.productID=product.ID AND a.start>=DATE_ADD(DATE(now()), INTERVAL 7 DAY)')
->leftJoin('meta_data', 'meta_data.ID=product.meta_dataID')
->where(['is', 'availability.ID', NULL])
->andWhere(['=', 'meta_data.published_state', 1])
->all();

Use this:
$sql = 'SELECT p.ID FROM product p
LEFT JOIN availability a ON a.productID=p.ID
AND a.start>=DATE_ADD(DATE(now()), INTERVAL 7 DAY)
LEFT JOIN meta_data m ON m.ID=p.meta_dataID
WHERE a.ID IS NULL
AND m.published_state=1';
$products = Product::findBySql($sql);
Yii Active Record has a findBySql($sql) method that allows you to run and get the data from database using a raw SQL query. It helps a lot when you got confused with Yii's query method or when your query get more complicated to be ran with Yii as in your case I suppose.
So basically, in above block of codes, you just put your raw SQL query to a variable called $sql, and use it as the parameter value of findBySql() method.

Related

Laravel DB Query WHERE date greater than not working with JOIN

Background
I have written a SQL query that works perfectly for my needs, and I am trying to add it to a queued job in Laravel. Instead of using Eloquent models I wanted to go with DB Query to ensure better performance as there will ultimately be many joins and conditions. I am having a problem with the where clause when checking a date, but it works perfectly fine when running in SQL. I have stripped out additional pieces of this query to only include what is necessary for debugging this issue.
Code
The original SQL:
SELECT `messages`.`created_at`, `participants`.`last_read`
FROM `messages`
LEFT JOIN `participants` ON `messages`.`thread_id` = `participants`.`thread_id` AND `messages`.`user_id` != `participants`.`user_id`
WHERE `messages`.`created_at` > `participants`.`last_read`;
When I run this directly in SQL, I see 2 results, which is expected with my current data.
created_at
last_read
2021-03-26 19:02:53
2021-03-23 19:31:30
2021-03-26 19:02:58
2021-03-23 19:31:30
This is how I have written it in Laravel:
$query = DB::table('messages')
->leftJoin('participants', function ($join) {
$join->on('messages.thread_id', '=', 'participants.thread_id')
->on('messages.user_id', '!=', 'participants.user_id');
})
->select('messages.created_at as message_date', 'participants.last_read as last_read')
->where('messages.created_at', '>', 'participants.last_read');
When I execute this, the results are empty.
I dumped the final SQL from the DB Query builder to make sure it's correct, and this is what it is:
select `messages`.`created_at` as `message_date`, `participants`.`last_read` as `last_read`
from `messages`
left join `participants`
on `messages`.`thread_id` = `participants`.`thread_id` and `messages`.`user_id` != `participants`.`user_id`
where `messages`.`created_at` > participants.last_read
And running that directly in SQL returns accurate results, as expected.
Context
For context, here is the data structure and some of the data I'm working with.
participants
id
thread_id
user_id
last_read
created_at
updated_at
deleted_at
last_notified
9
8
178
2021-03-23 23:31:53
2021-03-23 22:16:48
2021-03-23 23:31:53
NULL
NULL
messages
id
thread_id
user_id
body
created_at
updated_at
deleted_at
159
3
177
adfad
2021-03-26 19:02:53
2021-03-26 19:02:53
NULL
160
3
177
dadddda
2021-03-26 19:02:58
2021-03-26 19:02:58
NULL
The problem
It seems as though the DB query code is causing the columns with like names to be mixed up. Both tables have a column called created_at, but I only need that column from the messages table. My SELECT only asks for that column, specifying the correct table. But something in this DB Query join is causing it to get mixed up.
Playing with different joins, and removing the where clause, I realized that the dates aren't correct always. For example, here is the result when I use leftJoin
{
"message_date": "2021-03-23 00:30:42",
"last_read": "2021-03-26 00:22:48"
},
{
"message_date": "2021-03-23 00:31:25",
"last_read": "2021-03-26 00:22:48"
}
Notice, the message_date and last_read values are reverse of what they were when running the SQL directly. So this must be the problem.
I changed to rightJoin, and the results are reversed:
{
"message_date": "2021-03-26 19:02:53",
"last_read": "2021-03-23 19:31:30",
},
{
"message_date": "2021-03-26 19:02:58",
"last_read": "2021-03-23 19:31:30",
}
So that should work, right? I add the where clause back in, but still the results are empty.
I am guessing there is something I need to do to tell the query builder to handle these columns correctly, as they seem to be getting mixed up during the where and select. But I can't figure out how to clarify that. I have tried searching for others with this issue but I can't seem to find anything relevant.
Already tried
I have already tried a few things with no change in results.
Changing the order of the commands - like moving the select() to the beginning of the statement, things like this.
Using whereDate instead of where. (Note - for performance I'd rather avoid this, but wanted to try just in case).
Using join, joinLeft, and joinRight.
Using where in the on clause instead of two ons. Like this
->leftJoin('participants', function ($join) {
$join->on('messages.thread_id', '=', 'participants.thread_id')
->where('messages.user_id', '!=', 'participants.user_id');
})
Anyone have any guidance on things I can try? This should be such a simple task, and has turned into hours of trying to understand why it works in SQL and not Laravel's DB Query Builder.
The where function of the query builder will always assume the right hand side is a value and will use it in a prepared statement as a literal (in this case string). If you want to compare columns you need to use whereColumn:
$query = DB::table('messages')
->leftJoin('participants', function ($join) {
$join->on('messages.thread_id', '=', 'participants.thread_id')
->on('messages.user_id', '!=', 'participants.user_id');
})
->select('messages.created_at as message_date', 'participants.last_read as last_read')
->whereColumn('messages.created_at', '>', 'participants.last_read')->get();
Additional where clauses can be found in the docs

What is wrong with this laravel query with left join?

I am working on a laravel project(Laravel 6.8). I have a locations table and a dashboards table. I am trying to build a query that will return all matching records from the dashboards table. Looking at another SO question(Laravel 5.4 Raw Join Query), I saw a similar need and tried to adjust the code to fit my needs, but it still doesn't work.
This is what I have right now.
$locations = DB::table('locations')
->selectRaw("site_name,
COUNT(DISTINCT client) as client_count,
GROUP_CONCAT(client_lob) as lobs,
COUNT(DISTINCT client_lob) as lob_count,
SUM(agent_workstations) as aw_sum,
SUM(production_support_workstations) as psw_sum,
locations.*" )
->leftJoin('dashboards', 'locations.id', '=', 'dashboards.location_id')
->whereNotNull('latitude')
->groupBy(['site_name'])
->orderBy('site_name', 'asc')
->get();
It returns only the data from locations without returning anything from dashboards. I can't see what is wrong with this query. Can anyone offer some advice?
Because you never select the dashboard's fields, try to select the dashboard's field like this:
$locations = DB::table('locations')
->selectRaw("site_name,
COUNT(DISTINCT client) as client_count,
GROUP_CONCAT(client_lob) as lobs,
COUNT(DISTINCT client_lob) as lob_count,
SUM(agent_workstations) as aw_sum,
SUM(production_support_workstations) as psw_sum,
locations.*, dashboards.column1, dashboards.column2" )

Issue in Laravel 5.1 Inner Join Query

Below is my Query in Laravel 5.1
\App\Models\Project\Bids\ProjectBid_Model
::selectRaw('B.*')
->join('tblproject P','B.projectid','=','P.projectid')
->where('P.WhoCreatedTheProject',14)
->first()
and below is the equivalant query
select B.* from `tblprojectbid`
inner join `tblproject P` on `B`.`projectid` = `P`.`projectid`
where `P`.`WhoCreatedTheProject` = 14 limit 1
What's the problem ?
Please check the line 1 in Query: select B.* from tblprojectbid.
What's the question ?
How can I change
select B.* from tblprojectbid
to
select B.* from tblprojectbid B
If you want to use Eloquent I'm afraid there is no easy way to do it.
I use in this case full table name for model for instance
\App\Models\Project\Bids\ProjectBid_Model
::selectRaw('bid_table.*')
->join('tblproject AS P','bid_table.projectid','=','P.projectid')
->where('P.WhoCreatedTheProject',14)
->first()
However it's also possible that you set alias in ProjectBid_Model:
protected $table = 'bid_table AS B';
The con is you will have this table always aliased with B, so in case you have 2 models with same alias (in this case B), you won't be able to change it later just for one table, so I think the better is 1st approach (without using alias)
Here is the final solution.
\App\Models\Project\Bids\ProjectBid_Model
::selectRaw('B.*')
->from('tblprojectbid as B')
->join('tblproject as P','B.projectid','=','P.projectid')
->where('P.WhoCreatedTheProject',14)
->first()
try this.
\DB::table('tblprojectbid as B')
->select()
->join('tblproject as P','B.projectid','=','P.projectid')
->where('P.WhoCreatedTheProject',14)
->first()

Empty where clause in Laravel 5.1 preferring eloquent method

i am looking to produce the following query in laravel 5.1 with eloquent method.
The mysql query is a follows
SELECT * FROM orders WHERE 1 = 1 ORDER BY o_date DESC LIMIT 25
No matter what i cant get the
WHERE 1 = 1
part working.
am new to laravel and pretty sure this is easy. but can't figure it out.
I have tried the following variations
$orders = orders::where('1', 1)->orderBy('o_date', 'desc')->take(25)->get();
$orders = orders::where(1)->orderBy('o_date', 'desc')->take(25)->get();
$orders = orders::where('1', '=', '1')->orderBy('o_date', 'desc')->take(25)->get();
but its not working. the query results is as shown below
> select count(*) as aggregate from `orders`
Seems like (looking at 1=1) you need whereRaw
$orders = orders::whereRaw("any clause u wish")->orderBy('o_date', 'desc')->take(25)->get();
but if "any clause u wish" is not a VERY-VERY dinamical part u'd better look what else you can use
http://laravel.com/api/5.0/Illuminate/Database/Query/Builder.html
For the above example the below code works fine
$orders = orders::whereRaw("1 = 1")->orderBy('o_date', 'desc')->take(25)->get();
Why do you want to create a condition that will be always true? I think that Eloquent is smart enought to remove this useless part of the query.

Self Join Condition fails in PHP but works in mysql

I have this strange situation and not sure of what is wrong. I have a simple self join to find matches based on some conditions. I have this query running fine in mysql but when I call it through PHP, it doesn't return any values.
select * from Requests p inner join Requests c on c.ID<>p.ID
where usr_ID<>4
and p.c_ID = c.c_ID
This works fine but not the below one.
DB::table('Requests as parent')
->join('Requests as child', 'parent.ID', '<>', 'child.ID')
->where('parent.usr_ID', '<>', 4)
**->where('parent.c_ID', '=', 'child.c_ID')**
->get();
In the above query, if I remove the second where condition(c_ID), it returns correct values. For all rows, this has a value of 1. If I replace child.c_ID or parent.c_ID by 1, it works again. I have tried with other columns as well and found the same issue.
Any pointers?
What the query builder makes out of your second where condition is:
WHERE parent.c_ID = 'child.c_ID'
So instead of a "normal" where() use whereRaw(), which takes your input and injects it right into the final SQL query
->whereRaw('parent.c_ID = child.c_ID')
Alternatively you could also use DB::raw() on the third argument
->where('parent.c_ID', '=', DB::raw('child.c_ID'))
Both are essentially the same so use whichever you like more.

Categories