Why does RAND sometimes produce multiple results in a MySQL call? - mysql

I have table agent which includes 13 agent IDs and agent names.
agent_id|agent_name|
--------|----------|
1|Jack |
2|Jill |
3|Jo |
...
When I run this query...
select agent_name from agent where agent_id = (FLOOR( 1 + RAND( ) * 13 ))
...MySQL sometimes returns 0 names and sometimes returns many names. Since (FLOOR( 1 + RAND( ) * 13 )) on its own always seems to return a single, non-zero integer, I would expect to get a single name back, but this is not the case. On testing the above query, the following number of names are returned on each execution;
Execution | Total Names Returned
1 | 3
2 | 2
3 | 1
4 | 0
5 | 1
6 | 1
7 | 0
8 | 0
9 | 1
10 | 4
Clearly, when one runs...
select agent_name from agent where agent_id = 3
...the same, single, name is returned each time.
I see that the docs explain that
RAND() in a WHERE clause is evaluated for every row (when selecting from one table) or combination of rows (when selecting from a multiple-table join). Thus, for optimizer purposes, RAND() is not a constant value and cannot be used for index optimizations
I am not sure why this would mean that a single call would return many rows.
If I add LIMIT 1
select * from agent a where agent_id = (FLOOR( 1 + RAND() * 13 )) limit 1
...then the query sometimes returns NULL.
Questions
Why does RAND return different numbers of records?
What is the proper way to return a single random agent?

As the documentation says, it is evaluated for each row. It means, the following:
MySQL gets all rows
For each row, executes RAND() and if RAND() == id, return the row
For example
Get all rows
ID = 1, RAND() = 3, no return
ID = 2, RAND() = 2, return
ID = 3, RAND() = 4, no return
ID = 4, RAND() = 4, return
In this case, you have two results.
If you want to get random agent, maybe better approach would be
SELECT * FROM `agent` ORDER BY RAND() LIMIT 1

I assume that your first query is sometimes returning multiple records because RAND() is being evaluted for each record in the query. If you just want a single random agent, then use:
SELECT agent_name
FROM agent
ORDER BY RAND()
LIMIT 1;
To further explain your current observations, the following expression is being evaluated once per each agent record:
(FLOOR( 1 + RAND( ) * 13 ))
Imagine that for each record, the above happens, by chance, to equal the agent_id. If so, then your first query would return all 13 records. By the way, the documentation link you cited basically says this.

Related

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 match area code only when given the full number

I have a database that lists a few area codes, area code + office codes and some whole numbers and a action. I want it to return a result by the digits given but I am not sure how to accomplish it. I have some MySQL knowledge but its not very deep.
Here is a example:
match | action
_____________________
234 | goto 1
333743 | goto 2
8005551212| goto 3
234843 | goto 4
I need to query the database with a full 10 digit number -
query 8005551212 gives "goto 3"
query 2345551212 gives "goto 1"
query 3337431212 gives "goto 2"
query 2348431212 gives "goto 4"
This would be similar to the LIKE selection, but I need to match against the database value instead of the query value. Matching the full number is easy,
SELECT * FROM database WHERE `match` = 8005551212;
First the number to query will always be 10 digits, so I am not sure how to format the SELECT statement to differentiate the match of 234XXXXXXX and 234843XXXX, as I can only have one match return. Basically if it does not match the 10 digits, then it checks 6 digits, then it will check the 3 digits.
I hope this makes sense, I do not have any other way to format the number and it has to be accomplished with just a single SQL query and return over a ODCB connection in Asterisk.
Try this
SELECT match, action FROM mytable WHERE '8005551212' like concat(match,'%')
The issue is that you will get two rows in one case .. given your data..
SELECT action
FROM mytable
WHERE '8005551212' like concat(match,'%')
order by length(match) desc limit 1
That should get the row that had the most digits matched..
try this:
SELECT * FROM (
SELECT 3 AS score,r.* FROM mytable r WHERE match LIKE CONCAT(SUBSTRING('1234567890',1,3),'%')
UNION ALL
SELECT 6 AS score,r.* FROM mytable r WHERE match LIKE CONCAT(SUBSTRING('1234567890',1,6),'%')
UNION ALL
SELECT 10 AS score,r.* FROM mytable r WHERE match LIKE CONCAT(SUBSTRING('1234567890',1,10),'%')
) AS tmp
ORDER BY score DESC
LIMIT 1;
What ended up working -
SELECT `function`,`destination`
FROM reroute
WHERE `group` = '${ARG2}'
AND `name` = 0
AND '${ARG1}' LIKE concat(`match`,'%')
ORDER BY length(`match`) DESC LIMIT 1

Flag large number random records in a MySQL database - one time only

In my database table I've got 5 columns, id, l_num, s_num, win, claimed. There are 415,000 records. How would I randomly select 20,000 of those records and update the record with a 1 in the win field?
I understand that I could use SELECT * FROM tableName ORDER BY RAND() LIMIT 20000 to select those random records, but I've also read that this type of select would be inefficient for a large number.
I only need to perform this once on the database. Once all the records are flagged, we're simply checking against that flag one at a time.
You can use the following query:
UPDATE tableName SET win = 1 ORDER BY RAND() LIMIT 20000
Each record has a probability of 20000/415000 = 0,04819... of being chosen.
So you could select all records where RAND() is less than 0.049. This will select about 20335 records, and from these you can run a LIMIT 20000.
SELECT * FROM tableName WHERE RAND() < (20000/415000)*1.05 LIMIT 20000;
Above, I added a 5% margin to be "reasonably" sure of selecting enough records.
Same can be done for an UPDATE query, of course, i.e.
UPDATE tableName SET ... WHERE RAND() < (20000/415000)*1.05 LIMIT 20000;
Experimentally the probability of this method selecting less than 20000 records is 7% using a margin of 1.01, 0.5% with a margin of 1.02, and below 0.05% with a margin of 1.03.
Another drawback of this method is that selection isn't really random, in that the last records have a lower probability of being chosen (since the quota of 20,000 records is extremely likely to be satisfied before the full table has been examined).
To add a "winning code", you can use the same method (albeit even more approximate at this point)
... SET win = CASE WHEN RAND() BETWEEN 0 AND .333 THEN 'potting soil' WHEN RAND() BETWEEN .333 AND .666 THEN 'gift certificate' ELSE 'something else' END WHERE RAND() < ...
Here you need to carefully adjust the values, since RAND() is calculated once at each invocation. So if you wanted to equally divide between 3 possibilities, this would not work:
CASE WHEN RAND() BETWEEN 0 AND 0.333 THEN 1
WHEN RAND() BETWEEN .333 AND .666 THEN 2
ELSE 3
END
because the first case would be chosen 33% of the times (which is correct), then the second case would be chosen 33% of the remaining times, which is 2/9ths, and the third would cover the rest (4/9ths). To equally divide the range you would need 33%, 50% and 50%:
mysql> SELECT r, count(*)/1048576.0 FROM (select case when rand() between 0 and 0.333 then 1 when rand() between 0.333 and 0.666 then 2 else 3 end AS r from numbers) AS a GROUP BY r;
+---+--------------------+
| r | count(*)/1048576.0 |
+---+--------------------+
| 1 | 0.3330 |
| 2 | 0.2218 |
| 3 | 0.4452 |
+---+--------------------+
3 rows in set (0.03 sec)
mysql> SELECT r, count(*)/1048576.0 FROM (select case when rand() between 0 and 0.333 then 1 when rand() between 0 and 0.5 then 2 else 3 end AS r from numbers) AS a GROUP BY r;
+---+--------------------+
| r | count(*)/1048576.0 |
+---+--------------------+
| 1 | 0.3324 |
| 2 | 0.3337 |
| 3 | 0.3339 |
+---+--------------------+
3 rows in set (0.03 sec)
Note that the division is only probabilistic - you don't actually get exactly 1/3, 1/3 and 1/3.
Another possibility would be to assign a random integer to every record, with sufficient graininess (e.g. a random number from 0 to 415,000,000), index on it, and (a) find by bisection a value K so that the number of people with index below K are exactly 20,000, (b) assign prizes based on modulo (i.e., everyone with ndx % 100 between 0 and 33 gets the gift certificate. If you need to frequently run this kind of operations, assigning a "destiny" number to each participant might be worthwhile.
If the number of extractions compared to the total is large (as it is in this case), definitely go with Salman's more precise solution.

MySql Select Id <= Maximum value in the table

When using the <= in a MySql statement, is it possible to make MySql select the maximum value in a table without supplying a value to <= in the sql statement?
Eg:
id
----
1
2
3
4
5
6
Eg:
// start from the last record when no value is supplied
select id from table where id <= * ORDER BY id DESC LIMIT 5
Result
6
5
4
3
2
// start from the 5th record when a value is supplied
select id from table where id <= 5 ORDER BY id DESC LIMIT 5
Result
5
4
3
2
1
My problem is, this statement is in a php function, so I cannot change it dynamically. Is it somehow possible to do what I'm trying, or perhaps another way to get around this?
You can use NULL instead of using *
SET #var_search_value = NULL -- OR 5
SELECT id
FROM table
WHERE id <= #var_search_value OR #var_search_value IS NULL
ORDER BY id DESC LIMIT 5
If you want to get every record which is less than or equal to the maximum value in a particular column, then logically you want to get every record:
select id from table ORDER BY id DESC LIMIT 5
No WHERE clause is required.

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