Как я могу сохранить массивы PHP в базе данных MySQL, чтобы я мог выполнять поиск по массивам в базе данных?

Я пытаюсь создать доступную для поиска базу данных языков, на которых говорят мои пользователи.

Например, у меня может быть

$john = array("english", "french", "spanish");
$jack = array("french", "spanish");
$jill = array("english", "spanish");

И я хочу сохранить их в базе данных MySQL, чтобы потом запустить что-то вроде (псевдокода)

SELECT * FROM users WHERE spoken_languages = "french" and "spanish"

Я знаю, что если бы у меня были столбцы speaks_english, speaks_french и speaks_spanish, я мог бы искать

SELECT * FROM users WHERE speaks_french = "true" and speaks_spanish = "true"

Но масштабируемость добавления нового столбца каждый раз, когда я сталкиваюсь с новым языком, не очень хороша. Я рассматривал такую ​​таблицу, как

john | english
john | french
john | spanish
jack | french
jack | spanish
jill | english
jill | spanish

Потому что, по крайней мере, чтобы вернуть языки, на которых говорит пользователь, я мог бы просто запустить

SELECT * FROM spoken_languages WHERE user = "jack"

Но для того, чтобы выполнить поиск людей, говорящих как по-французски, так и по-испански, мне нужно будет запросить всех пользователей, говорящих по-французски, всех пользователей, говорящих по-испански, а затем вычислить пересечения. Это кажется ужасно неэффективным.

Поэтому я спрашиваю вас, как я могу сохранить этот массив разговорных языков, чтобы я мог позже искать в базе данных, не разрушая сервер?


person Nick Chapman    schedule 11.06.2014    source источник
comment
предложенный вами макет таблицы является рекомендуемым. вы можете настроить его, чтобы использовать user_id и language_id, чтобы сделать его меньше   -  person    schedule 12.06.2014
comment
Пользовательская таблица, языковая таблица и пользовательская/языковая таблица входят в панель... и возникает отношение многие ко многим.   -  person Jay Blanchard    schedule 12.06.2014
comment
Используйте структуру, которую вы рассмотрели, базы данных хороши для пересечения, если вы достаточно знаете SQL   -  person RiggsFolly    schedule 12.06.2014
comment
@RiggsFolly, можете ли вы предоставить мне образец? я не силен в SQL   -  person Nick Chapman    schedule 12.06.2014
comment
Почитайте об отношениях многие ко многим. Вы получите таблицу users, таблицу languages и таблицу отношений users_languages.   -  person jszobody    schedule 12.06.2014
comment
Вот пример: stackoverflow.com/questions/12461194/   -  person jeroen    schedule 12.06.2014
comment
моя демонстрация sqlfiddle.com/#!2/23824f/1   -  person    schedule 12.06.2014
comment
@NickChapman Поиск не выполнен, вы предполагаете реализацию. СУБД находит строки, соответствующие заданным вами критериям, это ее дело.   -  person philipxy    schedule 12.06.2014
comment
@JayBlanchard Нет, нет, подождите... Пользователь, язык и СУБД заходят в бар... ...бла-бла-бла-бла... ...и бармен говорит... из РАЗНЫХ ПОЛЕЙ, но ЖЕ СТОЛ ПОЛУЧИЛСЯ В РЯД!   -  person philipxy    schedule 12.06.2014


Ответы (3)


У вас есть правильное решение в вашем вопросе с таблицей person_language, которая выглядит так

john | english
john | french
jack | spanish

Вы можете запросить его так.

SELECT person
  FROM person_language
 WHERE language IN ( 'english', 'spanish')
 GROUP BY person
HAVING COUNT(*) = 2

Поместите индекс на (language, person), и это будет хорошо масштабироваться.

Если вы хотите, чтобы все, кто говорит по-испански и хотя бы еще на одном языке, вы могли бы сделать это.

 SELECT a.person
   FROM person_language AS a
   JOIN ( SELECT person
            FROM person_language
           GROUP BY person
          HAVING COUNT(*) >= 2
        ) AS b ON a.person = b.person
  WHERE a.language = 'spanish'

При этом используется директива JOIN для пересечения людей, говорящих по-испански, с людьми, говорящими на двух или более языках.

person O. Jones    schedule 11.06.2014
comment
Несвязанный вопрос, но будут ли какие-либо существенные различия в производительности, если этот запрос будет написан с предложением WHERE как WHERE language = 'english' || language = 'spanish'? - person echochamber; 12.06.2014
comment
не будет возвращать требуемые результаты таким образом - person ; 12.06.2014
comment
@echochamber Я считаю, что именно так MySQL выполняет запрос. В любом случае используйте объяснение в обоих запросах, чтобы увидеть различия. - person Joel Mellon; 12.06.2014
comment
... и я, скорее всего, ошибаюсь: stackoverflow. com/questions/782915/mysql-or-vs-in-performance - person Joel Mellon; 12.06.2014

Вы можете эффективно выполнить этот запрос с помощью самосоединения:

SELECT * FROM users u1
JOIN users u2 USING (user)
WHERE (u1.lang, u2.lang) = ('french', 'spanish')

См. примеры решений реляционного деления в моей презентации, SQL Query Шаблоны, оптимизированные.

Если у вас есть правильные индексы, мои тесты показывают, что это решение с самообъединением примерно в 20 раз быстрее, чем решение GROUP BY.

person Bill Karwin    schedule 11.06.2014

Я бы выбрал вариант с тремя столами

CREATE TABLE languages
(
  `id` int not null auto_increment primary key, 
  `language` varchar(32) unique
);
CREATE TABLE users
(
  `id` int not null auto_increment primary key,
  `name` varchar(32)
);
CREATE TABLE user_language
(
  `user_id` int, 
  `language_id` int,
  primary key (user_id, language_id)
);

ИМХО, если у вас нет миллионов пользователей и всех возможных языков и вы ищете гибкость и не боретесь за миллисекунды, особенно если вы проверяете более двух языков одновременно, вы можете добиться этого, используя агрегаты MAX() или SUM() в предложении HAVING .

Вот несколько примеров запросов для вас:

-- Speaks both French AND Spanish
SELECT u.name
  FROM user_language ul JOIN languages l
    ON ul.language_id = l.id JOIN users u
    ON ul.user_id = u.id
 GROUP BY u.id
HAVING MAX(l.language = 'french') = 1
   AND MAX(l.language = 'spanish') = 1;

Вывод:

| NAME |
|------|
| John |
| Jack |
-- Speaks both French OR Spanish
SELECT u.name
  FROM user_language ul JOIN languages l
    ON ul.language_id = l.id JOIN users u
    ON ul.user_id = u.id
 GROUP BY u.id
HAVING MAX(l.language = 'french') +
       MAX(l.language = 'spanish') > 0;

Вывод:

| NAME |
|------|
| John |
| Jack |
| Jill |
-- Speaks any language French OR Spanish BUT NOT English
SELECT u.name
  FROM user_language ul JOIN languages l
    ON ul.language_id = l.id JOIN users u
    ON ul.user_id = u.id
 GROUP BY u.id
HAVING MAX(l.language = 'french') +
       MAX(l.language = 'spanish') > 0
   AND MAX(l.language = 'english') = 0;

Вывод:

| NAME |
|------|
| Jack |
-- Speaks any language but English
SELECT u.name
  FROM user_language ul JOIN languages l
    ON ul.language_id = l.id JOIN users u
    ON ul.user_id = u.id
 GROUP BY u.id
HAVING MAX(l.language = 'english') = 0;

Вывод:

| NAME |
|------|
| Jack |
-- What languages does Jack speak
SELECT l.language
  FROM user_language ul JOIN languages l
    ON ul.language_id = l.id JOIN users u
    ON ul.user_id = u.id
 WHERE u.name = 'Jack';

Вывод:

| LANGUAGE |
|----------|
|   french |
|  spanish |
-- How many languages do users speak
SELECT u.name, COUNT(*) no_of_languages
  FROM users u LEFT JOIN user_language ul
    ON u.id = ul.user_id
 GROUP BY u.id;

Вывод:

| NAME | NO_OF_LANGUAGES |
|------|-----------------|
| John |               3 |
| Jack |               2 |
| Jill |               2 |
-- How many users do speak a particular language
SELECT l.language, COUNT(*) no_of_users
  FROM languages l LEFT JOIN user_language ul
    ON l.id = ul.language_id
 GROUP BY l.id;

Вывод:

| LANGUAGE | NO_OF_USERS |
|----------|-------------|
|  english |           2 |
|   french |           2 |
|  spanish |           3 |

Теперь в реальном приложении вы, скорее всего, не будете использовать язык или имена пользователей, а будете иметь дело с идентификаторами, поступающими из вашего пользовательского интерфейса (раскрывающиеся списки или что-то еще). Поэтому вы сможете исключить из уравнения одно соединение, и ваш запрос будет выглядеть примерно так:

-- Speaks both French AND Spanish with Ids
SELECT u.name
  FROM user_language ul JOIN users u
    ON ul.user_id = u.id
 GROUP BY u.id
HAVING MAX(ul.language_id = 2) = 1
   AND MAX(ul.language_id = 3) = 1;

Вот демонстрация SQLFiddle.

person peterm    schedule 11.06.2014