Doctrine 2 Querybuilder - Two joins leads to wrong sum results - doctrine-orm

I need to generate reports, this work fine
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('
c.category,
SUM(s.fulfillable) AS fulfillable
')
->from('App\Entity\Catalog', 'c')
->leftJoin('App\Entity\Stock', 's', Expr\Join::WITH, 'c.sku = s.sku')
->groupBy('c.category')
;
but when I also want to sum transaction results (multiple), my fulfillable sum is not true anymore
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('
c.category,
SUM(s.fulfillable) AS fulfillable,
SUM(t.sold) AS sold
')
->from('App\Entity\Catalog', 'c')
->leftJoin('App\Entity\Stock', 's', Expr\Join::WITH, 'c.sku = s.sku') // Only one Stock
->leftJoin('App\Entity\Transaction', 't', Expr\Join::WITH, 'c.sku = t.sku') // Multiple Transactions
->groupBy('c.category')
;
Is there a solution for such cases in Doctrine ORM? Or do I have to use raw sql querys with subselects.
Thank you & Best Regards

Related

Need Doctrine Many to Many QueryBuilder Query to Return NOT In Common Rows

I have two tables 1) Prices and 2) Users
Owner is a column in the Prices table.
Both tables have a many to many relationship between price.users and users.price.
The query below returns all Prices shared by owner1 and user1.
Question: How do I fix this query where it only returns all owner1 prices that are not synced with user1.
If I use ->andWhere('u.id = :user1Id') then I get only records for user1.
If I use ->andWhere('u.id != :user1Id') then I get all owner record including user1 records.
Again I want all owner records except those that are synced with user1.
I've tried the following so far:
1) $queryUsersPrices
->innerJoin('p.owner', 'o')
->leftJoin('p.users', 'u')
->andWhere('o.id = :ownerId')
/*I need to Remove records for u.id from results*/
->andWhere('u.id = :user1Id')
->setParameter('owner1Id', $owner->getId())
->setParameter('user1Id', $user->getId());
$userPrices = $queryUsersPrices->getQuery()->getResult();
2) $userPrices = $repository->createQueryBuilder($alias);
$userPrices
->select("u.prices")
->from("Price","p")
->innerJoin('p.users', 'u')
->andWhere('u.id = :userId')
->getDQL();
$query = $repository->createQueryBuilder($alias);
$query
->innerJoin($alias . '.owner', 'o')
->innerJoin($alias . '.priceType', 'pt')
->innerJoin($alias . '.model', 'm')
->where(
$query->expr()->not(
$query->expr()->in(
'p.id',
$userPrices
)
)
)
->andWhere('m.status = :m_status')
->andWhere('o.id = :adminId')
->andWhere('pt.site <> 1')
->setParameter('m_status', Model::STATUS_ACTIVE);
$result = $query->getQuery()->getResult();
3) $query = $repository->createQueryBuilder($alias);
$query
->innerJoin($alias . '.owner', 'o')
->innerJoin($alias . '.users', 'u', 'WITH', 'u.id =
:userId')
->innerJoin($alias . '.priceType', 'pt')
->innerJoin($alias . '.model', 'm')
->where('m.status = :m_status')
->andWhere('o.id = :adminId')
->andWhere('u.id IS NULL')
->andWhere('pt.site <> 1')
->setParameter('adminId', $adminUser->getId())
->setParameter('userId', $user->getId())
->setParameter('m_status', Model::STATUS_ACTIVE);
$test = $query->getQuery()->getResult();
Method #1 results in user1 prices only
Method #2 results in this error: Error: Method Doctrine\Common\Collections\ArrayCollection::__toString() must not throw an exception, caught Symfony\Component\Debug\Exception\ContextErrorException: Catchable Fatal Error: Object of class Doctrine\ORM\EntityManager could not be converted to string
Methos #3 results in owner prices only
THIS IS WHAT ACTUALLY WORKED BASED ON M Khalid Junaid ANSWER
$userPrices = $repository->createQueryBuilder('pr')
->innerJoin('pr.users', 'u')
->andWhere('u.id = :userId')
->setParameter('userId', $user->getId())
->getDQL();
$query = $repository->createQueryBuilder($alias);
$query
->innerJoin($alias . '.owner', 'o')
->innerJoin($alias . '.priceType', 'pt')
->innerJoin($alias . '.model', 'm')
->where(
$query->expr()->not(
$query->expr()->in(
$alias . '.id',
$userPrices
)
)
)
->andWhere('m.status = :m_status')
->andWhere('o.id = :adminId')
->andWhere('pt.site <> 1')
->setParameter('m_status', Model::STATUS_ACTIVE)
->setParameter('adminId', $adminUser->getId())
->setParameter('userId', $user->getId());
$result = $query->getQuery()->getResult();
I would suggest to break down your logic as
First select prices that belongs to $user->getId() as
$userPrices = $this->createQueryBuilder("u")
->select("u.prices")
->from("YourBundleName:Prices","p")
->innerJoin('p.users', 'u')
->andWhere('u.id = :user1Id')
->getDQL();
Then get prices for owner which is $owner->getId() and exclude prices from the subquery for $user->getId() as
$qb = $this->createQueryBuilder("pr");
$qb->select("pr")
->from("YourBundleName:Price", "pr")
->innerJoin('pr.owner', 'o')
->where(
$qb->expr()->not(
$qb->expr()->in(
"pr.id",
$userPrices
)
)
)
->andWhere('o.id = :ownerId')
->setParameter('owner1Id', $owner->getId())
->setParameter('user1Id', $user->getId())
;
$query = $qb->getQuery();
$result = $query->getResult();
This would be more like to your original query but not the exact one I guess and might need some tweaks as per your mappings, but will give you an idea to move forward with this
References
Doctrine EXIST and NOT EXIST
Doctrine Query Builder nested orX and andX conditions with join
I guess that would be very handy to transform your logic directly in DQL by doing a left join with price.users and with additional filter clause in joining part so that it will join only rows for price where user id is $user->getId() and to exclude these prices which belongs to $user->getId() we can use a where clause as u.id IS NULL
DQL
SELECT p
FROM Price p
JOIN p.owners o
LEFT JOIN p.users u WITH u.id = :user1Id
WHERE u.id IS NULL
AND o.id = :ownerId
Query builder will be like
$qb = $this->createQueryBuilder("p")
->select("p")
->from("Price", "p")
->innerJoin('p.owner', 'o')
->leftJoin(
'p.users',
'u',
'WITH',
'u.id = :user1Id'
)
->where('u.id IS NULL')
->andWhere('o.id = :ownerId')
->setParameter('owner1Id', $owner->getId())
->setParameter('user1Id', $user->getId())
;
$query = $qb->getQuery();
$result = $query->getResult();

How to write math symbols in prettytable?

I have the following prettytable headings:
row[0] = ('k','No', 'A', 'I', 'P', 'lambda', 'rho','Wq', 'Est. Cost', 'Time (s)')
t = PrettyTable(row[0])
In the above table, I want to write lambda, rho, and W^q in a math way. Is it possible here in the pretty table? Thanks!

Appending a list built using conditional and appended values to a list of lists

I'm currently working with two large csv files of numerical data. One such csv, which we will call X, is composed entirely of numerical data for test subjects. The columns of a are arranged as health measurements like so (id, v1,v2,v3,v4). I am trying to take this information and create a list of lists where each list contains the information for a single person i.e as in this fashion:
X=[['1','a','b','c','d'],
['1','e','f','g','h'],
['2','i','j','k','l'],
['3','m','n','o','p']]
listoflists=[ [['1','a','b','c','d'],['1','e','f','g','h']], #first row
['2','i','j','k','l'], #second
['3','m','n','o','p'] ] #third
(let me know if i should edit the formatting: i wanted to present X as columns for readability. On list of lists I just ran out of room, so listolists = [ a,b,c], where a is the first row, b is the second, and c is third
I've tried something to the effect of this, but my biggest issue is I'm not sure where to create the list of those entities with matching data and then append it to the "master list".
#create a set that holds the values of the subject ids.
ids=list(set([item[0] for item in X]))
#create the list of lists i want
listolists=[]
for value in ids:
listolists.append(sublist)
for i in range(len(X))
sublist=[] #I'm not sure where to create sublists of
#matching data and append to listolists
if value == X[i][0]
sublist.append(X[i]
All help is appreciated. thanks.
Here is something:
X =[
['1','a','b','c','d'],
['1','e','f','g','h'],
['2','i','j','k','l'],
['3','m','n','o','p'],
]
numbers = {x[0] for x in X}
output = []
for num in sorted(numbers):
new_list = [sub_list for sub_list in X if sub_list[0] == num]
output.append(new_list)
print(output)
...
[[['1', 'a', 'b', 'c', 'd'], ['1', 'e', 'f', 'g', 'h']],
[['2', 'i', 'j', 'k', 'l']],
[['3', 'm', 'n', 'o', 'p']]]
If you need to 2nd and third list not nested like the first let me know
EDIT - for exact format specified in your question
X =[
['1','a','b','c','d'],
['1','e','f','g','h'],
['2','i','j','k','l'],
['3','m','n','o','p'],
]
numbers = {x[0] for x in X}
output = []
for num in sorted(numbers):
new_list = [sub_list for sub_list in X if sub_list[0] == num]
if len(new_list) > 1:
output.append(new_list)
else:
output.append((new_list)[0])
print(output)

Same code different results from Py2.7 to Py3.4. Where is the mistake?

I am refactoring a few lines of code found in Harrington, P. (2012). Machine Learning in Action, Chapters 11 and 12. The code is supposed to build an FP-tree from a test dataset and it goes as it follows.
from __future__ import division, print_function
class treeNode:
'''
Basic data structure for an FP-tree (Frequent-Pattern).
'''
def __init__(self, nameValue, numOccur, parentNode):
self.name = nameValue
self.count = numOccur
self.nodeLink = None
self.parent = parentNode
self.children = {}
def inc(self, numOccur):
'''
Increments the count variable by a given amount.
'''
self.count += numOccur
def disp(self, ind=1):
'''
Displays the tree in text.
'''
print('{}{}:{}'.format('-'*(ind-1),self.name,self.count))
for child in list(self.children.values()):
child.disp(ind+1)
def createTree(dataSet, minSup=1):
'''
Takes the dataset and the minimum support
and builds the FP-tree.
'''
headerTable = {} #stores the counts
#loop over the dataset and count the frequency of each term.
for trans in dataSet:
for item in trans:
headerTable[item] = headerTable.get(item, 0) + dataSet[trans]
#scan the header table and delete items occurring less than minSup
for k in list(headerTable.keys()):
if headerTable[k] < minSup:
del(headerTable[k])
freqItemSet = set(headerTable.keys())
#if no item is frequent, quit
if len(freqItemSet) == 0:
return None, None
#expand the header table
#so it can hold a count and pointer to the first item of each type.
for k in list(headerTable.keys()):
headerTable[k] = [headerTable[k], None]
#create the base node, which contains the 'Null Set'
retTree = treeNode('Null Set', 1, None)
#iterate over the dataset again
#this time using only items that are frequent
for tranSet, count in list(dataSet.items()):
localD = {}
for item in tranSet:
if item in freqItemSet:
localD[item] = headerTable[item][0]
if len(localD) > 0:
#sort the items and the call updateTree()
orderedItems = [v[0] for v in sorted(list(localD.items()),
key=lambda p: p[1], reverse=True)]
updateTree(orderedItems, retTree, headerTable, count)
return retTree, headerTable
def updateTree(items, inTree, headerTable, count):
if items[0] in inTree.children:
inTree.children[items[0]].inc(count)
else:
#Populate tree with ordered freq itemset
inTree.children[items[0]] = treeNode(items[0], count, inTree)
if headerTable[items[0]][1] == None:
headerTable[items[0]][1] = inTree.children[items[0]]
else:
updateHeader(headerTable[items[0]][1],inTree.children[items[0]])
#Recursively call updateTree on the remaining items
if len(items) > 1:
updateTree(items[1::], inTree.children[items[0]], headerTable, count)
def updateHeader(nodeToTest, targetNode):
while (nodeToTest.nodeLink != None):
nodeToTest = nodeToTest.nodeLink
nodeToTest.nodeLink = targetNode
def loadSimpDat():
simpDat = [['r', 'z', 'h', 'j', 'p'],
['z', 'y', 'x', 'w', 'v', 'u', 't', 's'],
['z'],
['r', 'x', 'n', 'o', 's'],
['y', 'r', 'x', 'z', 'q', 't', 'p'],
['y', 'z', 'x', 'e', 'q', 's', 't', 'm']]
return simpDat
def createInitSet(dataSet):
retDict = {}
for trans in dataSet:
retDict[frozenset(trans)] = 1
return retDict
simpDat = loadSimpDat()
initSet = createInitSet(simpDat)
myFPtree, myHeaderTab = createTree(initSet, 3)
myFPtree.disp()
This code run without errors in both Python 2.7.9 and 3.4.3. However the output I get is different. Moreover, the output I get with using Py2.7 is consistent while running the same code over and over again with Py3.4 leads to different results.
The correct result is the one obtained using Py2.7 but I cannot figure out why it doesn't work on 3.4.
Why?
What is wrong with this code when interpreted with Python3?
The output describe a defined tree. The order of the branches can change, but the underlined tree shall be the same. This is always the case with Python2 where the output looks like this:
-x:1
--s:1
---r:1
-z:5
--x:3
---y:3
----s:2
-----t:2
----r:1
-----t:1
--r:1
It should represent this tree.
Null
/ \
x z
/ \ / \
s r x r
| |
r y
/ \
s r
| |
t t
This is an example of the wrong result I get using Python3.
-z:5
--r:1
--x:3
---t:3
----y:2
-----s:2
----r:1
-----y:1
-x:1
--r:1
---s:1
P.S. I have tried to use OrderedDict instead of {}, it doesn't change anything...
What seem to happen is that you rely on the ordering of dict iteration (ie the order of d.keys(), d.items() etc). While both python2 and python3 guarantee that the iteration order is consistent during the execution it doesn't guarantee that it is consistent from run to run.
Therefore it's a correct behaviour that the order of the output differs from run to run. That you get the same result from run to run in python2 is pure "luck".
You can get python3 to behave deterministic by setting the PYTHONHASHSEED environment variable to a fixed value, but probably you shouldn't rely on dict iteration to be deterministic.

Using query builder to return an average in a single query

Okay, so I want to return an average transaction size in query builder. This is definitely not my forte so hoping I can find some help on this:
This is what I have:
I need to get total transactions (i.e. $100)
I need to get count of transaction (i.e. 10)
The above would represent an average transaction value of $10
Now to do this in query builder:
Step 1: get the total:
$qb = $this->transactionRepository->createQueryBuilder('u');
$qb ->add('select','SUM(u.amount)')
->where('u.status = :status')
->andWhere('u.type = :type')
->andWhere('u.identity = :identity')
->setParameter('status' , 1)
->setParameter('type' , 1)
->setParameter('identity' , 1);
$total = $qb
->getQuery()
->getSingleScalarResult();
Step 2, get the total transactions:
$qb = $this->transactionRepository->createQueryBuilder('u');
$qb ->add('select','COUNT(u.amount)')
->where('u.status = :status')
->andWhere('u.type = :type')
->andWhere('u.identity = :identity')
->setParameter('status' , 1)
->setParameter('type' , 1)
->setParameter('identity' , 1);
$transaction_count = $qb
->getQuery()
->getSingleScalarResult();
Step 3: To get the average:
$total/$transaction_count
So my question is this, is it possible to do this in a single query?
Ok this was simpler than I thought:
$qb = $this->transactionRepository->createQueryBuilder('u');
$qb ->add('select','SUM(u.total)/COUNT(u.amount)')
->where('u.status = :status')
->andWhere('u.type = :type')
->andWhere('u.identity = :identity')
->setParameter('status' , 1)
->setParameter('type' , 1)
->setParameter('identity' , 1);
$transaction_count = $qb
->getQuery()
->getSingleScalarResult();