Laravel Eloquent - retrieving specific columns from related model - orm

I'm new to Laravel and still getting my head around Eloquent. I have two models (A, B) with a many-to-many relationship (belongsToMany in both models) with the following fields:
A: id, name, text, etc..
B: id, name, description, info, etc...
A_B: id, A_id, B_id
When retrieving an object of 'A', I just want to retrieve the 'id' and 'name' of its related 'B', rather than the entire 'B' objects - as these could be large. Is a way to retrieve only specific columns from a related model, or would it be better to separate my tables so that 'id' and 'name' are on their own?

I'm not really sure if grabbing all of the columns from table 'B' would effect speed of the query too much, but you can try this:
Model class for table 'A_B'
class A_B extends Eloquent {
public function a(){
$this->hasMany('A', 'id', 'a_id');
}
public function b(){
$this->hasMany('B', 'id', 'b_id');
}
}
In your model for table 'B'
class B extends Eloquent {
//Hide fields from displaying in your query
protected $hidden = array('description', 'info');
}
In your controller:
//Get all records with A and B
$all = A_B::with('a')->with('b')->get();
return View::make('view')->with('all', $all);
In your view:
#if($all->count() > 0)
#foreach($all as $record)
{{ $record->id }}
{{ $record->name }}
#endforeach
#else
There are no records to be displayed
#endif

Related

select model B with model A column name condition in laravel model

I have two models with different name and columns but the purpose/data of the two models is the same, namely, it contains the employee's name, here the model
Employee
name
code
country
city
John
A1
USA
NYC
Doe
A2
USA
LA
New_Employee
v_fullname
v_code
v_country
v_city
Mark
ZZ1
USA
LS
Zuc
FF2
USA
LS
as you can see the column name is different but the purpose is identical. I want to select data from New_Employee but use column name from Employee, so the query will look like this
SELECT v_fullname as name, v_code as code, v_country as country, v_city as city
FROM New_Employee
WHERE name = 'Mark'
Sorry if my explanation is hard to understand, but here's the code I have tried
SyncEmployee model (this model is like a bridge connecting employee and new_employee model)
<?php
namespace App\Models;
use App\Models\Employee;
use App\Models\NewEmployee;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class SyncEmployee extends Model
{
use HasFactory;
protected $connection = 'mysql_2';
public const TABLE_NAME = 'new_employee';
public function index()
{
$data = NewEmployee::select('v_fullname as name, v_code as code, v_country as country, v_city as city')->get();
return view('view_name', compact('data'));
}
}
I thought with that code when I call SyncEmployee::where('code', '=', 'ZZ1') from controller, the result will
name
code
country
city
Mark
ZZ1
USA
LS
*The data is from New_Employee but the column name using Employee
You could attempt to use the ability to hide or append attributes at serialization to do most of the work for you. You would need to define accessors and mutators and define what is 'hidden' and 'appended' for serialization:
use Illuminate\Database\Eloquent\Casts\Attribute;
class NewEmployee extends Model
{
...
protected $table = 'new_employee';
protected $hidden = [
...
'v_fullname',
'v_code',
'v_country',
'v_city',
];
protected $appends = [
...
'name',
'code',
'country',
'city',
];
protected function name(): Attribute
{
return Attribute::make(
get: fn () => $this->attributes['v_fullname'] ?? null,
set: fn ($v) => $this->attributes['v_fullname'] = $v
);
}
...
}
If you are not using the Model's data after serialization you can still access these fields:
// will hit the accessor
$newEmployee->code;
// will hit the mutator
$newEmployee->code = 'blah';
Laravel 9.x Docs - Eloquent: Mutators and Casting - Accessors & Mutators
Laravel 9.x Docs - Eloquent: Serialization - Hiding Attributes From JSON
Laravel 9.x Docs - Eloquent: Serialization - Appending Values to JSON

Laravel 8 left join where clause is ambiguous

I'm trying to use leftJoin but I'm having problem with conflicts between equal names in both tables.
Tables
products: added_by - user_id - published - approved - featured
product_types: added_by - user_id - published - approved - featured
$products = ProductType::
leftJoin('products', 'products.product_type_id', '=', 'product_types.id')
->select('product_types.*')
How to solve this problem?
You should start by implementing the correct relationships, it will help you in the future.
class ProductType extends Model {
public function products(): HasMany
{
return $this->hasMany(Product::class);
}
// Add the other missing relations too (user, etc)
}
class Product extends Model {
public function productType(): BelongsTo
{
return $this->belongsTo(ProductType::class);
}
// Add the other missing relations too (user, etc)
}
You can now use:
// Query all Product Types with related products
$productTypes = ProductType::with('products')->get();
// Query all ProductTypes that have products
$productTypes = ProductType::withWhereHas('products')->get();
/** #var ProductType $productType */
foreach ($productTypes as $productType) {
// You have access to the collection of associated products, in memory
$productType->products;
}
More information on eloquent relationships in https://laravel.com/docs/9.x/eloquent-relationships

Laravel/SQL: How to fetch data from multiple table in a single query? that too using 'where'

Working on a search functionality on Laravel App(Blog/Posts).
There are multiple types of posts (each having a separate table in the database)
Like Business posts, Social Life posts etc..
Below is the search function on SearchController
class SearchController extends Controller
{
public function search(Request $request, $query = null)
{
if($query == null)
return redirect()->route('home');
$search = Business::where([['title','like','%'.$query.'%'],['status','=',1]])
->orWhere([['description','like','%'.$query.'%'],['status','=',1]])
->paginate(10);
return view('front.search',[
'results' => $search,
'query' => $query
]);
}
}
So basically my question is how to add other types of Post's table also?
My main motive is that when someone searches for anything, the result should be fetched from all types of posts table(business, nature, life & so on..).
You have to maintain common id in both the table
NOTE: Join is the preferable method
$querys = DB::table('Business')->where([['Business.title','like','%'.$query.'%'],['Business.status','=',1]])
->orWhere([['Business.description','like','%'.$query.'%'],['Business.status','=',1]]);
$querys->join('socialtable','socialtable.userid','=','Business.userid');
// Just join the social table
$querys->where('socialtable.title', 'like','%'.$query.'%');
$result = $querys->paginate(10);
If you have a model called Book, like this:
class Book extends Model
{
/**
* Get the author that wrote the book.
*/
public function author()
{
return $this->belongsTo('App\Author');
}
}
Then you can retrieve all of your books with authors like this:
$books = App\Book::with(['author'])->get();
Check out Eager loading from Laravel documentation.
Just add table name before every field
$querys = DB::table('Business')->where([['Business.title','like','%'.$query.'%'],['Business.status','=',1]])
->orWhere([['Business.description','like','%'.$query.'%'],['Business.status','=',1]]);
$querys->join('socialtable','socialtable.userid','=','Business.userid');
// Just join the social table
$querys->where('socialtable.title', 'like','%'.$query.'%');
$result = $querys->paginate(10);

Laravel 5.5, Display news data from two tables

I am new to laravel and I experience some trouble. I try to obtain data stored in two different tables and display them:
News.php (model)
public static function Data($category) {
$perPage = config('var.news.perPage');
if ($category) {
$news = News::orderBy('id', 'desc')->where('category', $category)->SimplePaginate($perPage);
} else {
$news = News::orderBy('id', 'desc')->SimplePaginate($perPage);
}
return $news;
}
This is how I grab all data from News table which struct is:
id, title, body, created_at updated_at, created_by, updated_by, category
The category column contains values separated by comma, e.g. 1,2,3,4
Now, I have another table, News_Cat which has id, name columns.
In another method I try to grab the filters names against values stored in category column of News table
public static function getFilterNames($id) {
$filters = DB::table('News_Cat')
->select('News_Cat.name as name')
->leftJoin('News', DB::raw('CAST(News_Cat.id as nvarchar)'), DB::raw('ANY(SELECT(News.category))'))
->where('News.id', $id)
->get();
return $filters;
}
However, it completely does not work. What I try to achieve is to display filter name in view.blade as 'name' value for specified filter from News_Cat
#if($news->count())
#foreach($news as $article)
<a href="{{ route('news.show', $article->id) }}" class="item angled-bg" data-filters="{{ $filters }}">
<div class="row">
So as result I would get e.g. data-filters="news, update, hot, latest"> instead data-filters="1,2,3,4">
Thank you
You should use eloquent!
In your News Model
public function getFiltersAttribute(){
$categories = explode(',', $this->category);
return implode(', ', NewsCat::find($categories)->pluck('name')->toArray());
}
then in your view :
{{ $article->filters }}
will output news, update, hot, latest
BUT
You should use a pivot table between your categories and your news, it would be much easier.
This method can't allow you to eager load the relationship and make a request for each news
If you can't change your database structure, I can propose you this:
In the boot method of your AppServiceProvider:
Config::set('tags', NewsCat::all());
THEN
public function getFiltersAttribute(){
$categories = explode(',' $this->category);
return implode(', ', config('tags')->whereIn('id', $categories)->pluck('name')->toArray());
}
MANY TO MANY METHOD
I am using laravel naming convention for the table :
news, categories_news (the pivot), and categories
You will have 2 models : New and Category
In your New Model
public function categories(){
return $this->belongsToMany(Category::class)
}
in your Category Model :
public function news(){
return $this->belongsToMany(New::class);
}
if you are not using laravel naming conventions, you will have to customize these raltionship like this : https://laravel.com/docs/5.5/eloquent-relationships#many-to-many

GORM Domain Mapping Issue

I've got a bit of a complicated domain model I'm trying to implement and I'm having some trouble. (On top of that, I'm quite new to all this!)
I have a User domain which has multiple roles and multiple tests. The Role domain works great. The Test domain is a bit more compilciated though because it requires two foreign keys instead of just 1 like in the Role domain. The first foreign key is the user_id and the second is a uni_id (university ID).
The User domain model contains the following
class User {
static hasMany = [roles:Role, tests:Test]
Integer userId
...
static mapping = {
table 'user_data'
id generator: 'assigned', name: 'userId', type: 'long'
userId column: 'user_id'
version false
roles joinTable:[name:'user_role', key:'user_id']
tests joinTable:[name:'user_test', key:'user_id'] // Here is where I run into trouble
}
static constraints = {
}
}
The Test domain contains
class Test {
static belongsTo = User
static hasMany = [users:User]
static hasOne = [uni:Uni]
Integer testId // primary key
String testType
static mapping = {
table 'test'
id generator: 'assigned', name: 'testId', type: 'long'
testId column: 'test_id'
users joinTable:[name:'user_test', key:'test_id']
uni joinTable:[name:'user_test', key:'test_id'] // If I leave this out, everything is groovy
version false
}
static constraints = {
}
}
and the Uni domain contains
class Uni {
static belongsTo = Test
static hasMany = [tests:Test]
Integer uniId // primary key
String shortName
String fullName
static mapping = {
table 'uni'
id generator: 'assigned', name: 'uniId', type: 'long'
uniId column: 'uni_id'
version false
tests joinTable:[name:'user_test', key:'uni_id']
}
static constraints = {
}
}
If its not clear, what I'm trying to do is pull in the University ID, Test ID, and User ID to a table user_test to find based on the User ID which tests they have taken. Is there a simple way to do this?
The kinds of errors I'm getting lead me to believe that for some reason it is trying to perform all actions on the table test instead of user_test. For example,
Unsuccessful: alter table test add uni_id int not null
I'd like to be able to access the test and university information corresonding to the specific user via user.tests.testType and user.tests.uni.fullName or something to that extent. What am I doing wrong? More importantly, is there a better way to do this?! Thanks in advance!
Edit 1: something interesting I just thought of.. a user can have multiple tests, but the inverse isn’t true. A given test will never be shared among multiple people. So I think that changes things a bit.. I'll do some reading and post if I come up with anything new.
Edit 2: Here's the Role domain
class Role {
static belongsTo = User
static hasMany = [users:User]
Integer roleId
String shortName
String roleName
Integer roleLevel
static mapping = {
table 'role'
id generator: 'assigned', name: 'roleId', type: 'long'
roleId column: 'role_id'
users joinTable:[name:'user_role', column:'user_id', key:'role_id']
version false
}
static constraints = {
}
}
Edit 3: I am now trying to store all test information in the Test domain model and simply choose the Uni name to store as a field in Test, but am getting weird errors when I try this. My new files look like this
class User {
static hasMany = [roles:Role, tests:Test]
Integer userId
static mapping = {
table 'user_data'
id generator: 'assigned', name: 'userId', type: 'long'
userId column: 'user_id'
version false
roles joinTable:[name:'user_role', key:'user_id']
}
static constraints = {
}
}
and
class Test {
static belongsTo = User
Integer testId // primary key
Integer testTypeId
String testTypeName
String testUni
Date testDate
static mapping = {
table 'test'
id generator: 'assigned', name: 'testId', type: 'long'
testId column: 'test_id'
version false
}
static constraints = {
}
}
but now I'm getting the following error when I try to run it Caused by: org.hibernate.MappingException: Missing type or column for column[tests_test] on domain[User] referencing[Test]
Any idea what that's about?
Ok, one issue you have is that you're trying to share the User-to-Test association join table with the Test-to-Unit association. That's not going to work.
Lets look at it in database terms. I'm not an ASCII art expert, so I hope this diagram doesn't make your eyes bleed.
user_data (userId) |---|< (user_id) user_test (test_id) >|---| (testId) test
The diagram above shows the database implementation of the many-to-many association between the User and Test domain classes. You can see that the user_data.userId links to user_test.user_id and user_test.test_id links to test.testId.
Now here's where it starts to get weird. There are two different associations between Test and Uni: a bidirectional one-to-one and a one-to-many. I just don't understand that. But I want to illustrate an important issue with your join tables, so here it is.
test (testId) |---|< (test_id) user_test (uni_id) >|---| (uniId) uni
Because you're using the same join table (user_test) for two different associations you're asking GORM to create a table like this:
USER_TEST
- USER_ID
- TEST_ID
- UNIT_ID
GORM won't do that because join tables are supposed to have only two fields. Not only that, but also you're defining a many-to-many in database terms, and yet a bidirectional one-to-one and a one-to-many in GORM terms. Ouch!
TODO
The first change I recommend is to use a different join table for the Test-Uni association.
Finally got everything working (after a bit of modification in terms of the domain model)
class User {
static hasMany = [roles:Role, tests:Test]
Integer userId
static mapping = {
table 'user_data'
id generator: 'assigned', name: 'userId', type: 'long'
userId column: 'user_id'
version false
roles joinTable:[name:'user_role', column:'role_id', key:'user_id']
}
static constraints = {
}
}
and
class Test {
User user
Integer testId // primary key
String testType
String testUni
Date testDate
static mapping = {
table 'test'
id generator: 'assigned', name: 'testId', type: 'long'
testId column: 'test_id'
version false
}
static constraints = {
}
}
with
class Uni {
Integer uniId // primary key
String shortName
String fullName
static mapping = {
table 'uni'
id generator: 'assigned', name: 'uniId', type: 'long'
uniId column: 'uni_id'
version false
}
static constraints = {
}
}
So now what I'm doing is selecting the university from a drop down tab in my GSP and just saving it in Test as the string testUni. Then, the big change was removing all joinTables between the three and adding User user to Test. I'm still a little fuzzy on why what I was doing before didn't work, but I won't complain about a working app!