How to find the biggest difference in an SQL column? - mysql

I have a table with the following information:
Table: bar
minute | beer
1 | 48
2 | 24
3 | 92
4 | 17
5 | 38
6 | 64
I want to know what or where the biggest difference is in the column beer. By manually seeing it with my own eyes, it's between minute 3 and 4, but how can I do this in SQL?
I had something in mind:
Select minute, count(beer) as spike
from bar
where ???

You need nested aggregation:
select max(spike) - min(spike)
from
( -- count per minute
Select minute, count(beer) as spike
from bar
group by minute
) as dt

The simplest method would be:
select max(beer) - min(beer)
from bar;

You can use mysql MAX() and MIN() functions to get highest and lowest values.
SELECT MIN(beer) AS lowestBeer, MAX(beer) as highestBeer
FROM bar;

Since the order does not matter, you can do it with a self-join:
SELECT a.minute AS from_minute, b.minute AS to_minute, a.beer, b.beer
FROM bar a
CROSS JOIN bar b
ORDER BY a.beer-b.beer DESC
LIMIT 1
This would yield a row describing from what minute to what minute you have the biggest difference, along with the corresponding values of beer.

Related

How do I select row with the most recent reduction in a column value from same columns value in previous row?

I have a set of inventory data where the amount increases at a given rate. For example, the inventory increases by ten units every day. However, from time to time there will be an inventory reduction that could be any amount. I need a query that can find me the most recent inventory reduction and return to me the sum of that deduction.
My table holds date and amount for numerous item id's. In theory what I am trying to do is select all amounts and dates for a given item ID, and then find the difference between the most recent reduction between two days inventory. Due to the fact that multiple items are tracked, there is no guarantee that the id column will be consecutive for a set of items.
Researching to find a solution to this has been completely overwhelming. It seems like window functions might be a good route to try, but I have never used them and don't even really have a concept of where to start.
While I could easily return the amounts and do the calculation in PHP, I feel the right thing to do here is harness SQL but my experience with more complex queries is limited.
ID | ItemID | Date | Amount
1 2 2019-05-05 25
7 2 2019-05-06 26
34 2 2019-05-07 14
35 2 2019-05-08 15
67 2 2019-05-09 16
89 2 2019-05-10 5
105 2 2019-05-11 6
Given the data above, it would be nice to see a result like:
item id | date | reduction
2 2019-05-10 11
This is because the most recent inventory reduction is between id 67 and 89 and the amount of the reduction is 11 on May 10th 2019.
In MySQL 8+, you can use lag():
select t.*, (prev_amount - amount) as reduction
from (select t.*,
lag(amount) over (partition by itemid order by date) as prev_amount
from t
) t
where prev_amount > amount
order by date desc
limit 1;

Custom Sort Order for MYSQL Count Results?

I am trying to write a report on some data I collected using a google form. Each person was asked how many of an item they had in their closet. I want to present the data as a count of how many out of the total fell into each range. So, I used this mysql query to count the instances of each answer:
SELECT `Closet` , COUNT( * ) FROM `TABLE 1` GROUP BY `Closet`
And here is the resulting data:
Closet | COUNT( * )
--------+------------
0 | 8
1-5 | 124
101-200 | 7
11-20 | 181
201-300 | 3
21-50 | 171
51-100 | 48
6-10 | 156
The problem is that alphabetically, 101-200 items sorts before 6-10 items. I basically want to sort this in some way that would put the number ranges in a logical order. (1-5, 6-10, 11-20, etc).
How can I accomplish this?
You'll have to use convert & substring_index.
SELECT `Closet` , COUNT( * ) FROM `TABLE 1` GROUP BY `Closet` order by convert(substring_index(Closet,'-',1), unsigned integer)
This will sort the Closet by taking the first number of the ranges, which should essentially do the job.

MySQL - Select row with column + X > column

We have a database for patients that shows the details of their various visits to our office, such as their weight during that visit. I want to generate a report that returns the visit (a row from the table) based on the difference between the date of that visit and the patient's first visit being the largest value possible but not exceeding X number of days.
That's confusing, so let me try an example. Let's say I have the following table called patient_visits:
visit_id | created | patient_id | weight
---------+---------------------+------------+-------
1 | 2006-08-08 09:00:05 | 10 | 180
2 | 2006-08-15 09:01:03 | 10 | 178
3 | 2006-08-22 09:05:43 | 10 | 177
4 | 2006-08-29 08:54:38 | 10 | 176
5 | 2006-09-05 08:57:41 | 10 | 174
6 | 2006-09-12 09:02:15 | 10 | 173
In my query, if I were wanting to run this report for "30 days", I would want to return the row where visit_id = 5, because it's 28 days into the future, and the next row is 35 days into the future, which is too much.
I've tried a variety of things, such as joining the table to itself, or creating a subquery in the WHERE clause to try to return the max value of created WHERE it is equal to or less than created + 30 days, but I seem to be at a loss at this point. As a last resort, I can just pull all of the data into a PHP array and build some logic there, but I'd really rather not.
The bigger picture is this: The database has about 5,000 patients, each with any number of office visits. I want to build the report to tell me what the average wait loss has been for all patients combined when going from their first visit to X days out (that is, X days from each individual patient's first visit, not an arbitrary X-day period). I'm hoping that if I can get the above resolved, I'll be able to work the rest out.
You can get the date of the first and next visit using query like this (Note that this doesn't has correct syntax for date comparing and it is just an schema of the query):
select
first_visits.patient_id,
first_visits.date first_date,
max(next_visit.created) next_date
from (
select patient_id, min(created) as "date"
from patient_visits
group by patient_id
) as first_visits
inner join patient_visits next_visit
on (next_visit.patient_id = first_visits.patient_id
and next_visit.created between first_visits.created and first_visits.created + 30 days)
group by first_visits.patient_id, first_visits.date
So basically you need to find start date using grouping by patient_id and then join patient_visits and find max date that is within the 30 days window.
Then you can join the result to patient_visits to get start and end weights and calculate the loss.

MySQL custom sort by subquery impacts performance

I have 1 table from which I return search results and display them in a a specific order. This example is an exact, simplified version of my db structure: http://www.java2s.com/Code/SQL/Select-Clause/Orderbyvaluefromsubquery.htm
and here is my current code, which works but heavily impacts performance to a large extend because of the subquery used:
SELECT * FROM `table` AS p1
WHERE CONCAT(title,artist,creator,version) LIKE '%searchInput%'
ORDER BY
(SELECT
MAX(`rating`) FROM `table` AS p2 WHERE p1.setId=p2.setId
) DESC
the above code searches and sorts the result sets by the highest rating in the set and that all rows from the same set are kept together, for example:
id setId rating title,artist,etc...
1 1 5
2 1 5
3 2 7
4 1 6
5 2 1
6 3 3
would sort to:
id setId rating title,artist,etc...
3 2 7
5 2 1
4 1 6
1 1 5
2 1 5
6 3 3
Currently it takes around 8.5sec to query 1000 rows and over half a minute for a large amount of rows, is there any way to improve the performance or would it be better to fetch all the results and sort them in PHP memory?
Help is much appreciated
You can probably speed things up a bit by separating the LIKEs:
SELECT p1.* FROM `table` AS p1
WHERE (title LIKE '%searchInput%')
OR (artist LIKE '%searchInput%')
OR (creator LIKE '%searchInput%')
OR (version LIKE '%searchInput%')
ORDER BY
(SELECT MAX(`rating`) FROM `table` AS p2 WHERE p1.setId=p2.setId) DESC
You could also try to
CREATE INDEX tbl_ndx ON table(setId, rating)
to improve sorting performances.

MYSQL Can WHERE IN default to ALL if no rows returned

Have a existing table of results like this;
race_id race_num racer_id place
1 0 32 2
1 1 32 3
1 2 32 1
1 3 32 6
1 0 44 2
1 1 44 2
1 2 44 2
1 3 44 2
etc...
Have lots of PHP scripts that access this table output the results in a nice format.
Now I have a case where I need to output the results for only certain race_nums.
So I have created this table races_included.
race_view race_id race_num
Day 1 1 0
Day 1 1 1
Day 2 1 2
Day 2 1 3
And can use this query to get the right results.
SELECT racer_id, place from results WHERE race_id=1
AND race_num IN
(SELECT race_num FROM races_included WHERE race_id='1' AND race_view='Day 1')
This is great but I only need this feature for a few races and to have it work in a compatible mode for the simple case show all races. I need to add alot of rows to the races_included table. Like
race_view race_id race_num
All 1 0
All 1 1
All 1 2
All 1 3
95% of my races don't use the daily feature.
So I am looking for a way to change the query so that if for race 1 there are no records in the races_included table it defaults to all races. In addition I need it to be close the same execution speed as the query without the IN clause, because this query Or variations of it are used a lot.
One way that does work is to redefine the table as races_excluded and use NOT IN. This works great but is a pain to manage the table when races are added or deleted.
Is there a simple way to use EXISTS and IN in tandem as a subquery to get the desired results? Or some other neat trick I am missing.
To clarify I have found a working but very slow solution.
SELECT * FROM race_results WHERE race_id=1
AND FIND_IN_SET(race_num, (SELECT IF((SELECT Count(*) FROM races_excluded
WHERE rid=1>0),(SELECT GROUP_CONCAT(rnum) FROM races_excluded
WHERE rid=1 AND race_view='Day 1' GROUP BY rid),race_num)))
It basically checks if any records exists for that race_id and if not return a set equal to the current race_num and if yes returns a list of included race nums.
You can do this by using or in the subquery:
SELECT racer_id, plac
from results
WHERE race_id = 1 AND
race_num IN (SELECT race_num
FROM races_included
WHERE race_id = '1' AND (race_view = 'Day 1' or raw_view = 'ANY')
);