Getting odd behavior from $query->setMaxResults() - doctrine-orm

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.

Related

Table Function to get first row of MSEG in CDS view

I'm trying to build a CDS view that uses various fields from invoices in VBRK and VBRP. Another requirement is to display the price listed in the original purchase order (for example, I'm selling kiwis to someone and I want to display the original purchase price I paid). I'm supposed to use the connection to MSEG with parameter batch (MSEG-CHARG). The assumption here is that for every batch there is only one purchase order. I'm not sure how to make that connection from the invoices, though.
This is my basic CDS view:
#AbapCatalog.sqlViewName: <view_name>
#AbapCatalog.compiler.compareFilter: true
#AbapCatalog.preserveKey: true
#AccessControl.authorizationCheck: #CHECK
#EndUserText.label: '<text>'
#VDM.viewType: #BASIC
define view <view_name> as select distinct from I_BillingDocumentItemCube( P_ExchangeRateType: 'M', P_DisplayCurrency: 'EUR' )
{
key BillingDocument,
key BillingDocumentItem,
BillingDocumentType,
_BillingDocument._Item._PricingElement[ConditionType = 'XXX1'].ConditionRateValue as cost1,
_BillingDocument._Item._PricingElement[ConditionType = 'XXX2'].ConditionRateValue as cost2,
SoldToParty,
SoldToPartyName,
Material,
BillingDocumentItemText,
Batch,
BillingDocumentDate,
BillingQuantity,
BillingQuantityUnit,
SalesDocumentItemCategory
};
I tried using a table function to select the responding batch from MSEG but I'm not sure how to connect it to the CDS view.
#EndUserText.label: '<name>'
define table function <table_function>
with parameters #Environment.systemField: #CLIENT
clnt: abap.clnt,
charg: charg_d
returns {
clnt : abap.clnt;
charg_exp : charg_d;
dmbtr : dmbtr_cs;
menge : menge_d;
}
implemented by method <class> => <method>;
class:
CLASS <class_name> DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
CLASS-METHODS <method_name> FOR TABLE FUNCTION <table_function>.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS <class_name> IMPLEMENTATION.
METHOD <method_name>
BY DATABASE FUNCTION FOR HDB LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING nsdm_e_mseg.
RETURN select top 1 mandt as clnt, charg as charg_exp, dmbtr, menge
from nsdm_e_mseg
where mandt = :clnt
and charg = :charg
and dmbtr > 0
and menge > 0
order by mblnr;
ENDMETHOD.
ENDCLASS.
How can I use this table function in my basic CDS view to connect the position in VBRP to the position in MSEG?
Generally the linking is following:
VBRK-vbeln -> VBRP-vbeln VBFA-vbeln -> MSEG-mblnr
... VBRP-posnr VBFA-posnn -> MSEG-zeile
... ... ...
... ... vbtyp_n='R'
... VBRP-aubel -> VBFA-vbelv
... VBRP-aupos -> VBFA-posnv
However, I do not exactly now how I_BillingDocumentItemCube is built as I am not very familiar with S4HANA analytical cubes, does it really project to VBRK/VBRP?
Anyway, MSEG table has MBLNR/MJAHR/ZEILE key so passing batch (CHARG) is definitely not sufficient because it does not uniquely defines Purchase Order, even though you assume it will be 1:1 relation of PO:Billing, it can be multiple similar material (MATNR) positions within the same Batch and the same Purchase Order with the different prices.
Look at the MCHB table, only Plant/StorageLoc/Batch (MATNR/WERKS/LGORT) combination unambiguously defines material doc.
So, to conclude, at the very least you should pass into the method these 4 parameters which you will derive from I_BillingDocumentItemCube:
METHOD <method_name>
BY DATABASE FUNCTION FOR HDB LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING nsdm_e_mseg.
RETURN select top 1 mandt as clnt, charg as charg_exp, dmbtr, menge
from nsdm_e_mseg
where mandt = :clnt
and mblnr = :mblnr
and mjahr = :mjahr
and zeile = :zeile
and charg = :charg
and dmbtr > 0
and menge > 0
order by mblnr;
ENDMETHOD.
Also, to conform to the new S4HANA data model, why don't you read MATDOC table directly?
NSDM_E_MSEG is nothing more than legacy wrapper, so-called compatibility view that redirects to MATDOC table with some parameters, check 2206980 note
It may be a nice start to use new tables for a smoother shift to S4HANA model.

Adding nester OR statements in Doctrine 2, Querybuilder

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.

"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

Is it possible to add wildcards to #Query parameters?

I've defined my ContactDao as follows:
public interface ContactDao extends JpaRepository<Contact, Long> {
/**
* Finds all contacts that the given user has entered where the contact's full name matches {#code name}.
* #param userId The current user's LDAP id.
* #param name The name to search for.
* #return A list of contacts matching the specified criteria.
*/
#Query(" select c from Form as f" +
" inner join f.contacts as c" +
" where f.requestorUserId = :userId" +
" and lower(c.fullName) like lower(:name)" +
" order by lower(c.fullName)")
List<Contact> findUserContactsByUserIdAndName(#Param("userId") String userId, #Param("name") String name);
}
The above is working perfectly, and the SQL generated is what I'd write myself. The problem is that I'd like to add surrounding wild-cards to the :name parameter. Is there any nice way to do this? I've had a look at the Spring Data JPA reference document and I can't find anything regarding wildards and #query.
The following hack works, but is a bit ugly:
and lower(c.fullName) like '%' || lower(:name) || '%'
Does anyone have a better solution?
Thanks,
Muel.
I would also not call it a hack - it's the way JPQL syntax is defined for exactly these problems.
However, there is an alternative using a CriteriaBuilder/CriteriaQuery, but maybe you find it even more complex (but you get compile-time type safety in return).

MongoDB MapReduce update in place how to

*Basically I'm trying to order objects by their score over the last hour.
I'm trying to generate an hourly votes sum for objects in my database. Votes are embedded into each object. The object schema looks like this:
{
_id: ObjectId
score: int
hourly-score: int <- need to update this value so I can order by it
recently-voted: boolean
votes: {
"4e4634821dff6f103c040000": { <- Key is __toString of voter ObjectId
"_id": ObjectId("4e4634821dff6f103c040000"), <- Voter ObjectId
"a": 1, <- Vote amount
"ca": ISODate("2011-08-16T00:01:34.975Z"), <- Created at MongoDate
"ts": 1313452894 <- Created at timestamp
},
... repeat ...
}
}
This question is actually related to a question I asked a couple of days ago Best way to model a voting system in MongoDB
How would I (can I?) run a MapReduce command to do the following:
Only run on objects with recently-voted = true OR hourly-score > 0.
Calculate the sum of the votes created in the last hour.
Update hourly-score = the sum calculated above, and recently-voted = false.
I also read here that I can perform a MapReduce on the slave DB by running db.getMongo().setSlaveOk() before the M/R command. Could I run the reduce on a slave and update the master DB?
Are in-place updates even possible with Mongo MapReduce?
You can definitely do this. I'll address your questions one at a time:
1.
You can specify a query along with your map-reduce, which filters the set of objects which will be passed into the map phase. In the mongo shell, this would look like (assuming m and r are the names of your mapper and reducer functions, respectively):
> db.coll.mapReduce(m, r, {query: {$or: [{"recently-voted": true}, {"hourly-score": {$gt: 0}}]}})
2.
Step #1 will let you use your mapper on all documents with at least one vote in the last hour (or with recently-voted set to true), but not all the votes will have been in the last hour. So you'll need to filter the list in your mapper, and only emit those votes you wish to count:
function m() {
var hour_ago = new Date() - 3600000;
this.votes.forEach(function (vote) {
if (vote.ts > hour_ago) {
emit(/* your key */, this.vote.a);
}
});
}
And to reduce:
function r(key, values) {
var sum = 0;
values.forEach(function(value) { sum += value; });
return sum;
}
3.
To update the hourly scores table, you can use the reduceOutput option to map-reduce, which will call your reducer with both the emitted values, and the previously saved value in the output collection, (if any). The result of that pass will be saved into the output collection. This looks like:
> db.coll.mapReduce(m, r, {query: ..., out: {reduce: "output_coll"}})
In addition to re-reducing output, you can use merge which will overwrite documents in the output collection with newly created ones (but leaving behind any documents with an _id different than the _ids created by your m-r job), replace, which is effectively a drop-and-create operation and is the default, or use {inline: 1}, which will return the results directly to the shell or to your driver. Note that when using {inline: 1}, your results must fit in the size allowed for a single document (16MB in recent MongoDB releases).
(4.)
You can run map-reduce jobs on secondaries ("slaves"), but since secondaries cannot accept writes (that's what makes them secondary), you can only do this when using inline output.