I'm looking for a fast MySQL query that returns the id of the product with the lowest (min) or highest (max) price, compared to all price categories (a, b, c and d).
I have a product table called chocolate_stock with several price categories. It's pretty easy to receive the lowest (min) or highest (max) price from a specific category (a or b or c or d).
id | name | price_a | price_b | price_c | price_d |
--------------------------------------------------------------
1 | Chips Ahoy | 250 | 530 | 720 | 120
--------------------------------------------------------------
2 | Chocolate Chunk | 250 | 90 | 32.92 | 110
--------------------------------------------------------------
3 | Oreo | 103 | 44.52 | 250 | 850
--------------------------------------------------------------
The price categories are decimal(10,2). Here's a sample that returns the highest price from the categories, but not the id:
$t = 'chocolate_stock';
$arrIds = array(1, 3);
$strQuery = "SELECT id,
MAX(price_a) AS price_a,
MAX(price_b) AS price_b,
MAX(price_c) AS price_c,
MAX(price_d) AS price_d
FROM $t WHERE id IN(". implode(',', array_map('intval', $arrIds)) .")";
What is the fastest way to retrieve this information?
This query does what you want:
(select t.*
from $t t
where . . .
order by price_a desc
limit 1) union all
(select t.*
from $t t
where . . .
order by price_b desc
limit 1) union all
(select t.*
from $t t
where . . .
order by price_c desc
limit 1) union all
(select t.*
from $t t
where . . .
order by price_d desc
limit 1)
If you have an index on id it should perform reasonably well.
That approach requires four passes through the table (although the index on id should greatly reduce that). The following approach requires only one pass through the table:
select MAX(price_a),
substring_index(group_concat(id order by price_a desc), ',', 1),
max(price_b),
substring_index(group_concat(id order by price_b desc), ',', 1),
max(price_c),
substring_index(group_concat(id order by price_c desc), ',', 1),
max(price_d),
substring_index(group_concat(id order by price_d desc), ',', 1)
from $t
where . . .
It uses a trick with group_concat() and substring_index() to get the max id for each of the columns.
It might help if you were to tabulate what you want your output to look like, but I think the piece you are missing is the HAVING clause.
first - try this
select min(id), max(price_a) from $t having price_a = max(price_a)
Then try
select min(id), min(price_a) from $t having price_a = min(price_a)
union
select min(id), max(price_a) from $t having price_a = max(price_a)
The first thing you would want to do is normalise your data, for ease of later querying I would create the following view:
CREATE VIEW NormalT
AS
SELECT ID, Name, 'Price_a' AS Type, Price_a AS Price
FROM T
UNION ALL
SELECT ID, Name, 'Price_b' AS Type, Price_b AS Price
FROM T
UNION ALL
SELECT ID, Name, 'Price_c' AS Type, Price_c AS Price
FROM T
UNION ALL
SELECT ID, Name, 'Price_d' AS Type, Price_d AS Price
FROM T;
Then I am not sure of the format you want, if you want the min an max for each price you could use this:
SELECT mt.Type2,
mt.Type,
mt.Price,
t.ID,
t.Name
FROM ( SELECT Type, MIN(Price) AS Price, 'MIN' AS Type2
FROM NormalT
GROUP BY Type
UNION ALL
SELECT Type, MAX(Price) AS Price, 'MAX' AS Type2
FROM NormalT
GROUP BY Type
) mt
INNER JOIN NormalT T
ON mt.Type = T.Type
AND mt.Price = t.Price
ORDER BY mt.Type2, mt.Type, t.ID;
Which will output the following from your sample data:
TYPE2 TYPE PRICE ID NAME
MAX Price_a 250 1 Chips Ahoy
MAX Price_a 250 2 Chocolate Chunk
MAX Price_b 530 1 Chips Ahoy
MAX Price_c 720 1 Chips Ahoy
MAX Price_d 850 3 Oreo
MIN Price_a 103 3 Oreo
MIN Price_b 44.52 3 Oreo
MIN Price_c 32.92 2 Chocolate Chunk
MIN Price_d 110 2 Chocolate Chunk
However, if it is just the min and max of all prices (a, b, c and d) then you could use this:
SELECT mt.Type2,
t.Type,
mt.Price,
t.ID,
t.Name
FROM ( SELECT MIN(Price) AS Price, 'MIN' AS Type2
FROM NormalT
UNION ALL
SELECT MAX(Price) AS Price, 'MAX' AS Type2
FROM NormalT
) mt
INNER JOIN NormalT T
ON mt.Price = t.Price;
Which will output this:
TYPE2 TYPE PRICE ID NAME
MIN Price_c 32.92 2 Chocolate Chunk
MAX Price_d 850 3 Oreo
Examples on SQL Fiddle
Try this , It's emulating Analytics as MYSQL doesn't have them by default :
SELECT id,
( select MAX(price_a) from $t t2 where t2.id = t1.id ) AS price_a,
( select MAX(price_b) from $t t2 where t2.id = t1.id ) AS price_b,
( select MAX(price_c) from $t t2 where t2.id = t1.id ) AS price_c,
( select MAX(price_d) from $t t2 where t2.id = t1.id ) AS price_d
FROM $t t1 WHERE id IN(". implode(',', array_map('intval', $arrIds)) .")
Source From : http://www.oreillynet.com/pub/a/mysql/2007/03/29/emulating-analytic-aka-ranking-functions-with-mysql.html?page=3
You are not getting id because, MAX returns one value. But it is not so with id.You can use seperate queries like
SELECT id,MAX(price_a) FROM $t WHERE id IN (". implode(',', array_map('intval', $arrIds)).")";
SELECT id,MAX(price_b) FROM $t WHERE id IN (". implode(',', array_map('intval', $arrIds)).")";
etc
Related
I have table with following information
id | order_id | batch_id | bucket_id | menu_id | product_id | type_id | size
1 | 1 | 1 | 1 | 1 | 1 | 1 | small
2 | 1 | 1 | 1 | 1 | 5 | 1 | small
3 | 1 | 1 | 1 | 1 | 5 | 1 | medium
I want to achieve following
order_id | batch_id | product1 | product5
1 | 1 | 1 x small| 1 x small, 1 medium
Is this possible to write a query to achieve this?
It's possible in MySQL using this kind of query:
SELECT order_id, batch_id,
GROUP_CONCAT(CASE WHEN product_id=1 THEN CONCAT(type_id,' x ', size) END) AS product1,
GROUP_CONCAT(CASE WHEN product_id=5 THEN CONCAT(type_id,' x ', size) END) AS product5
FROM table1
GROUP BY order_id, batch_id
The problem with this is that it's not dynamic so if you have hundreds, thousands of products, the query will be very hard to maintain. One possible solution in MySQL is using prepared statement. Here is an updated example after #ggordon spotted that my previous attempt show duplicates:
SET #columns := (SELECT GROUP_CONCAT(CONCAT("GROUP_CONCAT(CASE WHEN product_id=",product_id,"
THEN CONCAT(cnt,' x ', size) END)
AS product",product_id,"
"))
FROM (SELECT DISTINCT product_id FROM table1) t1);
SET #query := CONCAT('SELECT order_id, batch_id, ',#columns,'
FROM (SELECT product_id, order_id, batch_id, size, COUNT(*) cnt
FROM table1 GROUP BY product_id, order_id, batch_id, size) t1
GROUP BY order_id, batch_id');
PREPARE stmt FROM #query ;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
There are 2 variables being used in there and I named each variable to represent what is it (hopefully).
Demo fiddle
The following query would return the desired data you would need
SELECT
order_id,
batch_id,
product_id,
concat(
count(product_id),
' x ',
size
) as size_cnt
FROM
t1
GROUP BY
order_id,
batch_id,
product_id,
size;
order_id
batch_id
product_id
size_cnt
1
1
1
1 x small
1
1
5
1 x small
1
1
5
1 x medium
View working demo on DB Fiddle
However, in order to get it in the desired format, you would need to pivot the data. You could achieve this with the assistance of group_concat as shown in the sql example below:
SELECT
order_id,
batch_id,
-- we can generate from here
REPLACE(
GROUP_CONCAT(
',',
CASE
WHEN product_id=1 THEN size_cnt
END
),
',,',
','
) as product1,
REPLACE(
GROUP_CONCAT(
',',
CASE
WHEN product_id=5 THEN size_cnt
END
),
',,',
','
) as product5
-- to here. See explanation below
FROM (
SELECT
order_id,
batch_id,
product_id,
concat(
count(product_id),
' x ',
size
) as size_cnt
FROM
t1
GROUP BY
order_id,
batch_id,
product_id,
size
) t2
GROUP BY
order_id,
batch_id
order_id
batch_id
product1
product5
1
1
,1 x small
,1 x small,1 x medium
View working demo on DB Fiddle
However, as you can see, you would have to know the product_ids before hand for the desired columns.
If you are uncertain about the product ids that you will have, writing a dynamic query would be helpful here. You could start by getting all the product_ids.
I'm using the DB facade from Laravel here, however, you may use the Eloquent ORM or other methods to achieve the following:
//I have a collection of product ids i.e. `collect([1,5])` based on your example
$productIds = DB::select("select distinct product_id from t1")
->pluck('product_id');
Then generating a dynamic sql query to run on your table
$productExpression = DB::select("select distinct product_id from t1")
->pluck('product_id')
//for each product id let us return an sql expression
->map(function($productId){
return "
REPLACE(
GROUP_CONCAT(
',',
CASE
WHEN product_id=$productId THEN size_cnt
END
),
',,',
','
) as product$productId
";
})
//combine the expressions into one string
->join(",");
We can now create a combined query as
$combinedQuery="
SELECT
order_id,
batch_id,
$productExpression
FROM (
SELECT
order_id,
batch_id,
product_id,
concat(
count(product_id),
' x ',
size
) as size_cnt
FROM
t1
GROUP BY
order_id,
batch_id,
product_id,
size
) t2
GROUP BY
order_id,
batch_id;
";
//running the query to retrieve the results
$results = DB::select($combinedQuery);
Let me know if this works for you.
If you are using php with either PDO or mysqli you can get PHP to concat the fields.
$result = $db->query("SELECT * FROM TABLE");
while($row = $result->fetch_assoc()) {
//do stuff with data
$product .= $row["product_id"] . " x " . $row["size"].", ";
}
This question already has answers here:
Get top n records for each group of grouped results
(12 answers)
Closed 6 years ago.
Hello I have a mysql database in which has multiple categories. I would like to be able to pull only the first 10 items per category for example:
I have the following table, I would like to pull the first 2 rows for name = a, same for name = b and name = c
-----------------------------------
name | value | description | logo
-----------------------------------
a | 2.00 | its a letter| image
-----------------------------------
a | 5.00 | its a letter| image
-----------------------------------
b | 6.00 | its a letter| image
-----------------------------------
c | 3.00 | its a letter| image
-----------------------------------
c | 1.00 | its a letter| image
------------------------------------
This is what I have so farm post filter is a string of objects that comes in when the call is made. unfortunately it only gives me the first 10 of everything together, if you can point me in the right direction that would be great thank you!
code:
SELECT *
FROM object_list
Where object IN (".$_POST['filter'].")
ORDER BY date DESC, object ASC,id DESC
You can get groups along with element count by the below query:
SELECT name, value,
#count := IF(#value = name, #count + 1, 1) AS count,
#value := name AS some_value
FROM test, (SELECT #count := 1, #value := NULL) a
WHERE test.name in ('a', 'b')
Now, if you need to restrict the rows to 2 per group then you just need to wrap this query into another select and add a criteria, e.g.:
SELECT *
FROM (
SELECT name, value,
#count := IF(#value = name, #count + 1, 1) AS count,
#value := name AS some_value
FROM test, (SELECT #count := 1, #value := NULL) a
WHERE test.name in ('a', 'b')) a
WHERE a.count <= 2;
Here's the SQL Fiddle.
Does this work?
SELECT
yourtable.*
FROM
yourtable
JOIN (
SELECT
t1.name,
t1.value,
COUNT(t2.name) AS theCount
FROM yourtable t1
LEFT JOIN yourtable t2 ON t1.name = t2.name AND t1.value > t2.value
WHERE t1.name in ('a', 'b')
GROUP BY t1.name, t1.value
HAVING theCount < 2
) AS dt USING (name, value);
Source: http://thenoyes.com/littlenoise/?p=36
well this question is related to my previous question How to left join 2 tables with SUM() and MAX() grouped by date
what i changed is i added user_id column (auto incremented) and want to select value with highest user_id per date
i have table loadhistory ( wanted to "select only value with highest user_id per date" group by and order by date DESC.)
so in this case, i want to select 150 for 2015-02-27 since it has the highest user_id in that date and 50 for 2015-02-28
| user_id | customer_id | date | bal |
1 1 2015-02-27 100
2 1 2015-02-27 150
3 1 2015-02-28 150
4 1 2015-02-28 50
and table transactionrecord (want to sum up values per date using SUM(bal) group by and order by date DESC)
| user_id |customer_id | date | bal |
1 1 2015-02-27 50
2 1 2015-02-27 20
3 1 2015-02-28 10
And i want to join the 2 tables which would look like this:
| date | balance | amount paid |
2015-02-28 50 10
2015-02-27 150 70
this is the code so far (i used the code i got from my previous question and edited it here in my new question hoping to arrive desired result but did not)
SELECT a.customer_id, a.date, (b.bal AS bal WHERE b.user_id = MAX(b.user_id) , a.paid
FROM (
SELECT customer_id, date, SUM(bal) AS paid
FROM transactionrecord
GROUP BY customer_id, date
) AS a LEFT JOIN loadhistory AS b
ON a.customer_id = b.customer_id AND a.date = b.date
WHERE a.customer_id = 1
GROUP BY a.customer_id, a.date, a.paid
ORDER BY a.date DESC
help please. thanks in advance
MySQL use the first row by group by, so you must order it before you can use group by like this:
SELECT * FROM (SELECT * FROM `loadhistory` ORDER BY user_id DESC) history GROUP BY date
So you can use the following query as solution:
SELECT h.date,h.bal as balance, t.amount as 'amount paid' FROM
(SELECT * FROM (SELECT * FROM `loadhistory` ORDER BY user_id DESC) history GROUP BY date) as h
JOIN
(SELECT SUM(bal) as amount, customer_id, date FROM `transactionrecord` GROUP BY date) as t
ON h.date = t.date AND h.customer_id = t.customer_id
ORDER BY date DESC
I need to select highest scorer as a monthly winner. I want to show previous winners too. My current query selects only previous one month winner, but how can I select all previous monthly winners?
my query:
function month_winner_now()
{
$query = $this->db->query("SELECT winner.id,winner.score,winner.user_id,winner.date, user.id,user.username,user.email,user_profile.user_image,user_profile.hometown,user_profile.country FROM `winner` LEFT JOIN `user` ON user.id = winner.user_id LEFT JOIN `user_profile` ON user_profile.user_id = winner.user_id WHERE MONTH(CURDATE())= MONTH(winner.date) ORDER BY winner.score DESC
LIMIT 1");
return $query->result_array();
}
My current output :
"monthly winners":[
{
"id":"5",
"score":"1256",
"user_id":"5",
"date":"2014-03-05",
"username":"",
"email":"",
"user_image":"",
"hometown":"",
"country":""
}
But I need output like
"monthly winners":[
{
"id":"4",
"score":"233",
"user_id":"4",
"date":"2014-03-02",
"username":"Mahmudul Hasan Swapon",
"email":"",
"user_image":"",
"hometown":"",
"country":""
},
{
"id":"7",
"score":"123",
"user_id":"7",
"date":"2014-03-04",
"username":"Prosanto Biswas",
"email":"",
"user_image":"",
"hometown":"",
"country":""
}
],
Monthly winners json array shows previous all month winners but every month should have one winner.
DB table look like
id | name | userid | score | date |
------------------------------------------------------------
1 | john | 1 | 44 | 2013-03-2
2 | mary | 2 | 59 | 2013-03-5
3 | john | 12 | 38 | 2013-03-8
4 | elvis | 3 | 19 | 2013-02-10
5 | john | 11 | 1002 | 2013-01-11
6 | johnah | 10 | 200 | 2013-01-11
I recreated sql query and added one more field "month_of_year", now I think it will be helpful for you according to your requirement
SELECT
winner.id,winner.score,winner.user_id,winner.date,
user.id,user.username,user.email,user_profile.user_image,
user_profile.hometown,user_profile.country,
date_format( winner.date, '%Y-%m' ) AS month_of_year
FROM
`winner`
LEFT JOIN `user`
ON user.id = winner.user_id
LEFT JOIN `user_profile`
ON user_profile.user_id = winner.user_id
GROUP BY month_of_year
ORDER BY winner.score DESC
i want to show previous all month winner
You are checking for equality of month hence other months (previous or later) are omitted. Change comparison to <= or >= as the case may be.
And if you use LIMIT 1 there is a chance that other month details are not fetched if exists a record in current month.
Try this:
SELECT
winner.id -- etc fields
FROM
`winner`
LEFT JOIN `user`
ON user.id = winner.user_id
LEFT JOIN `user_profile`
ON user_profile.user_id = winner.user_id
WHERE
date_format( winner.date, '%Y%m' ) <= date_format( CURDATE(), '%Y%m' )
ORDER BY
winner.score DESC
For the winner from previous month:
$query = $this->db->query("
SELECT winner.id,winner.score,winner.user_id,winner.date, user.id,user.username,user.email,user_profile.user_image,user_profile.hometown,user_profile.country
FROM `winner`
LEFT JOIN `user`
ON user.id = winner.user_id
LEFT JOIN `user_profile`
ON user_profile.user_id = winner.user_id
WHERE MONTH(DATE_SUB(CURDATE(), INTERVAL 1 MONTH)) = MONTH(winner.date) ORDER BY winner.score DESC LIMIT 1
");
Try something like this query:
SELECT * FROM user
INNSER JOIN
(SELECT user_id, MAX(score) AS s, MONTH(date) AS d
FROM winner
GROUP BY MONTH(date)) monthlyWinner ON (user.id = monthlyWinner.user_id)
Try this,
;with cte as
(
select Datename(mm,[date]) as m
--,max(amount_paid)
,Rank() over(PARTITION BY Datename(mm,[date]) order by [score] desc) as rr
,[score]
,id
from myTbl
--where DATEDIFF(YY,[date],'1/1/2013') = 0
)
select * from cte
left join myTbl as r on r.id=cte.id
where rr = 1
i have database with this condition :
table hotel -----> table hotel price
table hotel :
hotel_id | hotel_name |
1 hotel1
2 hotel2
table hotel price
price_id | hotel_id | room_type | single | Double | extra |
1 1 superior 5 10 20
2 1 deluxe 3 5 10
and i would show start smallest price from hotel1
hotel1 star from "smallest value"
i tried with this but not work
$query = ("SELECT LEAST(COL1,COL2,COL3) FROM rug WHERE COL1 != '' AND COL2!= '' AND COL3 != ''");
$result=mysql_query($query);
if (!$result) {
die('Invalid query: ' . mysql_error());}
$num=mysql_numrows($result);
$i=0;
while ($i < $num)
{
$pricing[$i]=mysql_result($result, $i);
$i++;
}
sort($pricing);
$lowest_price = $pricing[0]; //lowest price
thank raymond for the answer this is almost correct
select
*
, least(single, `double`, extra) as lowest_price
from hotel_price
where
hotel_id = 1
order by
lowest_price
;
with this will show lowest_price column at hotel price table
PRICE_ID HOTEL_ID ROOM_TYPE SINGLE DOUBLE EXTRA HOTEL_NAME LOWEST_PRICE
2 1 deluxe 3 5 10 hotel1 3
1 1 superior 5 10 20 hotel1 5
but i want just show one lowest price from lowest_price column
the smallest is 3
Any thoughts? Thanks!
Not completely sure if you need this..
if you know the id of hotel with name "hotel1" already
select
*
, least(single, `double`, extra) as lowest_price
from hotel_price
where
hotel_id = 1
order by
lowest_price
;
If you don't know the id of the hotel you need to join
select
*
, least(single, `double`, extra) as lowest_price
from
hotel_price
inner join
hotel
on
hotel_price.hotel_id = hotel.hotel_id
where
hotel.hotel_name = 'hotel1'
order by
lowest_price
;
see http://sqlfiddle.com/#!2/f947b/3 for demo note the demo has more queries what should give you the same results
By your SQL syntax I presume you are using MySQL. Than you can solve this by this approach:
SELECT
(SELECT COL1 from rug) as myField
UNION
(SELECT COL2 from rug)
UNION
(SELECT COL3 from rug)
order by myField ASC LIMIT 1