Laravel - Really struggling to understand eloquent - laravel

I'm fairly new to Laravel having come over from Codeigniter and for the most part I really like it, but I really can't get my head around Eloquent.
If I want to do a simple query like this:
SELECT * FROM site INNER JOIN tweeter ON tweeter.id = site.tweeter_id
I try doing something like this (with a "belongs to"):
$site = Site::with('tweeter')->find($site_id);
But now I have two queries and an IN() which isn't really needed, like so:
SELECT * FROM `site` WHERE `id` = '12' LIMIT 1
SELECT * FROM `tweeter` WHERE `id` IN ('3')
So I try and force a join like so:
$site = Site::join('tweeter', 'tweeter.id', '=', 'site.tweeter_id')->find($site_id);
And now I get an error like so:
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'id' in where clause is ambiguous
SQL: SELECT * FROM `site` INNER JOIN `tweeter` ON `tweeter`.`id` = `site.tweeter_id` WHERE `id` = ? LIMIT 1
Bindings: array (
0 => 12,
)
It's obvious where the error is, the where needs to use something like "site.id = ?". But I can't see anyway to make this happen?
So i'm just stuck going back to fluent and using:
DB::table('site')->join('tweeter', 'tweeter.id', '=', 'site.tweeter_id')->where('site.id','=',$site_id)->first()
I guess it's not a massive problem. I would just really like to understand eloquent. I can't help but feel that i'm getting it massively wrong and misunderstanding how it works. Am I missing something? Or does it really have to be used in a very specific way?
I guess my real question is: Is there anyway to make the query I want to make using Eloquent?

I actually find this behaviour advantageous. Consider this (I'll modify your example). So we have many sites and each has many tweeters. Each site has a lot of info in the DB: many columns, some of them text columns with lots of text / data.
You do the query your way:
SELECT * FROM site INNER JOIN tweeter ON tweeter.id = site.tweeter_id
There are two downsides:
You get lots of redundant data. Each row you get for a tweeter of the same site will have the same site data that you only need once so the communication between PHP and your DB takes longer.
How do you do foreach (tweeter_of_this_site)? I'm guessing you display all the sites in some kind of list and then inside each site you display all of it's tweeters. You'll have to program some custom logic to do that.
Using the ORM approach solves both these issues: it only gets the site data once and it allows you to do this:
foreach ($sites as $site) {
foreach($site->tweeters as $tweeter) {}
}
What I'm also saying is: don't fight it! I used to be the one that said: why would I ever use an ORM, I can code my own SQL, thank you. Now I'm using it in Laravel and it's great!

You can always think of Eloquent as an extension of Fluent.
The problem you're running into is caused by the find() command. It uses id without a table name, which becomes ambiguous.
It's a documented issue: https://github.com/laravel/laravel/issues/1050
To create the command you are seeking, you can do this:
$site = Site::join('tweeter', 'tweeter.id', '=', 'site.tweeter_id')->where('site.id', '=', $site_id)->first($fields);
Of course, your syntax with join()->find() is correct once that issue fix is adopted.

Related

Laravel returning Unknown column 'table.deleted_at'in 'where clause'

It seems there are lot of posts about this error, but before everything, I must say that I have use SoftDeletes; because I use it and I need it to manage trashed models.
Column 'deleted_at' also exists.
Solution like this saying to remove it does not suit in my case.
I am looking for help to keep use SoftDeletes; but making my request working properly.
Here is my query:
public function scopeWithRowNumber($query)
{
$sub = Model::selectRaw('*, #row:=#row+1 as row')
->orderBy('some_date', 'desc')->toSql();
$query->from(DB::raw("({$sub}) as sub"));
}
The sql exit gives me :
select *, #row:=#row+1 as row from `model_table`
where `model_table`.`deleted_at` is null order by `entry_date` desc
How to fix it?
I finally use this:
withoutGlobalScope('Illuminate\Database\Eloquent\SoftDeletingScope')
on my queries to avoid checking deleted_at column.
The issue is relative to nested queries and seems "normal". In my case, this helps me to fix/avoid error:
> Unknown column 'deleted_at' or column not found.
Maybe it will help someone else.
Also for ranking, I was inspired by this post. But I have to edit my query a little bit because of more conditions with dynamic parameters I added. But this is another subject.

Laravel (using Livewire ) to show result from 2 different tables

I am running Laravel 5.8 and hammering my head all day against walls, to find out how to achieve this MySQL query in Laravel -> render function in my livewire component to list all available "ABKs" which include a specific text in the other table "Parts".
What I have:
2 Tables one is ABKs and the other one is PARTs < relation between them is on to many one ABK can have one part, one Part can have many ABKs. Noted the relationship in the Models, works as expected.
But if I try right now to search over both tables(to order and paginate in my livewire component) I need to have this query for example in Laravel:
SELECT * FROM `abks` LEFT JOIN parts ON abks.part_id = parts.id WHERE parts.zeichnungsnummer LIKE '%46%'
if I try this in Tinker I get the result:
$abks = ABK::where('id', 'like','%%')->with('part')->where('parts.zeichnungsnummer','like','%46%')->get();
Illuminate/Database/QueryException with message 'SQLSTATE[42S22]:
Column not found: 1054 Unknown column 'parts.zeichnungsnummer' in
'where clause' (SQL: select * from abks where id like %% and
parts.zeichnungsnummer like %46%)',
what am I doing wrong here? Why doesn't Eloquent find this column?
just a short example what I want to achieve because my code is more complex to post here ... I think its easier to understand to post it in a little example.
a little further explanation why I need this: I have a data table which outputs all ABKs and have 2 search inputs one for the ABKs columns and one for the PARTs columns -> where the user can search for texts in ABKs and in the other one in the PARTs table. which is filtered in this way in livewire while the user types...
Thanks to all here for your great answers, and helping us a lot to learn and understand.
you missed join clause from your eloquent query:
$abks = ABK::where('id', 'like',$abksId)
->join('parts','parts.id','=','abks.part_id')
->where('parts.zeichnungsnummer','like','%46%')->get();

Forcing Partition Query Using Laravel

Hi I am currently writing a query where I would like to force the partition that the query uses. I know this code is working in MySQL but I don't know how I would write this using laravel.
This is the MySQL code I would use to force the partition
PARTITION (p46)
I have tried writing the following in Laravel but it doesn't seem to be working
->raw('PARTITION (p46)')
I'm not sure if raw is even a method you can use in laravel which is why it probably doesn't work.
I am using this partition enforcement instead of using the below code, it makes my queries much faster.
->where('venue_id', 46)
My table is partition using HASH(venue_id) and I have tried using the MySQL code and that is working perfectly.
Thank you in advance if you have a solution for this, I haven't been able to find anywhere which explains how to do this.
Here is my full laravel query to give you some context:
$bodyVisitors->selectRaw('SUM(visitors_new) AS new, SUM(visitors_total) AS total')
->raw('PARTITION (p' . $venue_filter . ')')
->where('day_epoch', '>=', $body_start)
->where('day_epoch', '<', $body_end)
->get();
The ->raw() is being ignored is there anyway to make this work? I am fine if the answer is no, I will just stop using laravel to write those queries.
I also suffer the partition problem for whole day...
The way I solve is
select the max timestamp and max ID for T1 then left join it to the T2 on T1.timestamp and ID
Hope that my suggestion would give a some help...
I am still writing the eloquent script almost done :'(
Try like this in your Eloquent Model:
$table = $this->getConnection()->getTablePrefix() . $this->getTable();
$table .= ' PARTITION ({your partition})';
$this->newQuery()->from($table)->where()->get();
that will be making a query like this:
select * from table PARTITION (xxxxxx) where
Try adding this scope
{
$query->from($partition);
}
and $partition is a name of your partition. Try $partition = 'p46' in your case

Laravel Eloquent Conditional inside Query like PLSQL

I'm trying to do conditional statement inside Laravel query, is it possible?
In PLSQL I would do the following to do IF ELSE, can I do it in Laravel Eloquent ?
select *
from x
where x.id = 1
and ( (varName = 'all') OR (x.name = 'red') )
What's happening up there is user is picking from a drop downbox (varName), and is either picking all colors as 'all', or picking individual colors.
I know it would be easy to write two IF Else statements and duplicate the SQL with each condition, but I have a really big SQL, and don't want to duplicate the whole SQL twice just for something simple. Is it possible to do this in Laravel? Or are there other ways to achieve it?
You may use whereRaw for this, for example
...
->where('x.id','=',1)
->whereRaw('(varName = "all" or x.name="red")')
...
Please note that the raw query isn't escaped by default, so make sure to do this if your handling user input.

Magento collection complex condition or union approach

Long story short, I need to get a collection that should be filtered using the following condition: where (A and (B or C) or (D and (E or F)).
I can do them separately using:
$collection = Mage::getModel('customModule/customModel')
->getCollection()
->addFieldToFilter('fieldA', valA)
->addFieldToFilter(
array('fieldB', 'fieldC'),
array(
array('eq' => valB),
array('eq' => valC)
)
);
But i don't know if I can make a single collection which would combine the two. I haven't found a "Magento" way of doing this. I also though of performing a union between them using
$collection1->getSelect()->union(array($collection2->getSelect()));
but it doesn't work. After the above statement, printing the $collection1->getSelect()->__toString() displays:
SELECT `main_table`.*SELECT `main_table`.* FROM `customModel` AS `main_table` WHERE..
which throws an error. For some reasons it concatenates the two select clauses rather then the two queries.
Can anyone suggest a fix in either direction? If I can either use Magento's native functions to perform the complex condition or how to fix the union?
I've also searched far & wide for this but unfortunately it is not implemented.
There are some problems with using UNION without DISTINCT, because the entity_id in a collection has to be unique otherwise you'll get an exception from some code layers bellow yours.
The Magento / Zend way:
It is possible by getting the underlying Zend_Select object and adding a union join in the Zend way. Meaning that the resulting query object(s) will be a Zend_Query which you will have to execute and fetch results on (the Zend way).
The pitfall of this is that the result is not a Magento Collection and the objects inside are not Magento model objects but Zend_Db_Table_Row.
The way I solved it:
I created multiple separate collections for which I called getAllIds() (this triggers a separate query which only fetches the ids) and then I merged the resulting arrays of ids and created one "master collection" WHERE entity_id IN(/*...*/).

Resources