Getting MAX int column but avoiding natural order MySQL - mysql

I need to get the biggest folio number record from a services table:
services
- id
- folio (int)
The folio number is an Int column formed by year + incremental number. Every time a record is inserted the folio number is formed by the_current_year + (max folio found + 1) Saying that, this is a sample list of available folios:
20191
...
2019124
2019125
20201
20202
...
202019
As per the sample list, I would have 125 services for year 2019 and 19 services for 2020 so far. Please note that on every year change, the latest digits for the folio number start again from 1.
I'm facing 2 issues here. Treating it as integer and getting the MAX by folio won't work because natural sort order. It will return the biggest int.
So doing:
SELECT MAX(folio) FROM services LIMIT 1; returns 2019125 when I actually need to get 202019
Treating it as varchar won't either work, since it will be ordered by char:
SELECT MAX(CONVERT(folio, CHAR(50))) FROM services LIMIT 1; returns 20202 instead of 202019
So my question is how to get the latest folio number?

This will do it:
SELECT folio
FROM services
ORDER BY LEFT(folio, 4) DESC, SUBSTR(folio, 5) + 0 DESC
LIMIT 1
In MySql you can treat integers as strings and apply functions like LEFT() and SUBSTR().
By applying +0 to a string, the string is implicitly converted to a number.
See the demo.
Results:
| folio |
| ------ |
| 202019 |

You would be better off having the year and the serial number in separate columns. You can separate them in the query:
SELECT folio
FROM (
SELECT
CAST(substring(folio, 1,4) AS UNSIGNED) as 'year',
CAST(substring(folio, 5) AS UNSIGNED) as 'service_no',
folio
FROM services
ORDER BY year DESC, service_no DESC
limit 1
) AS q;
See db-fiddle

Related

mySQL group numeric data together with last image from same table

I have a table with cell viability data containing both numeric data and images (dose-response curves). The table can contain multiple rows for the same compound (uniqued by Batch ID). each row has a unique ID, as well as a date field. Now I want to group the data by Batch ID and produce the average EC50 values, but show this together with the last dose-response Curve generated for each compound Batch_ID. The code below will select the first Curve encountered for a particular compound Batch_ID. How can I select the last one instead, but still show it together with average EC50? Any tips appreciated!
SELECT Batch_ID, avg(EC50), Curve FROM CELL_VIABILITY GROUP BY Batch_ID
Example data:
ID Batch_ID EC50 Curve Date
1 ABC123 6.72 blob_1 18-06-15
2 ABC123 4.74 blob_2 18-07-10
3 ABC123 8.72 blob_3 18-08-22
4 DEF456 1.95 blob_4 18-06-15
5 DEF456 1.66 blob_5 18-07-10
6 DEF456 3.06 blob_6 18-08-22
Expected outcome:
Batch_ID EC50 Curve
ABC123 6.73 blob_3
DEF456 2.22 blob_6
Remember that data is unordered set. Without defining a specific order, we cannot determine what is last, first etc.
We can use the Date column to define the Order. Latest updated entry (Maximum Date value) can be considered as "Last".
We can then use Correlated Subquery to determine the Last Curve for a specific Batch_ID.
SELECT cv1.Batch_ID,
AVG(cv1.EC50),
MAX((SELECT cv2.Curve
FROM CELL_VIABILITY AS cv2
WHERE cv2.Batch_ID = cv1.Batch_ID
ORDER BY cv2.Date DESC LIMIT 1)) AS Last_Curve
FROM CELL_VIABILITY AS cv1
GROUP BY Batch_ID
Another approach would be using a Derived Table. We can get the last Date value for every Batch_ID. Then "Self-Join" to the table using the maximum value of the Date to get the Last Curve:
SELECT
cv1.Batch_ID,
cv1.average,
cv2.Curve
FROM
(
SELECT Batch_ID,
AVG(EC50) AS average,
MAX(Date) AS last_date
FROM CELL_VIABILITY
GROUP BY Batch_ID
) AS cv1
JOIN CELL_VIABILITY AS cv2
ON cv2.Batch_ID = cv1.Batch_ID AND
cv2.Date = cv1.last_date

Mysql Sort price , when price thousand to K, million to M

In a MySQL database, prices are stored in a way like this:
98.06K
97.44K
929.14K
91.87K
2.66M
146.64K
14.29K
when i try to sort price ASC or Price DESC, it returns unexpected result.
Kindly suggest me how can i sort price when price is in
10K, 20M, 1.6B
I want result
14.29K
91.87K
97.44K
98.06K
146.64K
929.14K
2.66M
MySQL ignores trailing non-digits when casting string to numeric. This will return the correct price:
price *
case right(price,1)
when 'K' then 1000
when 'M' then 1000000
else 1
end
Of course, you can order by this, but you better apply it during load and store the price in a numeric column.
The problem lies in your data model. I understand that 2.66M is not necessarily exactly 2,660,000, which is why you don't want to store the whole number, but store '2.66M' instead to indicate the precision. This, however, is two pieces of information: the value and the precision, so use two columns:
mytable
value | unit
-------+-----
98.06 | K
97.44 | K
929.14 | K
91.87 | K
2.66 | M
146.64 | K
14.29 | K
Along with a lookup table:
units
unit | factor
-----+--------
K | 1000
M | 1000000
A possible query would be:
select *
from mytable
join units using (unit)
order by mytable.value * units.factor;
where you may want to extend the ORDER BY clause to something like
order by mytable.value * units.factor, units.factor;
or apply some rounding or whatever to consider precision of two seemingly equal values.
It is possible, though not advisable:
https://dbfiddle.uk/?rdbms=mariadb_10.3&fiddle=0a837287c7646823fa6657706f9ae634
SELECT *
, CAST(LEFT(price, LENGTH(price) - 1) AS DECIMAL(10,2)) AS value
, RIGHT(price, 1) AS unit
, CASE RIGHT(price,1)
WHEN 'K' THEN 1000
WHEN 'M' THEN 1000000
ELSE 1
END AS amount
FROM test1
ORDER BY amount, value;
Why not advisable? As the Explain in the dbfiddle shows, this query uses filesort for sorting, which is not very fast. If you do not have too many rows in your data, this should be no problem though.

MYSQL multiply column value by value in previous row

I hope I can phrase this properly - I'm not sure I've been approaching this correctly.
I am running a MySQLi query for which I need to order the results by the result of a sum (multiplicaiton) of the values in one column. The values are to 2 decimal places and are 'odds' for sporting results. As Such i can not simply sum the values from each row as the result (e.g. 1,1,1 adds to 3 but multiplies to 1) does not give me 'correct' ordering.
At present i am simply performing a sum in my query
SUM(Fav_odds) AS Total
But i'm stumped how i can get 'Total' to be the result of 'Fav_odds * number of rows' in my query.
Fav_odds | Vendor
------------------
1.2 | Name
2.1 | Name
3.2 | Name
So for Vendor called 'Name' i would like to give a multipled value for items in Fav_odds column (e.g. 1.2 * 2.1 * 3.2 = 8.064)
select round(EXP(SUM(LOG(fav_odds))),3) as fav_odds from table;
use a variable
set #miller := 1;
select orderingTotal from
(select #miller:=#miller*Fav_odds as orderingTotal
from mytable) mytotaltable
order by orderingTotal;
or it could be a matter of just saying
sum(Fav_odds) * count(Fav_odds)
if what you are saying at the end is the right way ("fav_total * the number of rows").

SQL SELECT ORDER BY multiple columns depending on value of other column

I have a table with the following columns:
id | revisit (bool) | FL (decimal) | FR (decimal) | RL (decimal) | RR (decimal) | date
I need to write a SELECT statement that will ORDER BY on multiple columns, depending on the value of the 'revisit' field.
ORDER BY 'revisit' DESC - records with this field having the value 1 will be first, and 0 will be after
If 'revisit' = 1 order by the lowest value that exists in FL, FR, RL and RR. So if record 1 has values 4.6, 4.6, 3.0, 5.0 in these fields, and record 2 has values 4.0, 3.1, 3.9, and 2.8 then record 2 will be returned first as it holds a lowest value within these four columns.
If 'revisit' = 0 then order by date - oldest date will be first.
So far I have the 'revisit' alone ordering correctly, and ordering by date if 'revisit' = 0, but ordering by the four columns simultaneously when 'revisit' = 1 does not.
SELECT *
FROM vehicle
ORDER BY
`revisit` DESC,
CASE WHEN `revisit` = 1 THEN `FL` + `FR` + `RR` + `RL` END ASC,
CASE WHEN `revisit` = 0 THEN `date` END ASC
Instead it seems to be ordering by the total of the four columns (which would make sense given addition symbols), so how do I ORDER BY these columns simultaneously, as individual columns, rather than a sum.
I hope this makes sense and thanks!
In your current query, you order by the sum of the four columns. You can use least to get the lowest value, so your order by clause could look like:
SELECT *
FROM vehicle
ORDER BY
`revisit` DESC,
CASE WHEN `revisit` = 1 THEN LEAST(`FL`, `FR`, `RR`, `RL`) END ASC,
CASE WHEN `revisit` = 0 THEN `date` END ASC
Of course this would sort only by the lowest value. If two rows would both share the same lowest value, there is no sorting on the second-lowest value. To do that is quite a bit harder, and I didn't really get from your question whether you need that.

Given a date find the previous &/or current and next x# Dates in MySQL non-linear

I have a table code_prices that looks something like this:
CODE | DATE | PRICE
ABC | 25-7-2011 | 2.81
ABC | 23-7-2011 | 2.52
ABC | 22-7-2011 | 2.53
ABC | 21-7-2011 | 2.54
ABC | 20-7-2011 | 2.58
ABC | 17-7-2011 | 2.42
ABC | 16-7-2011 | 2.38
The problem with the data set is there are gaps in the dates, so I may want to look for the price of item ABC on the 18th however there is no entry because the item wasnt sold on this date. So I would like to return the most recent hisotrical entry for the price.
Say if I query on the date 19-7-2011, I would like to return the entry on the 17th then the next 10 avalaible entries.
If however I query for the price of ABC on the 20th, I would want to return the price on the 20th and the next 10 prices after that...
What is the most efficient way to go about this either in SQL statement or using a stored proc.
I can think of just writing a stored proc which takes the date as a param and then querying for all rows where DATE >= QUERY-DATE ordering by the date and then selecting the 11 items (via limit). Then basically I need to see if that set contains the current date, if it does then return, otherwise I will need to return the 10 most recent entires out of those 11 and also do another query on the table to return the previous entry by getting the max date where date < QUERY-DATE. I am thinking there might be a better way, however I'm not an expert with SQL (clearly)...
Thanks!
This is for one specific code:
SELECT code, `date`, price
FROM code_prices
WHERE code = #inputCode
AND `date` >=
( SELECT MAX(`date`)
FROM code_prices
WHERE code = #inputCode
AND `date` <= #inputDate
)
ORDER BY `date`
LIMIT 11
For ABC and 19-7-2011, the above will you give the row for 17-7-2011 and the 10 subsequent rows (20-7-2011, 21-7-2011, etc)
I'm not entirely clear on what you want to achieve, but I'll have a go anyway. This searches for the ID of the row that contains a date less than or equal to your specified date. It then uses that ID to return all rows with an ID greater than or equal to that value. It assumes that you have a column other than the date column on which the rows can be ordered. This is because you said that the dates are non-linear - I assume that you must have some other way of ordering the rows.
SELECT id, code, dt, price
FROM code_prices
WHERE id >= (
SELECT id
FROM code_prices
WHERE dt <= '2011-07-24'
ORDER BY dt DESC
LIMIT 1 )
ORDER BY id
LIMIT 11;
Alternative with code condition - thanks to #ypercube for highlighting that ;-)
SELECT id, code, dt, price
FROM code_prices
WHERE code = 'ABC'
AND id >= (
SELECT id
FROM code_prices
WHERE dt <= '2011-07-23'
AND code = 'ABC'
ORDER BY dt DESC
LIMIT 1 )
ORDER BY id
LIMIT 11;