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

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.

Related

Getting MAX int column but avoiding natural order 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

Return rows matching one condition and if there aren't any then another in MYSQL

I have the following table as an example:
numbers type
--------------
1 1
5 2
6 1
8 2
9 3
14 2
3 1
From this table I would like to select the closest number that is less or equal to 5 AND of type 1 and if there is no such row matching, then (and only then) I would like to return the first closest number larger than 5 of type 2
I can solve this by running two queries:
SELECT number FROM numbers WHERE number <= 5 AND type = 1 ORDER BY number LIMIT 1
and if above query returns 0 results, I simply run the second query:
SELECT number FROM numbers WHERE number > 5 AND type = 2 ORDER BY number LIMIT 1
But is it possible, to achieve the same result by only using one query?
I was thinking something like
SELECT number FROM numbers WHERE (number <= 5 AND type = 1) OR (number > 5 AND type = 2) ORDER BY number LIMIT 1
But that would only work, if mysql first checks the first conditional in the parentheses against all rows and if it finds a match, it returns it, and if not, then it checks all rows against the second parenthesed conditional. It will not work, if it checks each row against both parentheses and only then moves to the next row, which is how I suspect it works.
This query will do what you want. It selects all numbers that match your two query constraints, and orders the results first by type (so that if there is a result for type 1 it will appear first) and then by either -number or number dependent on type (so that numbers <= 5 sort in descending order but numbers > 5 sort in ascending order):
SELECT number
FROM numbers
WHERE ( number <= 5 AND type = 1 )
OR ( number > 5 AND type = 2 )
ORDER BY type, CASE WHEN type = 1 THEN -number ELSE number END
LIMIT 1
Output:
3
Demo on dbfiddle
Combine the two, and you always prefer type 1 over type 2, hence the ORDER BY and LIMIT. The ABS means whichever is first by type, is the closes to the number 5.
SELECT number, type
FROM numbers
WHERE (number <=5 AND type=1) OR
(number > 5 AND type=2)
ORDER BY type ASC, ABS(number-5) ASC
LIMIT 1

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

How to bin/score based on sort order percentile

I am trying to give different cities a "score" from 1 to 5 based on multiple different criteria to eventually add up the scores and make a decision about which city is the best.
The table "international_tobacco_alcohol" contains values of the percentage of income that residents spend on alcohol and tobacco. I want to sort the results into 5 bins where 1 is the lowest percentage spending, 5 is the highest.
I added a "sort_order" column
ALTER TABLE international_tobacco_alcohol ADD COLUMN sort_order INT DEFAULT NULL;
SET #x = 0;
UPDATE international_tobacco_alcohol SET sort_order = (#x:=#x+1)
ORDER BY spent_on_alcohol_and_tobacco;
SELECT * FROM international_tobacco_alcohol;
And then I wanted to add the column "score" but I don't know how to do it correctly. I have tried basically every variation I can think of:
ALTER TABLE international_tobacco_alcohol ADD COLUMN score INT DEFAULT NULL;
UPDATE international_tobacco_alcohol
SET score = CASE
WHEN sort_order < .2*MAX(sort_order) THEN 1
WHEN sort_order=> .2*MAX(sort_order)and <.4*MAX(sort_order) THEN 2
WHEN sort_order=> .4*MAX(sort_order)and <.6*MAX(sort_order) THEN 3
WHEN sort_order=> .6*MAX(sort_order)and <.8*MAX(sort_order) THEN 4
WHEN sort_order=> .8*MAX(sort_order)and =<MAX(sort_order) THEN 5
END;
I want the CASE WHEN clause to be in proportion to the total number of rows, not a predefined value, so that it can be recreatable and used with new data.
I appreciate some help. If I could create the score without the intermediate step of creating the "sort_order" column that would be great too.
`
You don't need the [sort_order] by [spent_on_alcohol_and_tobacco].
An alternative is to use RANK() function or DENSE_RANK();
I don't know the columns but you can do something like this:
SELECT [col_1], [col_2]
,RANK() OVER
(PARTITION BY [Location] ORDER BY [spent_on_alcohol_and_tobacco] DESC) AS Rank
FROM [international_tobacco_alcohol]
Hope this helps

how can I tell if the last x rows of 'state' = 1

I need help with a SQL query.
I have a table with a 'state' column. 0 means closed and 1 means opened.
Different users want to be notified after there have been x consecutive 1 events.
With an SQL query, how can I tell if the last x rows of 'state' = 1?
If, for example, you want to check if the last 5 consecutive rows have a state equals to 1, then here's you could probably do it :
SELECT IF(SUM(x.state) = 5, 1, 0) AS is_consecutive
FROM (
SELECT state
FROM table
WHERE Processor = 3
ORDER BY Status_datetime DESC
LIMIT 5
) as x
If is_consecutive = 1, then, yes, there is 5 last consecutive rows with state = 1.
Edit : As suggested in the comments, you'll have to use ORDER BY in your query, to get the last nth rows.
And for more accuracy, since you have a timestamp column, you should use Status_datetime to order the rows.
You should be able to use something like this (replace the number in the HAVING with the value of x you want to check for):
SELECT Processor, OpenCount FROM
(
SELECT TOP 10 Processor, DateTime, Sum(Status) AS OpenCount
FROM YourTable
WHERE Processor = 3
ORDER BY DateTime DESC
) HAVING OpenCount >= 10