0

I have a form which sends selected values from a checkbox list to a php script this way:

$selected_languages=$_POST["selected_languages"]; // --> "en-GB,it,fr"

My 'accounts' mysql table has a field called 'spoken_languages' which contains the list of the code(s) of the languages spoken by each user. The field is a VARCHAR field and keeps the code(s) this way:

en-US,ru,fr

I'd like to select all the users who speak any of the selected languages in $selected_languages but I didn't find a efficient and quick way around to do something like that:

SELECT 
COUNT(accounts.user_id) as users_number, 
countries.country_name 
FROM accounts, countries 
WHERE accounts.country_code=countries.country_code

and the following:

AND $selected_languages IN accounts.spoken_languages 

then:

GROUP BY countries.code;

so that the recordset could give the number of users who speaks English or/and Italian or/and French group by country_code

The volume of the exiting dataset is very large. Do I create a separate join table users_languages? Is there a mysql instruction to do it? Do I create a user function in mySQL?

Thanks a lot. Eric

Eric
  • 3
  • 1
  • You have an SQL-injection hole in that code. Use `$selected_language = mysql_real_escape_string($_POST['selected_languages']);` and then (very important) surround your $var with single quotes in your query string like so: `$query = "SELECT .... AND '$selected_languages' IN accounts.spoken_languages ...." If you do it any other way you **will** get **pwned**. – Johan Aug 26 '11 at 10:24
  • (Even better would be to use PDO), anyway see: http://stackoverflow.com/questions/332365/xkcd-sql-injection-please-explain For more info. – Johan Aug 26 '11 at 10:25
  • BTW, if you replace `COUNT(accounts.user_id)` with `count(*)` your query will run faster. – Johan Aug 26 '11 at 10:43

2 Answers2

2
$langs = explode(',', $selected_languages);

foreach($langs as $lang) {
    $res[] = " FIND_IN_SET('". mysql_escape_string($lang) . "', accounts.spoken_languages)";
}

$query = 'SELECT 
    COUNT(accounts.user_id) as users_number, 
    countries.country_name 
    FROM accounts, countries 
    WHERE accounts.country_code=countries.country_code 
    AND (' . implode(' OR ', $res) . ') GROUP BY countries.code;';

 //execute $query

Note: I believe that it should be slow on big datasets.

Andrej
  • 7,474
  • 1
  • 19
  • 21
  • Cool as `find_in_set` is, it is very slow on large datasets because there cannot be an index. – Johan Aug 26 '11 at 10:28
  • +1 for introducing me to FIND_IN_SET. -1, however, for not cleaning $lang with `mysql_real_escape_string()`. – Doug Kress Aug 26 '11 at 10:29
  • I use mysql_escape_string because only in 5.3 this one is deprecated. And also it depends on selected driver. Anyway mysql_escape_string is better choice. – Andrej Aug 26 '11 at 10:33
  • @Johan I wrote about this one. – Andrej Aug 26 '11 at 10:34
  • Andrej, sorry overlooked that. @Doug AFAICT the code properly escapes, the escaping is hidden in the $res assignment, but it looks correct to me. – Johan Aug 26 '11 at 10:42
  • @Andrej Thank you very much. The query seems ok but it returns: "FUNCTION mydatabasename.FIND_IN_SET does not exist". I didn't find anywhere it could be a problem of mysql version. – Eric Aug 26 '11 at 11:46
  • It is in 4.1 version and higher. Not sure about this one mydatabasename.FIND_IN_SET but FIND_IN_SET is a global function – Andrej Aug 26 '11 at 11:55
  • The mysql version of my provider is 5.0.92-enterprise-gpl-log but I still have the same message 'FUNCTION mydatabasename.FIND_IN_SET does not exist' – Eric Aug 26 '11 at 14:28
  • @Eric You should remove space between FIND_IN_SET and (. I edited my code. – Andrej Aug 26 '11 at 14:30
  • I run this through phpmyadmin5 and it works: `SELECT * FROM accounts WHERE FIND_IN_SET('en-GB', spoken_languages) >0;` but yoour solution still gives me the same error message. The mysql version of my provider is 5.0.92-enterprise-gpl-log. – Eric Aug 26 '11 at 14:49
  • @Andrej, sorry for the disturb, I found the error and it was mine. There was a white space between `FIND_IN_SET` and the first `(` in the string. Thank you again, your help has been great. – Eric Aug 26 '11 at 15:14
  • No problem, I wrote you about this one earlier :). You can mark my answer as best and upvote. – Andrej Aug 26 '11 at 15:15
1

The volume of the exiting dataset is very large. Do I create a separate join table users_languages?

Yes, this is the best option because MySQL can then use an index to find the rows fast.

Is there a mysql instruction to do it?

yes, find_in_set see @Andrej's answer.

Do I create a user function in mySQL?

No, it will kill any option MySQL has to use indexes.

Johan
  • 74,508
  • 24
  • 191
  • 319
  • Thanks you very much @Johan. I had a problem with mysql_real_escape_string($_POST['selected_languages']), it gives me: "Warning: mysql_real_escape_string() [function.mysql-real-escape-string]: Can't connect to MySQL server on 'localhost' (10061) in myfile.php on line 63" – Eric Aug 26 '11 at 11:52
  • You need to connect to the SQL server **before** using mysql_real_escape_string. Also If you use the `mysqli` functions, you must also use `mysqli_real_escape_string` ditto for the `mysql` functions. – Johan Aug 26 '11 at 11:54
  • it works fine now but still have a problem with FIND_IN_SET. The query seems ok but it returns: "FUNCTION mydatabasename.FIND_IN_SET does not exist". Don't know what are mysqli functions. – Eric Aug 26 '11 at 12:07
  • @Eric, that's because `find_in_set` is a function in MySQL, not a function in php. You need to include it in your query string, not in your php code as such. **If your MySQL version is very old, it might not include it, but it's been in MySQL since at least version 5.0 and I do believe it's backported to the 4.x tree** – Johan Aug 26 '11 at 12:40
  • I knew that 'find_in_set' was not a php function. I did exactly what Andrej suggested to me: I build a part of the query in WHERE clause then I eecute the query. The mysql version of my provider is 5.0.92-enterprise-gpl-log but I still have the same message 'FUNCTION mydatabasename.FIND_IN_SET does not exist' – Eric Aug 26 '11 at 14:21
  • I run this through phpmyadmin5 and it works: `SELECT * FROM accounts WHERE FIND_IN_SET('en-GB', spoken_languages) >0;` but Andrej's solution still gives me the same error message. – Eric Aug 26 '11 at 14:48
  • sorry for the disturb, I found the error and it was mine. There was a white space between `FIND_IN_SET` and the first `(` in the string. Thank you again, your help has been great. – Eric Aug 26 '11 at 15:12
  • @Eric, np happy your problem is fixed. – Johan Aug 26 '11 at 16:11