Laravel multiple where clauses in query from given array - sql

I hope the title describes my problem good as enough.
I tried to make a geosearch-function in laravel. The queries as its own are correct. Now I try to get all articles from my table, whose match with the gotten zipcode of a former query. All functions I use you can found here: Laravel 5 add results from query in a foreach to array). But now I want to perform one query, within multiple or dynamic where clauses (with or).
The print_r($zipcodes) of my former query (get all zipcodes in a range from a zipcode $zipcodes = $this->getZipcodes($zipCoordinateId, $distance);) outputs:
Array
(
[0] => stdClass Object
(
[zc_zip] => 13579
[distance] => 0
)
[1] => stdClass Object
(
[zc_zip] => 12345
[distance] => 2.228867736739
)
[2] => stdClass Object
(
[zc_zip] => 98765
[distance] => 3.7191570094844
)
)
So how should my query in laravel should look, when I want to perform following?
SELECT *
FROM articles
WHERE zipcode = '13579'
OR zipcode = '98765'
OR zipcode = '12345';
Thank you in advance,
quantatheist
UPDATE
With the solution from balintant this is working fine. Here is my code:
// grabs all zipcodes matching the distance
$zipcodes = $this->getZipcodes($zipCoordinateId, $distance);
foreach ($zipcodes AS $key=>$val)
{
$zipcodes[$key] = (array) $val;
}
$codes = array_column($zipcodes, 'zc_zip');
$articles = Article::whereIn('zipcode', $codes)->get();
return view('pages.intern.articles.index', compact('articles'));

You can use both the whereIn and orWhere scopes. The first one better fits to your current example. Also, you can use array_column to get all the real zip codes from the array above.
$query->whereIn('zip', [12,34,999])->get();
// > array
Update:
When you want to use array_column to get the specific subvalues of the array (like zc_zip) you must first transform it's childs to an array. If it's a model you must transform it easily with toArray().
$zip_objects = [
(object) [ 'zc_zip' => 13579, 'distance' => 0 ],
(object) [ 'zc_zip' => 12345, 'distance' => 2.228867736739 ],
(object) [ 'zc_zip' => 98765, 'distance' => 3.7191570094844 ],
];
foreach ( $zip_objects AS $key=>$val )
{
$zip_objects[$key] = (array) $val;
}
$zip_codes = array_column($zip_objects, 'zc_zip');
var_dump($zip_codes);
// > array(3) {
// > [0]=>
// > int(13579)
// > [1]=>
// > int(12345)
// > [2]=>
// > int(98765)
// > }

Related

Query efficiency -> merge 2 queries with a join or union

I need some serious help/direction. I have two tables:
students
-id
-site_id
-name
-enter_date
-exit_date
student_meals
-id
-site_id
-student_id
-meal_type_id (1, 2, or 3)
-date_served
I need two arrays:
All students enrolled on the requested 'serviceDate' ('serviceDate is between their enter_date and exit_date) that DO NOT have a meal_type_id of the requested mealType on the rquested serviceDate.
All students enrolled on the requested 'serviceDate' ('serviceDate is between their enter_date and exit_date) that DO have a meal_type_id of the requested mealType on the requested serviceDate.
I got it to work with the following:
'unservedStudents' => Auth::user()->site
->students()
->where('enter_date', '<=',Request::only( 'serviceDate') )
->where('exit_date', '>=',Request::only( 'serviceDate') )
->OrderByName()
->filter(Request::only('search', 'serviceDate', 'mealType'))
->get()
->map(fn ($students) => [
'id' => $students->id,
'name' => $students->name,
]),
'servedStudents' => Auth::user()->site
->student_meals()
->with('student')
->where('meal_type_id', Request::only( 'mealType'))
->where('date_served', Request::only( 'serviceDate'))
->orderBy('created_at', 'DESC')
->get()
->map(fn ($served_students) => [
'id' => $served_students->id,
'student' => $served_students->student ? $served_students->student->only('id','name') : null,
]),
//Filter for students
public function scopeFilter($query, array $filters)
{
$mealType = $filters['mealType'] ?? null;
$serviceDate = $filters['serviceDate'] ?? null;
$search = $filters['search'] ?? null;
$query
->when($search, function ($query) use ($search) {
$query->where( fn ($query) =>
$query->where('first_name', 'like', '%'.$search.'%')
})
->when( $mealType, function ($query) use ($mealType, $serviceDate) {
$query->whereDoesntHave('student_meals', fn ($query) =>
$query->where('meal_type_id', $mealType )
->where('date_served', $serviceDate));
});
When I seeded my database ites that have more than 400 students or so gets really slow. I'm pretty sure I need to condense the two queries above, but I can't figure out the logic.
Below is an attempt, but it gives me an error 'Method Illuminate\Database\Eloquent\Collection::getBindings does not exist'.
$students = Auth::user()->site
->students()
->join('student_meals as m', 'm.student_id', '=', 'students.id')//this is my attempt to get the same columns as the table to union....
->where('enter_date', '<=',Request::only( 'serviceDate') )
->where('exit_date', '>=',Request::only( 'serviceDate') )
->where('date_served', '=',Request::only( 'serviceDate') )
->filter(Request::only('search', 'serviceDate', 'grade', 'hr'))
->select('students.id as studentId', 'first_name', 'students.site_id as siteId', 'm.id as mealId', 'm.meal_type_id', )
->get()
->map(fn ($students) => [
'id' => $students->studentId,
'name' => $students->first_name,
'siteId' => $students->site_id,
'mealId' => $students->mealId,
'mealType' => $students->meal_type_id,
]),
'student_meals' => Auth::user()->site
->student_meals()
->join('students as s', 's.id', '=', 'student_meals.student_id')
->where('date_served', '>=',Request::only( 'serviceDate') )
->where('meal_type_id', '>=',Request::only( 'mealType') )
->select('s.id as studentId', 'first_name',
's.site_id as siteId', 'student_meals.id as mealId', 'meal_type_id')
->union($students)
->map(fn ($students) => [
'id' => $students->studentId,
'name' => $students->first_name,
'siteId' => $students->site_id,
'mealId' => $students->mealId,
'mealType' => $students->meal_type_id,
]),
If you're up for it, I'd really appreciate any insight/help/pointers/tips.
I think that your problem is very simple if you use the collections
//Relation name should be meals instead of student_meals because is redundant that a student has many student meals
$students = Student::with([
'meals' => function ($query) use ($request) {
$query->where('date_served', $request['serviceDate']);
}
])
->where('site_id', $request->user()->site_id)
->where('enter_date', '<=', $request['serviceDate'])
->where('exit_date', '>=', $request['serviceDate'])
->get();
At this point you have all students that has the requested serviceDate between enter_date and exit_date and belongs to the same site_id of the current user (lazy loading all the meals of the student that belongs to the requested serviceDate), so, all you have to do is spread them in two different collections.
//Students with requested meal type
$swrmt = collect();
//Students without requested meal type
$swtrmt = collect();
foreach ($students as $student) {
//If student contains at least one meal with the requested mealType
if ($student->contains('meals.meal_type_id', $request['mealType'])) {
$swrmt->push($student);
} ese {
$swtrmt->push($student);
}
}
So you only have one query, and only need to be worried if the result is greater than 2000 students, if that happens would be necesary to change the with for a load using chunk of 2000 for preventing limit param query error. (Sorry if there is any type mistake, i write all of this on my cellphone), and don't forget to add your name filter at the main query with the same when that you alredy use.

How can I order a row into first position?

I have this form :
$builder
->add('restaurantsFilter1', 'entity', [
'label' => 'Commune',
'empty_value' => 'Dans toute la Narbonnaise',
'class' => 'AppBundle:City',
'choice_label' => 'name',
'query_builder' => function (EntityRepository $er) {
return $er
->createQueryBuilder('c')
->addSelect('d')
->leftJoin('c.documents', 'd')
->where('d.type = :type')
->orderBy('c.name')
->setParameter('type', Document::T_VILLAGE)
;
},
])
which is a select which displays a list of cities.
A client told me that he needed a field "Around me" which will display all cities around 20 km.
So to do so, I created a new city in my database with this name, but now I need to put it in the first position of my select.
In sql I would use something like ORDER BY (insee_code= '[specific_code_of_the_city]') but I dont know how I could that with the query builder.
Do you have an idea how I could do that with the symfony query builder ?
EDIT: That's the exact issue that How do I return rows with a specific value first?
You could create a hidden field and order by that.
return $er
->createQueryBuilder('c')
->addSelect('CASE
WHEN c.name = "specific_code_of_city"
THEN 0
ELSE 1
END as HIDDEN presetOrder')
->addSelect('d')
->leftJoin('c.documents', 'd')
->where('d.type = :type')
->orderBy('presetOrder', 'ASC')
->addOrderBy('c.name', 'ASC')
->setParameter('type', Document::T_VILLAGE)
;

Cakephp: find in a controller using "like" inside an array of possible values

The main idea of this post is that I need to combine search inside an array via "like" without doing a fetch.
In the following code I extract the zones (part of postal code) depending on session ID:
$sess_id = $this->Session->read('Auth.User');
$this->loadModel('Zone', 2);
$MyZones = $this->Zone->find('list', array('recursive' => -1, 'conditions' => array('Zone.user_id' => $sess_id['id']), 'fields' => array('Zone.zone')));
$this->set('MyZones', $MyZones);
print_r($MyZones);
//output : $MyZone = Array ( [1] => AB [2] => AL [3] => B1 )
Now that I have my array I want to look for persons who live in this zone (example his postalcode can be AB526PQ so he lives in the administrator zone)
$this->loadModel('Person', 2);
$num_persons = $this->Person->find('count', array('recursive' => -1, 'conditions' => array('Person.pc LIKE' => '%' . $MyZones)));
$this->set('num_persons', $num_persons);
I got the following error :
Notice (8): Array to string conversion [APP\Controller\ZonesController.php]
This is the generated sql query:
SELECT COUNT(*) AS `count` FROM `tn`.`personnes` AS `Personne` WHERE `Personne`.`pc` LIKE '%Array'
You will have to break your condition from
$conditions = array('Person.pc LIKE' => '%' . $MyZones);
to
foreach($MyZones as $MyZone) {
$conditions[] = array('Person.pc LIKE' => '%' . $MyZone);
}

PDO: Passing extra parameters to a prepared statment than needed

Can you send more parameters than needed to a prepared statement using PDO with no undesired side effects?
That mights seem like a strange question but I ask because I have 4 queries in a row which all use similar and different parameters. The relevant parts of the queries:
1st (select, different table to others):
WHERE threadID = :tid
2nd (select):
WHERE user_ID = :u_ID AND thread_ID = :tid
3rd (update if 2nd was successful):
SET time = :current_time WHERE user_ID = :u_ID AND thread_ID = :tid
4th (insert if 2nd was unsuccessful):
VALUES (:u_ID, :tid, :current_time)
Can I declare one array with the three parameters at the beginning and use it for all 4 queries?
To sort out any confusion, the queries would be executed seperately. It is the parameters variable being reused and so that would mean some queries would receive parameters they don't need. So something like:
$parameters = array(':tid' => $tid, ':u_ID' => $u_ID, ':current_time' => $time);
$1st = $db->prepare($query1);
$1st->execute($parameters);
$2nd = $db->prepare($query2);
$2nd->execute($parameters);
$3rd = $db->prepare($query3);
$3rd->execute($parameters);
$4th = $db->prepare($query4);
$4th->execute($parameters);
If I can, should I? Will this slow down or cause security flaws to my database or scripts?
If I can make this question a bit clearer, please ask.
Thank you!
Perhaps the documentation has been updated since this question was first asked, but now it is quite clearly stated "No"
You cannot bind more values than specified; if more keys exist in input_parameters than in the SQL specified in the PDO::prepare(), then the statement will fail and an error is emitted.
These answers should be useful in filtering out the extra parameters.
I know this is already answered and it's only asking about whether you can send extra params, but I thought people might arrive at this question, and want to know how to get around this limitation. Here's the solution I use:
$parameters = array('tid' => $tid, 'u_ID' => $u_ID, 'current_time' => $time);
$1st = $db->prepare($query1);
$1st->execute(array_intersect_key($parameters, array_flip(array('tid'))));
$2nd = $db->prepare($query2);
$2nd->execute(array_intersect_key($parameters, array_flip(array('u_ID', 'tid'))));
$3rd = $db->prepare($query3);
$3rd->execute(array_intersect_key($parameters, array_flip(array('u_ID', 'tid', 'current_time'))));
$4th = $db->prepare($query4);
$4th->execute(array_intersect_key($parameters, array_flip(array('u_ID', 'tid', 'current_time'))));
That array_interset_key and array_flip maneuver could be extracted to its own function, like:
function filter_fields($params,$field_names) {
return array_intersect_key($params, array_flip($field_names))
}
I just haven't got around to it yet.
The function flips your array of key names, so you have an array with no values, but the right keys. Then intersect filters the first array so you only have the keys that are in both arrays (in this case, only the ones in your array_flipped array). But you get the values for the original array (not the empties). So you make one array of parameters, but specify which params are actually sent to PDO.
So, with the function, you'd do:
$parameters = array('tid' => $tid, 'u_ID' => $u_ID, 'current_time' => $time);
$1st = $db->prepare($query1);
$1st->execute(filter_fields($parameters, array('tid')));
$2nd = $db->prepare($query2);
$2nd->execute(filter_fields($parameters, array('u_ID', 'tid')));
$3rd = $db->prepare($query3);
$3rd->execute(filter_fields($parameters, array('u_ID', 'tid', 'current_time')));
$4th = $db->prepare($query4);
$4th->execute(filter_fields($parameters, array('u_ID', 'tid', 'current_time')));
If you have PHP 5.4, you can use the square bracket array syntax, to make it even cooler:
$parameters = array('tid' => $tid, 'u_ID' => $u_ID, 'current_time' => $time);
$1st = $db->prepare($query1);
$1st->execute(filter_fields($parameters, ['tid']));
$2nd = $db->prepare($query2);
$2nd->execute(filter_fields($parameters, ['u_ID', 'tid']));
$3rd = $db->prepare($query3);
$3rd->execute(filter_fields($parameters, ['u_ID', 'tid', 'current_time']));
$4th = $db->prepare($query4);
$4th->execute(filter_fields($parameters, ['u_ID', 'tid', 'current_time']));
I got a chance to test my question, and the answer is you cannot send more parameters than the query uses. You get the following error:
PDOException Object
(
[message:protected] => SQLSTATE[HY093]: Invalid parameter number: parameter was not defined
[string:Exception:private] =>
[code:protected] => HY093
[file:protected] => C:\Destination\to\file.php
[line:protected] => line number
[trace:Exception:private] => Array
(
[0] => Array
(
[file] => C:\Destination\to\file.php
[line] => line number
[function] => execute
[class] => PDOStatement
[type] => ->
[args] => Array
(
[0] => Array
(
[:u_ID] => 1
[:tid] => 1
[:current_time] => 1353524522
)
)
)
[1] => Array
(
[file] => C:\Destination\to\file.php
[line] => line number
[function] => function name
[class] => class name
[type] => ->
[args] => Array
(
[0] => SELECT
column
FROM
table
WHERE
user_ID = :u_ID AND
thread_ID = :tid
[1] => Array
(
[:u_ID] => 1
[:tid] => 1
[:current_time] => 1353524522
)
)
)
)
[previous:Exception:private] =>
[errorInfo] => Array
(
[0] => HY093
[1] => 0
)
)
I don't know a huge amount about PDO, hence my question, but I think that because :current_time is sent but not used and the error message is "Invalid parameter number: parameter was not defined" you cannot send extra parameters which are not used.
Additionally the error code HY093 is generated. Now I can't seem to find any documentation explaining PDO codes anywhere, however I came across the following two links specifically about HY093:
What is PDO Error HY093
SQLSTATE[HY093]
It seems HY093 is generated when you incorrectly bind parameters. This must be happening here because I am binding too many parameters.
executing different type of multiple queries with one execute leads to problems. you can run multiple selects or multiple updates with one execute. For this case to create different prepared statements objects and pass the the parameters accordingly.
// for WHERE threadID = :tid
$st1 = $db->prepare($sql);
$st1->bindParam(':tid', $tid);
$st1->execute();
or
$st1->execute(array(':tid'=>$tid);
// for WHERE user_ID = :u_ID AND thread_ID = :tid
$st2 = $db->prepare($sql);
$st2->bindParam(':u_ID', $u_ID);
$st2->bindParam(':tid', $tid);
$st2->execute();
or
$st2->execute(array(':tid'=>$tid, ':u_ID' => $u_ID);
// for SET time = :current_time WHERE user_ID = :u_ID AND thread_ID = :tid
$st3 = $db->prepare($sql);
$st3->bindParam(':u_ID', $u_ID);
$st3->bindParam(':tid', $tid);
$st3->bindParam(':current_time', $current_time);
$st3->execute();
or
$st3->execute(array(':tid'=>$tid, ':u_ID' => $u_ID, ':current_time' => $current_time);
// for VALUES (:u_ID, :tid, :current_time)
$st4 = $db->prepare($sql);
$st4->bindParam(':u_ID', $u_ID);
$st4->bindParam(':tid', $tid);
$st4->bindParam(':current_time', $current_time);
$st4->execute();
or
$st4->execute(array(':tid'=>$tid, ':u_ID' => $u_ID, ':current_time' => $current_time);

Zend_Db fetchAll() to return values as keys, not as key => value

Is there a way to change the default functionality in Zend_Db fetchall() method, so that it doesn't return this:
[0] => 100000055944231
[1] => 100000089064543
[2] => 100000145893011
[3] => 100000160760965
but this:
[100000055944231]
[100000089064543]
[100000145893011]
[100000160760965]
Although your question is actually flawed (noted by BartekR), I suppose you're trying to receive a simple array, instead of a multidimensional one.
You could do:
$results = $this->db->fetchAll($select);
$values = array_map(function($row) { return $row['column']; }, $results);
This will turn:
array(
array('column' => 'test'),
array('column' => 'test2'),
array('column' => 'test3'),
);
into
array(
'test',
'test2',
'test3'
);
(note; my example only works in PHP5.3+, if you're working with PHP5.2, you can define the function and use it by its name with array_map (e.g. array_map('methodName', $results))
I'm looking for a similar solution, I'm trying to load a field returned by the fetchAll($select) as the array key.. Without looping through the entire resultset.
So:
$results = $this->db->fetchAll($select, <FIELDNAME_TO_MAKE_KEY_OF_RESULTS_ARRAY>);
results[<my fieldname>]->dbfield2;
Further to Pieter's, I'd add the case where the rows are themselves arrays, and not just scalars; it's possible to nest the results, to as many fields as the query contains.
e.g. Here with two levels of nesting, respectively on field1 and field2.
$complex_results = array_map(function($row) { return array($row['field1'] => array($row['field2'] => $row)); }, $results);
As always, each row contains all fields, but $complex_results is indexed by field1, then field2 only.