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"].", ";
}
Related
I have following table 'persons' with same persons in different rows
id | firstname | surname | date_created
------------------------------------------------------
3 | Nelli | Schaller | 2017-08-22 20:57:19
------------------------------------------------------
4 | Carl | Schaller | 2019-06-21 08:29:45
------------------------------------------------------
48 | Nelli | Schaller | 2020-06-25 13:06:09
------------------------------------------------------
49 | Carl | Schaller | 2020-06-25 13:06:09
What I want to get are all unique Schallers with the biggest id / newest date_created value.
I tried this
SELECT id, CONCAT(surname, ", ", firstname) AS person, date_created
FROM persons
WHERE
surname LIKE "schall%"
GROUP by firstname, surname
ORDER BY date_createdDESC, surname ASC LIMIT 0, 10
but get only as expected the first two entries (id 3 and 4) but I need 48 and 49.
As mentioned in some comment in this case the LIKE statement isn't necessary but in real live it will be the source for an autocomplete field so I need the LIKE
Any idea how to manage that?
Use NOT EXISTS:
SELECT p.id, CONCAT(p.surname, ', ', p.firstname) AS person, p.date_created
FROM persons p
WHERE p.surname LIKE '%schall%'
AND NOT EXISTS (SELECT 1 FROM persons WHERE firstname = p.firstname AND surname = p.surname AND id > p.id)
ORDER BY p.date_created DESC, person
If the condition to pick the latest of each group is the column date_created then change:
...AND id > p.id
with
...AND date_created > p.date_created
You could use subquery with group for max id
select t.max_id, t.person, m.date_created
from (
SELECT max(id) max_id, CONCAT(surname, ", ", firstname) AS person
FROM persons
WHERE surname LIKE "schall%"
ORDER BY date_createdDESC, surname ASC
GROUP BY CONCAT(surname, ", ", firstname)
) t
inner join persons m ON CONCAT(m.surname, ", ", m.firstname) = t.person
and m-id = t.max_id
SELECT p.*
FROM persons p
LEFT JOIN persons p2 ON p2.firstname = p.firstname
AND p2.lastname = p.lastname
AND p2.date_created > p.date_created
WHERE p2.id IS NULL
This is SQL Server syntax but MySQL is probably similar.
I'm assuming your id field doesn't need to be checked as well as the date_created since it's an identity column and would be larger anyway for the latter created records, but obviously adjust to your actual data.
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
I've a table like:
-----------+------|
product_id |price |
-----------+------|
1 + 5 |
-----------+------|
2 + 15 |
-----------+------|
3 + 25 |
-----------+------|
I need the output like:
-----------+------|
product_id |sum |
-----------+------|
1 + 45 |
-----------+------|
2 + 45 |
-----------+------|
3 + 45 |
-----------+------|
My current query:
SELECT product_id, (SELECT SUM(price) FROM tableName) `sum` FROM tableName.
Is it possible to do this without using 2 SELECT queries or GROUP_CONCAT ?
Please help.
Thanks in advance!
Note: I've thousands of rows in my table and more conditions (to include other table data) will be added to this query.
Just use group by.
SELECT
product_id, (SELECT SUM(price) FROM tableName) `sum`
FROM
tableName
group by .
Use a query like this:
SELECT *, sum(price) AS sum FROM table WHERE condition = value
SELECT product_id , sum(price) AS sum FROM table WHERE condition = value
try not using sub query in select statement.
You can try this code
select product_id, (select sum(price) from PRODUCTS) as sum FROM products
Basically, I have a mysql db table which contains a datetime column and a category column. I want to create a SQL query to retrieve all the values present in the category column and count how many occurences of each category values grouped by month/year of the datetime column. If it is possible, I'd also like totals to be returned. A total for the number of all occurences in a month and a total of category counted.
Note: the category values cannot be hardcoded because they are set by the user and stored in another table.
DB table has following structure:
datetime | category
2009-01-05 | fish
2009-01-06 | fish
2009-01-06 | potato
2009-01-16 | fish
2009-02-08 | pineapple
2009-02-15 | potato
I wish returned result from query would be:
Month | fish | potato | pineapple | total
2009-01 | 3 | 1 | 0 | 4
2009-02 | 0 | 1 | 1 | 2
Total | 3 | 2 | 1 | 6
I think (hope) it can be done in a single SQL query but I can't figure out how.
Can anyone help me?
Thanks!
Let me first say that I think this feels more like an issue to handle in your presentation logic (php code). However, SQL can produce such a result. You are trying to accomplish two different things.
First, you're looking for a PIVOT table. MySQL does not support the PIVOT command, but you can simulate it with MAX and CASE. This works well when you know the number of potential categories, but won't work in your case.
Next, you want to have row totals and then a final total row. Again, this is more appropriate to handle in the presentation layer.
However, using Dynamic SQL, you can achieve both a PIVOT table and row totals. Here is some sample code:
First build your PIVOT variable #sql:
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'COUNT(IF(category = ''', category, ''',1,NULL)) AS ', category)
) INTO #sql
FROM (
SELECT *,
#rn:=IF(#prevMonthYear=CONCAT(YEAR(datetime),'-',MONTH(datetime)),#rn+1,1) rn,
#prevMonthYear:=CONCAT(YEAR(datetime),'-',MONTH(datetime)) dt
FROM yourtable JOIN (SELECT #rn:=0,#prevParent:=0) t
) t
;
Now build your Row Summary variable #totsql:
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'SUM(', category, ') AS sum_', category)
) INTO #totsql
FROM (
SELECT *,
#rn:=IF(#prevMonthYear=CONCAT(YEAR(datetime),'-',MONTH(datetime)),#rn+1,1) rn,
#prevMonthYear:=CONCAT(YEAR(datetime),'-',MONTH(datetime)) dt
FROM yourtable JOIN (SELECT #rn:=0,#prevParent:=0) t
) t
;
Put it all together:
SET #sql = CONCAT('SELECT dt,
', #sql, ', COUNT(1) total
FROM (
SELECT *,
#rn:=IF(#prevMonthYear=CONCAT(YEAR(datetime),''-'',MONTH(datetime)),#rn+1,1) rn,
#prevMonthYear:=CONCAT(YEAR(datetime),''-'',MONTH(datetime)) dt
FROM yourtable JOIN (SELECT #rn:=0,#prevParent:=0) t
) t
GROUP BY dt
UNION
SELECT ''Totals'',', #totsql, ', SUM(total)
FROM (
SELECT dt,
', #sql, ', COUNT(1) total
FROM (
SELECT *,
#rn:=IF(#prevMonthYear=CONCAT(YEAR(datetime),''-'',MONTH(datetime)),#rn+1,1) rn,
#prevMonthYear:=CONCAT(YEAR(datetime),''-'',MONTH(datetime)) dt
FROM yourtable JOIN (SELECT #rn:=0,#prevParent:=0) t
) t
GROUP BY dt
) t2
;');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SQL Fiddle Demo
Results:
MONTH FISH POTATO PINEAPPLE TOTAL
2009-1 3 1 0 4
2009-2 0 1 1 2
Totals 3 2 1 6
You can have multiple nested queries or you can use mysql loops in this case. Easiest thing would be to get all the data you need and process it using php.
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