MYSQL join on condition1 else join on condition2 - php

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

Related

MySQL Join three tables and display 0 if null

I have three tables:
person_table
id| name | gender
1 | Joe | male
2 | Jane |female
3 | Janet | female
4| Jay | male
etc...
product_table
id| name
1 | magazine
2 | book
3 |paper
4 | novel
etc...
**person_product
person_id| product_id | quantity
1 | 1 | 1
1 | 3 | 3
2 | 3 | 1
4 | 4 | 2
etc...
I have tried to make a query that will return a table like this:
person_id| person_name | product_name| quantity
but i can't make it so that if lets say John has no books, it should display
(johns id) John|book|0
instead of just skipping this line.
Where did i go wrong?
here is what i managed to come up with:
SELECT p.*, f.name, l.quantity
FROM person_product AS l
INNER JOIN people_table AS p ON l.person_id=p.id
INNER JOIN product_table AS f ON l.product_id=f.id
ORDER BY id`
It seems that you're generating a report of all people, against all products with the relevant quantity; on a large data set this could take a while as you're not specifically joining product to person for anything other than quantity:
SELECT
p.id,
p.name,
p.gender,
f.name,
IFNULL(l.quantity,0) AS quantity
FROM person_table AS p
JOIN product_table AS f
LEFT JOIN person_product AS l
ON l.person_id = p.id
AND l.product_id = f.id
ORDER BY p.id, f.name
Which results in:
Is that more-or-less what you're after?
you need to start with people_table than using left join you need to bring other table data.
as you need 0 value if null than you can use function IFNULL
SELECT p.*, f.name, IFNULL(l.quantity,0)
FROM people_table AS p
LEFT JOIN person_product AS l ON l.person_id=p.id
LEFT JOIN product_table AS f ON l.product_id=f.id
ORDER BY p.id
if has no book shouldn't appear in the table , try this (easy to understand) :
SELECT NAME
,'0'
,'0'
FROM person_table
WHERE id NOT IN (
SELECT person_id
FROM person_product
)
UNION
SELECT person_id
,product_id
,quantity
FROM person_product;

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.

Mysql query inside a query

First, apologies if the title doesn't match the question. Well, the problem is how to build this query...
I have a table called category It contains categories of my stuff(movies). It's like this...
--------------------------------
ID | name | parent_category
--------------------------------
1 | love | 0
2 | action | 0
3 | fear | 0
4 | passion| 1
5 | danger | 2
6 | death | 3
--------------------------------
So, as you see, each category has a parent category. Except the first 3. They're parents.
And movies table is like this...
--------------------------------
ID | name | category
--------------------------------
1 | aaaa | 1
2 | bbbbbb | 2
3 | cccc | 2
4 | ddddddd| 1
5 | eeeeee | 3
6 | fffff | 3
--------------------------------
So, what i want to do is, to select movies by parent category. Which means if I click category, love, it should select all the movies of categories that having love as the parent category.
So, how to write this in a single query ?
If the parents are only one level deep, then you can use joins:
select m.*,
coalesce(cp.id, c.id) as parent_id,
coalesce(cp.name, c.name) as parent_name
from movies m left join
categories c
on m.category = c.id left join
categories cp
on c.parent_category = cp.id;
Actually, if you only want the id, you don't need two joins:
select m.*,
(case when c.parent_id > 0 then c.parent_id else c.id end) as parent_id
from movies m left join
categories c
on m.category = c.id ;
Or, more simply:
select m.*, greatest(c.parent_id, c.id) as parent_id
. . .
to select rows filtered by condition on secend table use join in FROM clause or subquery in condition with IN or EXISTS function. To compare field with some string you can use LIKE operator.
If you are filtering based on parent_category -
SELECT b.*, a.name FROM movies b
LEFT JOIN categories a ON a.id = b.category
WHERE a.parent_category = 1;

MySQL select products by filter

I need to select IDs of products which have specific value_id.
Table products:
| product_id | product_name | category_id | active |
| 125 | notebook1 | 3 | 1 |
| 236 | notebook2 | 3 | 1 |
Table filters:
| product_id | value_id | value_name |
| 125 | 35 | 15" display|
| 125 | 36 | 8GB RAM |
| 236 | 35 | 15" display|
This select works ok, if I want to select products IDs by one value_id:
SELECT DISTINCT p.product_id FROM products p
LEFT JOIN filters f ON (p.product_id=f.product_id)
WHERE p.active=1 AND p.category_id=3 AND f.value_id=36;
But when I check more filters on web, I need to select by more values, problem is that when I use:
SELECT DISTINCT p.product_id FROM products p
LEFT JOIN filters f ON (p.product_id=f.product_id)
WHERE p.active=1 AND p.category_id=3 AND f.value_id IN(35,36);
It gives me products which have 15" display OR 8GB RAM, I need products which have 15" display AND 8GB RAM. Thanks.
I assume since you mentioned web that you are using a program language to generate the queries. I am also going to assume that you do not have control over how the data is structured in the tables so we have to have an option that will work given the table structure provided.
The below option works but will get sloppy if you are dealing with large numbers of options being passed in.
Option Using Subsequent Joins for Each Option
SELECT
DISTINCT p.product_id
FROM
products p
JOIN filters f1
ON p.product_id=f1.product_id
and f1.value_id = 35
JOIN filters f2
ON p.product_id=f2.product_id
and f2.value_id = 36
WHERE
p.active=1
AND p.category_id=3
;
If you get your filter value ids in a comma delimited list and want to use it in that fashion (hints your IN statement) you can take this approach. I am making the assumption that you know the total number of filter values passed in. In your example you had 2 so the query would like this.
Group Count Equal to Filters Provided w/ IN Statement
SELECT
p.product_id
FROM
products p
JOIN filters f
ON p.product_id=f.product_id
AND p.active=1
AND p.category_id=3
AND f.value_id IN(35,36)
GROUP BY
p.product_id
HAVING
COUNT(p.product_id) = 2
Also putting as much in your join condition rather than the where clause will help speed up the query since the from clause is evaluated before the where clause.
If you need an output of 15" display AND 8GB RAM.
SELECT product_id FROM products
WHERE product_id IN(SELECT DISTINCT product_id from filters WHERE value_id IN(35,35) GROUP BY value_name)
SQL FIDDLE DEMO
Give this a try:
SELECT product_id FROM filters
WHERE value_id IN (35, 36)
GROUP BY product_id
HAVING COUNT(DISTINCT value_id) = 2
This will output 125.
Note that if a given product_id can not have the same value_id more than once then you can remove the DISTINCT keyword.
Fiddle here.
The complete query based on your last one is:
SELECT f.product_id FROM filters f
JOIN products p ON f.product_id = p.product_id
WHERE f.value_id IN (35, 36) AND p.active = 1 AND p.category_id = 3
GROUP BY f.product_id
HAVING COUNT(f.value_id) = 2

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