Нужна помощь в оптимизации запроса - PullRequest
5 голосов
/ 21 октября 2010

У меня есть две таблицы - incoming tours(id,name) и incoming_tours_cities(id_parrent, id_city)

id в первой таблице уникален, и для каждой уникальной строки из первой таблицы есть список id_city - во второй таблице (т. Е. id_parrent во второй таблице равен id из первой таблицы )

Например

incoming_tours

|--id--|------name-----|
|---1--|---first_tour--|
|---2--|--second_tour--|
|---3--|--thirth_tour--|
|---4--|--hourth_tour--|

incoming_tours_cities

|-id_parrent-|-id_city-|
|------1-----|---4-----|
|------1-----|---5-----|
|------1-----|---27----|
|------1-----|---74----|
|------2-----|---1-----|
|------2-----|---5-----|
........................

Это означает, что first_tour имеет список городов - ("4","5","27","74")

И second_tour имеет список городов - ("1","5")


Предположим, у меня есть два значения - 4 и 74:

Теперь мне нужно получить все строки из первой таблицы, где мои оба значения находятся в списке городов. т.е. он должен возвращать только first_tour (потому что 4 и 74 находятся в его списке городов)

Итак, я написал следующий запрос

SELECT t.name
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc0 ON tc0.id_parrent = t.id
AND tc0.id_city = '4'
JOIN `incoming_tours_cities` tc1 ON tc1.id_parrent = t.id
AND tc1.id_city = '74'

И это прекрасно работает.

Но я генерирую запрос динамически, а когда количество соединений велико (около 15), запрос замедляется.

т.е. когда я пытаюсь запустить

SELECT t.name
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc0 ON tc0.id_parrent = t.id
AND tc0.id_city = '4'
JOIN `incoming_tours_cities` tc1 ON tc1.id_parrent = t.id
AND tc1.id_city = '74'
.........................................................
JOIN `incoming_tours_cities` tc15 ON tc15.id_parrent = t.id
AND tc15.id_city = 'some_value'

запрос выполняется в 45s (несмотря на то, что я установил индексы в таблицах)

Что я могу сделать, чтобы оптимизировать это?

Большое спасибо

Ответы [ 6 ]

6 голосов
/ 21 октября 2010
SELECT t.name
FROM incoming_tours t INNER JOIN 
  ( SELECT id_parrent
    FROM incoming_tours_cities
    WHERE id IN (4, 74)
    GROUP BY id_parrent
    HAVING count(id_city) = 2) resultset 
  ON resultset.id_parrent = t.id

Но вам нужно изменить количество городов.

2 голосов
/ 21 октября 2010
SELECT name
FROM (
      SELECT DISTINCT(incoming_tours.name) AS name,
             COUNT(incoming_tours_cities.id_city) AS c
      FROM incoming_tours
           JOIN incoming_tours_cities
                ON incoming_tours.id=incoming_tours_cities.id_parrent
      WHERE incoming_tours_cities.id_city IN(4,74)
            HAVING c=2
      ) t1;

Вам придется изменить c=2 на любое количество id_city, которое вы ищете, но поскольку вы генерируете запрос динамически, это не должно быть проблемой.

1 голос
/ 21 октября 2010

Я почти уверен, что это работает, но гораздо менее уверен, что это оптимально.

SELECT * FROM incoming_tours 
WHERE 
id IN (SELECT id_parrent FROM incoming_tours_cities WHERE id_city=4)
AND id IN (SELECT id_parrent FROM incoming_tours_cities WHERE id_city=74)
...
AND id IN (SELECT id_parrent FROM incoming_tours_cities WHERE id_city=some_value)
0 голосов
/ 21 октября 2010

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

Declare @numCities int = 2

;with incoming_tours(id, name) AS
(
    select 1, 'first_tour' union all
    select 2, 'second_tour' union all
    select 3, 'third_tour' union all
    select 4, 'fourth_tour' 
)
, incoming_tours_cities(id_parent, id_city) AS
(
    select 1, 4 union all 
    select 1, 5 union all 
    select 1, 27 union all 
    select 1, 74 union all 
    select 2, 1 union all 
    select 2, 5
)
, cityIds(id_city) AS
( 
    select 4
    union all select 5
    /* Add all city ids you need to check in this table */
)
, common_cities(id_city, tour_id, tour_name) AS
(
    select c.id_city,  it.id, it.name
    from cityIds C, Incoming_tours_cities tc, incoming_tours it
    where C.id_city = tc.id_city
    and tc.id_parent = it.id
)
, tours_with_all_cities(id_city) As
(
    select tour_id from common_cities 
    group by tour_id 
    having COUNT(id_city) = @numCities
)
select it.name from incoming_tours it, tours_with_all_cities tic
where it.id = tic.id_city
0 голосов
/ 21 октября 2010

Кажется, что это странный способ сделать этот запрос, здесь

SELECT t.name FROM `incoming_tours` as t WHERE t.id IN (SELECT id_parrent FROM `incoming_tours_cities` as tc WHERE tc.id_city IN ('4','74'));

Я думаю , что делает это, но не проверено ...

РЕДАКТИРОВАТЬ: Добавлен псевдоним таблицы в подзапрос

0 голосов
/ 21 октября 2010

Просто намек.Если вы используете оператор IN в предложении WHERE, вы можете надеяться, что короткое замыкание оператора AND может удалить ненужные JOIN s во время выполнения для обходов, которые не соблюдают ограничение.

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