SQL query for rolling changes - mysql

I have the following two tables:
1) Table name: period
+----------+
| PeriodID |
+----------+
| 1 |
| 2 |
| 3 |
| 4 |
+----------+
2) Table name: value
+-------------+--------+
| StartPeriod | Amount |
+-------------+--------+
| 1 | 100 |
| 3 | 200 |
+-------------+--------+
The first table represents time periods, like months. The second table represents the amount for each month, but only when it's different from the previous month. The amount starts at 100, stays at 100 for period 2, then jumps up to 200 beginning in period 3, and stays at 200 after that.
I need a query (MySQL) to return the amount for each period, like so:
+----------+--------+
| PeriodID | Amount |
+----------+--------+
| 1 | 100 |
| 2 | 100 |
| 3 | 200 |
| 4 | 200 |
+----------+--------+
So the query would return the Amount for the latest StartPeriod in the value table that's less than or equal to the PeriodID. For example, for PeriodID 2 it returns the Amount for StartPeriod 1 because there is no value for StartPeriod2 and 1 is the largest number less than or equal to 2 that has an Amount in the value table.
(Sorry the tables are so ugly)
Thank you!

You can do it using a correlated sub-query:
SELECT PeriodID,
(SELECT Amount
FROM Value
WHERE StartPeriod <= PeriodID
ORDER BY StartPeriod DESC LIMIT 1) AS Amount
FROM Period AS p
Demo here
Using variables probably performs better compared to the correlated sub-query:
SELECT PeriodID,
#amount := IF(Amount IS NOT NULL, Amount, #amount) AS Amount
FROM (
SELECT PeriodID, Amount
FROM Period AS p
LEFT JOIN Value AS v ON p.PeriodID = v.StartPeriod) AS t
CROSS JOIN (SELECT #amount := -1) AS var
ORDER BY PeriodID
Demo here

A simple subselect that selects the value for the highest startperiod lower or equal to the period-id could achive that:
select
periodid,
(select amount from value where startperiod <= periodid order by startperiod desc limit 1)
from period
order by periodid;
http://sqlfiddle.com/#!9/9f29c/3

Related

How to get only first rows from table ordered by dublicating values in column?

I have a table looks like:
id | size | price | date
0 | 30 | 800 | 2021-10-01
1 | 30 | 900 | 2021-10-02
2 | 32 | 700 | 2021-09-11
3 | 30 | 800 | 2021-09-21
4 | 32 | 800 | 2021-09-01
5 | 32 | 0 | 2021-10-03
And i need to get the last updated prices of size <= 'size' to check it for zero and get first non-zero value. I try to sort table by size desc, date desc, but can't take only first rows with dublicating sizes.
SELECT *
FROM (SELECT *
FROM `prices`
WHERE model_id = '269'
AND partner_id = '0'
AND size <= '32'
AND date_time <= '2021-10-19'
ORDER BY size DESC, date_time DESC
) AS t_1
GROUP BY size
LIMI 1
does not help. The first result what i want is
id | size | price | date
5 | 32 | 0 | 2021-10-03
1 | 30 | 900 | 2021-10-02
then i want to get 900.
Using exists logic we can try:
SELECT p1.*
FROM prices p1
WHERE NOT EXISTS (SELECT 1 FROM prices p2
WHERE p2.size = p1.size AND p2.date > p1.date);
More typically, on MySQL 8+, we would handle this using ROW_NUMBER:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY size ORDER BY date DESC) rn
FROM prices
)
SELECT id, size, price, date
FROM cte
WHERE rn = 1;
One advantage of using ROW_NUMBER is that should there be two or more latest records tied on the same date, we can simply add another sorting level to the ORDER BY clause to break the tie.
SELECT *
FROM prices p
WHERE
size <= '32'
AND date LIKE (SELECT max(date) from prices where size = p.size and price <> 0)

Select multiple rows with highest rank in mysql

I am using MySQL.
From these two tables, I need to select all room(s) that have the highest number of nurses allocated per bed. This can be only one room or more than one if there is a tie.
Table Allocation
+-------+---------+
| nurse | room |
+-------+---------+
|911923 | 1 |
|916923 | 1 |
|931923 | 1 |
|931926 | 1 |
|931927 | 4 |
|931928 | 4 |
+-------+---------+
Table Room
+--------+--------+
| number | size |
+--------+--------+
| 1 | 2 |
| 4 | 1 |
+-------+---------+
I am trying to select the row(s) with the highest rank, but Limit 1 only limits for one value, in this example both rooms have the same rank. How can I select all rows with the highest rank, if multiple rows have the same rank?
SELECT ROOM.number,
(SELECT COUNT(*) FROM ALLOCATION
WHERE ALLOCATION.room = ROOM.number) / ROOM.size AS nurses_per_bed,
DENSE_RANK() OVER (ORDER BY nurses_per_bed DESC) AS SEQ
FROM ROOM
LIMIT 1
Step by step:
Aggregate allocations per room in order to get the rooms' numbers of nurses.
Join rooms and nurse counts (i.e. allocation aggregate results).
Rank the resulting rows by ratio.
Show only ranked #1 rows.
The query:
select room, nurses, ratio
from
(
select
r.room,
a.nurses,
a.nurses / r.size as ratio,
dense_rank() over (order by a.nurses / r.size) as rnk
from room r
join
(
select number as room, count(*) as nurses
from allocation
group by number
) a on a.room = r.room
) ranked
where rnk = 1
order by room;

how to count max and min average and median into the data mysql

i have 1 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 happen.
on 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 |
+----------------+-----------+------------+
| 19123 | 19 | 2017-02-02 |
| 193241 | 19 | 2017-02-02 |
| 123123 | 20 | 2017-02-02 |
| 32242 | 20 | 2017-02-02 |
| 32434 | 20 | 2017-02-02 |
+----------------+-----------+------------+
and if run the syntax, the expected result are:
+-----+-----+---------+--------+
| max | min | average | median |
+-----+-----+---------+--------+
| 3 | 2 | 2,5 | 3 |
+-----+-----+---------+---------
i've use with this syntax
select max(count(order_buyer_id)), min(count(order_buyer_id)), avg(count(order_buyer_id)), median(count(order_buyer_Id)) from order_match where createdby = 19 and 20 and createdAt = '2017-02-02' group by createdby;
Most of what you want to do is straightforward, but to compute median values you need a ROW_NUMBER function, which you have to simulate with variables in MySQL 5.7. Having computed the row number (based on ordering counts) you can then take the either the middle count (if there are an odd number of values) or the average of the two middle values (if there are an even number of values) to get the median. By using conditional aggregation, we can then compute the median at the same time as the other values:
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
GROUP BY createdby
ORDER BY count
) o
CROSS JOIN (SELECT #rn := 0) init
) c
Output (for your sample data):
max min average median
3 2 2.5 2.5
Demo on SQLFiddle

Select row <= value

So I'm currently using the following bit of SQL to select the closest rank value to the given variable but I'm looking to implement a feature so I can grab the closest rank value but nothing greater than the variable.
Here is my current SQL statement:
SELECT rank, points
FROM `4star`
WHERE arenaID = 6
ORDER BY ABS(rank - $v) ASC
LIMIT 1
$v indicates the PHP variable.
If this was my table:
+---------+----------+
| rank | points |
+---------+----------+
| 1 | 9 |
| 50 | 7 |
| 200 | 6 |
| 5000 | 4 |
| 10000 | 1 |
+---------+----------+
how would I select the closest rank to 3000 that was not greater than 3000? So the row I would get would be 200 => 6?
Try this:
SELECT rank,points
FROM `4star`
WHERE rank <=3000
AND arenaID = 6
ORDER BY rank Desc
LIMIT 1
how would I select the closest rank to 3000 that was not greater than
3000?
Use WHERE to select rows where rank is less than/equal to 3000, then ORDER BY rank descending and LIMIT the results to one row:
SELECT rank
FROM table
WHERE rank <= 3000 AND arenaID = 6
ORDER BY rank DESC
LIMIT 1

Fetch Unit consumption date-wise

I am struggling in to get result from mysql in the following way. I have 10 records in mysql db table having date and unit fields. I need to get used units on every date.
Table structure as follows, adding today unit with past previous unit in every record:
Date Units
---------- ---------
10/10/2012 101
11/10/2012 111
12/10/2012 121
13/10/2012 140
14/10/2012 150
15/10/2012 155
16/10/2012 170
17/10/2012 180
18/10/2012 185
19/10/2012 200
Desired output will be :
Date Units
---------- ---------
10/10/2012 101
11/10/2012 10
12/10/2012 10
13/10/2012 19
14/10/2012 10
15/10/2012 5
16/10/2012 15
17/10/2012 10
18/10/2012 5
19/10/2012 15
Any help will be appreciated. Thanks
There's a couple of ways to get the resultset. If you can live with an extra column in the resultset, and the order of the columns, then something like this is a workable approach.
using user variables
SELECT d.Date
, IF(#prev_units IS NULL
,#diff := 0
,#diff := d.units - #prev_units
) AS `Units_used`
, #prev_units := d.units AS `Units`
FROM ( SELECT #prev_units := NULL ) i
JOIN (
SELECT t.Date, t.Units
FROM mytable t
ORDER BY t.Date, t.Units
) d
This returns the specified resultset, but it includes the Units column as well. It's possible to have that column filtered out, but it's more expensive, because of the way MySQL processes an inline view (MySQL calls it a "derived table")
To remove that extra column, you can wrap that in another query...
SELECT f.Date
, f.Units_used
FROM (
query from above goes here
) f
ORDER BY f.Date
but again, removing that column comes with the extra cost of materializing that result set a second time.
using a semi-join
If you are guaranteed to have a single row for each Date value, either stored as a DATE, or as a DATETIME with the timecomponent set to a constant, such as midnight, and no gaps in the Date value, and Date is defined as DATE or DATETIME datatype, then another query that will return the specifid result set:
SELECT t.Date
, t.Units - s.Units AS Units_Used
FROM mytable t
LEFT
JOIN mytable s
ON s.Date = t.Date + INTERVAL -1 DAY
ORDER BY t.Date
If there's a missing Date value (a gap) such that there is no matching previous row, then Units_used will have a NULL value.
using a correlated subquery
If you don't have a guarantee of no "missing dates", but you have a guarantee that there is no more than one row for a particular Date, then another approach (usually more expensive in terms of performance) is to use a correlated subquery:
SELECT t.Date
, ( t.Units - (SELECT s.Units
FROM mytable s
WHERE s.Date < t.Date
ORDER BY s.Date DESC
LIMIT 1)
) AS Units_used
FROM mytable t
ORDER BY t.Date, t.Units
spencer7593's solution will be faster, but you can also do something like this...
SELECT * FROM rolling;
+----+-------+
| id | units |
+----+-------+
| 1 | 101 |
| 2 | 111 |
| 3 | 121 |
| 4 | 140 |
| 5 | 150 |
| 6 | 155 |
| 7 | 170 |
| 8 | 180 |
| 9 | 185 |
| 10 | 200 |
+----+-------+
SELECT a.id,COALESCE(a.units - b.units,a.units) units
FROM
( SELECT x.*
, COUNT(*) rank
FROM rolling x
JOIN rolling y
ON y.id <= x.id
GROUP
BY x.id
) a
LEFT
JOIN
( SELECT x.*
, COUNT(*) rank
FROM rolling x
JOIN rolling y
ON y.id <= x.id
GROUP
BY x.id
) b
ON b.rank= a.rank -1;
+----+-------+
| id | units |
+----+-------+
| 1 | 101 |
| 2 | 10 |
| 3 | 10 |
| 4 | 19 |
| 5 | 10 |
| 6 | 5 |
| 7 | 15 |
| 8 | 10 |
| 9 | 5 |
| 10 | 15 |
+----+-------+
This should give the desired result. I don't know how your table is called so I named it "tbltest".
Naming a table date is generally a bad idea as it also refers to other things (functions, data types,...) so I renamed it "fdate". Using uppercase characters in field names or tablenames is also a bad idea as it makes your statements less database independent (some databases are case sensitive and some are not).
SELECT
A.fdate,
A.units - coalesce(B.units, 0) AS units
FROM
tbltest A left join tbltest B ON A.fdate = B.fdate + INTERVAL 1 DAY