How do I select random rows in MySQL? - mysql

mytable
pid name field
=== ==== =====
1 A1 0
2 A2 1
3 A3 1
4 A4 0
5 A5 0
This is my table structure. Here I want to select randomly 4 rows so I use RAND() mysql function in my query
my questions is
How do I pair to rows. I mean, I wanna select pid 2 and 3 always one ofter another.
I need in bellow order. i don't want to break the pair A2 A3
A1 A2 A3 A4 or A2 A3 A4 A1 or A2 A3 A4 A5 or A4 A5 A2 A3 and etc
I used the query below but it's not working for me
SELECT * FROM mytable ORDER BY RAND() ASC limit 0,4

turbod was close with his answer, he was just ordering randomly, when it seems you wanted to order by pid, after getting the random rows you wanted in conjunction with the ones concerning A2 and A3:
(
SELECT *
FROM `mytable`
WHERE
name ='A2' OR
name ='A3'
LIMIT 2
)
UNION
(
SELECT DISTINCT *
FROM `mytable`
WHERE
name !='A2' OR
name !='A3'
ORDER BY RAND( ) LIMIT 2
)
ORDER BY `pid`

Generally, using ORDER BY RAND() is not a good idea. Please read the text by Jan Kneschke, showing why: http://jan.kneschke.de/projects/mysql/order-by-rand/

I ran a heavy test on this, passed.
(
SELECT * , 0.5 AS ordercol
FROM `mytable`
WHERE `name`IN ( "A2", "A3" )
LIMIT 2
)
UNION (
SELECT * , rand() AS ordercol
FROM `mytable`
WHERE `name` NOT IN ( "A2", "A3" )
LIMIT 2
)
ORDER BY ordercol, `name` IN ( "A2", "A3" ) , `name` ="A3"
This will do the job very well. But to make the result even more random, execute that statement with replacing that 0.5 value in 1st line with a random value chosen by your client application code like mt_rand(0, 1000000) / 1000000 in PHP . Make sure it falls between 0 and 1. But do NOT use mysql function rand() in place of that 0.5 because it will make A2 and A3 apart from each other. The trick is assigning a random value for "ordercol" in all rows but keep it same for A2 and A3
EDIT:
I believe we can replace the 0.5 value with a LEFT JOIN even instead of relying on discrete value by PHP, as we replace the first segment of the union, so the whole query becomes:
(
SELECT mt1.* , mt2.ordercol AS ordercol
FROM `mytable` AS mt1
LEFT JOIN (
SELECT RAND( ) AS ordercol
) AS mt2 ON TRUE
WHERE `name`
IN (
"A2", "A3"
)
LIMIT 2
)
UNION (
SELECT * , rand() AS ordercol
FROM `mytable`
WHERE `name` NOT IN ( "A2", "A3" )
LIMIT 2
)
ORDER BY ordercol, `name` IN ( "A2", "A3" ) , `name` ="A3"

I doubt there is a sane way to this in MySQL only.
I can think of one way of doing it, assuming you are using PHP/MySQL:
Essentially you query everything but A3, then put A3 next to A2
$res = mysql_query("SELECT name, field FROM mytable WHERE name <> 'A3' ORDER BY RAND()");
$res2 = mysql_query("SELECT name, field FROM mytable WHERE name = 'A3'");
$data = array();
while($row = mysql_fetch_array($res))
{
array_push($data, $row);
if ($row['name'] == "A2")
{
$row2 = mysql_fetch_array($res2);
array_push($data, $row2);
}
}
Now $data will contain your results in the desired order.

If you are always selecting all the rows in the table:
SELECT pid, name, field, idx
FROM (
SELECT pid, name, field,
#pos := IF(name = 'A3', #idx, #pos),
#idx := #idx + IF(name = 'A3', 2, 1), idx
FROM mytable, (SELECT #pos = -1, #idx := 0) dm
WHERE name <> 'A2'
ORDER BY RAND()
)
UNION SELECT pid, name, field, #pos + 1 idx
FROM mytable
WHERE name = 'A2'
ORDER BY idx;
If you are not always returning all the rows, thus need to check if A3 was returned to know if A2 should be included:
SELECT pid, name, field, idx
FROM (
SELECT pid, name, field,
#pos := IF(name = 'A3', #idx, #pos),
#idx := #idx + IF(name = 'A3', 2, 1), idx
FROM mytable, (SELECT #pos = -1, #idx := 0) dm
WHERE name <> 'A2'
ORDER BY RAND()
LIMIT 4
)
UNION SELECT pid, name, field, #pos + 1 idx
FROM mytable
WHERE #pos != -1 AND name = 'A2'
ORDER BY idx;

SELECT * FROM (
SELECT DISTINCT name FROM mytable
WHERE name <> 'A2' AND name <> 'A3'
ORDER BY RAND()
LIMIT 0,2
UNION
SELECT DISTINCT name FROM mytable
WHERE name = 'A2' OR name = 'A3'
ORDER BY name
)whateverQueryAlias
ORDER BY RAND()
That should do it.

Here is my solution:
SELECT *
FROM `mytable`
WHERE name ='A2'
OR name ='A3'
LIMIT 2
UNION
(SELECT DISTINCT *
FROM `mytable`
WHERE name !='A2'
OR name !='A3'
ORDER BY RAND( ) LIMIT 2) ORDER BY RAND()

SELECT *, RAND() "xrand" FROM yourtable A ORDER BY xrand LIMIT 4

SELECT * FROM `mytable` order by rand(), name asc limit 4.
i think this will satisfy your need.

Related

Mysql problem to see if two selects are the same

SELECT table_grouping_code, gui_field_code, gui_interface_id, dictionary_code, property_name, position INTO #test
FROM table_grouping_layout
WHERE company_code = "TEST";
SELECT table_grouping_code, gui_field_code, gui_interface_id, dictionary_code, property_name, position INTO #doximtrx
FROM table_grouping_layout
WHERE company_code = "DOXIMTRX";
select #test = #doximtrx;
I tried this, that seems very logica to me, but mysql says: ERROR CODE 1222: the used select statement have a different number of columns. How can it be possible?? I copied the query, only changing the code.
Example of data in the table
You are trying to select many columns into a single variable. The correct syntax for that part of your query is:
SELECT
GROUP_CONCAT(table_grouping_code ORDER BY table_grouping_code)
, GROUP_CONCAT(gui_field_code ORDER BY gui_field_code)
, GROUP_CONCAT(gui_interface_id ORDER BY gui_interface_id)
, GROUP_CONCAT(dictionary_code ORDER BY dictionary_code)
, GROUP_CONCAT(property_name ORDER BY property_name)
, GROUP_CONCAT(position ORDER BY position)
FROM
table_grouping_layout
WHERE
company_code = 'TEST'
INTO
#t1, #t2, #t3, #t4, #t5, #t6;
SELECT
GROUP_CONCAT(table_grouping_code ORDER BY table_grouping_code)
, GROUP_CONCAT(gui_field_code ORDER BY gui_field_code)
, GROUP_CONCAT(gui_interface_id ORDER BY gui_interface_id)
, GROUP_CONCAT(dictionary_code ORDER BY dictionary_code)
, GROUP_CONCAT(property_name ORDER BY property_name)
, GROUP_CONCAT(position ORDER BY position)
FROM
table_grouping_layout
WHERE
company_code = 'DOXIMTRX'
INTO
#d1, #d2, #d3, #d4, #d5, #d6;
How to compare results is a different story:
SELECT ifNull(
#t1 = #d1
and #t2 = #d2
and #t3 = #d3
and #t4 = #d4
and #t5 = #d5
and #t6 = #d6
, false
) as result;
TEST DATA:
select * from table_grouping_layout;
table_grouping_code
gui_field_code
gui_interface_id
dictionary_code
property_name
position
company_code
1
2
3
4
5
6
DOXIMTRX
1
3
3
4
5
6
TEST
1
1
3
4
5
6
DOXIMTRX
1
2
3
4
5
6
TEST
QUERY RESULT:
result
0
Variables are meant to hold one value. Neither one row nor one column nor one table.
Comparing two data sets can be achieved with a combination of UNIONand EXCEPT or with a full outer join. Unfortunately, MySQL does neither support EXCEPT nor FULL OUTER JOIN.
Here is a workaround:
with t as (select * from table_grouping_layout where company_code = 'TEST')
, d as (select * from table_grouping_layout where company_code = 'DOXIMTRX')
select 'MISMATCH' as status, t.position
from table_grouping_layout t
join table_grouping_layout d
on t.position = d.position
and not
(
t.table_grouping_code <=> t.table_grouping_code and
t.gui_field_code <=> t.gui_field_code and
t.gui_interface_id <=> t.gui_interface_id and
t.dictionary_code <=> t.dictionary_code and
t.property_name <=> t.property_name
)
union all
select 'TEST MISSING' as status, position
from d
where position not in (select position from t)
union all
select 'DOXIMTRX MISSING' as status, position
from t
where position not in (select position from d)
order by position;
Another approach using aggregation:
select
position,
case
when sum(company_code = 'TEST') = 0 then 'TEST MISSING'
when sum(company_code = 'DOXIMTRX') = 0 then 'DOXIMTRX MISSING'
when not
(
max(table_grouping_code) <=> min(table_grouping_code) and count(table_grouping_code) in (0,2)
max(gui_field_code) <=> min(gui_field_code) and count(gui_field_code) in (0,2)
max(gui_interface_id) <=> min(gui_interface_id) and count(gui_interface_id) in (0,2)
max(dictionary_code) <=> min(dictionary_code) and count(dictionary_code) in (0,2)
max(property_name) <=> min(property_name) and count(property_name) in (0,2)
) then 'MISMATCH'
else 'MATCH' end as status
from table_grouping_layout
where company_code in ('TEST', 'DOXIMTRX')
group by position
order by position;

Select a row along with its next and previous rows

Situation:
At the moment I have 3 queries:
First - Gets data by id which has ordering position;
Second - Gets data by position-1;
Third - Gets data by position+1.
I want to have only 1 Query which could take "needed one" by id + previous and next ones if they exists.
Queries:
First
set #position = 0;
SELECT
`position` FROM
(
SELECT `id`, #position:=#position+1 as `position` FROM {#table}
"other_part_of_query"
ORDER BY `modified_time` DESC
) t
WHERE
t.id = '".id."'
LIMIT 1
Second and third
set #position = 0;
SELECT
`id` FROM
(
SELECT `id`, #position:=#position+1 as `position` FROM {#table}
"other_part_of_query"
ORDER BY `modified_time` DESC
) t
WHERE
t.`position` = '".position."'
LIMIT 1
This is complicated, because you are selecting a row by id, but choosing the adjacent ones by another field, modified_time.
The idea is to use variables to enumerate the rows. And, use another row to calculate the value for the id that you care about. Do this in a subquery, and then select the rows that you want:
SELECT t.*
FROM (SELECT `id`,
#rn := if(#rnid := if(t.id = '".id."', #rn + 1, #rnid),
#rn + 1, #rn + 1
) as rn
FROM {#table} t
"other_part_of_query" cross join
(select #rn := 0, #rnid := 0) vars
ORDER BY `modified_time` DESC
) t
WHERE rn in (#rnid - 1, #rnid, #rn)
You can use extra conditions in your where clause to solve this. Consider the following three conditions:
One row must have the modified_time you want.
One row must have the maximum modified_time that is still less than the one you want. (The previous position)
One row must have the minimum modified_time that is still greater than the one you want. (The next position)
Try this:
SELECT *
FROM myTable
WHERE modified_time = #myParam
OR modified_time = (SELECT MAX(modified_time ) FROM myTable WHERE modified_time < #myParam)
OR modified_time = (SELECT MIN(modified_time ) FROM myTable WHERE modified_time > #myParam);
Selecting next and previous rows of a specific row
SET #j = 0;
SET #i = 0;
SELECT *
FROM (
SELECT id, col1, col2, ..., #j:=#j+1 AS pos
FROM `table`
WHERE col1=... ORDER BY col1 DESC, col2 ASC
) AS zz
WHERE (
SELECT position
FROM (
SELECT id AS id2, #i:=#i+1 AS position
FROM `table`
WHERE col1=... ORDER BY col1 DESC, col2 ASC
) AS zz
WHERE id2=$currId
)
IN (pos-1, pos, pos+1)

select from sql database with avg function

I have a database as shown below
id , name , var1
and I want to write a sql query like this:
select name
from table
where last var1 > avg of var1 s of each name
Notice that i want to select between names that last var1 is greater than avrage of var1s of each name
i write this code :
select name
from table
where var1>(select avg(var1) from table ) limit 0 , 1
but this code gets avrage from all var1s and I dont know whether this works or not!
for example we have these data:
1 , John , 32
2 , John , 21
3 , Mike , 22
4 , John , 11
5 , Mike , 5
6 , Mike , 45
=> for John , we have: 32+21+11 /3 =21.3 , but the last data is 11 , so John shouldnt be chosen
=> for Mike , avrage of var1 is 24 , and last row for Mike is 45 that is greater than the avrage , so Mike should be chosen.
Can anyone help me?
SELECT y.* FROM
your_table y
JOIN
(
SELECT name, AVG(var1) AS av, MAX(id) AS mx
FROM your_table
GROUP BY name
) tab
ON y.name = tab.name
AND y.id = tab.mx
AND y.var1 > tab.av
Here is the code at SQL Fiddle
[EDIT]:
Based on your latest requirement, what you want to accomplish is LIMIT N within group, which can be done with the following query:
SET #N := 2;
SELECT * FROM
(
SELECT (#rownumber:= #rownumber + 1) AS rn, yt.*
FROM your_table yt,(SELECT #rownumber:= 0) nums
ORDER BY name, id
) k
JOIN
(
SELECT t.name, MAX(rn) AS MaxRN FROM
(
SELECT (#rownumber:= #rownumber + 1) AS rn, yt.*
FROM your_table yt,(SELECT #rownumber:= 0) nums
ORDER BY name, id
) t
GROUP BY name
) l
ON k.rn <= l.MaxRN AND k.rn > l.MaxRN - #N
Here #N variable holds number of records we want to select within each group
Check the code at SQL Fiddle
Now in an outer query we can take the avg of the resultset returned by above.
Let me know if you could accomplish what you wanted with my inputs.

How many different ways are there to get the second row in a SQL search?

Let's say I was looking for the second most highest record.
Sample Table:
CREATE TABLE `my_table` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`value` int(10),
PRIMARY KEY (`id`)
);
INSERT INTO `my_table` (`id`, `name`, `value`) VALUES (NULL, 'foo', '200'), (NULL, 'bar', '100'), (NULL, 'baz', '0'), (NULL, 'quux', '300');
The second highest value is foo. How many ways can you get this result?
The obvious example is:
SELECT name FROM my_table ORDER BY value DESC LIMIT 1 OFFSET 1;
Can you think of other examples?
I was trying this one, but LIMIT & IN/ALL/ANY/SOME subquery is not supported.
SELECT name FROM my_table WHERE value IN (
SELECT MIN(value) FROM my_table ORDER BY value DESC LIMIT 1
) LIMIT 1;
Eduardo's solution in standard SQL
select *
from (
select id,
name,
value,
row_number() over (order by value) as rn
from my_table t
) t
where rn = 1 -- can pick any row using this
This works on any modern DBMS except MySQL. This solution is usually faster than solutions using sub-selects. It also can easily return the 2nd, 3rd, ... row (again this is achievable with Eduardo's solution as well).
It can also be adjusted to count by groups (adding a partition by) so the "greatest-n-per-group" problem can be solved with the same pattern.
Here is a SQLFiddle to play around with: http://sqlfiddle.com/#!12/286d0/1
This only works for exactly the second highest:
SELECT * FROM my_table two
WHERE EXISTS (
SELECT * FROM my_table one
WHERE one.value > two.value
AND NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
)
LIMIT 1
;
This one emulates a window function rank() for platforms that don't have them. It can also be adapted for ranks <> 2 by altering one constant:
SELECT one.*
-- , 1+COALESCE(agg.rnk,0) AS rnk
FROM my_table one
LEFT JOIN (
SELECT one.id , COUNT(*) AS rnk
FROM my_table one
JOIN my_table cnt ON cnt.value > one.value
GROUP BY one.id
) agg ON agg.id = one.id
WHERE agg.rnk=1 -- the aggregate starts counting at zero
;
Both solutions need functional self-joins (I don't know if mysql allows them, IIRC it only disallows them if the table is the target for updates or deletes)
The below one does not need window functions, but uses a recursive query to enumerate the rankings:
WITH RECURSIVE agg AS (
SELECT one.id
, one.value
, 1 AS rnk
FROM my_table one
WHERE NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
UNION ALL
SELECT two.id
, two.value
, agg.rnk+1 AS rnk
FROM my_table two
JOIN agg ON two.value < agg.value
WHERE NOT EXISTS (
SELECT * FROM my_table nx
WHERE nx.value > two.value
AND nx.value < agg.value
)
)
SELECT * FROM agg
WHERE rnk = 2
;
(the recursive query will not work in mysql, obviously)
You can use inline initialization like this:
select * from (
select id,
name,
value,
#curRank := #curRank + 1 AS rank
from my_table t, (SELECT #curRank := 0) r
order by value desc
) tb
where tb.rank = 2
SELECT name
FROM my_table
WHERE value < (SELECT max(value) FROM my_table)
ORDER BY value DESC
LIMIT 1
SELECT name
FROM my_table
WHERE value = (
SELECT min(r.value)
FROM (
SELECT name, value
FROM my_table
ORDER BY value DESC
LIMIT 2
) r
)
LIMIT 1

How to get the smallest Integers not yet in a database column

I have a table in a MySQL DB with an UNIQUE INT(10) column. The table is pretty populated and the row contains non-consecutive entries of Integer numbers in that column. I would like to do a query, which gets me the smallest number (or the n smallest numbers) that is not in any row.
Example: The table contains rows with values (1, 2, 3, 5, 7, 8, 10, 12, 15) for the column. The sql statement should return i.e. the five lowest non-contained values, which are 4, 6, 9, 11, 13 in this case.
Is this possible with MySQL?
You can use a "numbers" table (it's handy for various operations):
CREATE TABLE num
( i UNSIGNED INT NOT NULL
, PRIMARY KEY (i)
) ;
INSERT INTO num (i)
VALUES
(1), (2), ..., (1000000) ;
Then:
SELECT
num.i
FROM
num
LEFT JOIN
tableX AS t
ON num.i = t.columnX
WHERE
t.columnX IS NULL
ORDER BY
num.i
LIMIT 5
or:
SELECT
num.i
FROM
num
WHERE
NOT EXISTS
( SELECT *
FROM tableX AS t
WHERE num.i = t.columnX
)
ORDER BY
num.i
LIMIT 5
Another approach, without using an auxilary table, would be to use MySQL variables. You can test it in SQL-Fiddle, test-2. The output is not the same as the previous (just to show that it can be done):
SELECT start_id, end_id
FROM
( SELECT
IF( t.columnX <> #id, #id, NULL) AS start_id
, IF( t.columnX <> #id, t.columnX-1, NULL) AS end_id
, #rows := #rows + (t.columnX - #id) AS r
, #id := t.columnX + 1 AS running_id
FROM
tableX AS t
CROSS JOIN
( SELECT #rows := 0
, #id := 1
) AS dummy
WHERE
#rows < 5
ORDER BY
t.columnX
) AS tmp
WHERE
start_id IS NOT NULL
This will work, but I think it is pretty inefficient. You won't need an extra table though (a table that would be (2^31-1)*4/1024^3 = 8GB for all positive numbers in INT). Also I advise you look at why you need this, because it might not be neccesary.
Also it will return the start and end of a range, but not all numbers in that range. (e.g. if you have numbers 1 and 5 it will return {0,2,4,6})
SELECT (t.num-1) AS bound FROM t
WHERE t.num-1 NOT IN (SELECT t.num FROM t)
UNION
SELECT (t.num+1) AS bound FROM t
WHERE t.num+1 NOT IN (SELECT t.num FROM t)
As I said this will be pretty inefficient, JOINs might be faster but you would need benchmark it.
SELECT (t.num-1) AS bound FROM t
LEFT JOIN t AS u ON t.num-1 = u.num
WHERE u.num IS NULL
UNION
SELECT (t.num+1) AS bound FROM t
LEFT JOIN t AS u ON t.num+1 = u.num
WHERE u.num IS NULL