How do I limit the result of a subquery in MySQL? - mysql

Is there a way of limiting the result of a subquery? The sort of thing I'm trying to achieve can be explained by the query below:
SELECT *
FROM product p
JOIN (
SELECT price
FROM supplierPrices sp
ORDER BY price ASC
LIMIT 1
) ON (p.product_id = sp.product_id)
The idea would be to get only the lowest price for a particular product from a table that had all the price data in it. LIMIT 1 is limiting the entire result set, whereas excluding it would result in a row being returned for each price, with duplicated product data. I tried GROUP BY price as well to no avail.
Once the limit is working I need to apply IFNULL as well, so that if there is no price found at all for any supplier it can return a supplied string, such as "n/a" rather than NULL. I assume that would just mean modifying the SELECT as below, and changing the JOIN to a LEFT JOIN?
SELECT *, IFNULL(price,'n/a')

Just to expand on Wolfy's answer slightly, and bearing in mind this is untested:
SELECT *
FROM product p
LEFT JOIN (
SELECT product_id, MIN(price)
FROM supplierPrices sp
GROUP BY product_id
) x ON (p.product_id = x.product_id)
And, as you say, it should just be a matter of doing an IFNULL on that column to replace it with something sensible.

Related

SQL Fetch multiple result counts from same set of data using same query

I am working on product data, part of which has the below structure (let's call it product_serials):
The table is a collection of product serial numbers. The snapped field determines whether a specific product has been purchased or not via it's serial number. Am trying to query the table to get a count of both all serials and also all unpurchased serials of the same product_id, using a single SQL query. So far using COUNT(ps1.id) AND COUNT(ps2.id) ... WHERE ps2.snapped = FALSE does not seem to work, it still counts the same values for both all serials and unpurchased serials, and even exaggerates the count, so am definitely doing something wrong.
What could I be missing?
My SQL query as requested:
SELECT pd.id AS product_id, pd.description,
COUNT(pds.id) AS total, COUNT(pds2.id) AS available
FROM products pd
LEFT JOIN product_serials pds ON pds.product_id = pd.id
LEFT JOIN product_serials pds2 ON pds2.product_id = pd.id
WHERE pds2.snapped = FALSE
GROUP BY pd.id
ORDER BY pd.date_added DESC
Here you join tables (even multiplying them) and then apply a WHERE condition to both.
I suggest something like the following:
SELECT product_id, count(serial), count(unpurchased)
FROM (SELECT product_id, serial,
CASE WHEN snapped THEN NULL ELSE 1 END AS unpurchased)
GROUP BY product_id

MySQL: using main query variable on sub-sub-query

I'm trying to create query that will show me table of stock, name of the stock, id, date, url, price and list of prices from the last 2 weeks.
For the 14 days history I used sub-query with group_concat on the select.
But when I use group_concat it's return all results and ignore my limit, so I created another sub-query that will be the 14 prices and the group_concat will make it a list.
The table 'record_log' is records for all stocks:
parent_stock_id - the actual stock this line belongs
price - the price
search_date - date of the price
The second table is 'stocks':
id - id of the stock
name, market_volume....
Here is the problem:
In the sub-sub-query (last line of the SELECT), when i'm filtering parent_stock_id=stocks.id he don't recognize the stocks.id because it belongs to the main query.
How can I take the stock_id from top and pass it to the sub-sub-query? or maybe another idea?
SELECT
stocks.id AS stock_id,
record_log.price AS price,
record_log.search_date,
(SELECT GROUP_CONCAT(price) FROM (SELECT price FROM record_log WHERE parent_stock_id=stocks.id ORDER BY id DESC LIMIT 14) AS nevemind) AS history
FROM stocks
INNER JOIN record_log ON stocks.id = record_log.parent_stock_id
WHERE
record_log.another_check !=0
Thank you!
--- I'm are not really using it for stocks, it's just was the easiest way to explain :)
One method is to use substring_index() and eliminate the extra subquery:
SELECT s.id AS stock_id, rl.price AS price, rl.search_date,
(SELECT SUBSTRING_INDEX(GROUP_CONCAT(price ORDER BY id DESC), ',', 14)
FROM record_log rl2
WHERE rl2.parent_stock_id = s.id
) AS history
FROM stocks s INNER JOIN
record_log rl
ON s.id = rl.parent_stock_id
WHERE rl.another_check <> 0;
Note that MySQL has a settable limit on the length of the group_concat() intermediate result (group_concat_max_len). This parameter is defaulted to 1,024.

SQL subquery going heywire

I am trying to fetch some combined result from two separate individual tables.
The transaction_fact table has around 3.6 million rows and translation_table has around 300000 rows.
Now i want a sum of amount for all transactions grouped by location and the product within that location. But as the fact table has only location id and product id and i would like the names in the result , I am using sub query.
My query is as follows:
SELECT
( SELECT translation
FROM translation_table
WHERE dim_name LIKE 'location_dim'
AND lang_id LIKE 'es'
AND dim_id LIKE CAST(o.loc_id AS CHAR(50))
AND field_name LIKE 'city') AS Location
, ( SELECT product_name
FROM prod_dim
WHERE prod_id = o.prod_id) AS Product
, SUM(amount)
FROM transaction_fact o
GROUP
BY loc_id
, prod_id
ORDER
BY loc_id
, prod_id;
But this query is not returning anything , just keeps on processing.
I waited for about one and half hour but still no result.
Please tell me what might be going wrong.
Joining the tables should eliminate the need for subqueries and give some performance boost. If not you may need to provide more details on the table structure before we can help. Something like this should get you started:
SELECT t.translation AS Location, p.product_name AS Product, SUM(o.amount) AS Total
FROM transaction_fact o
INNER JOIN translation_table t ON CAST(o.loc_id AS char(50)) = t.dim_id
INNER JOIN prod_dim p ON p.prod_id = o.prod_id
WHERE t.dim_name = 'location_dim'
AND t.lang_id = 'es'
AND t.field_name = 'city'
GROUP BY t.translation, p.product_name
ORDER BY o.loc_id, o.prod_id;
Notes: I've changed the LIKEs to =, as LIKE is for when you want to match on a pattern that includes wildcards.
The CAST that is used in the join to translation_table is not ideal. If you could do away with that you'd get better performance.

How to get a default value when a MAX() column does not exist

So here is a sample query illustrating a simplified version of the problem I am having:
SELECT max_discount, product_name
FROM products
LEFT JOIN (
SELECT IF(MAX(discount_percentage) IS NOT NULL, MAX(discount_percentage), 5) AS max_discount,
product_id,
order_id
FROM orders
LEFT JOIN discounts ON discounts.id = orders.discount_id
GROUP BY order_id
) AS alias ON alias.product_id = products.product_id
GROUP BY alias.order_id
In this case, I can have multiple "discounts" (vouchers) related to the same order. But if there are multiple discounts related to a single order, I only want to actually apply the highest valued discount only. For example if you have a voucher for 50% off and another voucher for 20% off, in this case they would both be stored in the database. But I don't want to give you 70% off, I want to give you 50% off (the higher of the two). Thus, the subquery that selects the MAX() value.
If you don't have any discount vouchers, I still want to give you 5% off, because that's our promotion right now. Therefore the other part of the IF tries to set it to 5.
The problem here is that, even though my condition says MAX() IS NOT NULL, and I am trying to set it to 5 when it is NULL, it still comes up as NULL in the results.
When I do an IF condition with a simple field that can contain a NULL value, for example SELECT name, IF(age IS NULL, 'Unknown', age) AS age FROM people, then it works as expected. So why doesn't it work with a MAX() subquery?
Have you tried the COALESCE function - returns the first non-null value :
SELECT COALESCE(MAX(discount_percentage), 5) ......
You want the COALESCE() at the outer level. But you have a bigger problem in the query, because product_id is not in the subquery.
SELECT COALESCE(max_discount, 5), product_name
FROM products p LEFT JOIN
(SELECT MAX(discount_percentage) AS max_discount,
product_id, order_id
FROM orders o LEFT JOIN
discounts d
ON d.id = o.discount_id
GROUP BY o.order_id, o.product_id
) od
ON od.product_id = p.product_id
GROUP BY od.order_id
I'm still not sure this is correct, because the structure seems odd -- discounts on orders and not on products. Aggregating by order_id in the outer query, but not including that in the SELECT. Without sample data and desired results, it is hard to tell what you really need.

Counting rows in a Query containing Join

I have a query that goes like this:
SELECT Product.local_price*Rate.exchange_rate AS 'US_price' FROM Product
INNER JOIN Rate ON Rate.currency = Product.currency
WHERE Product.type='TV'
HAVING US_price BETWEEN 500 AND 600;
How do I do a count on the number of TV sets that satisfy this query?
Table structure
Product Table: ID, type, local_price
Rate Table: currency, exchange_rate
Replace the HAVING US_price with AND Product.local_price * Rate.exchange_rate and just do a COUNT(Product.ID) in the SELECT clause:
SELECT COUNT(Product.ID)
FROM Product
INNER JOIN Rate ON Rate.currency = Product.currency
WHERE Product.type='TV'
AND Product.local_price * Rate.exchange_rate BETWEEN 500 AND 600;
You would want to use a HAVING if you wanted criteria on aggregated data, like this:
SELECT p.type, AVG(p.local_price)
FROM Product p
GROUP BY p.type
HAVING AVG(p.local_price) > 50
There's no need to use a HAVING clause here; its special semantics are only relevant when you have a GROUP BY clause. So, we can simply replace US_price in the HAVING clause with the expression that generates it, and move it into the WHERE clause; and then, use SELECT COUNT(*):
SELECT COUNT(*)
FROM Product
JOIN Rate
ON Rate.currency = Product.currency
WHERE Product.type = 'TV'
AND Product.US_price * Rate.exchange_rate BETWEEN 500 AND 600
;
Also, as a general rule — not needed in this case — you can always (or almost always?) wrap your entire query in SELECT COUNT(*) FROM (...) t to get the total number of rows it returns.