How to search a MySQL table for the most accurate matching - mysql

I have a table that contains a row of phone number prefixes and a row of price for each prefix.
Sample table:
prefix | price
---------------------
| 21366 | 0.15 |
| 2010 | 0.1 |
| 213 | 0.13 |
---------------------
In the website, the user will be asked to insert a phone number (e.g. 21366123456), where i have to search the database table for the most accurate matching.
In this case, even though there is a prefix value of 213 in the table, i will be needing the result of 21366.
How can this be done using a simple MySQL query without looping recursively on the user input value?

Try this:
SELECT prefix, price
FROM mytable
ORDER BY LENGTH('21366123456') - LENGTH(REPLACE('21366123456', prefix, '')) DESC LIMIT 1
Demo here

Related

MySQL Left Join / explain why original order of first table not been kept? [duplicate]

i have a mysql db with a table 'difficulties' with a few records. If i do "select * from difficulties" i get them back in the order they were added, ordered by primary key id:
mysql> select * from difficulties;
+----+-------+-----------+--------+----------+-----------+
| id | value | name | letter | low_band | high_band |
+----+-------+-----------+--------+----------+-----------+
| 1 | 1 | very_easy | VE | 1 | 1 |
| 2 | 2 | easy | E | 2 | 5 |
| 3 | 3 | medium | M | 6 | 10 |
| 4 | 4 | hard | H | 11 | 12 |
| 5 | 0 | na | NA | 0 | 0 |
+----+-------+-----------+--------+----------+-----------+
However, if i do "select name from difficulties" i get them back in a different order:
mysql> select name from difficulties;
+-----------+
| name |
+-----------+
| easy |
| hard |
| medium |
| na |
| very_easy |
+-----------+
My question is: what determines this order? Is there any logic to it? Is it something like "the order the files representing the records happen to be in within the filesystem" or something else that is to all intents and purposes random?
thanks, max
This is correct and by design: if you don't ask for sorting, the server doesn't bother with sorting (sorting can be an expensive operation), and it will return the rows in whatever order it sees fit. Without a requested order, the way the records are ordered can even differ from one query to the next (although that's not too likely).
The order is definitely not random - it's just whatever way the rows come out of the query, and as you see, even minor modifications can change this un-order significantly. This "undefined" ordering is implementation dependent, unpredictable and should not be relied upon.
If you want the elements to be ordered, use the ORDER BY clause (that's its purpose) - e.g.
SELECT name FROM difficulties ORDER BY name ASC;
That will always return the result sorted by name, in ascending order. Or, if you want them ordered by the primary key, last on top, use:
SELECT name FROM difficulties ORDER BY id DESC;
You can even sort by function - if you actually want random order, do this (caveat: horrible performance with largish tables):
SELECT name FROM difficulties ORDER BY RAND();
For more details see this tutorial and the documentation.
As Piskvor said, MySQL will order the query however it finds most convenient. To address the "why" part of your question, the different result orders are probably a side effect of different execution plans. If you have an index on difficulties, the second query would make use of it but the first would not.
Without the ORDER BY clause, the results are returned in random order. However, it seems logical to me that the easiest (and the fastest) way for db engine to return data as it's stored. So it's why the fist resultset is ordered by PK (no fragmentation, logical order is the same as physical). In the second case I would assume that you have an index on field name, and for the query select name from difficulties this index is covering, so db engine scans this index, and it's why you see results ordered by name. Anyway, you shouldn't rely on such "default" ordering.
select name from difficulties should return the values in alphabetical order as it is a text field.
And select * from difficulties will return in numeric order i believe. dont hold me to that lol
best thing to do is use ORDER BY if you care about what order things are

Efficiently join (and update) a table by value into a min/max value range

I have two tables that look like this:
IpNumbers
+----------+----------+-----------------------+
|____min___|____max___|__correspondingCountry_|
| | | |
| 0 | 10 | Australia |
| 11 | 20 | US |
| 21 | 30 | Taiwan |
|____31____|___40_____|_________Canada________|
Users
+----------+----------+----------------+
|__userId__|__ipNumber__|____country___|
| 1 | 6 | |
| 2 | 13 | |
| 3 | 7 | |
|____4_____|_____21_____|______________|
There are IpNumbers table which has a number derrived from an IP address - and this corresponds to a certain country the IP address belongs to. If the Ip Number is between 11 and 20 for example then the user with that IP Address is from the United States (US).
My problem: I have about 60,000 users in the Users table - each users has an IP number associated with them. I also have the IpNumbers table with about 4,000,000 records of Ip Number vs Country in it.
What is the most efficient SQL query I could construct to assign a Country to each user in the Users table? And how long could I expect the query to be completed in?
Note: I have indexed min and max columns in the ipNumbers table and I have also indexed userId column in the Users table. The userId field is also unique.
Edit: The ipNumber in the users table is also the derived integer value
This is a rather common problem. I think the best approach starts with an index on IpNumbers(min, max, corresponding_country). (The last column is not strictly necessary).
Then, you can use the following query:
select u.*,
(select ipn.corresponding_country
from IpNumbers ipn
where ipn.min <= u.ipNumber
order by ipn.min desc
limit 1
) as corresponding_country
from users u;
This should use the index for the where and the order by. However, it does not validate the end condition. If you need, you can use the same logic to get max out and then do a comparison to see if the ip address falls in the range.
Try as below:
SELECT u.USERID, u.IPNUMBER, i.CorrespondingCOUNTRY FROM USERS u, IPNUMBERS i
WHERE u.IPNUMBER BETWEEN i.MIN AND i.MAX
/

mysql returns wrong results with random duplicate values

i need to return the best 5 scores in each category from a table.so far i have tried query below following an example from this site: selecting top n records per group
query:
select
subject_name,substring_index(substring_index
(group_concat(exams_scores.admission_no order by exams_scores.score desc),',',value),',',-1) as names,
substring_index(substring_index(group_concat(score order by score desc),',',value),',',-1)
as orderedscore
from exams_scores,students,subjects,tinyint_asc
where tinyint_asc.value >=1 and tinyint_asc.value <=5 and exam_id=2
and exams_scores.admission_no=students.admission_no and students.form_id=1 and
exams_scores.subject_code=subjects.subject_code group by exams_scores.subject_code,value;
i get the top n as i need but my problem is that its returning duplicates at random which i dont know where they are coming from
As you can see English and Math have duplicates which should not be there
+------------------+-------+--------------+
| subject_name | names | orderedscore |
+------------------+-------+--------------+
| English | 1500 | 100 |
| English | 1500 | 100 |
| English | 2491 | 100 |
| English | 1501 | 99 |
| English | 1111 | 99 |
|Mathematics | 1004 | 100 |
| Mathematics | 1004 | 100 |
| Mathematics | 2722 | 99 |
| Mathematics | 2734 | 99 |
| Mathematics | 2712 | 99 |
+-----------------------------------------+
I have checked table and no duplicates exist
to confirm there are no duplicates in the table:
select * from exams_scores
having(exam_id=2) and (subject_code=121) and (admission_no=1004);
result :
+------+--------------+---------+--------------+-------+
| id | admission_no | exam_id | subject_code | score |
+------+--------------+---------+--------------+-------+
| 4919 | 1004 | 2 | 121 | 100 |
+------+--------------+---------+--------------+-------+
1 row in set (0.00 sec)
same result for English.
If i run the query like 5 times i sometimes end up with another field having duplicate values.
can anyone tell me why my query is behaving this way..i tried adding distinct inside
group_concat(ditinct(exams_scores.admission_no))
but that didnt work ??
You're grouping by exams_scores.subject_code, value. If you add them to your selected columns (...as orderedscore, exams_scores.subject_code, value from...), you should see that all rows are distinct with respect to these two columns you grouped by. Which is the correct semantics of GROUP BY.
Edit, to clarify:
First, the SQL server removes some rows according to your WHERE clause.
Afterwards, it groups the remaining rows according to your GROUP BY clause.
Finally, it selects the colums you specified, either by directly returning a column's value or performing a GROUP_CONCAT on some of the columns and returning their accumulated value.
If you select columns not included in the GROUP BY clause, the returned results for these columns are arbitrary, since the SQL server reduces all rows equal with respect to the columns specified in the GROUP BY clause to one single row - as for the remaining columns, the results are pretty much undefined (hence the "randomness" you're experiencing), because - what should the server choose as a value for this column? It can only pick one randomly from all the reduced rows.
In fact, some SQL servers won't perform such a query and return an SQL error, since the result for those columns would be undefined, which is something you don't want to have in general. With these servers (I believe MSSQL is one of them), you more or less can only have columns in you SELECT clause which are part of your GROUP BY clause.
Edit 2: Which, finally, means that you have to refine your GROUP BY clause to obtain the grouping that you want.

Mysql where in multiple rows

I have this problem.
One table with.
id | routename | usersid |
1 | route 1 | 1,2,3,5 2 |
2 | route 2 | 5,20,15 3 |
4 | route 4 | 10,15,7,5 |
I need, search ej. userid 5 in colum usersid... but I have no idea how to do, because there are multiple rows.
If you cannot change the schema then you will have to use the REGEXP operator to match on a regular expression. For example
where column REGEXP '(^|,)5(,|$)'
This matches the number 5 either at the beginning or end of the field or surrounded by commas (or any combination thereof), to avoid matching other numbers like 15, 55 or 1234567890.
If the table is large this will perform very slowly as it will require a full table scan
You might be looking for FIND_IN_SET().
select * from Table1
WHERE FIND_IN_SET(5,usersid)
SAMPLE FIDDLE

Combine count rows in MySQL

I've got a table in MySQL that looks roughly like:
value | count
-------------
Fred | 7
FRED | 1
Roger | 3
roger | 1
That is, it was created with string ops outside of MySQL, so the values are case- and trailing-whitespace-sensitive.
I want it to look like:
value | count
-------------
Fred | 8
Roger | 4
That is, managed by MySQL, with value a primary key. It's not important which one (of "Fred" or "FRED") is kept.
I know how to do this in code. I also know how to generate a list of problem values (with a self-join). But I'd like to come up with a SQL update/delete to migrate my table, and I can't think of anything.
If I knew that no pair of records had variants of one value, with the same count (like ("Fred",4) and ("FRED",4)), then I think I can do it with a self-join to copy the counts, and then an update to remove the zeros. But I have no such guarantee.
Is there something simple I'm missing, or is this one of those cases where you just write a short function outside of the database?
Thanks!
As an example of how to obtain the results you are looking for with a SQL query alone:
SELECT UPPER(value) AS name, SUM(count) AS qty FROM table GROUP BY name;
If you make a new table to hold the correct values, you INSERT the above query to populate the new table as so:
INSERT INTO newtable (SELECT UPPER(value) AS name, SUM(count) AS qty FROM table GROUP BY name);
Strangely, MySQL seems to do this for you. I just tested this in MySQL 5.1.47:
create table c (value varchar(10), count int);
insert into c values ('Fred',7), ('FRED',1), ('Roger',3), ('roger',1);
select * from c;
+-------+-------+
| value | count |
+-------+-------+
| Fred | 7 |
| FRED | 1 |
| Roger | 3 |
| roger | 1 |
+-------+-------+
select value, sum(count) from c group by value;
+-------+------------+
| value | sum(count) |
+-------+------------+
| Fred | 8 |
| Roger | 4 |
+-------+------------+
I was surprised to see MySQL transform the strings like that, and I'm not sure I can explain why it did that. I was expecting to have to get four distinct rows, and to have to use some string functions to map the values to a canonical form.