0

I want to filter my ORM request with 2 relations many-to-many : Regions and Jobs.

I need paginate, but $final->paginate() is not possible, but i don't know, why.

How ameliorated my code for to use ->paginate() and no Paginatore::make

    /* sélectionne tout les candidats qui sont disponnibles, et qui ont une date inférieure
     * à celle configuré
     */
    $contacts = Candidate::with('regions', 'jobs')
                            ->where('imavailable', '1')
                            ->where('dateDisponible', '<=', $inputs['availableDate'])
                            ->get(); // ta requete pour avoir tes contacts, ou par exemple tu fais un tri normal sur tes regions. Il te reste à trier tes jobs.

    // ajoute un filtre, pour n'afficher que ceux qui reponde true aux 2 test dans les foreachs 
    $final = $contacts->filter(function($contact) use($inputs) {

        // par défaut ils sont false
        $isJob = false;
        $isRegion = false;

       // test que le candidat à l'un des jobs recherché par l'entreprise
       foreach($contact->jobs as $job) {

            // si le job id du candidat est dans la liste, alors on retourne true
            if(in_array($job->id, $inputs['job'])) {

               $isJob = true;
            }
       }
       // test que le candidat accepte de travailler dans l'une des régions echerchées
       foreach($contact->regions as $region) {

            // si region id du candidat est dans la liste, alors on retourne true
            if(in_array($region->id, $inputs['region'])) {

               $isRegion = true;
            }
       }

       // si les 2 renvoie true, alors nous returnons le candidat à la vue
       if($isRegion && $isJob){

            return true;
       }
       else{

            return false;
       }
    });

    // converti le resultat en tableau pour l'importer dans le Paginator
    $finalArray = $final->toArray();

    // calcule le nombre de candidat dans le tableau
    $finalCount = count($finalArray);

    // créer le pagniate manuellement, car on ne peux faire $final->paginate(20)
    $paginator = Paginator::make($finalArray, $finalCount, 2);

    // return la liste des candidats
    return $paginator;

Thanks.

timothylhuillier
  • 451
  • 1
  • 8
  • 20

3 Answers3

1

Alright, third time is the charm:

First, your performance problem comes from the database structure, not the query.

You need to add the following indexes to get a serious performance boost:

ALTER TABLE  `candidate_region` ADD INDEX  `REGION_ID` (  `region_id` )
ALTER TABLE  `candidate_region` ADD INDEX  `CANDIDATE_ID` (  `candidate_id` )
ALTER TABLE  `candidate_job` ADD INDEX  `JOB_ID` (  `job_id` )
ALTER TABLE  `candidate_job` ADD INDEX  `CANDIDATE_ID` (  `candidate_id` )

Pivot tables with the proper indexes work better.

Second, THIS is the (pure) SQL query you want to run:

SELECT * 
FROM candidates 
INNER JOIN candidate_region
ON candidates.id = candidate_region.candidate_id
INNER JOIN candidate_job
ON candidates.id = candidate_job.candidate_id
WHERE imavailable = 1 AND dateDisponible <= '2013-12-31' AND region_id IN (2,3,4,43,42) AND job_id IN (1,2,5,8)

With the indexes above, this query runs under a second. Without the indexes, it timed out on my machine.

Third, this is what this query should look like in Fluent:

DB::table('candidates')
  ->join('candidate_region', 'candidates.id', '=', 'candidate_region.candidate_id'); 
  ->join('candidate_job', 'candidates.id', '=', 'candidate_job.candidate_id'); 
  ->whereIn('candidate_region.region_id',$inputs['region'])
  ->whereIn('candidate_job.job_id',$inputs['job'])
  ->where('imavailable', '1')
  ->where('dateDisponible', '<=', $inputs['availableDate'])
  ->get(); // Or paginate()

This is untested, but should work as-is or with minor modifications.

Enjoy !

Alexandre Danault
  • 8,602
  • 3
  • 30
  • 33
0

Why don't you just replace your filter() with two plain simple whereIn() ?

$contacts = Candidate::with('regions', 'jobs')
                        ->where('imavailable', '1')
                        ->where('dateDisponible', '<=', $inputs['availableDate'])
                        ->whereIn('job_id', $inputs['job'])
                        ->whereIn('region_id', $inputs['region'])
                        ->get();

That way you should be able to use the paginate() like you wanted.

Alexandre Danault
  • 8,602
  • 3
  • 30
  • 33
  • Because i use 2 pivot : Regions and Jobs "candidate_region : id, candidate_id, region_id" and "candidate_job : id, candidate_id, job_id" – timothylhuillier Jun 06 '13 at 17:22
  • It seems you can't use where() with eager-loaded relationships [see source](http://stackoverflow.com/questions/14621943/laravel-how-to-use-where-conditions-for-relations-column) But that same link also shows you how you could do it without eager loading (the ::with(...)) and by using a public method to join() the appropriate pivot tables. If you pass your region and job as parameters to that method, and do two INNER JOINs, you should end up with the filtered data. – Alexandre Danault Jun 06 '13 at 18:11
  • I d'ont underdstand, sorry. I must include 2 joins() in my ORM ? – timothylhuillier Jun 06 '13 at 18:24
0

This is a shot in the dark (I don't have a laravel setup here) but should work pretty much as-is.

Change your Candidate model to add a new method, which does the joining and filtering:

class Candidate extends Eloquent {

    /* Le code existant de ton modèle "Candidate" est ici */


    /* Nouvelle méthode qu'on ajoute au modèle pour le filtrer par region et job */

    public static function forRegionsAndJobs($regions, $jobs)
    {
      return static::join(
        'candidate_region',
        'candidates.id', '=', 'candidate_region.candidate_id'
      )->whereIn('region_id', $regions)
      ->join(
        'candidate_job',
        'candidates.id', '=', 'candidate_job.candidate_id'
      )->where('job_id', $jobs);
    }
}

Then you can call that model with the filter, and paginate the way you want it:

$contacts = Candidate::forRegionsAndJobs($inputs['region'], $inputs['job'])
                            ->where('imavailable', '1')
                            ->where('dateDisponible', '<=', $inputs['availableDate'])
                            ->paginate(25);
Alexandre Danault
  • 8,602
  • 3
  • 30
  • 33
  • Thanks, It's very good, but the resquest takes 13/20 sec ans no return pivot jobs and regions – timothylhuillier Jun 06 '13 at 19:54
  • You could add your ->with(...) back and get your relations, but that would only add more time to the query, and read those tables twice from the DB. This is why I don't like models and Eloquent, writing this query in Fluent or pure SQL would be much easier and also faster to run. – Alexandre Danault Jun 07 '13 at 12:48
  • Thanks, i m trying to make Fluent request. It doesn't work or the request takes 13 sec `DB::table('candidates') ->join('candidates', function($join){ $join->on('candidates.id', '=', 'candidate_region.candidate_id'); }) ->whereIn('candidates.region_id',$inputs['region']) ->join('regions', function($join){ $join->on('regions.id', '=', 'candidate_region.region_id'); }) ->whereIn('candidate_job.job_id',$inputs['job']) ->where('imavailable', '1') ->get();` – timothylhuillier Jun 07 '13 at 13:29
  • How many records in each table ? And how many region IDs and job IDs are you passing to your two whereIn() ? – Alexandre Danault Jun 07 '13 at 13:41
  • My tables contain : `candidate_region = 8 068` total; `candidate_job = 6 735`; `candidate = 4 369`; And im passing `$inputs['region'] : array(2,3,4,43,42)` AND `$inputs['region'] : array(1,2,5,8)` – timothylhuillier Jun 07 '13 at 13:56
  • This query shouldn't take 13 seconds to run, can you send me a sanitized (with personal data blanked out) copy of that DB ? I'll DM you my email on twitter. – Alexandre Danault Jun 07 '13 at 14:03
  • Yes my twitter is @Tim_LHUILLIER, i will senf my email since twitter private message – timothylhuillier Jun 07 '13 at 14:13