Как реализовать поиск по вертикально оформленному столу? - PullRequest
2 голосов
/ 14 декабря 2009

У меня есть такая структура таблицы (вертикальный дизайн). Я могу иметь неограниченное количество атрибутов (например, город, телефон и т. Д.) Для каждого пользователя.

Таблица: tbl_UserAttributes

┌────────┬───────────┬────────────┐
| UserID │ FieldName │ Value      |
├────────┼───────────┼────────────┤
│ 341    │ city      │ MyCity1    │
│ 772    │ phone     │ 1234567890 │
│ 033    │ city      │ MyCity2    │
│ 044    │ sex       │ M          │
│ 772    │ firstname │ MyName     │
│ ---    │ ---       │ ---        │
└────────┴───────────┴────────────┘

Мне нужно реализовать функцию поиска, которая должна выводить строки, к которым мы применяем запрос, подобный этому, для таблицы с горизонтальным дизайном:

SELECT 
    FieldName 
FROM 
    tbl_UserAttributes 
WHERE 
    city='%Mumbai%' AND 
    sex='M' AND ...

Пожалуйста, не просите меня изменить дизайн базы данных.

ОБНОВЛЕНИЕ: В настоящее время у меня установлено решение JOIN, которое работает очень медленно и несколько раз зависает на сервере. Есть альтернативные методы?

Ответы [ 9 ]

7 голосов
/ 14 декабря 2009

EAV таблица - хорошая вещь, если вам не нужно искать несколько значений одновременно, и в этом случае она становится плохой.

Вы не можете индексировать несколько значений одновременно, потому что они находятся в разных записях.

В таблице SQL Server вы можете создать индексированное представление для нескольких значений и использовать его для поиска.

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

В PostgreSQL вы можете хранить все значения в одном массиве и индексировать его с помощью индекса GIN.

В MySQL вы не можете сделать ничего из этого.

Вот запрос, который вернет значения:

SELECT  *
FROM    tbl_UserAttributes tcity
JOIN    tbl_UserAttributes tsex
ON      tsex.userid = tcity.userid
WHERE   tcity.fieldname = 'city'
        AND tcity.value LIKE '%Mumbai%'
        AND tsex.fieldname = 'sex'
        AND tsex.value = 'M'

но не ожидайте, что это будет очень быстро.

Обновление:

Если вам нужно точное совпадение, вы можете создать составной индекс на (fieldname, value, userid), поместить наиболее селективный fieldname в первую таблицу и использовать STRAIGHT_JOIN, чтобы форсировать порядок:

SELECT  *
FROM    tbl_UserAttributes tcity
STRAIGHT_JOIN
        tbl_UserAttributes tsex
ON      tsex.userid = tcity.userid
WHERE   tcity.fieldname = 'city'
        AND tcity.value = 'Mumbai'
        AND tsex.fieldname = 'sex'
        AND tsex.value = 'M'

Однако это не поможет с вашим текущим запросом, так как вы ищете совпадение с подстановочными знаками, в этом случае индексы не очень полезны. И ваша вторая таблица не получит много пользы от индекса, если вы не запросите базу данных роддома.

Тем не менее, это сэкономит вам некоторое время, поскольку сканирование таблицы можно использовать вместо сканирования таблицы.

3 голосов
/ 14 декабря 2009

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

2 голосов
/ 14 декабря 2009

Есть ли фиксированный набор FieldNames?

Если есть, я могу предложить настроить представление, чтобы повернуть его в горизонтальное положение и упростить запрос. В SQL Server 2005 это было бы что-то вроде:

SELECT *
FROM
(SELECT [UserID], [FieldName], [Value]
FROM [tbl_UserAttributes] ) ps
PIVOT
(
MAX([Value])
FOR [FieldName] IN
( [City], [Phone], [sex], [firstname])
) AS pvt

Это должно сделать его горизонтальным, хотя все требуемые значения [FieldName] должны быть в разделе IN (), чтобы вытащить поле для каждого. Также использование Max означает, что если у вас есть несколько значений для одного и того же FieldName, оно вытянет Max.

1 голос
/ 14 декабря 2009

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

CREATE TEMPORARY table search_results (User_id,score)
  SELECT User_id, 1 FROM tbl_UserAttributes
    WHERE FieldName ='blah' and FieldValue='x'; //should put an index on search_results.User_id

UPDATE search_results s JOIN tbl_UserAttributes u USING (User_id)
SET s.score=s.score+1 WHERE u.FieldName ='foo' and FieldValue='y';

повторите ОБНОВЛЕНИЕ для многих условий.

SELECT User_id FROM search_results WHERE score= 'number of conditions'.

Вышеупомянутый SELECT может быть присоединен к атрибутам tbl_UserAttributes для вывода любых необходимых вам полей.

1 голос
/ 14 декабря 2009

Если упомянутый выше метод внутреннего соединения недопустим, все, что я могу предложить, это агрегировать таблицу в горизонтальный формат каждые X минут, а затем использовать новую таблицу в качестве базы для поиска.

1 голос
/ 14 декабря 2009
select ua.userID
from tblUserAttributes ua
INNER JOIN tblUserAttributes ua2
ON ua.userID = ua2.userID
and ua2.firstname = 'john'
INNER JOIN tblUserAttributes ua3
ON ua.userID = ua3.userID
and ua3.lastname = 'smith'
where ua.sex = 'M'
1 голос
/ 14 декабря 2009

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

Для запроса двух атрибутов необходимо объединение.

select a1.userid from tbl_UserAttributes a1, tbl_UserAttributes a2 where
a1.userid=a2.userid 
and a1.FieldName='city' and a1.Value='Mumbai'
and a2.FieldName='sex' and a2.Value='M'

Скоро станет громоздким.

Upd:

Как говорит Брайан, вам лучше считать количество матчей.

select userid, count(*) from tbl_UserAttributes 
where (FieldName='city' and a1.Value='Mumbai')
or (FieldName='sex' and a2.Value='M')
group by userid
having count(*)=2

Это должно работать намного быстрее

1 голос
/ 14 декабря 2009

Вы должны будете присоединиться к одной и той же таблице, по userid = userid, очевидно, что одна сторона соединения будет "where fieldname = 'city' и value = 'houston'", а другая сторона будет "where fieldname = ' sex 'and value =' M '". Надеюсь, вы не хотите иметь слишком много разных полей для поиска одновременно!

Кассной побил меня на 30 секунд.

1 голос
/ 14 декабря 2009

Для тех, кто предлагает помощь, это классический случай EAV (Значение атрибута сущности). Настоятельно НЕ рекомендуется при разработке приложений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...