The goal
Get the lowest price of a product.
The problem
To illustrate my problem:
Row 1
Product_Id = 1
Product_Name = "iPhone 5"
Market_Name = "Walmart"
Product_Original_Price = "359.00"
Product_Promotional_Price = "319.00"
Product_State = 1 (is on offer)
Row 2
Product_Id = 1
Product_Name = "iPhone 5"
Market_Name = "Apple"
Product_Original_Price = "359.00"
Product_Promotional_Price = "0.00"
Product_State = 0 (isn't on offer)
Row 3
Product_Id = 1
Product_Name = "iPhone 5"
Market_Name = "BestBuy"
Product_Original_Price = "359.00"
Product_Promotional_Price = "299.00"
Product_State = 1 (is on offer)
The query of the next topic (What I have) is returning me zero as the best price of the problem illustrated above — but the best price is 299.00, by BestBuy, because zero at Product_Promotional_Price means that the product isn't on offer.
What I have
SELECT
MIN(LEAST(`Product_Original_Price`, `Product_Promotional_Price`)) as `minProductPrice`
[...]
Details
My query:
SELECT `pr`.`Product_Id` as `productId`,
`pr`.`Product_Name` as `productName`,
ROUND(CAST(MIN(`map`.`Product_Original_Price`) AS DECIMAL)/100,2)
as `minProductPrice`,
`prm`.`Product_Measure_Name` as `measureName`,
`prm`.`Product_Measure_Shortname` as `measureShortName`,
`pri`.`Product_Thumbnail_Image_Url` as `thumbnailUrl`,
`pr`.`Product_Markets_Quantity` as `numberOfMarketsThatHaveThisProduct`
FROM `bm_market_products` as `map`
JOIN `bm_products` as `pr` ON `map`.`Product_Id` = `pr`.`Product_Id`
JOIN `bm_products_category_relationship` as `car` ON `pr`.`Product_Id` =
`car`.`Product_Id`
JOIN `bm_product_categories` as `ca` ON `car`.`Category_Id` = `ca`.`Category_Id`
JOIN `bm_products_measure_relationship` as `prmr` ON `pr`.`Product_Id` =
`prmr`.`Product_Id`
JOIN `bm_product_measures` as `prm` ON `prmr`.`Measure_Id` =
`prm`.`Product_Measure_Id`
JOIN `bm_products_images` as `pri` ON `pr`.`Product_Id` = `pri`.`Product_Id`
WHERE ("" IS NULL OR `map`.`Product_State` = 0)
AND ("" IS NULL OR `ca`.`Category_Id` = 14)
GROUP BY `map`.`Product_Id`;
What the query returns:
What I already have tried:
Considering that Product_State determines whether a product is on offer or not, follow this fragment:
SELECT `pr`.`Product_Id` as `productId`,
`pr`.`Product_Name` as `productName`,
(IF(`map`.`Product_State` <> 0) THEN
MIN(LEAST(`Product_Original_Price`, `Product_Promotional_Price`))
ELSE (`map`.Product_Original_Price) as `minProductPrice`,
`prm`.`Product_Measure_Name` as `measureName`,
`prm`.`Product_Measure_Shortname` as `measureShortName`,
`pri`.`Product_Thumbnail_Image_Url` as `thumbnailUrl`,
`pr`.`Product_Markets_Quantity` as `numberOfMarketsThatHaveThisProduct`
[...]
Can you see the IF/THEN/ELSE? This is what has been added in relation to the previous query.
The above query doesn't work — syntax isn't correct, I know, but it was just to illustrate.
The solution
Gordon Linoff posted this answer and with it, I made this:
SELECT [...]
ROUND(CAST(MIN(CASE WHEN `map`.`Product_Promotional_Price` = 0 THEN `map`.`Product_Original_Price`
ELSE LEAST(`map`.`Product_Promotional_Price`, `map`.`Product_Original_Price`)
end) AS DECIMAL)/100,2) as `minProductPrice`,
[...]
To clarify, I just adapted his [Gordon Linoff] syntax to my scenario — with ROUND to rounding numbers and CAST to set a value as a certain type.
Worked perfectly!! Thanks!!
You need to fix your logic for getting the lowest price. A case statement is the best way. Here is an example:
select MIN(case when `Product_Promotional_Price` = 0 then `Product_Original_Price`
else least(`Product_Promotional_Price`, `Product_Original_Price`)
end)
put where Product_Original_Price!=0 and Product_Promotional_Price!=0 to the end;
Related
MySQL here. I have the following data model:
[applications]
===
id : PK
status : VARCHAR
...lots of other fields
[invoices]
===
id : PK
application_id : FK to applications.id
status : VARCHAR
... lot of other fields
It is possible for the same application to have 0+ invoices associated with it, each with a different status. I am trying to write a query that looks for applications that:
have a status of "Pending"; and
have only invoices whose status is "Accepted"
My best attempt at such a query is:
SELECT a.id,
i.id,
a.status,
i.status
FROM application a
INNER JOIN invoice i ON a.id = i.application_id
WHERE a.status = "Pending"
AND i.status = "Accepted"
The problem here is that this query does not exclude applications that are associated with non-Accepted invoices. Hence it might return a row of, say:
+--------+--------+-----------+-----------+
| id | id | status | status |
+--------+--------+-----------+-----------+
| 123 | 456 | Pending | Accepted |
+--------+--------+-----------+-----------+
However, when I query the invoice table for any invoices tied to application id = 123, there are many non-Accepted invoices that come back in the results. Its almost as if I wished SQL support some type of "AND ONLY HAS" so I could make my clause: "AND ONLY HAS i.status = 'Accepted'"
So I'm missing the clause that excludes results for applications with 1+ non-Accepted invoices. Any ideas where I'm going awry?
You can use the following logic:
SELECT *
FROM application
WHERE status = 'pending'
AND EXISTS (
SELECT 1
FROM invoice
WHERE invoice.application_id = application.id
HAVING SUM(CASE WHEN invoice.status = 'accepted' THEN 1 ELSE 0 END) > 0 -- count of accepted invoices > 0
AND SUM(CASE WHEN invoice.status = 'accepted' THEN 0 ELSE 1 END) = 0 -- count of anyother invoices = 0
)
I am about to a build a notification feature
The app is a car ads website
The dealer inserts car ads
The visitor Could save searches as string (URL)
---------------------------------------------------------
saved_search_id|visitor_id |search_url
---------------------------------------------------------
0 | 1 |type=0&price_max=10000&color=red
1 | 1 |type=2&price_max=15000&color=black
2 | 2 |type=3&price_max=20000&color=white
Whene the dealer inserts a new car, i parse all saved searches into SQL queries
//array(arrays(saved_search_id, saved_search_query))
array(
array(0, "EXISTS(SLECT car_id FROM Car WHERE type=0 AND price <= 10000 AND color = red)"),
array(1, "EXISTS(SLECT car_id FROM Car WHERE type=2 AND price <= 15000 AND color = black)"),
array(2, "EXISTS(SLECT car_id FROM Car WHERE type=3 AND price <= 20000 AND color = white)")
)
For each saved_search_query i check Whether the new car is included in search result or not. if yes, i send an Email to notify the visitor
i can't figure out how to build one query that returns relevant saved_search_id … instead of running all queries one by one (thousands of Saved searches)
Below is the closest expression to what i am trying to translate
CREATE FUNCTION get_saved_search_id(query, id){
if(query){
return id;
}
}
SELECT get_saved_search_id('EXISTS(SLECT car_id FROM Car WHERE type=0 AND price <= 10000 AND color = red)', 0)
UNION
SELECT get_saved_search_id('EXISTS(SLECT car_id FROM Car WHERE type=2 AND price <= 15000 AND color = black) ', 1)
UNION
SELECT get_saved_search_id('EXISTS(SLECT car_id FROM Car WHERE type=3 AND price <= 20000 AND color = white)', 2)
You could potentially do it by using a CROSS JOIN and generating a humongous WHERE/OR clause (instead of your EXISTS), with one condition for each saved_search_id, as follows:
SELECT saved_search_id,
visitor_id,
car_id
FROM searches a
CROSS JOIN cars b
-- generated WHERE clause below based on saved_search_id + search_url column
WHERE (saved_search_id = 0 AND type = 0 AND price <= 10000 AND color = 'red')
OR (saved_search_id = 1 AND type = 2 AND price <= 15000 AND color = 'black')
OR (saved_search_id = 2 AND type = 3 AND price <= 20000 AND color = 'white')
EDIT: add in a filter on inserted car id (10, for example)
SELECT saved_search_id,
visitor_id,
car_id
FROM searches a
CROSS JOIN cars b
-- generated WHERE clause below based on saved_search_id + search_url column
WHERE (
(saved_search_id = 0 AND type = 0 AND price <= 10000 AND color = 'red')
OR (saved_search_id = 1 AND type = 2 AND price <= 15000 AND color = 'black')
OR (saved_search_id = 2 AND type = 3 AND price <= 20000 AND color = 'white')
)
AND car_id = 10 --<-- inserted car id
The dealer inserts a new car with particular #type, #price, and #color. Use these parameters to find the related searches:
select *
from searches
where search_url like concat('type=', #type, '%color=', #color)
and cast(substring_index(search_url, '&price_max=', -1) as int) >= #price;
(Casting to integer in MySQL takes the number only and ignores the rest of the string.)
And if you want to use the already inserted car row instead:
select *
from searches s
where exists
(
select null
from cars c
where c.id = 12345 -- the inserted row's ID
and s.search_url like concat('type=', c.type, '%color=', c.color)
and cast(substring_index(s.search_url, '&price_max=', -1) as int) >= c.price
);
Of course you can also write a function for this accepting the search string and the car parameters - or the car ID for an already inserted car row. With the latter you'd have something like:
select *
from searches s
where search_matches_car(s.search_url, 12345);
The same with a join for the case you want to see car information, too:
select *
from cars c
join searches s on search_matches_car(s.search_url, c.id)
where c.id = 12345;
I want to select a userid from a single table based on multiple and condition.
UserID FieldID Value
-----------------------------------
1 51 Yes
1 6 Dog
2 6 Cat
1 68 TX
1 69 78701
2 68 LA
What I'm trying to get in simple words:
if user search for texas or 78701,
Select userId where (68 = TX OR 69=78701) AND (51=yes) AND (6=Dog)
This should return user id 1.
This is what I tried, but returns null.
SELECT user_id FROM `metadata`
WHERE ( (`field_id` = '68' AND value LIKE '%TX%')
OR (`field_id` = '69' AND value LIKE '%78701%') )
AND `field_id` = '51' AND value = 'Yes'
AND `field_id` = '6' AND value = 'Dog'
You can use GROUP BY with a HAVING clause that makes use of multiple conditional aggregates:
SELECT UserID
FROM metadata
GROUP BY UserID
HAVING SUM(field_id = '68' AND value LIKE '%TX%' OR
field_id = '69' AND value LIKE '%78701%') >= 1
AND
SUM(field_id = '51' AND value = 'Yes') >= 1
AND
SUM(field_id = '6' AND value = 'Dog') >= 1
Demo here
Explanation: In MysQL a boolean expression, like
field_id = '51' AND value = 'Yes'
returns 1 when true, 0 when false.
Also, each predicate of HAVING clause is applied to the whole group of records, as defined by GROUP BY.
Hence, predicate:
SUM(field_id = '51' AND value = 'Yes') >= 1
is like saying: return only those UserID groups having at least one (>=1) record with
field_id = '51' AND value = 'Yes' -> true
Your table structure resembles attribute+value modelling, which essentially splits up the columns of a row into individual pairs, and has the side effect of very weak typing.
As you've noted, this can also make things tricky to query, since you have to reason over multiple rows in order to make sense of the original data model.
One approach could be to take an opinion of a 'primary' criterion, and then apply additional criteria by reasoning over the shredded data, joined back by user id:
SELECT DISTINCT m.user_id
FROM `metadata` m
WHERE ((`field_id` = '68' AND value LIKE '%TX%')
OR (`field_id` = '69' AND value LIKE '%78701%'))
AND EXISTS
(SELECT 1
FROM `metadata` m2
WHERE m2.user_id = m.user_id AND m2.field_id = '51' AND m2.value = 'Yes')
AND EXISTS
(SELECT 1
FROM `metadata` m3
WHERE m3.user_id = m.user_id AND m3.field_id = '6' AND m3.value = 'Dog');
However, IMO, it may be better to attempt to remodel the table like so (and ideally choose better descriptions for the attributes as columns):
UserID Field51 Field6 Field68 Field69
----------------------------------------
1 Yes Dog TX 78701
2 No Cat LA NULL
This will make things much easier to query.
This approach is typically slower than simply LEFT JOINing that table on each criterion, but it can make the problem simpler to comprehend...
SELECT userid
, MAX(CASE WHEN fieldid = 51 THEN value END) smoker
, MAX(CASE WHEN fieldid = 6 THEN value END) favourite_pet
, MAX(CASE WHEN fieldid = 68 THEN value END) state
, MAX(CASE WHEN fieldid = 69 THEN value END) zip
FROM eav
GROUP
BY userid;
You can use HAVING, or bundle this into a subquery to get the desired results.
SELECT user_id FROM metadata
WHERE
(field_id = '68' AND value LIKE '%TX%')
OR (field_id = '69' AND value LIKE '%78701%')
AND (field_id = '51' AND value = 'Yes')
AND (field_id = '6' AND value = 'Dog');
I have little bit changed your query and tried with the same,it gives output as, user_id is 1
I have 2 tables: players and items. Now I'm looking for player who has item with known properties ex.
SELECT players.`name` FROM `players`
INNER JOIN `items` ON players.`id`=items.`ownerId`
WHERE items.`itemType` = 1 AND items.`itemClass` = 2 AND items.`itemColor` = 3
How i can find player which has more than one item i want? It is even possible in one query?
Ex. i wanna find player which has both items : type=1 class=2 color=3 , type=2 class=3 color=4
I have an idea how to do it in multiple querys: just add players.id IN (...) on every next query.
Thanks for all your help!
The best way to do this is with aggregation.
select i.ownerId
from Items i
group by i.ownerId
having max(case when i.itemType = 1 and i.itemClass = 2 and i.itemColor = 3
then 1 else 0
end) = 1 and
max(case when i.itemType = 2 and i.itemClass = 3 and i.itemColor = 4
then 1 else 0
end) = 1
If you want other information about the owner, you need to join in the players table.
In MySQL, you can simplify the having clause to:
having max(i.itemType = 1 and i.itemClass = 2 and i.itemColor = 3) = 1 and
max(i.itemType = 2 and i.itemClass = 3 and i.itemColor = 4) = 1
I have 3 columns in CATEGORY TABLE for storing pre-calculated counts of records for it in another table PRODUCTS.
CATEGORY(c_id,name,c30,c31,c32)
c30=count for New Products (value 30)
c31 count for used products (value 31)
c32 count for Damaged products (value 32)
PRODUCT(p_id,c_id,name,condition)
condition can be 30,31 or 32.
I am thinking to write a single UPDATE statement so, it will update respective category count.
Althogh below statement is syntactically wrong, but i am looking for similar type of solution.
select case product.condition
when 30 then update category set category.c30=category.c30+1 where category.c_id=product.category3
when 31 then update category set category.c31=category.c31+1 where category.c_id=product.category3
when 32 then update category set category.c32=category.c32+1 where category.c_id=product.category3
end case
from product
where product.c_id=12
Any suggestion!
You can do this:
UPDATE CATEGORY c
INNER JOIN
(
SELECT
c_id,
SUM(CASE WHEN `condition` = 30 THEN 1 ELSE 0 END) c30,
SUM(CASE WHEN `condition` = 31 THEN 1 ELSE 0 END) c31,
SUM(CASE WHEN `condition` = 32 THEN 1 ELSE 0 END) c32
FROM product
GROUP BY c_id
) p ON c.c_id = p.c_id
SET c.c30 = p.c30,
c.c31 = p.c31,
c.c32 = p.c32;
SQL Fiddle Demo
You can join both the tables and then update the value in same join query.