Limit SQL join when using CodeIgniter active record class - php

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

Related

DQL Select every rows having one column's MAX value

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

Codeigniter active record left join issue

I have two tables 'accounts_transactions' and 'accounts_bills_transactions'.
I have to left join these two using active record of codeigniter.But the names of key columns used to join are different.So I am not getting the key column from the left table in the output .What query should I write to get the key column from the left table included in the result.
My code is
$this->db->select('*');
$this->db->from('accounts_transactions');
$this->db->join('accounts_bills_transactions', 'accounts_transactions.id = accounts_bills_transactions.transaction_id','left');
$query = $this->db->get();
So, as you see the key columns used to join here are , id from left table and transaction_id from second table.The problem is that I am not getting the id from left table in the result.But I am getting all other columns.I assume the problem is because of difference in column names used to join.ie both the column names are not named 'id' .So how can I get the id from left table included in the result.
You could alias them:
$this->db->select('accounts_transatctions.*, account_transactions.id AS a_id,
accounts_bills_transactions.*,
account_bills_transactions.id AS ab_id');
$this->db->from('accounts_transactions');
$this->db->join('accounts_bills_transactions', 'accounts_transactions.id = accounts_transactions.transaction_id','left');
$query = $this->db->get();
The two IDs will now be available as a_id and ab_id (or whatever alias you choose)
Note: I'm not sure if you can alias in AR without avoiding escaping (haven't been using CI for a while). Should you get any error for that reason, just pass false as second parameter of $this->db->select():
$this->db->select('...', false);
you can try this if you confuse of using $this->where or $this->join
$query = $this->db->query("select ......");
return $query;
You problem is so simple. You can use this query
$query = $this->db
->select('at.*')
->select('abt.id as abt_id');
->from('accounts_transactions at');
->join('accounts_bills_transactions abt', 'at.id = abt.transaction_id','left');
->get()
->result();
When same column are used in join it selects only one. You need to give alise to the other column in second table. The best practice is to use a structure like this
accounts_transatctions
--------------------------
accounts_transatctions_id
other_columns
accounts_bills_transactions
---------------------------
accounts_bills_transactions_id
accounts_transatctions_id
other_columns

Whats quicker - Multiple PDO queries or Array Query

I am just wondering which way to go with a database query. I have started using PDO recently with mysql. I am writing a small script that checks for manufacturers and then it checks items against each manufacturer. I am stuck whether it would be quicker to place the items in an array and (only use 1 query) then as i loop for the manufacturer array use an array_count_values to get the item quantities or do a seperate query in the loop to count the items.
I have about 400 manufacturers and 70000 items at present.
my current code using array is :
$itemquery = $conn->query("SELECT manufacturer FROM item_info_short");
$itemquery->execute();
$itemrow = $itemquery->fetchall(PDO::FETCH_ASSOC);
foreach ($itemrow as $itkey => $itvalue) {
$items[] = $itvalue[manufacturer];
}
$it_qty = array_count_values($items);
and then for my loop :
$manu_query = $conn->query("SELECT manufacturer FROM manufacturers ORDER BY manufacturer");
while($rowsx = $manu_query->fetch(PDO::FETCH_ASSOC)){
$rowid = $rowsx[manufacturer];
$count = $it_qty[$rowid];
if($count == '') $count = 0;
echo "<option value=\"$rowsx[manufacturer]\">$rowsx[manufacturer] $count Items</option>";
}
As you can see i use 2 PDO queries altogether.
The other method would use 401 queries.
I am trying to see which method is best practise and/or quicker.
Thanks in Advance for any advice.
Your question is irrelevant to PDO
You're doing it extremely inefficient way, but it's irrelevant to the question you've asked.
The question you have to ask have to be not "which is faster" but "which is proper way".
To get count of manufacturers with cout of their goods, you have to make SQL to count them for you
SELECT manufacturer, count(*) cnt FROM item_info_short GROP BY manufacturer
will return all manufacturers with their goods count
if you want to get manufacturer details along - join this query with manufacturers table
if you need to list all manufacturers with their goods - use LEFT JOIN
something like this
SELECT m.manufacturer, count(i.manufacturer) cnt
FROM manufacturers m LEFT JOIN item_info_short i
ON i.manufacturer = m.manufacturer GROUP BY m.manufacturer
Thanks 'Your Common Sense' for your assistance but it still did nt show me 0 results against manufacturers that were not in the 'item_info_short' table.
SELECT m.manufacturer,
(SELECT COUNT(i.manufacturer) FROM item_info_short i
WHERE m.manufacturer = i.manufacturer) cnt
FROM manufacturers m ORDER BY m.manufacturer ASC;
This was the final mysql statement I actually have used which gives me a full list of manufacturers and their quantities from item_info_short including 0 values. In answer to my own question this method is alot quicker than putting into an array first, and i believe this to be the correct way.

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.

MySql displaying results in same order no matter "array-order"

I am using "solr" search engine to query an index for classifieds that match a given criteria. The results are the ID:numbers of the classifieds, which I then use to find all matches in a MySql database with those ID:s.
The ID:s returned are put into an array.
As you can see below the array is imploded.
Then I use the "IN" to find all matches.
$solr_id_arr_imploded = implode("', '", $solr_id_arr);
$query = "SELECT mt.*, $sql_tbl.* FROM classified mt LEFT JOIN $sql_tbl ON
$sql_tbl.classified_id = mt.classified_id WHERE mt.ad_id IN ('$solr_id_arr_imploded')";
$sql_tbl is the category chosen by the user, in this case lets say it is "cars".
My problem is this:
I have the ID:numbers in an order (inside the array), but MySql doens't "care" about this order. MySql displays the oldest item first no matter what order the array is in.
So here is one same query displayed with two different "array-directions":
SELECT mt.*, fordon.* FROM classified mt LEFT JOIN fordon ON fordon.classified_id = mt.classified_id WHERE mt.ad_id IN ('Bmw_520i_Svensksald_784332731', 'Bmw_M3_Svensksald_755599519', 'Bmw_M3_E46_Full-utrustad_338210082')
SELECT mt.*, fordon.* FROM classified mt LEFT JOIN fordon ON fordon.classified_id = mt.classified_id WHERE mt.ad_id IN ('Bmw_M3_E46_Full-utrustad_338210082', 'Bmw_M3_Svensksald_755599519', 'Bmw_520i_Svensksald_784332731')
As you can see the ID:s are reversed in the second query above... But they are still displayed in the same order anyways. Why?
Should I use some other method of finding all MySql matches with ID:s from an array?
Ideas?
Thanks
This should do it:
SELECT mt.*, $sql_tbl.* FROM classified mt
LEFT JOIN $sql_tbl
ON $sql_tbl.classified_id = mt.classified_id
WHERE mt.ad_id IN ('$solr_id_arr_imploded')
ORDER BY FIELD(mt.ad_id,'$solr_id_arr_imploded')
See Order By Field in Sorting Rows.
MySQL will return the data in the order it "wants" (I suppose it'll be the order of the clustered index, or something like that), if you do not specify an order by clause.
If you want to change the order in which MySQL returns the results, you'll have to add an order by clause.
If that's not possible in your case, you'll have to re-order the elements from the PHP code -- for instance, instead of displaying the results from what MySQL returns, you should iterate over the list of ids returned by Solr, and display the results starting from there.
Basically, you'll first execute the MySQL query to fetch the results :
SELECT mt.*, fordon.*
FROM classified mt
LEFT JOIN fordon ON fordon.classified_id = mt.classified_id WHERE mt.ad_id IN (
'Bmw_520i_Svensksald_784332731', 'Bmw_M3_Svensksald_755599519',
'Bmw_M3_E46_Full-utrustad_338210082'
)
Then you can loop over those results, in PHP, storing them in an associative array (pseudo-code) :
$hash = array();
foreach ($db_results as $elem) {
$hash[$elem->ad_id] = $elem;
}
$hash will contain the data, indexed by id.
And, then, you'll display the data, using what Solr returned as a starting point for the loop (pseudo-code) :
foreach ($solr_results as $id_solr) {
echo $hash[$id_solr]->some_field . '<br />';
}
With this, you will :
display the results in the order returned by Solr
not do an additionnal (and possibily costly) sort on the database-side.

Categories