Laravel 4 SQL error when joining tables - php

I am trying to show 1 record out of highlights, with both Services and Pages joined into this table to show their details (instead of only showing service_id and page_id)
In my HighlightsController I have the following to get the items from my database:
$highlight = Highlight::where('id', $id)->with(array('Service','Page'))->get();
I get this error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'services.highlight_id' in 'where clause' (SQL: select * from `services` where `services`.`highlight_id` in (1))
I know that this column doesn't exist because it's looking in the wrong table. I don't know what I'm doing wrong. I've been over and over my models compairing it with my SQL and thinking where I went wrong
Here are all the details that could be useful:
The SQL I want to get:
SELECT * FROM highlights
LEFT JOIN pages ON pages.id = highlights.page_id
LEFT JOIN services ON services.id = highlights.service_id
WHERE highlights.id = '1'
My tables:
Highlights
+------------+
| Field |
+------------+
| id |
| service_id |
| page_id |
| text |
+------------+
services
+------------+
| Field |
+------------+
| id |
| title |
| description|
+------------+
pages
+------------+
| Field |
+------------+
| id |
| name |
+------------+
Models and their functions:
class Highlight extends Eloquent
{
function Service(){
return $this->HasMany('Service');
}
function Page(){
return $this->HasMany('Page');
}
}
class Service extends Eloquent
{
function Highlight(){
return $this->HasMany('Highlight');
}
}
class Service extends Eloquent
{
function Highlight(){
return $this->belongsTo('Highlight');
}
}

To make it clear - eager loading (with() method) does not join anything, but runs another query per each loaded relation with WHERE id IN clause.
Change those relations as they are completely incorrect:
// Let's call methods snakeCased just for clarity and sticking to the convention
// and singular or plural depending on what is returned
class Highlight extends Eloquent
{
function service(){
return $this->belongsTo('Service'); // returns single Model
}
function page(){
return $this->belongsTo('Page'); // same as above
}
}
class Service extends Eloquent
{
function highlights(){
return $this->HasMany('Highlight'); // returns Collection of Models
// you can have hasOne instead, depending on your app
}
}
class Service extends Eloquent
{
function highlights(){
return $this->hasMany('Highlight'); // again Collection
}
}
Then you call it:
// returns Collection, so let's call it plural:
$highlights = Highlight::where('id', $id)->with(array('service','page'))->get();

Related

Get results by finding an ID which is two joins away using Eloquent relationships

I need to get rows from one table using an id which is two joins away.
I know I can use join('table_name') but I am trying to use the model names rather than raw table names.
I'm trying to select shipping_shipment.* by joining order_item_join_shipping_shipment then joining order_item, and filtering where order_item.order_id = x
I tried this in the ShippingShipment class, but I can't figure it out.
return $this->hasManyThrough(OrderItem::class, ShippingShipment::class, 'shipment_id', 'order_item_id', 'id', 'id');
There are many items in an order, and many shipments. I need to get the shipments.
There can be more than one shipment per order - items come from various places.
There can be more than one shipment per item - if something is returned and needs shipping again.
The table I want to get rows from, shipping_shipment, is joined to order_item by a join table order_item_join_shipping_shipment. That join table has the order_item_id. I need then to join order_item table so that I can search for order_item.order_id
Table order_item model OrderItem
+-----+---------------+
| id | order_id |
+-----+---------------+
| 6 | 13464 |
| 8 | 13464 |
| 9 | 13464 |
+-----+---------------+
Table order_item_join_shipping_shipment model OrderItemJoinShippingShipment
+-----+---------------+-------------+
| id | order_item_id | shipment_id |
+-----+---------------+-------------+
| 1 | 6 | 12 |
| 1 | 9 | 12 | two items in one shipment
| | | |
| 2 | 8 | 13 |
| 3 | 8 | 14 | one item was returned so shipped again
+-----+---------------+-------------+
Table shipping_shipment don't need describing except to say it has an id column.
If I was to do it with MySQL it would look like this
SELECT ss.*, oiss.order_item_id FROM
order_item_join_shipping_shipment AS oiss
INNER JOIN shipping_shipment AS ss ON (oiss.shipment_id = ss.id)
INNER JOIN order_item AS oi ON (oiss.order_item_id = oi.id)
WHERE oi.order_id = 13464
I noticed you are not using the default table names, so your Models must have the table names explicit, e.g.:
class OrderItem extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'order_item';
}
In the same Model file of the above example, you need to indicate how the relationship works, i.e.:
public function shippingShipments()
{
return $this->belongsToMany(ShippingShipment::class, 'order_item_join_shipping_shipment', 'order_item_id', 'shipment_id');
}
Here you can check in Laravel documentation the whole explanation.
You need to apply the same concept in ShippingShipment Model, so your Model will be something like this:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'order_item';
/**
* The roles that belong to the user.
*/
public function orderItens()
{
return $this->belongsToMany(OrderItem::class, 'order_item_join_shipping_shipment', 'shipment_id', 'order_item_id');
}
}
This way you can get shipments by order item and vice-versa:
//Shipments of Order item 13464
$shipments = OrderItem::find(13464)->shippingShipments()->get();
//Order items of Shipment 1
$orders = ShippingShipment::find(1)->orderItems()->get();
Source: Laravel Documentation
As far as I can tell you are using a pivot table between ShippingShipment and OrderItem. If I understand you correctly you want to get OrderItems that are connected to ShippingShipment, if that is the case this is what you can do:
Make belongs to many relationships in both models, such as:
ShippingShipment:
public function orderItems(){
return $this->belongsToMany(OrderItem::class, 'table_name', 'column_id');
}
OrderItem:
public function shippingShipment(){
return $this->belongsToMany(ShippingShipment::class, 'table_name', 'column_id');
}
And then you can get the desired result by typing this query:
ShippingShipment::find(1)->with('orderItems')->get();
OrderItem::find(13464)->with('shippingShipments')->get();
Note: you can use orderItems:id,order or shippingShipment:id,some_other_field for more optimized query

Can we make a "Class Level Relationship" in Laravel, as opposed to an "Object Level Relationship"?

Consider the following scenario:
There are couple of entities in my Laravel application like the following:
Post
Page
Image
Video
All the above entities can have CustomFieldValues, which is another entity. The structure of the custom_field_values table is as follows:
ID
entity_id
custom_field_definition_id
value
[Timestamp fields]
All the CustomFieldValues belong to a single CustomFieldDefinition entity. Its table custom_field_definitions looks like following:
ID
parent_entity_name
definition_name
[Timestamp fields]
Following are some sample data from the custom_field_definitions table:
| ID | parent_entity_name | definition_name |
|----|--------------------|-------------------|
| 1 | Post | AuthorTwitterUrl |
| 2 | Page | SeoTitle |
| 3 | Image | OriginalSourceUrl |
| 4 | Video | MpaaRating |
As you can see, CustomFieldDefinitions are definitions of extra data, that we can store about each type of entity.
Following are some sampel data from the custom_field_values table:
| ID | entity_id | custom_field_definition_id | value |
|----|-----------|----------------------------|-----------------------------------|
| 1 | 1 | 1 | https://twitter.com/StackOverflow |
| 2 | 1 | 2 | My Page's SEO Title |
| 3 | 1 | 3 | http://example.com/image.jpg |
| 4 | 1 | 4 | G – General Audiences |
A little description about the data contained in the custom_field_values table:
CustomFieldValue:1: The value for CustomFieldDefinition:1 and its entity 1 (Post:1, in this case, because CustomFieldDefinition:1 is related to Post.) is "https://twitter.com/StackOverflow".
CustomFieldValue:2: The value for CustomFieldDefinition:2 and its entity 1 (Page:1, in this case, because CustomFieldDefinition:2 is related to Page.) is "My Page's SEO Title".
CustomFieldValue:3: The value for CustomFieldDefinition:3 and its entity 1 (Image:1, in this case, because CustomFieldDefinition:3 is related to Image.) is "http://example.com/image.jpg".
CustomFieldValue:4: The value for CustomFieldDefinition:4 and its entity 1 (Video:1, in this case, because CustomFieldDefinition:4 is related to Video.) is "G – General Audiences".
custom_field_values table's entity_id can refer to any entity class, therefore it is not a foreign key in the DB level. Only in combination with custom_field_definition_id we can find to which entity it actually refers to.
Now, all is well and good, until I need to add a relationship called customFieldDefinitions to any of the entities (Say Post.).
class Post extends Model {
public function customFieldDefinitions(){
$this -> hasMany ('CustomFieldDefinition');
}
}
The above does not work, because the datapoint that indicates the CustomFieldDefinition's relationship is not a foreign key field in the custom_field_definitions table, named post_id. We have to somehow build the relationship based on the fact that some records in the custom_field_definitions table has "Post" as the value of the field parent_entity_name.
CustomFieldDefinition::where('parent_entity_name', '=', 'Post');
The above snippet fetches the CustomFieldDefinitions that are related to the Post, however, it is not possible to do something like the following with the relationship:
class Post extends Model {
public function customFieldDefinitions(){
$this
-> hasMany ('CustomFieldDefinition')
-> where ('parent_entity_name', '=', 'Post')
;
}
}
The where constraint works. But Laravel also injects the ID of the current Post object into the set of constraints.
So, what I want to do is, not consider the current object's ID at all, and build a "Class Leavel Relationship", and not an "Object Level Relationship".
Is this possible under Laravel?
There might be a workaround but I'm not pretty sure about it.
What you could try doing is to define a mutated attribute and set it as the local key of the relationship:
class Post extends Model
{
public function getEntityNameAttribute()
{
return 'Post';
}
public function customFieldDefinitions()
{
return $this->hasMany(
'CustomFieldDefinition',
'parent_entity_name',
'entity_name'
);
}
}
You could also go further and define a trait which could be used by all your models which have customFieldDefinitions. It could look like:
trait HasCustomFieldDefinitionsTrait
{
public function getEntityNameAttribute()
{
return (new ReflectionClass($this))->getShortName();
}
public function customFieldDefinitions()
{
return $this->hasMany(
'CustomFieldDefinition',
'parent_entity_name',
'entity_name'
);
}
}
Then you can use it wherever needed:
class Post extends Model
{
use HasCustomFieldDefinitionsTrait;
}
class Video extends Model
{
use HasCustomFieldDefinitionsTrait;
}
class Page extends Model
{
use HasCustomFieldDefinitionsTrait;
}
class Image extends Model
{
use HasCustomFieldDefinitionsTrait;
}
Instead of hasMany(), you can create One To Many (Polymorphic) relationship between Post, Page, Image, Video and CustomFieldDefinition.
More about polymorphic relationships here.

Laravel - model modifications

I need to refactor project and I have problem. Below is old, working model, where 'active' column is in "people" table. I need to move 'active' column into "people_translations" table.
Do you have any Idea to modify scopeActive method?
Thanks a lot!
Old working model:
class BaseModel extends Eloquent
{
public function scopeActive($query)
{
return $query->where($this->table . '.active', '=', 1);
}
}
class People extends BaseModel
{
protected $table = 'peoples';
protected $translationModel = 'PeopleTranslation';
}
class PeopleTranslation extends Eloquent
{
public $timestamps = false;
protected $table = 'peoples_translations';
}
Old tables structure:
Table: peoples
id | type | date | active
-------------------------
7 | .... | ... | 1
Table: peoples_translations
id | people_id | language_id | name
-----------------------------------
1 | 7 | 1 | Ann
Old query:
$peoples = \People::active()->get();
New tables structure:
Table: peoples
id | type | date
----------------
7 | .... | ...
Table: peoples_translations
id | people_id | language_id | name | active
--------------------------------------------
1 | 7 | 1 | Ann | 1
Create a relation for translations inside People Model
public function translations()
{
return $this->hasMany('PeopleTranslation', 'people_id');
}
Create active scope in People model
public function scopeActive($query)
{
return $query->whereHas('translations', function($query) {
$query->where('active', 1);
});
}
It will make subquery for this table and as a result it will get where (count of translations with active = 1) > 0.
If you have one-to-one relation - look for hasOne relation method instead of hasMany.

Laravel 4.2 : How to use whereIn() and with() to get a list of items in a one-to-many relationship?

I want to try to display messages according to their conversation ID. The rules are a conversation hasMany messages and a message belongsTo only one conversation.
What I've tried
I have this in my controller :
public function index()
{
$from = Input::get('from',null); //timestamp of latest message
$conv_id = Input::get('conv_id'); //get an array of conversation ids
$allmessages = Conversations::whereIn('id',$conv_id)->with('messages');
if(is_null($from)){
$messages = $allmessages->orderBy('created_at','desc')->take(10);
}else{
$messages = $allmessages->where('created_at','>',$from);
}
return $messages->get()->reverse();
}
but I got the error message,
"SQLSTATE[42S22]:Column not found: 1054 Unknown column 'messages.conversations_id' in
'where clause'(SQL: select * from `messages` where `messages`.`conversations_id` in (1, 2))",
I can't seem to figure out where I've gone wrong. Improvement of code will be a bonus. Thank you!
I have two models, Conversations and Messages. These are the tables. I intentionally left out the timestamp columns of both tables for this question's purpose.
Conversations
+---------+----------+
| id | name |
+---------+----------+
| 1 | haha |
| 2 | hehe |
+---------+----------+
Messages
+---------+----------+-----------------+
| user_id | conv_id |body |
+---------+----------+-----------------+
| 1 | 1 |user1 says hi! |
| 2 | 1 |user2 says seen! |
+---------+----------+-----------------+
Here is the function linking those two models.
Conversation model
public function messages(){
return $this->hasMany('Messages');
}
Messages model
public function conversations(){
return $this->belongsTo('Conversations');
}
Your error message says
messages.conversations_id
But your table schema is listed as
conv_id
So it seems you are using the wrong id field somewhere (I cant tell where as you have not posted enough code - but you should be able to find it. If not - post more code from your model).
Try changing the relation in your Messages model to:
public function conversations() {
return $this->belongsTo('Conversations', 'conv_id');
}
The second parameter specifies the column to use as the foreign key: by default it would be 'conversations_id' (based on the model name).

Query relation in same table with Eloquent ORM in Laravel 4.1

I am just discovering Laravel, and getting into Eloquent ORM. But I am stumbling on a little issue that is the following.
I have three tables with the following structures and data :
words
id | language_id | parent_id | word
-------------------------------------------
1 | 1 | 0 | Welcome
-------------------------------------------
2 | 2 | 1 | Bienvenue
-------------------------------------------
documents
id | title
---------------------
1 | Hello World
---------------------
documents_words
document_id | word_id
--------------------------
1 | 1
--------------------------
As you see, we have a parent/child relationship in the words table.
The documents Model is defined as following
class Documents extends Eloquent {
protected $table = 'documents';
public function words()
{
return $this->belongsToMany('Word', 'documents_words', 'document_id');
}
}
And the words model :
class Word extends Eloquent {
protected $table = 'words';
public function translation()
{
return $this->hasOne('Word', 'parent_id');
}
}
Now my problem is that I want to retrieve documents that have translated words, so I thought this would do it :
$documents = Documents::whereHas('words', function($q)
{
$q->has('translation');
})
->get();
But I get 0 results, so I checked the query that Eloquent generates and uses :
select * from `prefix_documents`
where
(
select count(*) from
`prefix_words`
inner join `prefix_documents_words`
on `prefix_words`.`id` = `prefix_documents_words`.`word_id`
where `prefix_documents_words`.`document_id` = `prefix_documents`.`id`
and (select count(*)
from `prefix_words`
where `prefix_words`.`parent_id` = `prefix_words`.`id`) >= 1
) >= 1
The problem is that it doesn't use aliases for the tables, what my query should be more like this to work (and it does) :
select * from `prefix_documents`
where
(
select count(*) from
`prefix_words`
inner join `prefix_documents_words`
on `prefix_words`.`id` = `prefix_documents_words`.`word_id`
where `prefix_documents_words`.`document_id` = `prefix_documents`.`id`
and (select count(*)
from `prefix_words` as `w`
where `w`.`parent_id` = `prefix_words`.`id`) >= 1
) >= 1
But how can I do this with Eloquent ORM ?
Thanks a lot for your help guys, hope I am clear enough.
In the Word Model, change the
public function translation()
{
return $this->hasOne('Word', 'parent_id');
}
to
public function translation()
{
return $this->belongsToMany('Word', 'words', 'id', 'parent_id');
}
This way we are telling the Laravel to create an alias in the eloquent when using your query. I didn't test the other cases, but I think it will work.

Categories