DQL Select every rows having one column's MAX value - php

Working with Symfony 2 and Doctrine, I'm searching for a way to select every rows having the max value in a specific column.
Right now, I'm doing it in two queries:
One to get the max value of the column in the table
Then I select rows having this value.
I'm sure this can be done with one query.
Searching, I have found this answer in a thread, that seems to be what I am searching for, but in SQL.
So according to the answer's first solution, the query I'm trying to build would be something like that:
select yt.id, yt.rev, yt.contents
from YourTable yt
inner join(
select id, max(rev) rev
from YourTable
group by id
) ss on yt.id = ss.id and yt.rev = ss.rev
Does anybody know how to make it in Doctrine DQL?
For now, here is the code for my tests (not working):
$qb2= $this->createQueryBuilder('ms')
->select('ms, MAX(m.periodeComptable) maxPeriode')
->where('ms.affaire = :affaire')
->setParameter('affaire', $affaire);
$qb = $this->createQueryBuilder('m')
->select('m')
//->where('m.periodeComptable = maxPeriode')
// This is what I thought was the most logical way of doing it:
->innerJoin('GAAffairesBundle:MontantMarche mm, MAX(mm.periodeComptable) maxPeriode', 'mm', 'WITH', 'm.periodeComptable = mm.maxPeriode')
// This is a version trying with another query ($qb2) as subquery, which would be the better way of doing it for me,
// as I am already using this subquery elsewhere
//->innerJoin($qb2->getDQL(), 'sub', 'WITH', 'm.periodeComptable = sub.maxPeriode')
// Another weird try mixing DQL and SQL logic :/
//->innerJoin('SELECT MontantMarche mm, MAX(mm.periodeComptable) maxPeriode ON m.periodeComptable = mm.maxPeriode', 'sub')
//->groupBy('m')
->andWhere('m.affaire = :affaire')
->setParameter('affaire', $affaire);
return $qb->getQuery()->getResult();
The Entity is GAAffairesBundle:MontantMarche, so this code is in a method of the corresponding repository.
More generally, I'm learning about how to handle sub-queries (SQL & DQL) and DQL syntax for advanced queries.
Thx!

After some hours of headache and googling and stackOverflow readings...
I finally found out how to make it.
Here is my final DQL queryBuilder code:
$qb = $this->createQueryBuilder('a');
$qb2= $this->createQueryBuilder('mss')
->select('MAX(mss.periodeComptable) maxPeriode')
->where('mss.affaire = a')
;
$qb ->innerJoin('GAAffairesBundle:MontantMarche', 'm', 'WITH', $qb->expr()->eq( 'm.periodeComptable', '('.$qb2->getDQL().')' ))
->where('a = :affaire')
->setParameter('affaire', $affaire)
;
return $qb->getQuery()->getResult();

For me when i trying to make a subquery i make:
->andWhere($qb->expr()->eq('affaire', $qb2->getDql()));

To achieve this using pure DQL and without use of any aggregate function you can write doctrine query as
SELECT a
FROM GAAffairesBundle:MontantMarche a
LEFT JOIN GAAffairesBundle:MontantMarche b
WITH a.affaire = b.affaire
AND a.periodeComptable < b.periodeComptable
WHERE b.affaire IS NULL
ORDER BY a.periodeComptable DESC
The above will return you max record per group (per affaire)
Expalnation
The equivalent SQL for above DQL will be like
SELECT a.*
FROM MontantMarche a
LEFT JOIN MontantMarche b
ON a.affaire = b.affaire
AND a.periodeComptable < b.periodeComptable
WHERE b.affaire IS NULL
ORDER BY a.periodeComptable DESC
Here i assume there can be multiple entries in table e.g(MontantMarche) for each affaire, so here i am trying to do a self join on affaire and another tweak in join is i am trying to join only rows from right table(b) where a's periodeComptable < b's periodeComptable, So the row for left table (a) with highest periodeComptable will have a null row from right table(b) thus to pick the highest row per affaire the WHERE right table row IS NULL necessary.
Similarly using your posted sample query with inner join can be written as
select yt.id, yt.rev, yt.contents
from YourTable yt
left join YourTable ss on yt.id = ss.id and yt.rev < ss.rev
where ss.rev is null
Hope it makes sense

Related

unsure about query result

i have this working query
Sum(`sales`.`quantity`) AS totquantity,
`transactions`.`price` AS price,
Sum(`sales`.`quantity`) * `transactions`.`price` AS grantot
from (`sales` join `transactions` on((`transactions`.`idtransaction` = `sales`.`idtransaction`)))
where ((`sales`.`createon` > '01/01/2017') and (`sales`.`createon` < 'now()'))
group by `sales`.`idtransaction`
but it would be usefully to consult to create this view
select `products`.`idproduct` AS `idproduct`,`transactions`.`idtransaction` AS
`idtransaction`,`transactions`.`idline` AS `idline`,
`products`.`name` AS `name`,`products`.`code` AS `code`,`transactions`.`price` AS `price`,`sales`.`quantity` AS `quantity`,`sales`.`createon` AS `createon`
from (`sales` left join (`transactions` left join `products` on((`products`.`idproduct` = `transactions`.`idproduct`))) on((`transactions`.`idtransaction` = `sales`.`idtransaction`)))
and make a query on the view like
select * from myview where `sales`.`createon` > '01/01/2017' and `sales`.`createon` < 'now()'
now my question is are the two result the same?
thx in advance
Your original query uses two tables and full joins.
However, the view has already three tables and left joins.
That’s enough to conclude there is no guarantee they produce the same result in general.
You can provide more precise inputs to get more precise answer.

Left Join between two databases using PDO

I have two databases and I am trying to compare two tables. My code does not seem to be working, not sure what I am doing wrong.
Here is the code.
<?php
include 'connection.php';
/*
* This code compares between two tables
*/
//SQL call
$getData = $connection->prepare("SELECT `CustomerCity` FROM `auth` LEFT JOIN `tb_data.cobs.city` WHERE `CustomerCity` = `tb_data.cobs.city` LIMIT 3");
$getData->execute();
$gotData = $getData->fetchAll(PDO::FETCH_ASSOC);
print_r($gotData);
In my database I have two tables, on is cobs, the other is tb_data. tb_data has a table called cobs and auth is a table within a database called d_data. Both of these tables have a city column. I need get every record in auth that has a city that matches in the cobs table.
That looks like the query is using a mixture of explicit join syntax with obsolescent syntax for the join using the WHERE clause for the join conditions.
If so try:-
SELECT CustomerCity
FROM auth
LEFT JOIN tb_data.cobs
ON auth.CustomerCity = cobs.city
LIMIT 3
Others have pointed out that your query is wrong, but have not provided a correct answer. This is what you are likely looking for:
SELECT `auth.CustomerCity` FROM `auth`
LEFT JOIN `tb_data.cobs` ON `tb_data.cobs.city` = `auth.CustomerCity`
LIMIT 3
Try this :
Select *.auth, *.cobs from auth join cobs on auth.city = cobs.city limit 3
The query is incorrect, you need to specify the link betweeen the tables auth and tb_data.cobs.city. For example:
SELECT
*
FROM
FOOTABLE FOO
LEFT JOIN
BARTABLE BAR ON BAR.FOO_ID FOO.ID = -- here goes the link between them
WHERE
...

Query two tables for different/separate values

I have two tables:
task
id name dueDate completed projectID
project
id name dueDate completed
I need to query both tables for rows with the same data. I tried doing a sample statement just looking for rows with completed=0, but never got any results. I think it has to do with using OR instead of AND, but it's just a little above my level right now...Any ideas?
TO CLARIFY, I'm not looking for duplicate rows, I'm looking for 'all tasks and projects with completed = 0'
The query is:
SELECT * FROM "task" t, "project" p WHERE t.dueDate = "2012-08-17" OR p.dueDate = "2012-08-17" AND t.completed = 0 OR p.completed = 0
I did manage to get one of the answers' code to work, however I realized that my entire app was written to talk to one table, and that it would be much easier to just combine the task and project table and use an isProject column to differentiate projects from tasks. This also adds the ability to nest projects inside of projects, because projects will now have a projectID column as well.
In the end, KISS prevails...
Thanks for all the help! I will mark the answer that worked, even though I won't be using it.
Try using parenthesis.
SELECT * FROM "task" t, "project" p WHERE (t.dueDate = "2012-08-17" OR p.dueDate = "2012-08-17") AND (t.completed = 0 OR p.completed = 0)
If You want only values matches from both tables with completed=0 from dueDate='2012-08-17':
You can use join to bound that tables results into one.
Inner join will return only results which matches on both sides.
So You can use it to match them in both tables by it and then filter for Your wanted value by classic where:
select * from task t inner join project p on t.dueDate = p.dueDate and t.completed = p.completed
where t.dueDate = '2012-08-17' and t.completed = 0
Try this instead:
SELECT dueDate, completed
FROM task AS t
WHERE (dueDate = "2012-08-17" AND completed = 0)
UNION ALL
SELECT dueDate, completed
FROM project AS p
WHERE (dueDate = "2012-08-17" AND completed = 0)
This should give you all records from each table where dueDate = "2012-08-17" and completed = 0.

Limit SQL join when using CodeIgniter active record class

I'm getting a product listing. Each product may have 1 or more image, I only want to return the first image.
$this->db->select('p.product_id, p.product_name i.img_name, i.img_ext');
$this->db->join('products_images i', 'i.product_id = p.product_id', 'left');
$query = $this->db->get('products p');
Is there anyway to limit the db->join to 1 record using the CI active record class?
Add $this->db->limit(1); before calling $this->db->get('products p');. See the docs at ellislab.com: search the page for limit.
EDIT: I misread the fact that you were trying to apply the LIMIT to the internal JOIN statement.
No. Since you can not do a LIMIT on an internal JOIN statement in regular SQL you can not do it with Code Igniter's ActiveRecord class.
You can achieve what you want using $this->db->group_by with a left join:
$this->db->select('products.id, products.product_name, products_images.img_name, products_images.img_ext');
$this->db->from('products');
$this->db->join('products_images', 'products_images.product_id = products.id', 'left');
$this->db->group_by('products.id');
$query = $this->db->get();
This should give you results by products.id (without repetition of products), with the first matching record from products_images joined to each result row. If there's no matching row from the joined table (i.e. if an image is missing) you'll get null values for the products_images fields but will still see a result from the products table.
To expand on #Femi's answer:
There's no good way to limit the JOIN, and, in fact, you don't really want to. Assuming both products_image.product_id and products.id have indexes (and they absolutely should if you're going to join against them repeatedly) when the database engine does a join, it uses the indexes to determine what rows it needs to fetch. Then the engine uses the results to determine where on the disk to find the records it needs. If you
You should be able to see the difference by running these SQL statements:
EXPLAIN
SELECT p.product_id, p.product_name, i.img_name, i.img_ext
FROM products p
LEFT JOIN products_images i
ON i.product_id = p.product_id
as opposed to:
EXPLAIN
SELECT p.product_id, p.product_name, i.img_name, i.img_ext
FROM (SELECT product_id, product_name FROM products) p
LEFT JOIN (SELECT img_name, img_ext FROM products_images) i
ON i.product_id = p.product_id
The first query should have an index, the second one will not. There should be a performance difference if there's a significant number of rows the the DB.
Had this issue too the way I solved it was iterating over the results and removing the current object if the product_id had existed in a previous one. Create a array, push the product_id's to it while checking if they are repeats.
$product_array = array();
$i = 0;
foreach($result as $r){
if(in_array($r->product_id,$product_array)){
unset($result[$i]);
}else{
array_push($product_array,$r->product_id);
}
$i++;
}
$result = array_values($result); //re-index result array
Now $result is what we want

Returning data from multiple tables in one query even if secondary tables do not apply

I have an issue getting data from three tables, which I want to return using one query.
I've done this before using a query something like this one:
$query = mysql_query("SELECT
maintable.`id`,
maintable.`somedata`,
maintable.`subtable1_id`,
subtable1.`somedata`,
subtable1.`subtable2_id`,
subtable2.`somedata`
FROM
`maintable` maintable,
`subtable1` subtable1,
`subtable2` subtable2
WHERE
maintable.`somedata` = 'some_search' AND
subtable1.`id` = maintable.`subtable1_id` AND
subtable2.`id` = subtable1.`subtable2_id`
")or die(mysql_error());
The problem this time is that the extra details might not actually apply. I need to return all results that match some_search in maintable, even if there is no subtable1_id specified.
I need something that will go along the lines of
WHERE
maintable.`somedata` = 'some_search'
AND IF maintable.`subtable1_id` IS NOT NULL (
WHERE subtable1.`id` = maintable.`subtable1_id` AND
subtable2.`id` = subtable1.`subtable2_id`
)
As you will probably guess, I am not an advanced mysql user! Try as I might, I cannot get the syntax right, and I have had no luck searching for solutions on the web.
Any help much appreciated!
It seems like the basic distinction you're looking for is between an INNER JOIN and a LEFT JOIN in MySQL.
An INNER JOIN will require a reference between the two tables. There must be a match on both sides for the row to be returned.
A LEFT JOIN will return matches in both rows, like an INNER, but it will also returns rows from your primary table even if no rows match in your secondary tables -- their fields will be NULL.
You can find example syntax in the docs.
If I got this right, you need to use MySQL LEFT JOIN. Try this:
SELECT
m.id,
m.somedata,
m.subtable1_id,
s1.somedata,
s1.subtable2_id,
s2.somedata
FROM
maintable m
LEFT JOIN
subtable1 s1
ON
m.subtable1_id = s1.id
LEFT JOIN
subtable2 s2
ON
s1.subtable2_id = s2.id
WHERE
m.somedata = 'some search'
This will return the data of maintable even if there's no equivalent data in subtable1 or 2 OR data of maintable and subtable1 if there's no equivalent data in subtable2.
How about this:
WHERE
maintable.`somedata` = 'some_search'
AND (maintable.`subtable1_id` IS NULL OR (
subtable1.`id` = maintable.`subtable1_id` AND
subtable2.`id` = subtable1.`subtable2_id` )
)
Keep in mind that this will result in a cross product of subtable1 and subtable2 with maintable when subtable1_id is NULL.

Categories