I am trying and failing to translate my relatively simple SQL statement into one that will work within Doctrine.
This is the SQL statement, which works as required when run against my database:
SELECT a.*
FROM score a
INNER JOIN (
SELECT name, MAX(score) AS highest
FROM score
GROUP BY name
) b
ON a.score = b.highest AND a.name = b.name
GROUP BY name
ORDER BY b.highest DESC, a.dateCreated DESC
Here's the DQL attempt thus far:
$kb = $em->createQuery(
"SELECT a
FROM ShmupBundle:Score a
INNER JOIN a.name ShmupBundle:Score b WITH a.score = b.score AND a.name = b.name GROUP BY b.name
WHERE a.platform='keyboard'
GROUP BY a.name
ORDER BY b.score DESC, a.dateCreated DESC"
);
Which is currently giving this error:
[Semantical Error] line 0, col 73 near 'ShmupBundle:Score': Error: Class ShmupBundle\Entity\Score has no association named name
The table itself is pretty simple:
id, name, score, platform, dateCreated
There are multiple entries with the same name, but different scores. I want to show only the "high score" per name. I've been trying on and off for a day or two now, with no luck. Can anyone point me in the right direction?
The query you are trying to do with doctrine is related to greatest-n-per-group. To use a sub query and then join with main query get things complicated to handle with doctrine. So below is the rewritten SQL version to get the same results without use of any aggregate functions:
SELECT
a.*
FROM
score a
LEFT JOIN score b
ON a.name = b.name
AND a.score < b.score
WHERE b.score IS NULL
ORDER BY a.score DESC
DEMO
To convert above query equivalent to doctrine or DQL is easy, below is the DQL version of above SQL:
SELECT a
FROM AppBundle\Entity\Score a
LEFT JOIN AppBundle\Entity\Score b
WITH a.name = b.name
AND a.score < b.score
WHERE b.score IS NULL
ORDER BY a.score DESC
Or with query builder you can write something like i have tested below with symfony 2.8 using the DEMO Schema
$DM = $this->get( 'Doctrine' )->getManager();
$repo = $DM->getRepository( 'AppBundle\Entity\Score' );
$results = $repo->createQueryBuilder( 'a' )
->select( 'a' )
->leftJoin(
'AppBundle\Entity\Score',
'b',
'WITH',
'a.name = b.name AND a.score < b.score'
)
->where( 'b.score IS NULL' )
->orderBy( 'a.score','DESC' )
->getQuery()
->getResult();
Another idea would be create a view using your query in database and in symfony create an entity put the view name in table annotation and just start calling your entity it will give the results returned by your query but this approach is not recommended just a temporary fix.
Inner Join Statement needs first argument as a table, that is a semantic error in your query.
$kb = $em->createQuery(
"SELECT a
FROM ShmupBundle:Score a
INNER JOIN ShmupBundle:Score b ON a.score = b.score AND a.name = b.name GROUP BY b.name
WHERE a.platform='keyboard'
GROUP BY a.name
ORDER BY b.score DESC, a.dateCreated DESC");
MySQL does not understand the : syntax. If ShmupBundle:Score is supposed to be a database and table, then use .. If Doctrine is supposed to replace it with something, then what does it do with it?
There can be only one GROUP BY clause, and it must be after the WHERE clause. Try removing the GROUP BY b.name.
There is no need to GROUP BY both b.name and a.name since they are equal.
use this
in class
$name = $em->getRepository('AppBundle:BlogPost')->getMaxId();
in repository you can use something like
public function getMaxId()
{
$qb = $this->createQueryBuilder('u');
$qb->select('u, MAX(id) as idMax');
return $qb->getQuery()->getSingleResult();
}
each entity come with some default repository functions either we defined or not
if we wish to add some extra functionality we do write down custom functions in repository class. like i want to add one more function getMaxId i will write down in repository to access this function.
for getting max or min from each group we can do with given query
select * from (select * from mytable order by `Group`, age desc, Person) x group by `Group
this is not good way to fetch max from each group as we need to write down sub query for that.
other than that we have Row_number() function
SELECT sd.* FROM ( SELECT sale_person_id,sale_person_name,no_products_sold,commission_percentage,sales_department,ROW_NUMBER() OVER(PARTITION BY sale_person_id ORDER BY no_products_sold DESC) rowNumber FROM sales_department_details )sd WHERE sd.rowNumber =1;
here you find out all work arounds
Related
My query looks like
SELECT
COUNT(*) as cnt, ROUND(SUM(cost),2) as cost
FROM
(SELECT ROUND(SUM(cost),2) as cost FROM accounts LEFT JOIN earnings ON earnings.id = accounts.id WHERE earnings.date >= ? GROUP BY earnings.id) src;
How to write it using $querybuilder from Doctrine?
I have tried:
$qb = $this->em->getConnection()->createQueryBuilder();
$qb->select('COUNT(*) as cnt, ROUND(SUM(cost),2) as cost');
$qb->from(
$qb->select('SELECT ROUND(SUM(cost),2) as cost')
->from('accounts')
->leftJoin('accounts', 'earnings', 'earnings', 'earnings.id = accounts.id')
->where('earnings.date >= :date')->setParameter(':date', $dto->getFromSqlFormat())
->groupBy('earnings.id')
);
But it is not working and I am getting error:
Warning: Illegal offset type
It looks like you need a total count of accounts and their sum of total cost from earnings table If so then i guess you can simplify your original query as
SELECT COUNT(DISTINCT a.id) cnt,
ROUND(SUM(e.cost),2) as cost
FROM accounts a
LEFT JOIN e ON e.id = a.id
WHERE e.date >= ?
No need for group by if you want a scalar result
In DQL it would be as
SELECT COUNT(DISTINCT a.id) cnt,
SUM(e.cost) as cost
FROM AppBundle\Entity\Account a
LEFT JOIN a.earnings e
WHERE e.date >= ?
In query builder it would look like
$this->createQueryBuilder('a')
->select('count(distinct a.id) as cnt, sum(e.cost) as cost')
->from('AppBundle\Entity\Account','a')
->leftJoin('a.earnings', 'e')
->where('e.date = :date')
->setParameter('date', $date)
->getQuery();
You can always format your end result from your codebase so I have removed round from query builder and DQL
Prior to above make sure you have defined proper relation in your Account entity that points to Earning entity (one to many /many to many) otherwise this won't help
I've got a event table with title and description of an event, and i've got a eventInstance table where the dates and venues of an event are stored. So it's a one to n relation: one event can have many instances.
What i'm trying now is to select only the most current event instance for an event. In SQL the query is:
select e.id, e.title, ei.start_date
from event e
LEFT join event_instance ei on ei.id =
(SELECT id FROM event_instance ei where ei.event_id = e.id ORDER BY start_date asc, start_time asc LIMIT 1)
ORDER BY start_date asc, start_time asc LIMIT 20;
I'm trying to rewrite the sql command in dql. So far i've got this:
SELECT e, ei
FROM AppBundle:Event e LEFT JOIN e.eventInstances ei WITH ei =
(SELECT a FROM AppBundle:EventInstance a WHERE a.event = e ORDER BY a.startDate asc, a.startTime asc)
My problem is, there's no LIMIT command in dql, so i cannot limit the subquery to give me one result. So the error i'm getting when executing this query is:
SQLSTATE[21000]: Cardinality violation: 7 ERROR: more than one row returned by a subquery used as an expression
Is there any way i can make this work?
Create a subquery then set max results on that.
$subDql = 'SELECT a
FROM AppBundle:EventInstance a
WHERE a.event = e
ORDER BY a.startDate asc, a.startTime asc';
$subQuery = $this->getEntityManager()
->createQuery($subDql)
->setMaxResults(1)
;
$dql = 'SELECT e, ei FROM AppBundle:Event e
LEFT JOIN e.eventInstances ei
WITH ei = (' .$subQuery . ')'
;
$query = $this->getEntityManager()
->createQuery($dql)
->setMaxResults($yourOtherLimit)
;
$resultCollection = $query->getResult();
An equivalent DQL for your solution (to get latest row per group) will be something like
SELECT e,a
FROM AppBundle:Event e
JOIN e.eventInstances a
LEFT JOIN AppBundle\Entity\Score b
WITH a.event = b.event
AND a.startDate < b.startDate
AND a.startTime < b.startTime
WHERE b.event IS NULL
ORDER BY a.startTime DESC
OR
SELECT e,a
FROM AppBundle:Event e
JOIN e.eventInstances a
LEFT JOIN AppBundle\Entity\Score b
WITH a.event = b.event
AND a.startDate = b.startDate
AND a.startTime < b.startTime
WHERE b.event IS NULL
ORDER BY a.startTime DESC
I want to get the lowest bid of the user for each product id.
return $this->getEntityManager()
->createQuery('
SELECT PARTIAL b.{Id, MIN(amount) as amount, currency}, PARTIAL p.{id, firstName, lastName}
FROM AppBundle:Bid b
LEFT JOIN b.provider p
WHERE b.product in (:product)
AND b.status = :status
GROUP BY b.product
ORDER BY b.amount'
)
->setParameter('product', $ids)
->setParameter('status', 'active')
->getResult()
;
I get following error
[Syntax Error] line 0, col 42: Error: Expected
Doctrine\ORM\Query\Lexer::T_CLOSE_CURLY_BRACE, got '('
I have run exactly similar query in SQL, it works fine, but I'm not sure how to achieve same in doctrine query builder.
If you want user mysql function with doctrine query you must be add extension of doctrine mysql function OR you can user native query. I have added native query in follow:
use Doctrine\ORM\Query\ResultSetMapping;
$rsm = new ResultSetMapping();
$rsm->addEntityResult('AppBundle:Bid', 'b');
$rsm->addFieldResult('b', 'Id', 'Id');
$rsm->addFieldResult('b', 'amount', 'amount');
$rsm->addFieldResult('b', 'currency', 'currency');
...
$sqlQuery = "SELECT b.Id, MIN(b.amount), b.currency, p.firstName, p.lastName
FROM AppBundle:Bid b
LEFT JOIN b.AppBundle:Provider p
WHERE b.AppBundle:Product in (:product)
AND b.status = :status
GROUP BY b.product
ORDER BY b.amount";
$query = $em->createNativeQuery($sqlQuery, $rsm)
->setParameter('product', $ids)
->setParameter('status', 'active');
$query->getResult();
Please let me know if any problem with this one
When using the PARTIAL keyword in your DQL query, you can only list properties of the entities you select from (the MIN() function is no property of your entity). You can simply leave out the PARTIAL part and use a query like this (please note that Doctrine will not return objects in your result set but only a flat array, see also http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#partial-object-syntax):
SELECT b.Id, MIN(b.amount) as amount, b.currency as bid, p.id, p.firstName, p.lastName as provider
FROM AppBundle:Bid b
LEFT JOIN b.provider p
WHERE b.product in (:product) AND b.status = :status
GROUP BY b.product
ORDER BY b.amount
I query a database and order by a date column in the table and also by another column in another table. This is working perfectly.
SELECT * FROM myTable1 LEFT JOIN myTable2 b ON myTable1.id = b.id WHERE myTable1.id !='foo' ORDER BY myTable1.dateColumn DESC, b.column2 ASC
What I would like is to do is ORDER by myTable1.dateColumn ONLY if there is no value in b.column2 for that particular record. As you can see from my above current implementation, it will always ORDER BY myTable1.dateColumn regardless.
Stackoverflow Suggested tag for this question is mysql but in fact I am using mysqli. It makes no difference for the purpose of this question.
You should better provide some data sample and expected result, but so far you can try:
SELECT * FROM myTable1
LEFT JOIN myTable2 b
ON myTable1.id = b.id
WHERE myTable1.id !='foo'
ORDER BY CASE WHEN b.column2 IS NULL myTable1.dateColumn ELSE NULL END DESC, b.column2 ASC
You can put CASE Statements inside of an ORDER BY
SELECT *
FROM myTable1
LEFT JOIN myTable2 b
ON myTable1.id = b.id
WHERE myTable1.id != 'foo'
ORDER BY CASE WHEN b.column2 IS NULL THEN 1 ELSE 0 END ASC, myTable1.dateColumn DESC
You can use a function in the order part; something like the following:
SELECT * FROM myTable1 LEFT JOIN myTable2 b ON myTable1.id = b.id WHERE myTable1.id !='foo'
ORDER BY IF(b.column2 IS NULL,1,0),IF(b.column2 IS NULL,myTable1.dateColumn,NULL) DESC, b.column2 ASC
I have the following query which works fine (see below).
But when I add a condition, for example AND (specialtyName = '...') the main results are fine, but the GROUP_CONCAT only shows the results that match the condition.
Can anyone please help me with this?
Thanks in advance.
Fred.
SELECT
tblJobs.jobID,
tblJobs.jobName,
DATE_FORMAT(tblJobs.jobDate,'%d-%m-%Y'),
tblCompanies.companyID,
tblCompanies.companyName,
tblCompanies.companyNameConvert,
GROUP_CONCAT(DISTINCT tblSpecialties.specialtyName
ORDER BY FIELD (
specialtyName,
'specialtyName1',
'specialtyName2',
'specialtyName3'),
specialtyName ASC)
AS specialtyNames,
GROUP_CONCAT(DISTINCT tblSpecialties.specialtyNameConvert
ORDER BY FIELD (
specialtyName,
'specialtyName1',
'specialtyName2',
'specialtyName3'),
specialtyName ASC)
AS specialtyNamesConvert,
GROUP_CONCAT(DISTINCT tblRegions.regionName),
GROUP_CONCAT(DISTINCT tblRegions.regionNameConvert)
FROM tblJobs
LEFT JOIN tblCompanies ON
(tblJobs.jobCompany = tblCompanies.companyID)
LEFT JOIN tblSpecialties ON
FIND_IN_SET(tblSpecialties.specialtyID, REPLACE(tblJobs.jobSpecialty,' ',','))
LEFT JOIN tblRegions ON
FIND_IN_SET(tblRegions.regionID, REPLACE(tblJobs.jobRegion,' ',','))
WHERE
AND jobActive = '1'
AND jobDate >= '2013-01-01'
AND companyActive = '1'
GROUP BY jobID
ORDER BY jobDate DESC, jobID DESC, jobCompany DESC
If you say:
WHERE jobActive = '1' AND jobDate >= '2013-01-01' AND companyActive = '1' AND
specialties = XXX
Then you are only going to get exactly those specialties. The filtering is done before the aggregation. As a note: including such conditions in the where clause also turns the outer joins to inner joins. Your joins are probably on properly aligned foreign key relationships, so inner joins may be appropriate.
I'm guessing what you really want is to filter jobs by those having that specialty, but to keep all other information. You want to do the filtering after the aggregation. Do this with a having clause instead of a where clause:
having sum(specialties = XXX) > 0;
This will keep only the rows that have the particular specialty, and keep all the other information.
I suppose that using aliases for your tables and subqueries could resolve your problem.
You can try something like this:
SELECT
tblJobs.jobID,
tblJobs.jobName,
DATE_FORMAT(tblJobs.jobDate,'%d-%m-%Y'),
tblCompanies.companyID,
tblCompanies.companyName,
tblCompanies.companyNameConvert,
(SELECT GROUP_CONCAT(DISTINCT ts.specialtyName
ORDER BY FIELD (
specialtyName,
'specialtyName1',
'specialtyName2',
'specialtyName3'),
specialtyName ASC)
FROM tblSpecialties ts) AS specialtyNames ,
, ... ,
FROM tblJobs
LEFT JOIN tblCompanies ON
(tblJobs.jobCompany = tblCompanies.companyID)
LEFT JOIN tblSpecialties ON
FIND_IN_SET(tblSpecialties.specialtyID, REPLACE(tblJobs.jobSpecialty,' ',','))
LEFT JOIN tblRegions ON
FIND_IN_SET(tblRegions.regionID, REPLACE(tblJobs.jobRegion,' ',','))
WHERE
AND jobActive = '1'
AND jobDate >= '2013-01-01'
AND companyActive = '1'
GROUP BY jobID
ORDER BY jobDate DESC, jobID DESC, jobCompany DESC
I didn't tested this code, but It could help.