Как найти наиболее популярные слова в MySQL?

У меня есть таблица с именем results с 5 столбцами.

Я хотел бы использовать столбец title для поиска строк, которые говорят: WHERE title like '%for sale%', а затем перечисления наиболее популярных слов в этом столбце. Один из них будет for, а другой будет sale, но я хочу посмотреть, что другие слова коррелируют с этим.

Пример данных:

title
cheap cars for sale
house for sale
cats and dogs for sale
iphones and androids for sale
cheap phones for sale
house furniture for sale

Результаты (отдельные слова):

for    6
sale    6
cheap    2
and    2
house    2
furniture 1
cars    1
etc...

Ответы

Ответ 1

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

select substring_index(substring_index(r.title, ' ', n.n), ' ', -1) as word,
       count(*)
from results r join
     numbers n
     on n.n <= length(title) - length(replace(title, ' ', '')) + 1
group by word;

Если у вас нет таблицы чисел, вы можете создать ее вручную, используя подзапрос:

from results r join
     (select 1 as n union all select 2 union all select 3 union all . . .
     ) n
     . . .

Сценарий SQL (любезно предоставлен @GrzegorzAdamKowalski) здесь.

Ответ 2

Вы можете использовать ExtractValue каким-то интересным способом. См. Скрипт SQL здесь: http://sqlfiddle.com/#!9/0b0a0/45

Нам нужна только одна таблица:

CREATE TABLE text (`title` varchar(29));

INSERT INTO text (`title`)
VALUES
    ('cheap cars for sale'),
    ('house for sale'),
    ('cats and dogs for sale'),
    ('iphones and androids for sale'),
    ('cheap phones for sale'),
    ('house furniture for sale')
;

Теперь мы построим ряд выборок, которые извлекают целые слова из текста, преобразованного в XML. Каждый выбор извлекает N-го слова из текста.

select words.word, count(*) as `count` from
(select ExtractValue(CONCAT('<w>', REPLACE(title, ' ', '</w><w>'), '</w>'), '//w[1]') as word from `text`
union all
select ExtractValue(CONCAT('<w>', REPLACE(title, ' ', '</w><w>'), '</w>'), '//w[2]') from `text`
union all
select ExtractValue(CONCAT('<w>', REPLACE(title, ' ', '</w><w>'), '</w>'), '//w[3]') from `text`
union all
select ExtractValue(CONCAT('<w>', REPLACE(title, ' ', '</w><w>'), '</w>'), '//w[4]') from `text`
union all
select ExtractValue(CONCAT('<w>', REPLACE(title, ' ', '</w><w>'), '</w>'), '//w[5]') from `text`) as words
where length(words.word) > 0
group by words.word
order by `count` desc, words.word asc

Ответ 3

Это даст вам одно слово (просто, если я понимаю, что означает ваш single word.):

select concat(val,' ',cnt) as result from(
    select (substring_index(substring_index(t.title, ' ', n.n), ' ', -1)) val,count(*) as cnt
        from result t cross join(
         select a.n + b.n * 10 + 1 n
         from 
                (select 0 as n union all select 1 union all select 2 union all select 3 
                        union all select 4 union all select 5 union all select 6 
                        union all select 7 union all select 8 union all select 9) a,
                (select 0 as n union all select 1 union all select 2 union all select 3 
                        union all select 4 union all select 5 union all select 6 
                        union all select 7 union all select 8 union all select 9) b
                order by n 
        ) n
    where n.n <= 1 + (length(t.title) - length(replace(t.title, ' ', '')))
    group by val
    order by cnt desc
) as x

Результат должен выглядеть следующим образом:

Result
--------
for 6
sale 6
house 2
and 2
cheap 2
phones 1
iphones 1
dogs 1
furniture 1
cars 1
androids 1
cats 1

Но если single word вам нужно вот так:

result
-----------
for 6 sale 6 house 2 and 2 cheap 2 phones 1 iphones 1 dogs 1 furniture 1 cars 1 androids 1 cats 1

Просто измените запрос выше:

select group_concat(concat(val,' ',cnt) separator ' ') as result from( ...

Ответ 4

Update

Идея взята из fooobar.com/questions/124823/...

Этот запрос работает на моей машине (MySQL 5.7), однако Sqlfiddle сообщает об ошибке. Основная идея заключается в том, что вы должны либо создать таблицу с числами от 1 до максимального значения слова (например, 4) в вашем поле, либо, как я и сделал, для упрощения используйте UNION 1.. 4.

CREATE TABLE products (
  `id` int,
  `name` varchar(45)
);

INSERT INTO products
    (`id`, `name`)
VALUES
    (1, 'for sale'),
    (2, 'for me'),
    (3, 'for you'),
    (4, 'you and me')
;

SELECT name, COUNT(*) as count FROM
(
SELECT
  product.id,
  SUBSTRING_INDEX(SUBSTRING_INDEX(product.name, ' ', numbers.n), ' ', -1) name
FROM
  (
    SELECT 1 AS n
    UNION SELECT 2
    UNION SELECT 3
    UNION SELECT 4
  ) AS numbers
  INNER JOIN products product
  ON CHAR_LENGTH(product.name)
     -CHAR_LENGTH(REPLACE(product.name, ' ', ''))>=numbers.n-1
ORDER BY
  id, n
)
AS result
GROUP BY name
ORDER BY count DESC

Результат будет

for | 3
you | 2
me  | 2
and | 1
sale| 1

Ответ 5

SQL не подходит для этой задачи, хотя возможны ограничения (например, количество слов)

быстрый PHP script для выполнения одной и той же задачи может быть проще использовать долгосрочный (и, скорее всего, быстрее)

<?php
$rows = [
    "cheap cars for sale",
    "house for sale",
    "cats and dogs for sale",
    "iphones and androids for sale",
    "cheap phones for sale",
    "house furniture for sale",
];

//rows here should be replaced by the SQL result
$wordTotals = [];
foreach ($rows as $row) {
   $words = explode(" ", $row);
    foreach ($words as $word) {
        if (isset($wordTotals[$word])) {
            $wordTotals[$word]++; 
            continue;
        }

        $wordTotals[$word] = 1;
    }
}

arsort($wordTotals);

foreach($wordTotals as $word => $count) {
    echo $word . " " . $count . PHP_EOL;
}

Выход

for 6
sale 6
and 2
cheap 2
house 2
phones 1
androids 1
furniture 1
cats 1
cars 1
dogs 1
iphones 1

Ответ 6

Здесь работает SQL Fiddle: http://sqlfiddle.com/#!9/0b0a0/32

Давайте начнем с двух таблиц: один для текстов и один для чисел:

CREATE TABLE text (`title` varchar(29));

INSERT INTO text
    (`title`)
VALUES
    ('cheap cars for sale'),
    ('house for sale'),
    ('cats and dogs for sale'),
    ('iphones and androids for sale'),
    ('cheap phones for sale'),
    ('house furniture for sale')
;

CREATE TABLE iterator (`index` int);

INSERT INTO iterator
    (`index`)
VALUES
    (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),
    (16),(17),(18),(19),(20),(21),(22),(23),(24),(25),(26),(27),(28),(29),(30)
;

Вторая таблица iterator должна содержать числа от 1 до N, где N больше или равно длине самой длинной строки в text.

Затем запустите этот запрос:

select
  words.word, count(*) as `count`
from 
(select
  substring(concat(' ', t.title, ' '), i.index+1, j.index-i.index) as word
from
  text as t, iterator as i, iterator as j
where
    substring(concat(' ', t.title), i.index, 1) = ' '
and substring(concat(t.title, ' '), j.index, 1) = ' '
and i.index < j.index
) AS words
where
    length(words.word) > 0
and words.word not like '% %'
group by words.word
order by `count` desc, words.word asc

Есть два выбора. Внешняя просто группирует и подсчитывает одиночные слова (слова длиной больше 0 и без пробелов). Inner one извлекает все строки, начиная с любого символа пробела и заканчивая любым другим символом пробела, поэтому строки не являются словами (несмотря на то, что они назвали этот подзапрос words), потому что они могут содержать другие пробелы, чем начало и конец.

Результаты:

word    count
for     6
sale    6
and     2
cheap   2
house   2
androids    1
cars    1
cats    1
dogs    1
furniture   1
iphones     1
phones  1