How to round off the value in MySQL stored procedure - mysql

I have this stored procedure:
CREATE DEFINER=`brambang`#`%` PROCEDURE `TNP_PRODUK_FrekuensiPembelianUlang`(IN paramdatefrom CHAR(19), paramdateto CHAR(19))
BEGIN
SELECT MAX(count) AS max,
MIN(count) AS min,
AVG(count) AS average,
AVG(CASE WHEN rn IN (FLOOR((#tr+1)/2), FLOOR((#tr+2)/2)) THEN count END) AS median
FROM (
SELECT count,
#rn := #rn + 1 AS rn,
#tr := #rn AS tr
FROM (
SELECT COUNT(*) AS count
FROM order_match om1
where om1.createdAt between paramdatefrom and paramdateto
and om1.order_status_id in (4, 5, 6, 8)
and EXISTS(SELECT 1 from order_match om2
where om1.createdby = om2.createdby
and om2.createdAt < paramdatefrom
and om2.order_status_id in (4, 5, 6, 8))
GROUP BY createdby
ORDER BY count
) o
CROSS JOIN (SELECT #rn := 0) init
) c;
END
and this is the result if i insert the parameter
+-----+-----+---------+---------+
| max | min | average | median |
+-----+-----+---------+---------+
| 24 | 1 | 1.6382 | 1.00000 |
+-----+-----+---------+---------+
What should I add to my stored procedure so that the value can be rounded off to be like this
+-----+-----+---------+---------+
| max | min | average | median |
+-----+-----+---------+---------+
| 24 | 1 | 1.64 | 1.0 |
+-----+-----+---------+---------+

Just use ROUND(). If you want two decimals maximum, then:
SELECT ROUND(MAX(count), 2) AS max,
ROUND(MIN(count), 2) AS min,
ROUND(AVG(count), 2) AS average,
ROUND(AVG(CASE WHEN rn IN (FLOOR((#tr+1)/2), FLOOR((#tr+2)/2)) THEN count END), 2) AS median
FROM ...

Related

Time difference between rows

In a mysql database (ver.5.7.15) I've got the following table named operation_list containing the following data: id (autoincrement integer, primary key), operation_date_time (datetime), operation (enumerated values: START,STOP), so the table looks like that:
+------+---------------------+-----------+
| id | operation_date_time | operation |
+------+---------------------+-----------+
| 1 | 2000-01-01 06:30:45 | START |
| 2 | 2000-01-01 07:45:00 | STOP |
| 3 | 2000-01-01 08:18:12 | START |
| 4 | 2000-01-01 11:23:58 | STOP |
| 5 | 2000-01-01 15:45:01 | START |
| 6 | 2000-01-01 19:01:33 | STOP |
+------+---------------------+-----------+
Now, assuming that the first row is always a START, the last ROW is always a STOP, the STOP is always placed after a START, I need to retrieve the time difference between START and STOP in seconds. Hence, I need to write an SQL that would produce the following recordset:
+------+---------------------+-----------+
| id | operation_date_time | duration |
+------+---------------------+-----------+
| 1 | 2000-01-01 06:30:45 | 4455 |
| 3 | 2000-01-01 08:78:12 | 11146 |
| 5 | 2000-01-01 15:45:01 | 11792 |
+------+---------------------+-----------+
Where 4455 is equivalent to 1 hour, 14 minutes and 15 seconds,
11146 is equivalent to 3 hours, 5 minutes and 46 seconds,
11792 is equivalent to 3 hours, 16 minutes and 32 seconds, and so on.
What's the best way to do it in a single SQL statement without creating additional tables or dedicated scripting?
This works IN mysql 5.X
But it is uglier as in 8.0
SELECT
MIN(id) id
,MIN(`operation_date_time`) `operation_date_time`
,MAX(diff) duration
FROM
(SELECT
id
, IF(`operation` = 'START', 0,TIME_TO_SEC(TIMEDIFF(`operation_date_time`, #datetime))) diff
,IF(`operation` = 'START', #count := #count + 1,#count := #count) groupby
,#datetime := `operation_date_time` `operation_date_time`
FROM
(SELECT * FROM timetable ORDER by `operation_date_time` ASC) t1, (SELECT #datetime := NOW()) a,
(SELECT #count := 0) b) t2
GROUP by groupby;
CREATE TABLE timetable (
`id` INTEGER,
`operation_date_time` VARCHAR(19),
`operation` VARCHAR(5)
);
INSERT INTO timetable
(`id`, `operation_date_time`, `operation`)
VALUES
('1', '2000-01-01 06:30:45', 'START'),
('2', '2000-01-01 07:45:00', 'STOP'),
('3', '2000-01-01 08:18:12', 'START'),
('4', '2000-01-01 11:23:58', 'STOP'),
('5', '2000-01-01 15:45:01', 'START'),
('6', '2000-01-01 19:01:33', 'STOP');
✓
✓
SELECT
MIN(id) id
,MIN(`operation_date_time`) `operation_date_time`
,MAX(diff) duration
FROM
(SELECT
id
, IF(`operation` = 'START', 0,TIME_TO_SEC(TIMEDIFF(`operation_date_time`, #datetime))) diff
,IF(`operation` = 'START', #count := #count + 1,#count := #count) groupby
,#datetime := `operation_date_time` `operation_date_time`
FROM
(SELECT * FROM timetable ORDER by `operation_date_time` ASC) t1, (SELECT #datetime := NOW()) a,
(SELECT #count := 0) b) t2
GROUP by groupby;
id | operation_date_time | duration
-: | :------------------ | -------:
1 | 2000-01-01 06:30:45 | 4455
3 | 2000-01-01 08:18:12 | 11146
5 | 2000-01-01 15:45:01 | 11792
db<>fiddle here
SELECT operation_date_time,DURATION FROM (
SELECT *,DATEDIFF(SECOND,operation_date_time,LEAD(operation_date_time)OVER(ORDER BY ID)) AS DURATION FROM PRACTICE
)A
WHERE operation='START'
Use window functions to get the ending time. I am going to use a cumulative conditional min:
select t.*,
timestampdiff(second, operation_date_time, stop_dt) as diff_seconds
from (select t.*,
min(case when operation = 'STOP' then operation_date_time end) over (order by operation_date_time) as stop_dt
from t
) t
where operation = 'START';
If the data really is interleaved, then you could just use lead():
select t.*,
timestampdiff(second, operation_date_time, stop_dt) as diff_seconds
from (select t.*,
lead(operation_date_time) over (order by operation_date_time) as stop_dt
from t
) t
where operation = 'START';
EDIT:
In MySQL pre-8.0:
select t.*,
timestampdiff(second, operation_date_time, stop_dt) as diff_seconds
from (select t.*,
(select min(t2.operation_date_time)
from t t2
where t2.operation = 'STOP' and
t2.operation_date_time > t.operation_date_time
) as stop_dt
from t
) t
where operation = 'START';

how to count max, min, average, and median of quantity order

I have a table called order_match which contain order_buyer_Id as the id of the transaction, createdby as the id of the buyer, and createdAt as the date when the transaction happened and quantity as the quantity of each order.
In this case, I want to count of the order (order_buyer_Id) for each buyer (createdby) and find out the maximum and the minumum count after that.
this is the example data:
+----------------+-----------+------------+--------+
| order_buyer_id | createdby | createdAt |quantity|
+----------------+-----------+------------+--------+
| 19123 | 19 | 2017-02-02 |0.4 |
| 193241 | 19 | 2017-02-02 |0.5
| 123123 | 20 | 2017-02-02 |1 |
| 32242 | 20 | 2017-02-02 |4
| 32434 | 20 | 2017-02-02 |3 |
+----------------+-----------+------------+---------
and if I run the query, the expected result is:
+-----+-----+---------+--------+
| max | min | average | median |
+-----+-----+---------+--------+
| 4 | 0.4 | 1,78 | 1 |
+-----+-----+---------+---------
This is the fiddle
http://www.sqlfiddle.com/#!9/d89772/15
and this is my query
SELECT MAX(quantity) AS max,
MIN(quantity) AS min,
AVG(quantity) AS average,
AVG(CASE WHEN rn IN (FLOOR((#tr+1)/2), FLOOR((#tr+2)/2)) THEN quantity END) AS median
FROM (
SELECT count,
#rn := #rn + 1 AS rn,
#tr := #rn AS tr
FROM (
SELECT COUNT(*) AS count
FROM order_match
GROUP BY order_buyer_Id
order by quantity
) o
CROSS JOIN (SELECT #rn := 0) init
) c
You are getting the error because quantity is not in your subquery.
Either you have join with your table again to get the quantity or you can include the quantity in you select (based on your sample data even group by with quantity gives the same result)
SELECT MAX(quantity) AS max,
MIN(quantity) AS min,
AVG(quantity) AS average,
AVG(CASE WHEN rn IN (FLOOR((#tr+1)/2), FLOOR((#tr+2)/2)) THEN quantity END) AS median
FROM (
SELECT count, quantity,
#rn := #rn + 1 AS rn,
#tr := #rn AS tr
FROM (
SELECT COUNT(*) AS count,Quantity
FROM order_match
GROUP BY order_buyer_Id,Quantity
order by quantity
) o
CROSS JOIN (SELECT #rn := 0) init
) c
SQL FIDDLE
SELECT t.max,t.min,t.average,0.00 AS 'Median'
FROM
(SELECT MAX(quantity) AS max,
MIN(quantity) AS min,
SUM(quantity)/COUNT(distinct created_by) AS average
FROM order_match)t
union
SELECT 0.00 AS 'max',0.00 AS 'min',0.00 AS 'Average',
((2*t1.average/3)+t1.mode) AS 'Median'
FROM (SELECT count(FLOOR(quantity)),IFNULL(FLOOR(quantity),min(quantity)) AS 'mode'
FROM order_match GROUP BY quantity HAVING
count(FLOOR(quantity))>1)t1

SQL how to get the max price of the 33% cheapests products

I need to to get the max price of the 33% cheapests products. My idea is like this. Of course, this code is just an example. I need to use subqueries.
select max((select price from products order by preco limit 33% )) as result from products
For example
product_id price
1 10
2 50
3 100
4 400
5 900
6 8999
I need I query that returns 50, since 33% of the rows are 2, and the max value of the 2(33%) of the rows is 50.
In MySQL 8+, you would use window functions:
select avg(precio)
from (select p.*, row_number() over (order by precio) as seqnum,
count(*) over () as cnt
from products p
) p
where seqnum <= 0.33 * cnt;
Obviously there are multiple approaches to this but here is how I would do it.
Simply get a count on the table. This will let me pick the max price of the cheapest 33% of products. Let's say it returned n records. Third of that would be n/3. Here you can either round up or down but needs to be rounded in case of a fraction.
Then my query would be something like SELECT * FROM products ORDER BY price ASC LIMIT 1 OFFSET n/3. This would return me a single record with minimal calculations and look ups on MySQL side.
For MySQL versions under MySQL 8.0 you can use MySQL's user variables to simulate/emulate a ROW_NUMBER()
Query
SELECT
t.product_id
, t.price
, (#ROW_NUMBER := #ROW_NUMBER + 1) AS ROW_NUMBER
FROM
t
CROSS JOIN (SELECT #ROW_NUMBER := 0) AS init_user_variable
ORDER BY
t.price ASC
Result
| product_id | price | ROW_NUMBER |
| ---------- | ----- | ---------- |
| 1 | 10 | 1 |
| 2 | 50 | 2 |
| 3 | 100 | 3 |
| 4 | 400 | 4 |
| 5 | 900 | 5 |
| 6 | 8999 | 6 |
When we get the ROW_NUMBER we can use that in combination with ROW_NUMBER <= CEIL(((SELECT COUNT(*) FROM t) * 0.33));
Which works like this
(SELECT COUNT(*) FROM t) => Counts and returns 6
(SELECT COUNT(*) FROM t) * 0.33) Calculates 33% from 6 which is 1.98 and returns it
CEIL(..) Return the smallest integer value that is greater than or equal to 1.98 which is 2 in this case
ROW_NUMBER <= 2 So the last filter is this.
Query
SELECT
a.product_id
, a.price
FROM (
SELECT
t.product_id
, t.price
, (#ROW_NUMBER := #ROW_NUMBER + 1) AS ROW_NUMBER
FROM
t
CROSS JOIN (SELECT #ROW_NUMBER := 0) AS init_user_variable
ORDER BY
t.price ASC
) AS a
WHERE
ROW_NUMBER <= CEIL(((SELECT COUNT(*) FROM t) * 0.33));
Result
| product_id | price |
| ---------- | ----- |
| 1 | 10 |
| 2 | 50 |
see demo
To get get the max it's just as simple as adding ORDER BY a.price DESC LIMIT 1
Query
SELECT
a.product_id
, a.price
FROM (
SELECT
t.product_id
, t.price
, (#ROW_NUMBER := #ROW_NUMBER + 1) AS ROW_NUMBER
FROM
t
CROSS JOIN (SELECT #ROW_NUMBER := 0) AS init_user_variable
ORDER BY
t.price ASC
) AS a
WHERE
ROW_NUMBER <= CEIL(((SELECT COUNT(*) FROM t) * 0.33))
ORDER BY
a.price DESC
LIMIT 1;
Result
| product_id | price |
| ---------- | ----- |
| 2 | 50 |
see demo
If your version supports window functions, you can use NTILE(3) to divide the rows into three groups ordered by price. The first group will contain (about) "33%" of lowest prices. Then you just need to select the MAX value from that group:
with cte as (
select price, ntile(3) over (order by price) as ntl
from products
)
select max(price)
from cte
where ntl = 1
Demo
Prior to MySQL 8.0 I would use a temprary table with an AUTO_INCREMENT column:
create temporary table tmp (
rn int auto_increment primary key,
price decimal(10,2)
);
insert into tmp(price)
select price from products order by price;
set #max_rn = (select max(rn) from tmp);
select price
from tmp
where rn <= #max_rn / 3
order by rn desc
limit 1;
Demo

MySQL - Group By Contigous Blocks

I am struggling to make a GROUP BY contiguous blocks, I've used the following two for references:
- GROUP BY for continuous rows in SQL
- How can I do a contiguous group by in MySQL?
- https://gcbenison.wordpress.com/2011/09/26/queries-that-group-tables-by-contiguous-blocks/
The primary idea that I am trying to encapsulate periods with a start and end date of a given state. A complexity unlike other examples is that I'm using a date per room_id as the indexing field (rather than a sequential id).
My table:
room_id | calendar_date | state
Sample data:
1 | 2016-03-01 | 'a'
1 | 2016-03-02 | 'a'
1 | 2016-03-03 | 'a'
1 | 2016-03-04 | 'b'
1 | 2016-03-05 | 'b'
1 | 2016-03-06 | 'c'
1 | 2016-03-07 | 'c'
1 | 2016-03-08 | 'c'
1 | 2016-03-09 | 'c'
2 | 2016-04-01 | 'b'
2 | 2016-04-02 | 'a'
2 | 2016-04-03 | 'a'
2 | 2016-04-04 | 'a'
The objective:
room_id | date_start | date_end | state
1 | 2016-03-01 | 2016-03-03 | a
1 | 2016-03-04 | 2016-03-05 | b
1 | 2016-03-06 | 2016-03-09 | c
2 | 2016-04-01 | 2016-04-01 | b
2 | 2016-04-02 | 2016-04-04 | c
The two attempts I've made at this:
1)
SELECT
rooms.row_new,
rooms.state_new,
MIN(rooms.room_id) AS room_id,
MIN(rooms.state) AS state,
MIN(rooms.date) AS date_start,
MAX(rooms.date) AS date_end,
FROM
(
SELECT #r := #r + (#state != state) AS row_new,
#state := state AS state_new,
rooms.*
FROM (
SELECT #r := 0,
#state := ''
) AS vars,
rooms_vw
ORDER BY room_id, date
) AS rooms
WHERE room_id = 1
GROUP BY row_new
ORDER BY room_id, date
;
This is very close to working, but when I print out row_new it starts to jump (1, 2, 3, 5, 7, ...)
2)
SELECT
MIN(rooms_final.calendar_date) AS date_start,
MAX(rooms_final.calendar_date) AS date_end,
rooms_final.state,
rooms_final.room_id,
COUNT(*)
FROM (SELECT
rooms.date,
rooms.state,
rooms.room_id,
CASE
WHEN rooms_merge.state IS NULL OR rooms_merge.state != rooms.state THEN
#rownum := #rownum+1
ELSE
#rownum
END AS row_num
FROM rooms
JOIN (SELECT #rownum := 0) AS row
LEFT JOIN (SELECT rooms.date + INTERVAL 1 DAY AS date,
rooms.state,
rooms.room_id
FROM rooms) AS rooms_merge ON rooms_merge.calendar_date = rooms.calendar_date AND rooms_merge.room_id = rooms.room_id
ORDER BY rooms.room_id, rooms.calendar_date
) AS rooms_final
GROUP BY rooms_final.state, rooms_final.row_num
ORDER BY room_id, calendar_date;
For some reason this is returning some null room_id's results as well as generally inaccurate.
Working with variables is a bit tricky. I would go for:
SELECT r.state_new, MIN(r.room_id) AS room_id, MIN(r.state) AS state,
MIN(r.date) AS date_start, MAX(r.date) AS date_end
FROM (SELECT r.*,
(#grp := if(#rs = concat_ws(':', room, state), #grp,
if(#rs := concat_ws(':', room, state), #grp + 1, #grp + 1)
)
) as grp
FROM (SELECT r.* FROM rooms_vw r ORDER BY ORDER BY room_id, date
) r CROSS JOIN
(SELECT #grp := 0, #rs := '') AS params
) AS rooms
WHERE room_id = 1
GROUP BY room_id, grp
ORDER BY room_id, date;
Notes:
Assigning a variable in one expression and using it in another is unsafe. MySQL does not guarantee the order of evaluation of expressions.
In more recent versions of MySQL, you need to do the ORDER BY in a subquery.
In the most recent versions, you can use row_number(), greatly simplifying the calculation.
Thanks to #Gordon Linoff for giving me insights to get to this answer:
SELECT
MIN(room_id) AS room_id,
MIN(state) AS state,
MIN(date) AS date_start,
MAX(date) AS date_end
FROM
(
SELECT
#r := #r + IF(#state <> state OR #room_id <> room_id, 1, 0) AS row_new,
#state := state AS state_new,
#room_id := room_id AS room_id_new,
tmp_rooms.*
FROM (
SELECT #r := 0,
#room_id := 0,
#state := ''
) AS vars,
(SELECT * FROM rooms WHERE room_id IS NOT NULL ORDER BY room_id, date) tmp_rooms
) AS rooms
GROUP BY row_new
order by room_id, date
;

SQL Switch even/odd values of the selection

Currently I have this selection from 2 tables:
(SELECT e.id, e.num, '1' as TBL
FROM events e
)
UNION ALL
(SELECT p.id, p.num, '2' as TBL
FROM places p
)
ORDER BY 2 DESC
It returns values ordered by num like this:
id | num | TBL
3 | 9 | 2
1 | 8 | 2
4 | 7 | 1
1 | 4 | 1
7 | 1 | 2
But my goal is to mix tables in selection not losing ORDER within a specific table. Like this:
id | num | TBL
3 | 9 | 2
4 | 7 | 1
1 | 8 | 2
1 | 4 | 1
7 | 1 | 2
Thanks in advance! I appreciate ANY help!
If you want to interleave the tables, you will need additional information. If you enumerate each row, then you can use that for sorting. Something like this:
(SELECT e.id, e.num, '1' as TBL, (#rn1 := #rn1 + 1) as rn
FROM events e CROSS JOIN
(SELECT #rn1 := 0) vars
ORDER BY e.num desc
)
UNION ALL
(SELECT p.id, p.num, '2' as TBL, (#rn2 := #rn2 + 1) as rn
FROM places p CROSS JOIN
(SELECT #rn2 := 0) vars
ORDER BY p.num desc
)
ORDER BY rn, tbl desc;
Try this:
DECLARE #t TABLE (rn INT IDENTITY,id INT,TBL INT)
INSERT INTO #t (id, TBL)
SELECT id,1 FROM events
INSERT INTO #t
SELECT id,2 FROM places
DECLARE #t2 TABLE (rn INT IDENTITY,num numeric(18,2))
INSERT INTO #t2 (num)
(
SELECT num
FROM events
UNION ALL
SELECT num
FROM places)
ORDER BY num DESC
SELECT id, num, TBL
FROM #t a
JOIN #t2 b ON a.rn = b.rn