How to use one query for different selects in laravel? - php

I thought it would be a good idea to define a query and use it for several selects or counts later, but it does not work. The second select has both wheres in the sql statement:
$query = Pic::where('pics.user_id',$user->id);
if($cat) $query->where('cat',$cat);
if($year) $query->where('jahrprod',$year);
$zb = $query->select('pics.id','pics.title','pics.created_at')
->where('pics.id', '>', $pic->id)
->orderBy('pics.id')
->take(2)
->get()->reverse();
$za = $query->select('pics.id','pics.title','pics.created_at')
->where('pics.id', '<', $pic->id)
->orderBy('pics.id')
->take(13)
->get();
Query:
SELECT `pics`.`id`, `pics`.`title`, `pics`.`created_at`
FROM `pics`
WHERE `pics`.`user_id` = '3'
AND `pics`.`id` > '2180'
AND `pics`.`id` < '2180'
ORDER BY `pics`.`id` ASC, `pics`.`id` ASC
LIMIT 13
I tried to "pass it as reference" i.e. &$query->select... but "only variables can be passed as reference".
How can I use the query , or save it, and use it for both actions. Is it possible?

You are updating object state with the statements when you do $query->where(), so yeah, when you're doing a second select, all conditions from the first one are still there. Thats the reason why these lines work without any assignments:
if($cat) $query->where('cat',$cat);
if($year) $query->where('jahrprod',$year);
To achieve described behaviour you would need to create an query object copy:
$query = Pic::where('pics.user_id',$user->id);
if($cat) $query->where('cat',$cat);
if($year) $query->where('jahrprod',$year);
$queryClone = clone $query;
$zb = $query->select('pics.id','pics.title','pics.created_at')
->where('pics.id', '>', $pic->id)
->orderBy('pics.id')
->take(2)
->get()->reverse();
$za = $queryClone->select('pics.id','pics.title','pics.created_at')
->where('pics.id', '<', $pic->id)
->orderBy('pics.id')
->take(13)
->get();
Notice that mere assignment would not work here:
$queryClone = $query;
Because that would pass object reference and would result in the same behaviour as in your case. Clone creates a full object copy.
http://php.net/manual/en/language.oop5.cloning.php

Related

Operetion on child collection

I have code like this
$tag = Tag::where('slug' = $slug)->first();
$posts = $tag->posts;
It works correctly but I want to use limit, orderBy, offset and other operation on posts. So it works
$posts = $tag->posts->where('accept', 1);
But it doesn't works
$posts-> $tag->posts->orderBy('created_at', 'desc');
//or
$posts-> $tag->posts
->offset($offset)
->limit($limit);
I must use offset and limit into query from var.
How I can do that?
When you set up your initial query Tag::where('slug' = $slug)->first(); you're using Query Builder and it's methods. But when Laravel returns the results, they're returned as a collction object -- those have very similar but slightly different methods available. https://laravel.com/docs/5.8/collections#available-methods
On a collection or its children, instead of orderBy() you would use sortBy() or sortByDesc(). Those will return an instance of the collection, sorted by your specified key. $results = $posts->sortBy($sorting);
The same idea with limit, in this case you can use the splice method. (Collections are basically php arrays on steroids) Splice accepts two parameters, a starting index and a limit. So, to get only the first 10 items, you could do this: $results = $posts->splice(0, 10);
And of course, you can also chain those togeather as $results = $tag->posts->sortBy('id')->splice(0, 10);
When you use child, Eloquent create another subquery, then result is added to parent, thats way its not sorting properly.
A solution could be join tables:
$tags = Tag::where('tags.slug' = $slug)
->join('tags', 'tag.post_id', '=', 'posts.id')
->orderBy('posts.created_at', 'desc')
->select('tags.*')
->get();

PHP Laravel Query Cloning Not Working as Expected

So I'm coming across an issue with QueryBuilder cloning. He's my situation, I need to run 3 separate queries, all based off a single "BaseQuery":
$baseQuery = $model->selectRaw("column")->whereNull("deleted_at");
$query1 = clone($baseQuery);
$query1Results = $query1->where("condition", "=", 0)->get();
$query2 = clone($baseQuery);
$query2Results = $query2->where("condition2", "=", 1)->get();
$query3 = clone($baseQuery);
$query3Results = $query3->where("condition3", "=", 0)->get();
By the time I get to $query3, it has 3 where statements. Substituting $query3 ... get() with $query3 ... ->toSql(); shows these results:
SELECT `column` FROM `models` WHERE `deleted_at` IS NULL AND `condition` = ? AND `condition2` = ? AND `condition3` = ?;
It seems that even though I am defining each $queryX as a clone of $baseQuery, the additional ->where() clause is being applied to each of the queries. I've tried doing the clone() before appending any of the ->where() clauses, but that doesn't have any affect.
Is there a way to clone() something and ensure that it creates a new instance? It looks like clone() isn't separating $baseQuery from $queryX, so the ->where()s are being applied to every subsequent copy, even though each should be a new query with only the ->whereNull() clause.
Another interesting point is that running
\Log::info($baseQuery == $queryX);
After each modification returns true (=== is false though)
You should use scopes here.
Local scopes allow you to define common sets of constraints that you may easily re-use throughout your application. For example, you may need to frequently retrieve all users that are considered "popular". To define a scope, simply prefix an Eloquent model method with scope
public function scopeBase($q)
{
return $q->selectRaw('column')->whereNull('deleted_at');
}
Then just do something like this:
$query1Results = $model->where('condition', 0)->base()->get();
$query2Results = $model->where('condition2', 1)->base()->get();
$query3Results = $model->where('condition3', 0)->base()->get();

ORDER BY "the date" Laravel 5 SQL Query

I want to sort the results by the date, but this Laravel 5 SQL Query is not working as i want.
$b = DB::table('entry')
->select(DB::raw("DATE_FORMAT(created_at,'%d-%m-%Y') as tanggal"))
->groupBy(DB::raw("DATE_FORMAT(created_at,'%d-%m-%Y')"))
->orderBy(DB::raw("DATE_FORMAT(created_at,'%d-%m-%Y')"), 'asc')
->get();
Here's the easy way to achieve this
Step 1 :
Create a Model named as Entry by artisan command or manually
Step 2 :
Then from your Controller just do
$entry = Entry::orderBy('created_at', 'ASC')->get();
Then you should get the $entry array of what you need.
Hope this helps you
You can still use DB::table and simply put this orderBy. It will work just like the above mentioned.
$query = DB::table('entry')
->select(DB::raw("DATE_FORMAT(created_at,'%d-%m-%Y') as tanggal"))
->orderBy('created_at','ASC')->get(); //either this
->orderBy('updated_at','ASC')->get(); // or this
$query = DB::table('entry')
->select(DB::raw("DATE_FORMAT(created_at,'%d-%m-%Y') as tanggal"))
->orderBy('created_at','DESC')->get(); //either this
->orderBy('updated_at','DESC')->get(); // or this
Note: Either you put 'ASC' or not it will automatically sort it ascendingly.

Doctrine WHERE IN Problems

Before I begin, I believe I have tried everything from this previous post: How to use WHERE IN with Doctrine 2
So I have a Silex application connected to a MySQL DB using Doctrine via composer (doctrine/dbal 2.2.*)
The query builder I am trying to run is this:
$qb = $this->db->createQueryBuilder();
$stmt = $qb->select('DAY(datefield1) x, COUNT(*) value')
->from('table1', 's')
->join('s', 'table2', 't', 't.key=s.key')
->where('MONTH(datefield1) = :billMonth')
->andWhere('YEAR(datefield1) = :billYear')
->andWhere('t.key IN (:keylist)')
->groupBy('x')
->orderBy('x', 'asc')
->setParameter(':billMonth', $month)
->setParameter(':billYear', $year)
->setParameter(':keylist', implode(",", $keylist))
->execute();
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
The parameters are (month=8)(year=2014)(keylist=array(1,2,3,4))
The query does not fail but it curiously doesn't contain all the data that it should.
I have tried ->setParameter(':keylist', $keylist) to use the raw array, and this didn't work.
I have tried this kind of syntax too:
$qb->add('where', $qb->expr()->in('r.winner', array('?1')));
However that threw up an error because the in method wasn't available in expression builder class.
Please will someone cast an eye over this and save me from having to hardcode my SQL?
OK seeing as this old thread has seen some action since I last looked I wanted to confirm that the issue is long resolved - a third parameter in setParameter allows you to inform Doctrine how to handle the array:
$qb = $this->db->createQueryBuilder();
$stmt = $qb
->select('*')
->from(self::DB_TABLE, 'x')
->where('x.service IN (:services)')
->orderBy('x.session_end', 'DESC')
->addOrderBy('x.direction', 'DESC')
->setParameter(':services', $services, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
->setFirstResult($offset)
->setMaxResults($count)
->execute();
$result = $stmt->fetchAll(\PDO::FETCH_ASSOC);
DB placeholders/parameters are for single values. You're passing in a monolithic string 1,2,3,4 due to calling implode() on the array. Given:
WHERE t.key IN (:keylist)
then this query will be executed as the equivalent of
WHERE t.key IN ('1,2,3,4')
^-------^---note the quotes
Since it's a string, and only one single string in the IN clause, it's the functional equivalent of
WHERE t.key = '1,2,3,4'
and not the
WHERE (t.key = 1 OR t.key = 2 OR ....)
you want it to be. Either set up multiple parameters, one for each value in your array, or embed your string in the query directly
->andWhere('t.key IN (' . implode(',', $keylist) . ')')
which of course opens you up to sql injection attack vulnerabilities.
If you build the sql query yourself you can use DBAL's PARAM_INT_ARRAY type:
use Doctrine\DBAL\Connection as DB;
$db->executeQuery('SELECT DAY(datefield1) x, COUNT(*) value
FROM table1 s
JOIN table2 t ON t.key=s.key
WHERE MONTH(datefield1) = :billMonth
AND YEAR(datefield1) = :billYear
AND t.key IN (:keylist)
GROUP BY x
ORDER BY x ASC',
array(':billMonth' => $month, ':billYear' => $year, ':keylist' => $keylist),
array(':billMonth' => \PDO::PARAM_INT, ':billYear' => \PDO::PARAM_INT, ':keylist' => DB::PARAM_INT_ARRAY
)->fetchAll(\PDO::FETCH_ASSOC);
Correct way to handle such IN clause is as simple as it can be:
$qb = $this->db->createQueryBuilder();
$stmt = $qb->(...)
->andWhere('t.key IN (:keylist)')
->setParameter(':keylist', $keylist)
->getQuery()
->getArrayResult();
I have used this dozen times and it works - doctrine is smart enough to handle your $keylist being array.
Other thing is that I don't know why you're using fetchAll method which is reduntant here - execute() is just enough. Maybe this is a root of your problem.
My suggestion: try to fire action in dev mode (app_dev.php) and check your app/logs/dev.log - you will find all sql queries performed. Verify that database returns data which you are expecting from Doctrine.
->add('where', $qb->expr()->andX(
$qb->expr()->in('r.winner', ':winner')
))
->setParameters(array(
"winner" => $winners,
"billMonth", $month,
// add all params here
));
You should use the in expression via the querybuilder like below, and change how you set the parameter:
->andWhere($qb->expr()->in('t.key', ':keylist'))
The complete code:
$qb = $this->db->createQueryBuilder();
$stmt = $qb->select('DAY(datefield1) x, COUNT(*) value')
->from('table1', 's')
->join('s', 'table2', 't', 't.key=s.key')
->where('MONTH(datefield1) = :billMonth')
->andWhere('YEAR(datefield1) = :billYear')
->andWhere('t.key')
->andWhere($qb->expr()->in('t.key', ':keylist'))
->groupBy('x')
->orderBy('x', 'asc')
->setParameter(':billMonth', $month)
->setParameter(':billYear', $year)
->setParameter(':keylist', $keylist)
->execute();
return $stmt->fetchAll(\PDO::FETCH_ASSOC);

Adding a Having Clause to Doctrine Statement

I am new to Doctrine and I am trying to figure out how to add a having clause on my statement. Basically I want to be able to filter down on items returned based on how many attributes the user selects. The code is as follows:
// create query builder
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('p')
->from($this->_entityName, 'p')
->leftJoin('p.options', 'o')
->where('p.active = :active')
->setParameter('active', 1);
// add filters
$qb->leftJoin('o.attributes', 'a');
$ands = array();
foreach ($value as $id => $values)
{ echo count($values);
$ands[] = $qb->expr()->andX(
$qb->expr()->eq('a.attribute_id', intval($id)),
$qb->expr()->in('a.attribute_value_id', array_map('intval', $values))
$qb->having('COUNT(*)=3) // THIS DOESN'T WORK
//$qb->expr()->having('COUNT(*)=3) // THIS DOESN'T WORK EITHER
);
}
$where = $qb->expr()->andX();
foreach ($ands as $and)
{
$where->add($and);
}
$qb->andWhere($where);
$result = $qb->getQuery()->getResult();
return $result;
When I try to execute the statement with the having() clause I get this error:
Expression of type 'Doctrine\ORM\QueryBuilder' not allowed in this context.
Without the having() clause everything works perfectly.
I have no idea how to solve this.
HAVING clause requires a GROUP BY. In doctrine it would be something like that:
$qb->groupBy('p.id'); // or use an appropriate field
$qb->having('COUNT(*) = :some_count');
$qb->setParameter('some_count', 3);
Assuming you're using mysql, here is a having clause tutorial: http://www.mysqltutorial.org/mysql-having.aspx
Perhaps you should bind number 3 to a parameter:
$qb->having('COUNT(*)=:some_count')
$qb->setParameter('some_count',3)
Goal: filter down The one side where we have some known summable conditions we want to filter by (e.g., the count of Original Parts in a Bundle) on the many side of a O2M relationship wehere want to limit the One side along with some other criteria to select on.
We are then adding in a few conditions for the LEFT_JOIN operation:
Condition #1 - the bundle.id == the original_part.bundle ID.
Condition #2 - The original_part's partStatusType == 3 (some hard-coded value).
Then we filter down to the COUNT(op) >= 1 which gives our more limited response that works just fine with pagination.
$qb->leftJoin(OriginalPart::class, 'op', Expr\Join::WITH,
$qb->expr()->andX(
$qb->expr()->eq('op.bundle', 'row.id'),
$qb->expr()->eq('op.partStatusType', 3)));
$qb->groupBy('row.id');
$qb->having($qb->expr()->gte('COUNT(op)', 1));
row is the alias for the One (bundle entity).

Categories