MySQL work out lowest of multiple columns - php

I have a table with the following:
+----+-------+-------+-----------+-----------+--------------+--------------+
| id | stock | price | usedStock | usedPrice | specialStock | specialPrice |
+----+-------+-------+-----------+-----------+--------------+--------------+
| #1 | 1 | 10.00 | 0 | 0 | 0 | 0 |
| #2 | 0 | 0 | 1 | 15.00 | 1 | 20.00 |
| #3 | 0 | 0 | 0 | 11.00 | 1 | 14.00 |
+----+-------+-------+-----------+-----------+--------------+--------------+
I would like to create a query that orders by lowest price first if the type of item is in stock.
So the query would result in the following order:
#1 - 10.00 (because 10.00 is the lowest result)
#3 - 14.00 (because although 11.00 is less, it's not in stock)
#2 - 15.00 (because 15.00 is lower than 20.00)
I've added the PHP tag to this question in case there's a quicker way of calculating using PHP after selecting the table.

I would order by the least price, in case item is present in stock:
ORDER BY
LEAST(CASE WHEN stock THEN price ELSE GREATEST(price, usedPrice, specialPrice) END,
CASE WHEN usedStock THEN usedPrice ELSE GREATEST(price, usedPrice, specialPrice) END,
CASE WHEN specialStock THEN specialPrice ELSE GREATEST(price, usedPrice, specialPrice) END)
If the condition is true (>=1), each CASE WHEN will return the price, otherwise it will return the greatest price. Fiddle here.

I would create a view first
CREATE VIEW stock_min_price AS
SELECT id, LEAST(IF(stock > 0, price, 1000000000),
IF (usedStock > 0, usedPrice, 10000000000),
IF (specialStock > 0, specialPrice, 10000000000)) AS actualPrice
FROM tablename;
then select and order by actual price of all items
SELECT id, IF(actualPrice = 1000000000, NULL, actualPrice) AS price
FROM stock_min_price
ORDER BY actualPrice
//edit: comparing to > 0 when stock can be greater than 1

Frankly, you should rethink your Data Modeling for exactly this kind of operation. There is no direct connection between the three types of stock, so you really should normalize them into another Table. For this i am going to assume your old table is named oldTable
CREATE TABLE stock(item_id integer, amount integer, price double, type enum('normal', 'used', 'special'));
INSERT INTO stock SELECT o.id, o.stock, o.price,'normal' FROM _oldTable as o
WHERE o.stock >0 OR o.amount >0 ;
INSERT INTO stock SELECT o.id, o.usedStock, o.usedPrice, 'used' FROM _oldTable as o
WHERE o.stock >0 OR o.amount >0 ;
INSERT INTO stock SELECT o.id, o.specialStock, o.specialPrice,'special' FROM _oldTable as o
WHERE o.stock >0 OR o.amount >0 ;
And there you have it. As you can see you can now basically get your information with a simple join on the stock table.
SELECT o.id, s.kind, min(s.price) FROM _oldTable_ AS o
JOIN stock as s ON s.item_id = o.id AND s.amount > 0
GROUP BY s.item_id
If you want to produce an output like in your original Table you use:
SELECT o.id, n.amount as amount, n.price as price, u.amount as usedAmount, u.price as usedPrice, s.amount as specialAmount, s.price as specialPrice
FROM _oldTable_ AS o
LEFT JOIN stock as n ON n.item_id = o.id AND n.type = 'normal'
LEFT JOIN stock as u ON u.item_id = o.id AND n.type = 'used'
LEFT JOIN stock as s ON s.item_id = o.id AND n.type = 'special';
It is now also more easy to a) get the total amount for each in stock (sum on the amount column with a GROUP BY Statement for the item_id) b) get the kind of stock that is used.
p.s.: you should probably set an index on the item_id column if you have lots of items. Also note that this solution potentially saves you disc space if not all items have all kinds of stock

Related

Display Highest Offer, But Still Display With No Offer

I'm trying to display businesses along with their highest discount offer. But I still would like to display businesses with no offer.
Businesses are stored in business_tb
business_id | business_name
------------+---------------
1 | aaa
2 | bbb
3 | ccc
offered discounts by those businesses are stored in deal_offer_tb
deal_offer_id | business_id | deal_id
--------------+-------------+----------
1 | 1 | 3
2 | 1 | 2
3 | 2 | 0
4 | 1 | 1
5 | 3 | 3
and types of discounts are stored in deal_tb.
deal_id | discount
--------+----------
1 | 40%
2 | 30%
3 | 20%
4 | 10%
So the display I wanted should look something like this:
1 | aaa | 40%
2 | bbb | ---
3 | ccc | 20%
But with my current query:
SELECT a.business_id, a.business_name, c.discount
FROM business_tb a
LEFT JOIN (SELECT min(deal_id) AS deal_id, business_id FROM deal_offer_tb GROUP BY business_id) b ON a.business_id = b.business_id
LEFT JOIN deal_tb c ON b.deal_id = c.deal_id
I only get:
1 | aaa | 40%
3 | ccc | 20%
It does not display businesses with no offered discounts.
How am I suppose to get my desired output?
UPDATE: I don't know what happened earlier, but my query is working the way I wanted it. Thanks to the effort of those who answered. Appreciate it, big time!
I would approach this by using a subquery to find the greatest discount for each business, joining the deal_offer_tb and deal_tb tables. Then, join this subquery to the business_tb table to get the final result. Note that I use an initial LEFT JOIN to account for that a given business may not even have an deals associated with it. In that case, I assign a maximum discount of 0 to that business (which makes sense, since then the full regular price would apply).
SELECT
t1.business_id,
t1.business_name,
COALESECE(t2.max_discount, 0) AS max_discount
FROM business_tb t1
LEFT JOIN
(
SELECT t1.business_id, MAX(t2.discount) AS max_discount
FROM deal_offer_tb t1
INNER JOIN deal_tb t2
ON t1.deal_id = t2.deal_id
GROUP BY t1.business_id
) t2
ON t1.business_id = t2.business_id
This query is essentially your query (with table aliases):
SELECT b.business_id, b.business_name, d.discount
FROM business_tb b LEFT JOIN
(SELECT MIN(deal_id) AS deal_id, business_id
FROM deal_offer_tb dot
GROUP BY business_id
) dot
ON b.business_id = dot.business_id LEFT JOIN
deal_tb d
ON d.deal_id = dot.deal_id;
By the definition of LEFT JOIN, it will keep all rows in business_tb, regardless of whether or not there are matches in the rest of the FROM clause. You have no additional filtering (via WHERE) or aggregation. Hence, this should returns all the rows in business_tb.
Below is one way to do the query:
SELECT
a.business_id, a.business_name, b.max
FROM business_tb a
INNER JOIN (
SELECT
c.business_id, MAX(d.discount) AS max
FROM deal_offer_tb c
LEFT JOIN deal_tb d ON c.deal_id = d.deal_id
GROUP BY c.business_id
) b ON a.business_id = b.business_id;
You don't have deal_id 0 in deal_tb, so I assume it's null for deal_offer_id 3.

Select MAX value from SQL table to apply in another table

My site is a Prestashop (1.5.6.2).
Some of my product can have a lower price according to the number of product ordered. And I'd like to mention somewhere the minimum price of the product (so I need the maximum amount of reduction to make this happen).
Table 1 (my price is in this table)
+------------+-------+
| id.product | price |
+------------+-------+
| 1 | 1000 |
+------------+-------+
Table 2 (my reduction is in this table)
+------------+--------+-----------+
| id.product | amount | reduction |
+------------+--------+-----------+
| 1 | 2 | 100 |
| 1 | 3 | 200 |
| 1 | 4 | 300 |
+------------+--------+-----------+
According to this is example, I would like to display:
Product 1 from 700 euros
1000 - 300 (which is the maximum reduction on product.id 1) = 700
(I'd like to UPDATE the existing price because this is a second field which I create actually called price_from but i didn't want to make the example too complicate)
This is my code so far:
UPDATE table1
INNER JOIN table2 ON (table1.id_product = table2.id_product )
SET table1.price = table1.price - (SELECT MAX(table2.reduction) FROM table2 GROUP BY id_product)
Any ideas?
If you only want to display the modified price use this:
select t1.id_product, (price - max_reduction) as new_price
from table1 t1 inner join (
select id_product, max(reduction) max_reduction FROM table2
GROUP BY id_product
) t2 on t1.id_product = t2.id_product
If you want to modify the price try this:
update table1 t1, (
select id_product, MAX(t2.reduction) as max_reduction
FROM table2 t2
GROUP BY id_product) t2
SET t1.price = t1.price - t2.max_reduction
WHERE t1.id_product = t2.id_product;
Try this:
update table1
inner join (SELECT max(`reduction`) as maxprice, id FROM table2 group by id ) t
on
table1.id = t.id
SET
table1.price = table1.price - t.maxprice

MYSQL join on condition1 else join on condition2

I've got two tables where I want to JOIN one with the other. The JOIN works fine, but I get two records returned. This is (looking at my query) correct, but I need to return one of the two records based on a condition. Can this be managed with a single query?
table oc_order_product:
product_id | name | quantity | order_id | store_url
51 | prod1 | 1 | 17 | http://www.opencarttest.com/
48 | prod2 | 3 | 17 | http://www.opencarttest.com/
table product:
oc_product_id | price | store_url
51 | 50.00 | http://www.opencarttest.com/
51 | 89.95 |
48 | 15.00 |
My current query:
SELECT
oop.product_id,
oop.name,
oop.quantity,
p.price,
p.store_url
FROM
oc_order_product AS oop
JOIN
product AS p
ON
p.oc_product_id = oop.product_id
WHERE
oop.order_id='17' AND oop.store_url='http://www.opencarttest.com/'
This returns:
product_id | name | price | store_url
51 | prod1 | 50.00 | http://www.opencarttest.com/
51 | prod1 | 89.95 |
48 | prod2 | 15.00 |
But I need only this:
product_id | name | price | store_url
51 | prod1 | 50.00 | http://www.opencarttest.com/
48 | prod2 | 15.00 |
I only need one result from product that matched the oc_product_id. But it can be one of both products. I need the product with the store_url equals the oc_order_product.store_url, or else the product without the store_url.
EDIT: Added trailing slashes
This will give you what you are looking for, however at the expense of performance:
SELECT
oop.product_id,
oop.name,
oop.quantity,
p.price,
p.store_url
FROM
oc_order_product AS oop
JOIN
product AS p
ON
p.oc_product_id = oop.product_id
and
(p.store_url = oop.store_url
or
not exists (select 1 from product where oc_product_id=p.oc_product_id and store_url is not null)
)
WHERE
oop.order_id='17' AND oop.store_url='http://www.opencarttest.com'
Here is the sqlfiddle.
Note that with your specific data as shown in the question, this won't work - and neither will your own query - as you have URL's with the trailing slash in one table and without it in the other. In my example, I removed trailing slashes in all places.
Updated - I made an assumption that fields were empty strings instead of NULLs
Thanks to #aleks-g since I stole his SQLFiddle for this code test
Here is one way - Using LEFT JOINs and COALESCES
SELECT
oop.product_id,
oop.name,
oop.quantity,
COALESCE(p_filled.price, p.price) AS price,
COALESCE(p_filled.store_url, p.store_url) AS store_url
FROM oc_order_product AS oop
LEFT JOIN product AS p_filled
ON p_filled.oc_product_id = oop.product_id
AND oop.store_url= p_filled.store_url
LEFT JOIN product AS p
ON p.oc_product_id = oop.product_id
AND COALESCE(p_filled.store_url,'') = ''
WHERE
oop.order_id='17' AND oop.store_url='http://www.opencarttest.com';
You can use the mysql variable in your query to record the occurrence of product id and by using CASE you can check either product has url OR product is not repeated then select it,some columns in query are just to elaborate the working
SELECT
oop.product_id,
oop.name,
oop.quantity,
p.price,
p.store_url,
(CASE when #test=oop.product_id then 'repeated'
else 'new' end) occurance,
(CASE when p.store_url =oop.store_url OR #test!=oop.product_id
then 'has'
else 'none' end) url,
#test:=oop.product_id `testvar`
FROM
oc_order_product AS oop
join (SELECT #test:=0) t
JOIN
(SELECT * FROM product ORDER BY store_url DESC) AS p
ON
p.oc_product_id = oop.product_id
WHERE
oop.order_id='17'
HAVING url='has'
Fiddle Demo
$rs = $db->Select('products p, products_images pi','p.*,image_id',"p.product_id=pi.product_id","product_id=$a1");
Here's another way to do it, without requiring a subquery:
SELECT
oop.product_id,
oop.name,
oop.quantity,
IFNULL(p1.price, p2.price),
p1.store_url
FROM
oc_order_product AS oop
LEFT JOIN
product AS p1
ON
p1.oc_product_id = oop.product_id AND p1.store_url = oop.store_url
LEFT JOIN
product AS p2
ON
p2.oc_product_id = oop.product_id
WHERE
oop.order_id='17'
AND oop.store_url='http://www.opencarttest.com/'
AND p1.oc_product_id IS NOT NULL OR p2.oc_product_id IS NOT NULL

SQL UPDATE in a loop ( mysql , PHP )

I have two tables :
Order :
date | Product | Quantity
01/03| P1 | 2
01/03| P2 | 2
02/03| P1 | 3
02/03| P2 | 1
02/03| P3 | 5
Stock :
Purchase number | product | Quantity
01/03 | P1 | 4
01/03 | P2 | 1
02/03 | P2 | 2
02/03 | P3 | 5
02/03 | P1 | 1
02/03 | P1 | 1
The first table is my order (what I sold), with this table I want to update the second table (which is my stock) to know exactly what i have in stock.
But i want to do that following the date, I first update the Quantity.Stock 01/03 before 02/03 .
Right now I have a loop , the problem is I don't want to have any " -1 " and i don't want to update all the rows with the same data .
In this example I have 5 P1 in order so in stock the first line of P1 must be 0 , the next P1 line must be 0 too but the last line must stay at 1 .
Any ideas ? or direction ?
Bruno
I suppose date and purchase_number are some sort of datetime values.
Get orders, which you haven't used yet. I suppose you have some used/processed column or table, where you saved them as marked.
SELECT id, product, quantity
FROM orders
WHERE used = 0 AND quantity > 0
ORDER BY date ASC
Get all relevant purchases you can use in order you want to use them, so from the oldest.
SELECT id, product, quantity
FROM stock
WHERE quantity > 0
ORDER BY purchase_number ASC
You can then iteratively mark orders and update purchases. I assume you saved your results to $orders and $purchases respectively.
foreach ($orders as $order) {
$remaining = $order['quantity'];
foreach ($purchases as &$purchase)
if ($order['product'] !== $purchase['product']
|| $purchase['quantity'] === 0) {
continue;
}
$remaining = $purchase['quantity'] - $remaining;
$purchase['quantity'] = max($remaining, 0);
// update purchase, where
// :quantity is $purchase['quantity']
// :id is $purchase['id']
// UPDATE stock SET quantity = :quantity WHERE id = :id
if ($remaining >= 0) {
break;
}
$remaining = -$remaining;
}
unset($purchase);
if ($remaining > 0) {
// we have problem, we sold more, then we have in stock
}
// mark order as used, where :id is $order['id']
// UPDATE orders SET used = 1 WHERE id = :id
}
Example has time complexity O(M*N), where M is orders and N is purchases. You can change it to O(M+N), when you group data by products and do M SQL queries for purchases or some preprocessing. It has memory complexity O(M+N), which is a tradeoff for less queries.

mysql getting max min values across multiple tables

I want to get a price range for some products from two tables.
Table1 (products):
pid | products_name | products_model
1 | Product 1.....| model 1
2 | Product 2.....| model 2
Table2 (products_prices):
pid | nerchant_id | price | sale_price | sale_start_date | sale_expire_date
1 | RVTBV | 11.90 | 0 | NULL | NULL
1 | QNUNU | 11.90 | 9.90 | 2013-05-01 | 2013-12-31
1 | YOLOR | 12.90 | 10.90 | 2013-04-01 | 2013-12-31
2 | RVTBV | 20.90 | 0 | NULL | NULL
2 | QNUNU | 29.90 | 25.90 | 2013-04-01 | 2013-12-31
2 | YOLOR | 29.90 | 0 | NULL | NULL
How do I get a result with price range to look like this:
pid | products_name | products_model | min_price | max_price
1 | Product 1.... | model 1 ...... | 10.90 ... | 12.90
2 | Product 2.... | model 2 ...... | 20.90 ... | 29.90
I am using a main query to get products data from table1 then a loop with php foreach product to get the min max values depending on sale start and expiry dates.
It does the work but I don't like subqueries with php. I prefer one MySQL query for performance reasons.
Thanks for helping.
Until now the following statement the best
SELECT p.pid,
p.manufacturers_id,
p.products_image,
p.products_name,
(select min(if(CURRENT_DATE BETWEEN pp.sale_start_date AND pp.sale_expire_date and pp.sale_price>'0', pp.sale_price, pp.price)) from products_prices pp where p.pid = pid) as min_price,
(select max(if(CURRENT_DATE BETWEEN pp.sale_start_date AND pp.sale_expire_date and pp.products_sale_price>'0', pp.sale_price, pp.price)) from products_prices pp where p.pid = pp.pid) as max_price
FROM products p
WHERE p.products_status = '1'
AND p.categories_id = '1'
ORDER BY min_price ASC LIMIT 0, 100
is it possible to optimize it a little bit?
Resumé:
sometimes is the solution so simple that i don´t see it;)
ok the project is an price comparison plattform. Products will be updated hourly or something like that, but not all prices will change. So let´s say 10% will be updated.
But the data must be retrieverd with each visit of the website.
In this case it will be more reads than writes (80-20).
I can add two extra columns to the products table (min_price and max_price) that i update only once if price_data changes.
on one Hand the update will be a little bit more complicated but that´s not a drama. On the other hand the data will be retrieved very fast.
I have testet 3 options based on 15000 products to retrieve 100 rows:
worst: the group by approch (over 1 sec)
good: the approach of arheops (0,12 sec)
best: update once with two extra colums (0,07 sec)
I go with the third option.
thanks for your help anyway!
That depend of you query.
If you query only some values from product, this will be optimal:
select pid,products_name,products_model,
(select min(price) from price where price.pid=product.pid) as min_price,
(select max(price) from price where price.pid=product.pid) as max_price
from product where some_filter_here;
If you need got FULL table, this one is best:
select a.pid,products_name,products_model,min_price,max_price
from product as a
left join (
select pid,min(price) as min_price, max(price) as max_price
from price group by pid
) as b on b.pid=a.pid
SELECT products.*,
MIN(IF(CURRENT_DATE BETWEEN sale_start_date AND sale_expire_date, sale_price, price)) min_price,
MAX(price) max_price
FROM products JOIN products_prices USING (pid)
GROUP BY pid
See it on sqlfiddle.
The below should work for your requirements.
Update:
The first query now also considers the start/end date for the sale price
SELECT
p.pid,
p.products_name,
p.products_model,
pp.price as min_price,
pp.sale_price as max_price
FROM
products p
JOIN products_prices pp ON ( p.pid = pp.pid )
LEFT JOIN products_prices pp2 ON ( pp2.pid = pp.pid AND pp2.price > pp.price AND pp.sale_start_date BETWEEN pp2.sale_start_date AND pp2.sale_expire_date )
WHERE
pp2.pid IS NULL AND NOW() BETWEEN pp.sale_start_date and pp.sale_expire_date
The below one, gets the max, min of the prices avaliable for a product
SELECT
p.pid,
p.products_name,
p.products_model,
MIN( LEAST( pp.price, pp.sale_price) ) as min_price,
MAX( GREATEST( pp.price, pp.sale_price) ) as max_price
FROM
products p
JOIN products_prices pp ON ( p.pid = pp.pid )
WHERE
pp.sale_price <> 0
GROUP BY
p.pid
SQLFIDDLE
I think you're actually after this. I've amended sale_price to NULL in the event that there is no sale which is just as it should be if you're going to insist on including sale information in the products_prices table...
DROP TABLE IF EXISTS products;
CREATE TABLE products
(pid INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,products_name VARCHAR(12) NOT NULL
,products_model VARCHAR(12) NOT NULL
);
INSERT INTO products VALUES
(1 ,'Product 1','model 1'),
(2 ,'Product 2','model 2');
DROP TABLE IF EXISTS products_prices;
CREATE TABLE products_prices
(pid INT NOT NULL
,merchant_id CHAR(5) NOT NULL
,price DECIMAL(5,2)NOT NULL
,sale_price DECIMAL(5,2) NULL
,sale_start_date DATE
,sale_expire_date DATE
,PRIMARY KEY(pid,merchant_id)
);
INSERT INTO products_prices VALUES
(1,'RVTBV',11.90,NULL,NULL,NULL),
(1,'QNUNU',11.90,9.90,'2013-05-01','2013-12-31'),
(1,'YOLOR',12.90,10.90,'2013-04-01','2013-12-31'),
(2,'RVTBV',20.90,NULL,NULL,NULL),
(2,'QNUNU',29.90,25.90,'2013-04-01','2013-12-31'),
(2,'YOLOR',29.90,NULL,NULL,NULL);
SELECT p.*
, MIN(CASE WHEN CURDATE() BETWEEN sale_start_date AND sale_expire_date THEN pp.sale_price ELSE pp.price END) min_price
, MAX(CASE WHEN CURDATE() BETWEEN sale_start_date AND sale_expire_date THEN pp.sale_price ELSE pp.price END) max_price
FROM products p
JOIN products_prices pp
ON pp.pid = p.pid
GROUP
BY p.pid;
+-----+---------------+----------------+-----------+-----------+
| pid | products_name | products_model | min_price | max_price |
+-----+---------------+----------------+-----------+-----------+
| 1 | Product 1 | model 1 | 10.90 | 11.90 |
| 2 | Product 2 | model 2 | 20.90 | 29.90 |
+-----+---------------+----------------+-----------+-----------+

Categories