Adding nester OR statements in Doctrine 2, Querybuilder - doctrine-orm

I have a one to many entity:
User -> OrderPerson
A user can own multiple orderPersons.
An orderPerson is linked to Orders and can have multiple orders.
What I want to do is build a dynamic query to deal with this, this is what I have thus far:
public function getPaged($page, $count , $orderPersons = null)
{
$qb = $this->orderRepository->createQueryBuilder('c')
->orderBy('c.id', 'DESC');
if ($orderPersons != null )
{
foreach ($orderPersons AS $orderPerson)
{
$qb->where('c.orderPerson='.$orderPerson); ***
}
}
$query = $qb->getQuery();
}
Where I am struggling is how to write the line:
$qb->where('c.orderPerson='.$orderPerson);
I had a read of the documents and I think I need to use something like this but am not sure:
$qb->andWhere(
$qb->expr()->orX(
$qb->expr()->eq('c.orderPerson='.$orderPerson)
)
);
I am however unsure how to put this into a loop.

The one big criticism I have about the D2 documentation is that they spend a lot of time on exprs but most of the time you really don't need them. Just makes the code hard to read.
Having said that, you don't need OR conditions for your query, just an IN clause.
// Join the order persons
$qb->leftJoin(c.orderPersons,'orderPerson');
// Need at least one
if (is_array($orderPersons) && count($orderPersons)) {
$qb->andWhere('orderPerson.id IN (:orderPersons));
$qb->setParameter('orderPersons',$orderPersons);
}
Untested of course so there may be a syntax error but you should get the idea.

Related

CouchDB View - filter keys before grouping

I have a CouchDB database which has documents with the following format:
{ createdBy: 'userId', at: 123456, type: 'action_type' }
I want to write a view that will give me how many actions of each type were created by which user. I was able to do that creating a view that does this:
emit([doc.createdBy, doc.type, doc.at], 1);
With the reduce function "sum" and consuming the view in this way:
/_design/userActionsDoc/_view/userActions?group_level=2
this returns a result with rows just in the way I want:
"rows":[ {"key":["userId","ACTION_1"],"value":20}, ...
the problem is that now I want to filter the results for a given time period. So I want to have the exact same information but only considering actions which happened within a given time period.
I can filter the documents by "at" if I emit the fields in a different order.
?group_level=3&startkey=[149328316160]&endkey=[1493283161647,{},{}]
emit([doc.at, doc.type, doc.createdBy], 1);
but then I won't get the results grouped by userId and actionType. Is there a way to have both? Maybe writing my own reduce function?
I feel your pain. I have done two different things in the past to attempt to solve similar issues.
The first pattern is a pain and may work great or may not work at all. I've experienced both. Your map function looks something like this:
function(doc) {
var obj = {};
obj[doc.createdBy] = {};
obj[doc.createdBy][doc.type] = 1;
emit(doc.at, obj);
// Ignore this for now
// emit(doc.at, JSON.stringify(obj));
}
Then your reduce function looks like this:
function(key, values, rereduce) {
var output = {};
values.forEach(function(v) {
// Ignore this for now
// v = JSON.parse(v);
for (var user in v) {
for (var action in v[user]) {
output[user][action] = (output[user][action] || 0) + v[user][action];
}
}
});
return output;
// Ignore this for now
// return JSON.stringify(output);
}
With large datasets, this usually results in a couch error stating that your reduce function is not shrinking fast enough. In that case, you may be able to stringify/parse the objects as shown in the "ignore" comments in the code.
The reasoning behind this is that couchdb ultimately wants you to output a simple object like a string or integer in a reduce function. In my experience, it doesn't seem to matter that the string gets longer, as long as it remains a string. If you output an object, at some point the function errors because you have added too many props to that object.
The second pattern is potentially better, but requires that your time periods are "defined" ahead of time. If your time period requirements can be locked down to a specific year, specific month, day, quarter, etc. You just emit multiple times in your map function. Below I assume the at property is epoch milliseconds, or at least something that the date constructor can accurately parse.
function(doc) {
var time_key;
var my_date = new Date(doc.at);
//// Used for filtering results in a given year
//// e.g. startkey=["2017"]&endkey=["2017",{}]
time_key = my_date.toISOString().substr(0,4);
emit([time_key, doc.createdBy, doc.type], 1);
//// Used for filtering results in a given month
//// e.g. startkey=["2017-01"]&endkey=["2017-01",{}]
time_key = my_date.toISOString().substr(0,7);
emit([time_key, doc.createdBy, doc.type], 1);
//// Used for filtering results in a given quarter
//// e.g. startkey=["2017Q1"]&endkey=["2017Q1",{}]
time_key = my_date.toISOString().substr(0,4) + 'Q' + Math.floor(my_date.getMonth()/3).toString();
emit([time_key, doc.createdBy, doc.type], 1);
}
Then, your reduce function is the same as in your original. Essentially you're just trying to define a constant value for the first item in your key that corresponds to a defined time period. Works well for business reporting, but not so much for allowing for flexible time periods.

"IN" predicate with Criteria filtering isn't working

I'm trying to filter a doctrine collection directly with Criteria in order to avoid all collection-loading when I'm searching only for certain elements.
public function bar()
{
$entity = ...; // not useful for the example
$property = ...; // not useful for the example but is a getter for a collection
$ids = [22, 13]; // just for the sake of this example
$criteria = Criteria::create()->where(Criteria::expr()->in("id", $ids));
return $this->foo($entity, $property, $criteria);
}
public function foo($entity, $property, Criteria $criteria)
{
return $this->propertyAccessor->getValue($entity, $property)->matching($criteria);
}
Above code will produce this DQL (I'll show only last and relevant part)
AND te.id = ?' with params [22,13]:
And so, this error
Notice: Array to string conversion
I suppose that wrong DQL is generated here, but I don't know why.
Does anyone got a clue?
This is the collection property I'm trying to match on
/**
* #ORM\ManyToMany(targetEntity="Vendor\Bundle\Entity\Foo")
* #ORM\JoinTable(name="foo_bar",
* joinColumns={#ORM\JoinColumn(name="foo_bar_id", referencedColumnName="id", onDelete="cascade")},
* inverseJoinColumns={#ORM\JoinColumn(name="foo_id", referencedColumnName="id")}
* )
*/
protected $foo;
Edit
If I try to dump $criteria object, I obtain this
Criteria {#10958 ▼
-expression: Comparison {#10956 ▼
-field: "id"
-op: "IN"
-value: Value {#10948 ▼
-value: array:2 [▼
0 => 22
1 => 13
]
}
}
-orderings: []
-firstResult: null
-maxResults: null
}
So this seems to be correct. I'm also aware I could use "or" expressions, but I would prefer to understand why this issue is taking place.
Edit2
I've changed $criteria as follows
$criteria = Criteria::create()
->where(Criteria::expr()->eq('id', 22))
->orWhere(Criteria::expr()->eq('id', 13));
As now, query is correct but collection isn't: it is empty but shouldn't be (if I run manually that query from mysql cli I obtain what I expect).
So ...
BONUS QUESTION
Can I use Criteria only inside entities getters or repository if I collection isn't already loaded?
Edit3 - Bonus question
It seems that if you don't have collection already loaded, matching criteria is pretty useless. Infact if I loop onto collection elements and call some getters and, then, I use criteria, resulting collection is what I was searching for (but, of course, ALL collection elements are now loaded)
I've found this
https://github.com/doctrine/doctrine2/issues/4910
It seems that is a "well-known" issue in ManyToMany collection situations

Syntax error in SQLite (C bindings)

I got this error recently, and couldn't get rid of it. It's puzzling me, because the exact same request (copy-paste) works from the CLI like a charm.
I am using the C bindings, from a Qt application (I only discovered later that there was a Qt wrapper class for SQL databases).
The request in question is the following :
NSERT INTO classes(score,classe) VALUES((SELECT avg((julianday(arrivee)-julianday(debutCourse))/moyenne) FROM coureurs NATURAL JOIN resultats NATURAL JOIN courses NATURAL JOIN categories WHERE classe='4èmeB' AND moyenne IS NOT NULL),'4èmeB');
The error is :
near "'4èmeB'": syntax error
I have three tables I use there, which are classes, coureurs (runners), resultats (results), courses (races), categories.
For each class, I want to compute the following :
average of (runner's time/average time of its category).
Departure times are stored inside the courses (races) table, arrival times are stored inside the resultats (results) table and categories' average time are stored into categories.moyenne. If you have a more elegant way of performing such a request, I would be pleased to hear it. I would however like to have an explanation of what's going on here. Is this because of the UTF-8 characters ? It didn't seem to be a problem in other places.
In case it helps, here is the relevant C++ code :
void database::voidQuery(QString query)
{
sqlite3_stmt *res;
int rc =sqlite3_prepare_v2(db,query.toStdString().c_str(),query.length(),&res,NULL);
if(rc != SQLITE_OK){
fprintf(stderr, "Error in voidquery : %s in the request %s\n", sqlite3_errmsg(db),query.toStdString().c_str());
return;
}
sqlite3_step(res);
sqlite3_finalize(res);
}
Together with The calling code :
for (QStringList::iterator it = classes.begin();it != classes.end(); ++it)
{
QString query("INSERT INTO classes(score,classe) "
"VALUES(("
"SELECT avg((julianday(arrivee)-julianday(debutCourse))/moyenne) "
"FROM coureurs NATURAL JOIN resultats NATURAL JOIN courses NATURAL JOIN categories "
"WHERE classe='%1' and moyenne is not null),'%2');");
voidQuery(query.arg(*it).arg(*it));
}
classes is a QStringList containing the different rows of a precedent query.
Thanks in advance ! (I hope it's not a trivial problem; it took me quite some time to debug it).
Use
INSERT INTO classes (score,classe)
SELECT avg((julianday(arrivee)-julianday(debutCourse))/moyenne), '4èmeB'
FROM coureurs
NATURAL JOIN resultats
NATURAL JOIN courses
NATURAL JOIN categories
WHERE classe='4èmeB'
AND moyenne IS NOT NULL

Sitecore Search Multiple words

How can I search for multiple words with Sitecore 7 ?
I tried with Contains for every word but doesn't work fine and I think performance it's not really good.
you can user PredicateBuilder for this issue :
Code will be something like:
Queryable<SearchItem> SearchText(List<string> keywords, IQueryable<SearchItem> itemList)
{
var predicate = PredicateBuilder.True<SearchItem>();
foreach (string keyword in keywords)
{
predicate = predicate.And(i => i.FieldOne.Contains(keyword) || i.FieldTwo.Contains(keyword) || i.FieldThree.Contains(keyword) || i.Fieldyyy.Contains(keyword));
}
return itemList.Where(predicate);
}
About predicateBuilder you can find here :
Dynamic query using predicate builder
Performance can get better if you use the ToList(); on the IQueryable only when you have filtered out all items (using LINQ statements) you need from the IQueryable. When you call .ToList(); the query will be executed.

Getting odd behavior from $query->setMaxResults()

When I call setMaxResults on a query, it seems to want to treat the max number as "2", no matter what it's actual value is.
function findMostRecentByOwnerUser(\Entities\User $user, $limit)
{
echo "2: $limit<br>";
$query = $this->getEntityManager()->createQuery('
SELECT t
FROM Entities\Thread t
JOIN t.messages m
JOIN t.group g
WHERE
g.ownerUser = :owner_user
ORDER BY m.timestamp DESC
');
$query->setParameter("owner_user", $user);
$query->setMaxResults(4);
echo $query->getSQL()."<br>";
$results = $query->getResult();
echo "3: ".count($results);
return $results;
}
When I comment out the setMaxResults line, I get 6 results. When I leave it in, I get the 2 most recent results. When I run the generated SQL code in phpMyAdmin, I get the 4 most recent results. The generated SQL, for reference, is:
SELECT <lots of columns, all from t0_>
FROM Thread t0_
INNER JOIN Message m1_ ON t0_.id = m1_.thread_id
INNER JOIN Groups g2_ ON t0_.group_id = g2_.id
WHERE g2_.ownerUser_id = ?
ORDER BY m1_.timestamp DESC
LIMIT 4
Edit:
While reading the DQL "Limit" documentation, I came across the following:
If your query contains a fetch-joined collection specifying the result limit methods are not working as you would expect. Set Max Results restricts the number of database result rows, however in the case of fetch-joined collections one root entity might appear in many rows, effectively hydrating less than the specified number of results.
I'm pretty sure that I'm not doing a fetch-joined collection. I'm under the impression that a fetch-joined collection is where I do something like SELECT t, m FROM Threads JOIN t.messages. Am I incorrect in my understanding of this?
An update : With Doctrine 2.2+ you can use the Paginator http://docs.doctrine-project.org/en/latest/tutorials/pagination.html
Using ->groupBy('your_entity.id') seem to solve the issue!
I solved the same issue by only fetching contents of the master table and having all joined tables fetched as fetch="EAGER" which is defined in the Entity (described here http://www.doctrine-project.org/docs/orm/2.1/en/reference/annotations-reference.html?highlight=eager#manytoone).
class VehicleRepository extends EntityRepository
{
/**
* #var integer
*/
protected $pageSize = 10;
public function page($number = 1)
{
return $this->_em->createQuery('SELECT v FROM Entities\VehicleManagement\Vehicles v')
->setMaxResults(100)
->setFirstResult($number - 1)
->getResult();
}
}
In my example repo you can see I only fetched the vehicle table to get the correct result amount. But all properties (like make, model, category) are fetched immediately.
(I also iterated over the Entity-contents because I needed the Entity represented as an array, but that shouldn't matter afaik.)
Here's an excerpt from my entity:
class Vehicles
{
...
/**
* #ManyToOne(targetEntity="Makes", fetch="EAGER")
* #var Makes
*/
public $make;
...
}
Its important that you map every Entity correctly otherwise it won't work.