views:

1135

answers:

2

I am working on a database that contains 3 tables:

  1. A list of companies
  2. A table of the products they sell
  3. A table of prices they offered on each date

I'm doing a query like this in my php to generate a list of the companies offering the lowest prices on a certain product type on a certain date.

SELECT
  a.name AS company,
  c.id,
  MIN(c.price) AS apy
FROM `companies` a
JOIN `company_products` b ON b.company_id = a.id
JOIN `product_prices` c ON c.product_id = b.id
WHERE
  b.type = "%s"
  AND c.date = "%s"
GROUP BY a.id
ORDER BY c.price ASC
LIMIT %d, %d

This gets me the data I need, but in order to implement a pager in PHP I need to know how many companies offering that product on that day there are in total. The LIMIT means that I only see the first few...

I tried changing the SELECT clause to SELECT COUNT(a.id) or SELECT COUNT(DISTINCT(a.id)) but neither of those seem to give me what I want. I tried removing the GROUP BY and ORDER BY in my count query, but that didn't work either. Any ideas?

+3  A: 

Looks to me like you should GROUP BY a.id, c.id -- grouping by a.id only means you'll typically have several c.ids per a.id, and you're just getting a "random-ish" one of them. This seems like a question of basic correctness. Once you have fixed that, an initial SELECT COUNT(*) FROM etc etc should then definitely give you the number of rows the following query will return, so you can prepare your pager accordingly.

Alex Martelli
Doesn't the MIN(c.price) take care of which c.id is chosen?
mishac
No. Anything in the select-list that is not an aggregate is supposed to be listed in the GROUP BY clause. Some DBMS relax that restriction, but most don't.
Jonathan Leffler
Good suggestion. Still, the MySQL LIMIT clause will fark up the total count(*).
Andomar
If I add c.id to the group by clause, it means that multiple items show in the list with the same company. I need to have each company show up just once. Any ideas where I went wrong?
mishac
Why are you trying to display c.id? There are multiple c.id's per company per day. Omit c.id from the group by clause, and the select.
Andomar
The c.id is there because the HTML table I'm generating will have an AJAX hover div that shows the other (irrelevant to this query) details from the price table, and I need to grab that ID to make sure the right row is shown there.
mishac
Use the company id for that? Since there's only one row per company. Your original solution would work too: a random c.id would be unique per row, whichever c.id you got. Relying on randomness is unusual, so if you go that way, place a comment explaining it.
Andomar
I can't use the company id because each company has multiple prices per day, and each price has specific conditions which are also stored in the price table. I'd need to have the exact c.id for the row with the price being displayed.
mishac
+2  A: 

This website suggests MySQL has a special trick for this, at least as of version 4:

Luckily since MySQL 4.0.0 you can use SQL_CALC_FOUND_ROWS option in your query which will tell MySQL to count total number of rows disregarding LIMIT clause. You still need to execute a second query in order to retrieve row count, but it’s a simple query and not as complex as your query which retrieved the data.

Usage is pretty simple. In you main query you need to add SQL_CALC_FOUND_ROWS option just after SELECT and in second query you need to use FOUND_ROWS() function to get total number of rows. Queries would look like this:

SELECT SQL_CALC_FOUND_ROWS name, email 
FROM users 
WHERE name LIKE 'a%' 
LIMIT 10;

SELECT FOUND_ROWS();

The only limitation is that you must call second query immediately after the first one because SQL_CALC_FOUND_ROWS does not save number of rows anywhere.

Andomar