Putting subquery into offset clause - mysql

Let's say I have table (numbers) with one column:
n
---
4
5
67
23
7
89
and I want to get median (only even list of numbers). I thought it would be easy so I wrote:
SELECT SUM(ord.n)/2
FROM (
SELECT n
FROM numbers
ORDER BY n ASC
LIMIT 2 OFFSET (SELECT COUNT(n)/2-1 FROM numbers)
) AS ord
but of course it throw me syntax error. I guess I can't insert subquery into offset but I want to know what I have to do to get expected result? I know there are different ways to write query to get median but I need to know is there any possibility to insert 'variable' into offset instead of placing some number?

I think you are looking for the query below this should work from MySQL version 5.1 and up
SELECT
AVG(filter.n)
FROM (
SELECT
*
, (#position := #position + 1) AS init_position
FROM
t
CROSS JOIN (
SELECT
#position := 0
, #max := (SELECT COUNT(t.n) FROM t)
, #median_mode := (CASE WHEN ((#max % 2) = 0) THEN 'even' ELSE 'odd' END)
) AS init_user_param
ORDER BY
t.n ASC
) AS filter
WHERE
CASE
WHEN #median_mode = 'even'
THEN filter.init_position BETWEEN (#max / 2) AND ((#max / 2) + 1)
WHEN #median_mode = 'odd'
THEN filter.init_position = ((#max + 1) / 2)
END
Result
| AVG(filter.n) |
| ------------- |
| 15 |
see demo
Result when 89 is out the list.
| AVG(filter.n) |
| ------------- |
| 7 |
see demo

You can use ROW_NUMBER() window function:
WITH cte AS (SELECT COUNT(*) counter FROM numbers)
SELECT AVG(n) median
FROM (
SELECT
row_number() over (order by n) ordinal,
n
FROM numbers
) t
WHERE (SELECT counter FROM cte) IN (2 * ordinal, 2 * (ordinal - 1))
See the demo.
Result:
| median |
| ------ |
| 15 |

The (probably) most similar solution to your algorithm is to use two queries. First get the offset. Then insert it into your query. The SQL-only way would be to use a prepared statement:
set #offset = (SELECT COUNT(n)/2-1 FROM numbers);
set #sql = "
SELECT SUM(ord.n)/2
FROM (
SELECT n
FROM numbers
ORDER BY n ASC
LIMIT 2 OFFSET ?
) AS ord
";
prepare stmt from #sql;
execute stmt using #offset;
db-fiddle

Related

Sum until certain value

i've tried some other topics for this but couldn't get answers that actually worked for me.
I have a activities table with some values ( in mysql)
| id| user_id | elevation | distance |
|---|------------|--------------------|----------|
| 1 | 1 | 220 | 5000 |
| 2 | 1 | 300 | 7000 |
| 3 | 2 | 520 | 2000 |
| 4 | 2 | 120 | 3500 |
I need to sum distance and elevation until distance sum up to certain value, per user_id.
Example, sum until 5000 is reached:
User 1 - distance 5000 - elevation 220
User 2 - distance 5500 - elevation 640
I found many solutions but none with group_by. How i do this in mysql?
Update : I used that query but now i'm with another problem. The join always use the insert order, and not a datetime field i want.
SELECT
t.*
FROM
(
SELECT
t.*,
(
#d := #d + DISTANCE
) AS running_distance
FROM
(
SELECT
t.*,
c.meta
FROM
inscricao i
INNER JOIN categorias c ON
i.categoria_id = c.id
LEFT JOIN(
select
t.data_inicio,t.usuario_id,t.aplicativo,t.data_fim,t.distance,t.tempo_decorrido,t.ritmo_cardiaco,t.velocidade_media,t.type,t.ganho_de_altimetria
from
corridas t
order by
data_inicio asc
) t ON
t.usuario_id = i.usuario_id
AND t.data_inicio >= i.inicio
AND t.data_fim <= i.fim
WHERE
i.desafio_id = 29
AND(
i.usuario_id = 5354
)
ORDER BY
data_inicio asc
-- usuario_id
) t
join (
SELECT
#u :=- 1,
#d := 0
) params
ORDER BY
data_inicio asc
) t
WHERE
(
running_distance >= meta * 1000
AND running_distance - DISTANCE < meta * 1000
)
OR(
running_distance <= meta * 1000
)
order by
data_inicio desc
So if a older activity is inserted after, the sum gets wrong. Someone knows how to handle it?
You can use variables to get the cumulative sum . . . then some simple filtering logic:
select t.*
from (select t.*,
(#d := if(#u = user_id, #d + distance,
if(#u := user_id, distance, distance)
)
) as running_distance -- pun intended ??
from (select t.*
from t
order by user_id, id
) t cross join
(select #u := -1, #d := 0) params
) t
where running_distance >= 5000 and
running_distance - distance < 5000;
Notes:
The more recent versions of MySQL are finicky about variable assignment and order by. The innermost subquery is not needed in earlier versions of MySQL.
MySQL does not guarantee the order of evaluation of expressions in a select. Hence, all variable assignments are in a single expression.
If distance can be negative, then a user may have more than one row in the result set.
This is not an aggregation query.

Count row number from a mysql table subquery

I have a table department_courses with following structure :
id department_id name
-- ------------- -----
1 11 Abcd
2 11 Bghg
3 11 Lopps
4 13 Abvgf
So from this table I need to count the position of the subquery. I mean to say , The position of the name Lopps for department_id is 3 . How to get this in mysql query?
If you only need to do this for one row, then a single query is simpler:
select count(*)
from department_courses dc
where dc.id <= (select dc2.id
from department_courses dc2
where dc2.name = 'Lopps'
);
If you want to assign a row number to all rows, then variables are probably a better method.
Try:
select row_num
from (
select t.*, #r := #r + 1 row_num
from department_courses t,
(select #r := 0) r
) x
where x.name = 'Lopps'
x.department_id = 3

MySQL return 2 rows per result

Until today i thought i know something about MySQL.
OK lets say we have one table like this:
id | some_name |some_number
-----------------------------
1 | test | 33
2 | test | 34
3 | test | 35
3 | test2 | 36
3 | test2 | 37
and i want to write query to return something like this:
test 33
test 34
test2 36
test2 37
test3 12
test3 34
.
.
.
and so on. I want to return only 2 result per same name. It is easy to use limit and return only one result per name but I'm stuck to return multiple results per same name in this case 2 but might be an n results. Work around is to make some script that will do:
select some_name, some_number from tbl_name limit 2;
and to repeat it for every distinct some_name i have in table.
Is there any elegant solution for MySQL? I would be grateful if you share that with me.
You can use a user variable to add a counter of each row of a name, then just select the rows where the counter is less than or equal to 2 (untested):-
SELECT some_name, some_number
FROM
(
SELECT some_name, some_number, #cnt=(#some_name = some_name, #cnt + 1, 1) AS cnt, #some_name:=some_name
FROM
(
SELECT some_name, some_number
FROM tbl_name
ORDER BY some_name, some_number
) sub0
CROSS JOIN
(
SELECT #some_name:='', #cnt:=0
)
sub1
) sub2
WHERE cnt <= 2
you could try this,
select some_name,some_number from yourTable t
where
(select count(*) from yourTable
where
some_number<=t.some_number and some_name=t.some_name)<3
This partially solved my problem:
set #num := 0, #name := '';
select distinct number, name
from (
select number, name,
#num := if(#name = name, #num +1, 1) as row_number,
#name := name as dummy
from karian2
) as x where x.row_number <= 2;
It fails some time to return 2 results because sub query is not returning distinct values. Example here:
http://sqlfiddle.com/#!2/952ca/23

Case when loop in MySQL returns the wrong value

To finalize my MySQL questions for today I'd like to get some suggestions on this matter. I have a table to which I need to add a column with a calculated value. I'm not looking to update the table - just combine information.
The table I'm working with is looking like this:
+----------------+----------------+--------------------------------------+
| VOTE_CANDIDATE | ORIGINAL_VOTES | SURPLUS_REDISTRIBUTION_TO_CANDIDATES |
+----------------+----------------+--------------------------------------+
| 1 | 8 | -1 |
| 2 | 1 | -1 |
| 3 | 2 | -1 |
| 4 | 4 | -1 |
| 5 | 2 | -1 |
| 6 | 3 | -1 |
+----------------+----------------+--------------------------------------+
My problem is that only row 1 should return -1 on column SURPLUS_REDISTRIBUTION_TO_CANDIDATES since it's ORIGINAL_VOTES is greater than the threshold of 7. The rest should (as the case when below states) return zero since it's below the threshold. It seems as the query locks on the first row and then reuses SURPLUS_REDISTRIBUTION_TO_CANDIDATES on all rows. This is the query I'm working with:
SELECT vote_candidate, COUNT(*) original_votes, CASE
WHEN (
(
SELECT MAX(
(
SELECT COUNT(*) votes_above_the_threshold
FROM vote_orders
)
) votes
FROM vote_orders
WHERE vote_order = 1
) >= (
SELECT FLOOR((COUNT(*) / (2 + 1)) + 1) threshold
FROM votes
)
)
THEN (
SELECT FLOOR((COUNT(*) / (2 + 1)) + 1) threshold
FROM votes
) - (
SELECT MAX(votes_above_the_threshold) votes
FROM (
SELECT vote_candidate vote_candidate, COUNT(*) votes_above_the_threshold
FROM vote_orders
WHERE vote_order = 1
GROUP BY vote_candidate
HAVING votes_above_the_threshold >= (
SELECT FLOOR((COUNT(*) / (2 + 1)) + 1) threshold
FROM votes
)
) t
WHERE votes_above_the_threshold = (
SELECT MAX(votes_above_the_threshold)
FROM vote_orders
)
)
ELSE 0
END surplus_redistribution_to_candidates
FROM vote_orders
WHERE vote_order = 1
GROUP BY vote_candidate;
How do I achieve the result I'm looking for?
I'm having difficulty deciphering what your query does, and why it's doing what it does.
It looks like there's a single "threshold"; that's calculated as an expression from a query against the votes table.
If the goal is to return a non-positive difference between the "threshold" value and the "orginal_votes" value for each candidate, I would do it like this:
SELECT c.vote_candidate
, c.original_votes
, LEAST(0,t.threshold - c.original_votes) AS surplus_redistribution_to_candidates
FROM ( SELECT o.vote_candidate
, COUNT(*) original_votes
FROM vote_orders o
WHERE o.vote_order = 1
GROUP
BY o.vote_candidate
) c
CROSS
JOIN ( SELECT FLOOR((COUNT(*) / (2 + 1)) + 1) AS threshold
FROM votes
) t
ORDER BY c.vote_candidate
The inline view aliased as c gets the "original_votes" for each candidate, and the inline view aliased as "t" gets the "threshold". The outer query compares the two values, and returns either the difference (if it is negative) or zero.
I don't think your query is locking onto the first row. Rather, I think the issue is that every execution of those SELECT MAX() subqueries returns the same values for every vote_candidate. And its just coincidence that the maximum value happens to be associated with the "first" vote_candidate in the resultset.
It looks like you just want to compare the "original_votes" for each vote_candidate to the "threshold" value. It looks as if all those subqueries are entirely unnecessary; they are just causing confusion.
If you want to avoid using the MySQL LEAST() function, and use an ANSI standard CASE expression, then you could
replace this:
LEAST(0,t.threshold - c.original_votes)
with this:
CASE
WHEN t.threshold < c.original_votes
THEN t.threshold - c.original_votes
ELSE 0
END

finding second position in mysql

I need to pull the name of the students who stood second positions from grade 1 to grade 12. each grade has separate databases with similar table structure
I have the following data:
Set 1
uid marks
1 10
2 20
3 17
4 17
5 20
6 20
Set 2
uid marks
1 10
2 20
3 17
4 17
5 20
6 17
7 20
I need a query which can say uid 3,4 are second in set 1 and 3,4,6 are second in set 2.
i need it in a single query because there are several set of databases
what could be the possible way?
I tried:
SELECT * FROM TBL WHERE marks ! = SELECT MAX(marks) from tbl
but it fetched all marks except the highest
Try this out:
SELECT uid, marks FROM (
SELECT uid, marks, #rank := #rank + (#prevMarks != marks) rank, #prevMarks := marks
FROM t, (SELECT #rank := 0, #prevMarks := 0) init
ORDER BY marks
) s
WHERE rank = 2
Fiddle here.
Another alternative without User Defined Variables:
SELECT t.uid, t.marks FROM t
JOIN (
SELECT DISTINCT marks FROM t
ORDER BY marks
LIMIT 1, 1
) s
ON t.marks = s.marks
Output:
| UID | MARKS |
|-----|-------|
| 3 | 17 |
| 4 | 17 |
Use LIMIT and ORDER BY
SELECT * FROM TBL ORDER BY marks DESC LIMIT 1,1
There you ordered all students by marks fro hi to low. And then limit return from second (0 is first record) and return only one record.
If need all students with second mark, the use subquery
SELECT * FROM TBL WHERE marks = (
SELECT marks FROM TBL ORDER BY marks DESC GROUP BY marks LIMIT 1,1
)
SELECT *
FROM table
WHERE mark = (
SELECT MAX(mark)
FROM table
WHERE mark <
(
SELECT MAX(mark)
FROM table
)
)
Try this
SELECT t.marks, t.uid, (
SELECT COUNT( marks ) +1
FROM tbl t1
WHERE t.marks < t1.marks
) AS rank
FROM tbl t
LIMIT 0 , 30
now you can use rank column with bit modification below
SELECT * from (
SELECT t.marks, t.uid, (
SELECT COUNT( marks ) +1
FROM tbl t1
WHERE t.marks < t1.marks
) AS rank
FROM tbl t
) alias where rank=n (2 here)