Mysql Query numbering groups rows - mysql

I looked for days for a way to show a compact continuous numbering for group rows.
The products can be single type in the carton or mix together. Some of the carton markings are already printed so I cannot rearrange carton markings.
I have this table:
+-----+------------+--------+-----------+
| qty | product_id | Type | carton_no |
+-----+------------+--------+-----------+
| 18 | 111 | single | 1 |
| 18 | 111 | single | 2 |
| 18 | 111 | single | 3 |
| 48 | 115 | single | 4 |
| 48 | 115 | single | 5 |
| 48 | 115 | single | 6 |
| 36 | 119 | single | 7 |
| 36 | 119 | single | 8 |
| 18 | 111 | single | 9 |
| 36 | 119 | single | 10 |
| 16 | 199 | single | 11 |
| 16 | 199 | single | 12 |
| 4 | 111 | mix | 13 |
| 4 | 115 | mix | 13 |
| 4 | 119 | mix | 13 |
| 4 | 199 | mix | 13 |
+-----+------------+--------+-----------+
The documents processor needs a view like this:
+-----------+-----+------------+--------+
| Numbering | QTY | product_id | Type |
+-----------+-----+------------+--------+
| 1-4 | 72 | 111 | single |
| 5-7 | 144 | 115 | single |
| 8-10 | 108 | 119 | single |
| 11-12 | 32 | 199 | single |
| 13 | 4 | 111 | mix |
| 13 | 4 | 115 | mix |
| 13 | 4 | 119 | mix |
| 13 | 4 | 199 | mix |
+-----------+-----+------------+--------+
The numbering are actually counting of total cartons for each product_id order by type, product_id ASC.
Any ideas?

WITH
cte1 AS (
SELECT qty,
product_id,
Type,
carton_no,
CASE WHEN product_id = LAG(product_id) OVER (ORDER BY carton_no)
THEN 0
ELSE 1
END new_group
FROM src ),
cte2 AS (
SELECT qty,
product_id,
Type,
carton_no,
SUM(new_group) OVER (ORDER BY carton_no) group_num
FROM cte1
)
SELECT CASE WHEN MAX(carton_no) > MIN(carton_no)
THEN CONCAT(MIN(carton_no), '-', MAX(carton_no))
ELSE MIN(carton_no)
END Numbering ,
SUM(qty) QTY,
product_id,
ANY_VALUE(Type) Type
FROM cte2
GROUP BY group_num, product_id;
fiddle

WITH
cte1 AS (
SELECT qty,
product_id,
Type,
carton_no,
CASE WHEN product_id = LAG(product_id) OVER (ORDER BY type desc, product_id)
THEN 0
ELSE 1
END new_group
FROM src order by type desc, product_id ),
cte2 AS (
SELECT qty,
product_id,
Type,
carton_no,
SUM(new_group) OVER (ORDER BY type desc, product_id) group_num
FROM cte1 ),
cte3 AS (
SELECT SUM(qty) QTY,
product_id,
Type,
group_num,
carton_no,
count(group_num) sum,
LAG(count(group_num)) OVER () prevsum
FROM cte2 group by group_num order by type desc, carton_no
)
SELECT CASE WHEN group_num = 1 THEN CONCAT(group_num,'-', sum)
WHEN group_num <> 1 and Type = "mix" and LAG(carton_no) OVER (ORDER BY carton_no) <> carton_no THEN CONCAT(SUM(prevsum) OVER (ORDER BY type desc, product_id) + 1)
WHEN group_num <> 1 and Type = "mix" and LAG(carton_no) OVER (ORDER BY carton_no) = carton_no THEN CONCAT(LAG(carton_no) OVER (ORDER BY carton_no))
WHEN group_num <> 1 and Type = "single" THEN CONCAT(SUM(prevsum) OVER (ORDER BY type desc, product_id) + 1,'-', SUM(prevsum) OVER (ORDER BY type desc, product_id) + sum)
END numbering,
qty,
product_id,
type
FROM cte3
I think I solved the problem, but the code is working in Workbench, but not in fiddle. Any idea how to compress it more and not working in fiddle?

Related

MySQL: Calculating Median of Values grouped by a Column

I have the following table:
+------------+-------+
| SchoolName | Marks |
+------------+-------+
| A | 71 |
| A | 71 |
| A | 71 |
| B | 254 |
| B | 135 |
| B | 453 |
| B | 153 |
| C | 453 |
| C | 344 |
| C | 223 |
| B | 453 |
| D | 300 |
| D | 167 |
+------------+-------+
And here is the average of marks grouped by school names:
+------------+------------+
| SchoolName | avg(Marks) |
+------------+------------+
| A | 71.0000 |
| B | 289.6000 |
| C | 340.0000 |
| D | 233.5000 |
+------------+------------+
https://www.db-fiddle.com/f/5t7N3Vx8FSQmwUJgKLqjfK/9
However rather than average, I want to calculate median of the marks grouped by school names.
I am using,
SELECT AVG(dd.Marks) as median_val
FROM (
SELECT d.Marks, #rownum:=#rownum+1 as `row_number`, #total_rows:=#rownum
FROM tablename d, (SELECT #rownum:=0) r
WHERE d.Marks is NOT NULL
ORDER BY d.Marks
) as dd
WHERE dd.row_number IN ( FLOOR((#total_rows+1)/2), FLOOR((#total_rows+2)/2) );
to calculate the average of entire Marks column, but I don't know how to do it for each school separately.
Your query computes row numbers using user variables, which makes it more complicated to handle partitions. Since you are using MySQL 8.0, I would suggest using window functions instead.
This should get you close to what you expect:
select
SchoolName,
avg(Marks) as median_val
from (
select
SchoolName,
Marks,
row_number() over(partition by SchoolName order by Marks) rn,
count(*) over(partition by SchoolName) cnt
from tablename
) as dd
where rn in ( FLOOR((cnt + 1) / 2), FLOOR( (cnt + 2) / 2) )
group by SchoolName
The arithmetic stays the same, but we are using window functions in groups of records having the same SchoolName (instead of a global partition in your initial query). Then, the outer query filters and aggregates by SchoolName.
In your DB Fiddlde, this returns:
| SchoolName | median_val |
| ---------- | ---------- |
| A | 71 |
| B | 254 |
| C | 344 |
| D | 233.5 |

MySQL Select top N Cheapest product from each category

Here is mySQL product table
+--------+-------+-----+
|Product | Price | Cat |
+--------+-------+-----+
| iPhone | 1 | 32 |
| Samsung| 2 | 32 |
| Dell | 1 | 21 |
| HP | 2 | 21 |
| RedMi | 3 | 32 |
| Acer | 3 | 21 |
+--------+-------+-----+
Required Result top 2 cheapest in each category:
+--------+-------+-----+
|Product | Price | Cat |
+--------+-------+-----+
| iPhone | 1 | 32 |
| Samsung| 2 | 32 |
| Dell | 1 | 21 |
| HP | 2 | 21 |
+--------+-------+-----+
I tried select * from products group by cat order by price but it returns only first cheapest price product. I need top 2 cheapest.
You can use variables to get the ranking of the products for each category:
SET #rn := 0;
SET #cat := 0;
SELECT product, price, cat FROM (
SELECT #rn := case
WHEN #cat = cat then #rn + 1
ELSE 1
END AS rn, product, price, cat,
#cat := cat
FROM products
ORDER BY cat, price
) t
WHERE rn <= 2
ORDER BY cat, rn
See the demo.
For MySQL 8.0+ there is ROW_NUMBER():
SELECT product, price, cat
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY cat ORDER BY price) rn
FROM products
) t
WHERE rn <= 2
ORDER BY cat, rn
See the demo.
Results:
| product | price | cat |
| ------- | ----- | --- |
| Dell | 1 | 21 |
| HP | 2 | 21 |
| iPhone | 1 | 32 |
| Samsung | 2 | 32 |
select type, variety, price
from fruits
where price = (select min(price) from fruits as f where f.type = fruits.type)
or price = (select min(price) from fruits as f where f.type = fruits.type
and price > (select min(price) from fruits as f2 where f2.type = fruits.type));
+--------+----------+-------+
| type | variety | price |
+--------+----------+-------+
| apple | gala | 2.79 |
| apple | fuji | 0.24 |
| orange | valencia | 3.59 |
| orange | navel | 9.36 |
| pear | bradford | 6.05 |
| pear | bartlett | 2.14 |
| cherry | bing | 2.55 |
| cherry | chelan | 6.33 |
+--------+----------+-------+
Quoted from a comment under #forpas answer
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use
near '(PARTITION BY –
You also could try to simulate the results of
SELECT product, price, cat
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY cat ORDER BY price) rn
FROM products
) t
WHERE rn <= 2
ORDER BY cat, rn
More or less with the queries below, as your MariaDB seams not to be supporting window functions.
But try to upgrade to a MariaDB version to which does supports window
functions.
Query
SELECT
products1.Product
, products1.price
, products1.cat
FROM
products products1
LEFT JOIN
products products2
ON
products1.cat = products2.cat
AND
products1.price >= products2.price
GROUP BY
products1.Product
, products1.cat
, products1.price
HAVING
COUNT(*) <= 2
ORDER BY
products1.Product ASC
, products1.cat ASC
, products1.price ASC
Result
| Product | price | cat |
| ------- | ----- | --- |
| Dell | 1 | 21 |
| HP | 2 | 21 |
| iPhone | 1 | 32 |
| Samsung | 2 | 32 |
see demo
Note: this query requires
INDEX(Product, Cat, price), INDEX(Cat, price)
This index requirement might be a bit to much as that would make the index file (much) larger then the table file. Assuming Product | Price | Cat are the only columns..
If Product is unique within a cat and price group you should be able to get away with.
Query
SELECT
(SELECT
products_inner.Product
FROM
products products_inner
WHERE
products_inner.cat = products1.cat
AND
products_inner.price = products1.price
) AS "Product"
, products1.price
, products1.cat
FROM
products products1
LEFT JOIN
products products2
ON
products1.cat = products2.cat
AND
products1.price >= products2.price
GROUP BY
products1.cat
, products1.price
HAVING
COUNT(*) <= 2
ORDER BY
products1.cat ASC
, products1.price ASC
Result
| Product | price | cat |
| ------- | ----- | --- |
| Dell | 1 | 21 |
| HP | 2 | 21 |
| iPhone | 1 | 32 |
| Samsung | 2 | 32 |
see demo
Note: this query requires only
INDEX(Cat, price)
But on the other side it is using a corelated subquery against the grouped record to get the product which might optimize badly.
Note the shorter ANSI/ISO SQL 92 standard query which you can also write and use, which i totally forgot about offcource also works with INDEX(Cat, price)
SELECT
*
FROM
products products_outer
WHERE
2 <= (
SELECT
COUNT(*) FROM products products_inner
WHERE
products_inner.cat = products_outer.cat
AND
products_inner.price >= products_outer.price
)
;
Result
| Product | Price | Cat |
| ------- | ----- | --- |
| iPhone | 1 | 32 |
| Samsung | 2 | 32 |
| Dell | 1 | 21 |
| HP | 2 | 21 |
see demo
Anyhow lots of roads are headed to Rome, now find the correct one

How to SUM values for DISTINCT values of another column and GROUP BY date?

How can we SUM amount for each activity only on same date and output a row for each date? This query is not working.
SELECT SUM(amount), type, date FROM table GROUP BY DISTINCT date;
Table
+----+------------+-----------+---------+
| id | date | activity | amount |
+----+------------+-----------+---------+
| 1 | 2017-12-21 | Shopping | 200 |
| 2 | 2017-12-21 | Gift | 240 |
| 3 | 2017-12-23 | Give Away | 40 |
| 4 | 2017-12-24 | Shopping | 150 |
| 5 | 2017-12-25 | Give Away | 120 |
| 6 | 2017-12-25 | Shopping | 50 |
| 7 | 2017-12-25 | Shopping | 500 |
+----+------------+-----------+---------+
Required Result
+------------+-----------+------+-----------+
| date | Shopping | Gift | Give Away |
+------------+-----------+------+-----------+
| 2017-12-21 | 200 | 240 | |
| 2017-12-23 | | | 40 |
| 2017-12-24 | 150 | | |
| 2017-12-25 | 550 | | 120 |
+------------+-----------+------+-----------+
Use:
select `date`,
sum(if (activity='Shopping', amount, null)) as 'Shopping',
sum(if (activity='Gift', amount, null)) as 'Gift',
sum(if (activity='Give Away', amount, null)) as 'Give Away'
from table
group by `date`
You can try this. It returns exact result that you want
SELECT t.date,
SUM(t.shopping_amount) AS shopping,
SUM(t.gift_amount) AS gift,
SUM(t.give_away_amount) AS give_away
FROM
(
SELECT p.`date`, p.`activity`, p.`amount` AS shopping_amount,
0 AS gift_amount, 0 AS give_away_amount
FROM products p
WHERE p.`activity` = 'Shopping'
UNION
SELECT p.`date`, p.`activity`, 0 AS shopping_amount,
p.amount AS gift_amount, 0 AS give_away_amount
FROM products p
WHERE p.`activity` = 'Gift'
UNION
SELECT p.`date`, p.`activity`, 0 AS shopping_amount,
0 AS gift_amount, p.amount AS give_away_amount
FROM products p
WHERE p.`activity` = 'Give Away'
) t
GROUP BY t.date
Hmmm, you can't pivot your results into column headers unless you know all possible values as demonstrated by slaasko but you can get the results using sql into a form which can be pivoted using your display tool ( e.g. slice of BI tool).
SELECT SUM(amount), activity, date FROM table GROUP BY date, activity;

Subtract two columns of different tables with different number of rows

How can I write a single query that will give me SUM(Entrance.quantity) - SUM(Buying.quantity) group by product_id.
The problem is in rows that not exist in the first or second table. Is possible to do this?
Entrance:
+---+--------------+---------+
| id | product_id | quantity|
+---+--------------+---------+
| 1 | 234 | 15 |
| 2 | 234 | 35 |
| 3 | 237 | 12 |
| 4 | 237 | 18 |
| 5 | 101 | 10 |
| 6 | 150 | 12 |
+---+--------------+---------+
Buying:
+---+------------+-------------+
| id | product_id | quantity|
+---+------------+-------------+
| 1 | 234 | 10 |
| 2 | 234 | 20 |
| 3 | 237 | 10 |
| 4 | 237 | 10 |
| 5 | 120 | 15 |
+---+------------+------------+
Desired result:
+--------------+-----------------------+
| product_id | quantity_balance |
+--------------+-----------------------+
| 234 | 20 |
| 237 | 10 |
| 101 | 10 |
| 150 | 12 |
| 120 | -15 |
+--------------+-----------------------+
This is tricky, because products could be in one table but not the other. One method uses union all and group by:
select product_id, sum(quantity)
from ((select e.product_id, quantity
from entrance e
) union all
(select b.product_id, - b.quantity
from buying b
)
) eb
group by product_id;
SELECT product_id ,
( Tmp1.enterquantity - Tmp2.buyquantity ) AS Quantity_balance
FROM entrance e1
CROSS APPLY ( SELECT SUM(quantity) AS enterquantity
FROM Entrance e2
WHERE e1.product_id = e2.product_id
) Tmp1
CROSS APPLY ( SELECT SUM(quantity) AS buyquantity
FROM Buying b2
WHERE e1.product_id = b2.product_id
) Tmp2
GROUP BY Product_id,( Tmp1.enterquantity - Tmp2.buyquantity )

How to get max value with various conditions from a single MySQL table

I have table with a bunch of (machine id) mid's and (sensor id) sid's, and their corresponding (values) v's. Needless to say the id column is a unique row number. (NB: There are other columns in the table, and not all mid's have the same sid's)
Current Table:
+------+-------+-------+-----+---------------------+
| id | mid | sid | v | timestamp |
+------+-------+-------+-----+---------------------+
| 51 | 10 | 1 | 40 | 2015/5/1 11:56:01 |
| 52 | 10 | 2 | 39 | 2015/5/1 11:56:25 |
| 53 | 10 | 2 | 40 | 2015/5/1 11:56:42 |
| 54 | 11 | 1 | 50 | 2015/5/1 11:57:52 |
| 55 | 11 | 2 | 18 | 2015/5/1 11:58:41 |
| 56 | 11 | 2 | 19 | 2015/5/1 11:58:59 |
| 57 | 11 | 3 | 58 | 2015/5/1 11:59:01 |
| 58 | 11 | 3 | 65 | 2015/5/1 11:59:29 |
+------+-------+-------+-----+---------------------+
Q: How would I get the MAX(v)for each sid for each mid?
Expected Output:
+------+-------+-------+-----+---------------------+
| id | mid | sid | v | timestamp |
+------+-------+-------+-----+---------------------+
| 51 | 10 | 1 | 40 | 2015/5/1 11:56:01 |
| 53 | 10 | 2 | 40 | 2015/5/1 11:56:42 |
| 54 | 11 | 1 | 50 | 2015/5/1 11:57:52 |
| 56 | 11 | 2 | 19 | 2015/5/1 11:58:59 |
| 58 | 11 | 3 | 65 | 2015/5/1 11:59:29 |
+------+-------+-------+-----+---------------------+
The expected output is to obtain the whole row with all the (single) max value for all the sids in all the mids.
Addendum:
Due to a very big table, I need to place boundaries with dates. For the sample above the two boundary dates should be 2015/05/01 00:00:00 (1st of May'15) till 2015/05/02 00:00:00 (2nd of May'15). Q: How could I add this date boundary?
Find the max v in subquery for each combination of mid, sid and then join it with your original table to get the desired result.
select *
from your_table t
join (
select mid, sid, max(v) as v
from your_table
group by mid, sid
) t2 using (mid, sid, v);
Note here that if there are multiple rows with same sid, mid and v, it will return all of them.
As mentioned in the comments, since you have an id column, you can include that in limited correlated query like this:
select *
from your_table t1
where id = (select id
from your_table t2
where t1.mid = t2.mid
and t1.sid = t2.sid
order by v desc, id desc
limit 1
);
This will give you one single row per mid, sid combination with max v (and latest id in case of ties).
Use MAX() function with GROUP BY clause
SELECT id, mid, sid, MAX(v) AS v, `timestamp`
FROM MyTable
GROUP BY mid, sid;
This returns rows with maximum values of v for each combination of mid and sid.