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

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

Related

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

Transaction in cakephp- 3.0

In my cakephp3.0 application registration page the registration data is inserting into two tables. I am using query builder for doing this. After the successful insertion of data into the first table(Projects table) projectId(primary key of Projects table which is auto incremented value) will return and which is used to insert into second table that is ProjectCustomers table, in projectCustomers table projectId is used as forign key. My controller look like this
public function initialize() {
parent::initialize();
$this->Project = new ProjectsTable();
$this->ProjectCustomer = new ProjectCustomersTable();
}
public function add() {
if ($this->request->is('post')) {
$project_id = $this->Project->putProject($formData['survey_id'], $formData['title'], $formData['operator'], $this->Auth->user('id'));
$this->ProjectCustomer->putProjectCustomer($project_id, $formData['air_id'], $formData['email'], $formData['name'], $formData['company_name'], $formData['department_name'], SYS_ADMIN, PROJECT_CUSTOMER_STATUS_ACTIVE, $date, $date);
return $this->redirect(['action' => 'view', $project_id]);
}
}
What I need is that I want to make this two insertion action in a transaction so that if second insertion is failed anyhow, the first insertion in Projects table should roll back. Thank in advance for the help...

Codeigniter populate nested associations

this is the results i need from the database
{comment: 'hello there', user: [{name: 'sahan', id: 1}], id: 2}
function used to get comments
public function get_comments(){
$query = $this->db->get('comments');
return $query->result_array();
}
I have a comments table and a users table, When a user comments on something the
comment is saved as follows
comment > Comment text
user: userid
So when the data is shown I need codeigniter to populate the user field with the user data found from the users table
Does anyone know how to do this ?
I used this functionality in SailsJS but dont know how to do it here in CodeIG
Sails.js populate nested associations
Codeigniter 's active record is not as advanced as SailJS active record, but you can achieve what you are asking for with two queries within the same method.
I'm assuming the comments table has a foreign key to the users table through a user_id field.
public function get_comments() {
/* Get all the comments */
$query = $this->db->get('comments');
$comments = $query->result_array();
/* Loop through each comment, pulling the associated user from the db */
foreach $comments as &$comment {
$query = $this->db->get_where('users', array('id' => $comment['user_id']));
$user = $query->result_array();
/* Put the user's data in a key called 'user' within the comment array */
$comment['user'] = $user;
/* Remove the unnecessary user_id and $user variable */
unset($comment['user_id']);
unset($user);
}
return $comments;
}

Preserve Order of IN in ORM Order

I'm trying to do a query where I preserve the order of the ids in a IN statement. I can't seem to do it with either the Model Manage Query Builder or the standard ORM 'order' array parameter. Am I missing something? I keep getting:
UNEXPECTED TOKEN IDENTIFIER(, NEAR TO 'id`enter code here`,17743,16688,16650
Here's my model manager:
$query = $this->modelsManager->createQuery('SELECT * FROM Projects WHERE id IN ('.implode(',', array_keys($finalIterations)).')
ORDER BY FIELD(id,'.implode(',', array_keys($finalIterations)).'');
It's pretty obvious PhQL doesn't like the FIELD key word. Is there a way for me to do what I'm trying to do with PhQL? It seems I will not be able to do what I need to.
Unfortunately as previously said, this is missing a feature in Phalcon.
Have a look at this function, I've put it into my ModelBase abstract class which is parent class of all my models. It uses PhQL variable binding, so it's safe for handling direct user input.
You could have reimplemented custom \Phalcon\Mvc\Model\Criteria but this solution seems to be easier to work with, at least for me.
ModelBase abstract
public function appendCustomOrder( \Phalcon\Mvc\Model\CriteriaInterface &$criteria, $orderField, array &$orderValues = [] ) {
if(!empty($orderValues)) {
$queryKeys = $bindParams = [];
foreach($orderValues as $key => $id) {
$queryKey = 'pho'.$key;
$queryKeys[] = ':'.$queryKey.':';
$bindParams[$queryKey] = $id;
}
// TODO: add support for multiple orderBy fields
$criteria->orderBy('FIELD('.$orderField.','.implode(',',$queryKeys).')');
// there's no 'addBind' function, need to merge old parameters with new ones
$criteria->bind( array_merge( (array) #$criteria->getParams()['bind'], $bindParams ) );
}
}
Controller usage
$projectIDs = [17743, 16688, 16650];
$projectsModel = new Projects();
$criteria = $projectsModel->query->inWhere( 'id', $projectIDs );
$projectsModel->appendCustomOrder( $criteria, 'id', $projectIDs );
$projectsData = $criteria->execute();
This will generate valid PhQL syntax similar to this one:
SELECT `projects`.`id` AS `id`, `projects`.`title` AS `title`
FROM `projects`
WHERE `projects`.`id` IN (:phi0, :phi1, :phi2)
ORDER BY FIELD(`projects`.`id`, :pho0, :pho1, :pho2)