Eloquent table relations - sql

I am trying to pull a list of characters that belong to a certain user. When I make the request I get an SQL Error. Reading through the error it is trying to us fields that don't exist.
Error:
SQLSTATE[42000]: Syntax error or access violation: 1066 Not unique table/alias: 'characters' (SQL: select `characters`.*, `characters`.`id` as `pivot_id`,
`characters`.`character_id` as `pivot_character_id`
from `characters` inner join `characters` on `characters`.`id` = `characters`.`character_id` where `characters`.`id` = 1)
"character_id" does not exist in my database. The problem is I can't find where Eloquent is making that field. I looked through the source code and there was a lot of "If this is not provided use $variable.'_id'. I could not find that code anywhere for this though.
Models are below.
class Character extends Eloquent {
protected $guarded = array('id');
protected $table = 'characters';
public function User ()
{
return $this->belongsTo('User', 'id');
}
}
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
protected $table = 'users';
protected $hidden = ['password', 'remember_token'];
protected $guarded = ['password'];
public function Character ()
{
return $this->belongsToMany('Character', 'characters', 'id');
}
}
There is a foreign key between user_id in the characters table, and id in the users table.

belongsToMany is for many-to-many relations. Laravel throws you an error because it expects third table - pivot table - containing both character_id and user_id.
If you dont want many-to-many but one-to-many then you should use hasMany and belongsTo.

Related

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 ,php -artisan suffixe had to my table when i seed it

I have 2 small issues with the "php artisan db:seed" command.
WHen i run the command, i have this error message :
"SQLSTATE[42S02] Base table or view not found : 1146 La table
"bootstrap_template_commerciauxes" n'existe pas ..."
The problem is : my table name is commerciaux, and not commerciauxes.
I checked all my file, my model is Commerciaux.php, my factory CommerciauxFactory.
So ... what kind of sorcely is it ? I'am missing something ?
Secondly, the SQL request from db:seed add some columns i dont want to :
SQLSTATE[42S02]: Base table or view not found: 1146 La table 'bootstrap_template.commerciauxes' n'existe pas (SQL: insert into commerciauxes (nom, prenom, ville, updated_at, created_at) values (Dr. Luis Champlin PhD, Dr. Luella Leuschke, Leathaberg, 2022-06-03 21:42:44, 2022-06-03 21:42:44))
Here is my Commerciaux model :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Commerciaux extends Model
{
use HasFactory;
protected $fillable = [
'nom',
'prenom',
'ville',
'nbre_commande',
];
}
My CommerciauxFactory (in case)
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class CommerciauxFactory extends Factory
{
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
'nom' => $this->faker->name(),
'prenom' => $this->faker->name(),
'ville' => $this->faker->city(),
];
}
}
Thanks you very much for your time, i wanted to try this nice tool but i get blocked since 2 days on thoses mistakes.
To answer your issues:
Laravel by default treats table names as plural due to default conversion and I would advise keeping it that way. If you want to define your own table name then, In your model Class you can define following for your name of table:
protected $table = 'commerciaux';
Also, in your migration's Up function, set your table name like following:
public function up()
{
Schema::create('commerciaux', function (Blueprint $table) {
//Your table columns and structure
});
}
Regarding the additional columns, those are laravel timestamps that keep a track of the timestamps of the record when it was created (created_at) and updated(updated_at) last time. In this case, I would also suggest keeping these fields as they keep a track of record creation and last modifying timestamps.
If you don't want these fields in your table then in your model you can define the following code to exclude the timestamps:
public $timestamps = false;
Other than that, you can also remove following line from your migration:
table->timestamps();
EDIT: Before running migration again, try the roll back command so the created base table and migration records can get deleted from the migrations table.

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);

CRUD very slow with conditions, is there any other faster way?

im having problem with CRUd now that i filled the database. CRUD is taking ages to show, becouse it takes condition from M:M tables.
Tables:
Table USER. has many labels (hasMany)
Table LABLE, has many users (hasMany)
Intermidiate Table UserLabel, has two hasOne
I want to show all users from some label with CRUD like this:
MODEL USER:
class Model_User extends Model_Table {
public $table ='user';
function init(){
parent::init();
$this->addField('fbid')->mandatory('Facebook id required');
...
$this->hasOne('Application');
$this->hasMany('UserLabel');
$this->addExpression('ratio')->set(function($model,$select){
return $select->expr('ROUND(([f2] / [f1]) * 100,0)')
->setCustom('f1',$model->getElement('sends'))
->setCustom('f2',$model->getElement('clicked'));
});
$this->addHook('beforeSave',function($m){
$m['updated']=$m->dsql()->expr('now()');
});
}
MODEL LABEL:
class Model_Label extends Model_Table {
public $table ='label';
function init(){
parent::init();
$this->addField('name')->mandatory('Name required');
$this->addFIeld('application_id')->refModel('Model_Application')->defaultValue($this->api->recall('app'))->system(true);
$this->addField('active')->type('boolean')->defaultValue('true')->system(true);
$this->addField('created')->type('timestamp')->defaultValue($this->dsql()->expr('now()'))->system(true);
$this->addField('updated')->type('timestamp')->system(true);
$this->hasMany('UserLabel');
$m = $this->add("Model_UserLabel");
$this->addExpression("users", $m->dsql()
->field($m->dsql()->expr("count(*)"), "all users")
->where("label_id", $this->getField("id"))
);
MODEL USER LABEL
class Model_UserLabel extends Model_Table {
public $table ='userlabel';
function init(){
parent::init();
$this->hasOne('User');
$this->hasOne('Label');
}
}
CODE FOR CRUD
$c = $this->add('CRUD');
$c->setModel('User', array('name', 'gender','country','city'));
$c->model->addCondition('id','in',
$this->add('Model_UserLabel')->addCondition('label_id', $_GET['l'])->dsql()->field('user_id')
);
Is there any better way to do this?
ps. I tested this solution, it is a lot faster but still very slow at around > 5.000 users:
//get all users
$records = $this->api->db->dsql()->option('distinct')->table('user')->join('userlabel.user_id')->field('user.id')->where('userlabel.label_id',$_GET['l'])->do_getAll();
foreach($records as $record){
$users .= ','.$record['id'];
}
//create CRUD
$c = $this->add('CRUD');
$c->setModel('User', array('name', 'gender','country','city','sends','clicked','ratio'));
$c->model->addCondition("application_id", $this->api->recall('app'));
$c->model->addCondition('id','in',
'('.$users.')'
);
Source code express more than words, so you better add your model definition source code (maybe not full) in your question.
What should be one row in your CRUD/Grid? I guess it's not 1 user = 1 row, but 1 user_label should be one row in grid. So you should set UserLabel model as model for your grid.
And then define some additional fields in Model_UserLabel by joining them from user and/or label tables directly like this:
class Model_UserLabel extends SQL_Model {
function init() {
parent::init();
// ...
// fields from user table
$join_u = $this->join('user', 'user_id');
$join_u->addField('username'); // this adds fields in current model from joined table
$join_u->addField('email');
// fields from label table
$join_l = $this->join('label', 'label_id');
$join_l->addField('name');
}
}
Note: source code above is untested and put here only as example.
EDIT:
Try this solution - almost the same as I wrote earlier above:
MODEL USER LABEL
class Model_UserLabel extends Model_Table {
public $table ='userlabel';
function init(){
parent::init();
$this->hasOne('User');
$this->hasOne('Label');
// join user table and add fields to this model from joined user table
$j = $this->join('user', 'user_id');
$j->addField('name');
$j->addField('gender');
$j->addField('country');
$j->addField('city');
}
}
CODE FOR CRUD
$m = $this->add('Model_UserLabel'); // UserLabel here not User
$m->addCondition('label_id', $_GET['l']); // and then this is simple
$c = $this->add('CRUD');
$c->setModel($m, array('name', 'gender','country','city'));
Try this solution and as (almost) always - code is untested.
EDIT:
Please try this version - is it working faster? That's basically your P.S. example, but you shouldn't extract all user IDs, join them and then create huge select with a lot of 'in'.
Faster result should be if you could do all with just one DB request without any additional processing of data.
// parameters
$app_id = $this->api->recall('app);
$label_id = $_GET['l'];
// prepare model for grid
$m = $this->add('Model_User'); // default User model
$m->_dsql()->option('distinct') // add join to userlabel table + conditions
->join('userlabel.user_id')
->where('userlabel.label_id', $label_id)
->where($m->getField('application_id'), $app_id);
// create CRUD and set it's model. All conditions already set on model above
$c = $this->add('CRUD');
$c->setModel($m, array('name', 'gender','country','city','sends','clicked','ratio'));
NOTE: Source code above as often - untested :)

Doctrine2 update many-to-many relations

I hava relations Many-to-Many with Product entity and Feature entity
Product entity:
/**
* #ORM\ManyToMany(targetEntity="Feature")
* #ORM\JoinTable(name="Product_Feature",
* joinColumns={#ORM\JoinColumn(name="Product_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="Feature_id", referencedColumnName="id")}
* )
*/
private $features;
Feature entity:
/**
* #ORM\ManyToMany(targetEntity="Product", mappedBy="features")
* #ORM\OrderBy({"position" = "ASC"})
*/
private $products;
ProductRepository.php:
public function updateFeatures($id, $featuresIds)
{
return $this->getEntityManager()->createQueryBuilder()
->update('TestCatalogBundle:Product', 'p')
->set('p.features', ':features')
->where('p.id = :id')
->setParameter('features', $featuresIds)
->setParameter('id', $id)
->getQuery()
->getResult();
}
But when I call updateFeatures I get error:
features = :features': Error: Invalid PathExpression.
StateFieldPathExpression or SingleValuedAssociationField expected
How can I update Product_Feature table? Also I can't delete all features from Product_Feature by product's id.
I changed my controller in next way:
$em = $this->getDoctrine()->getEntityManager();
$features = $em->getRepository('TestCatalogBundle:Feature')->findBy(array('id' => $featureIds));
$product = $em->getRepository('TestCatalogBundle:Product')->find($id);
$product->getFeatures()->clear();
foreach ($features as $feature) {
$product->addFeature($feature);
}
$em->persist($product);
$em->flush();
But if I use native sql I need 2 queries for deleting features and insert new features. But here I need 2 select queries. Maybe I made this task wrong?
You're doing it the wrong way. You should read this chapter of the documentation: Working with associations. You should add an "inversedBy" keyword in the $features field of the Product class.
When you have a bi-directional many-to-many relation, the usual way to do this is:
$product->getFeatures()->add($feature); // Or $product->setFeatures($features);
$feature->getProducts()->add($product);
$em->persist($product);
$em->persist($feature);
$em->flush();