更快的相当于 mysql 内连接与 sum

Posted

技术标签:

【中文标题】更快的相当于 mysql 内连接与 sum【英文标题】:faster equivalent of mysql inner join with sum 【发布时间】:2013-09-29 07:05:05 【问题描述】:

我从 mysql DB 中有一个很长的选择。它工作得非常快,直到我添加了这个 INNER JOIN。在我添加这个 INNER JOIN 选择时间之前是 2 秒,现在大约是 20 秒...

INNER JOIN 
   (SELECT 
      accomodation_id,
      SUM(accomodation_rooms.rooms) AS total_rooms,
      SUM(accomodation_rooms.beds * accomodation_rooms.rooms) AS total_persons 

   FROM accomodation_rooms 

   GROUP BY accomodation_id) accomodation_rooms

ON 
   accomodation_rooms.accomodation_id = accomodation.id
   AND 
   accomodation_rooms.total_persons >= '".$persons."'

有人可以帮我提供一些更快的替代方案吗?

【问题讨论】:

请发布您的完整查询,而不是其中的一部分 好的,你想要它:) 检查我的帖子 在这部分运行解释并发布结果:EXPLAIN SELECT accomodation_id, SUM(accomodation_rooms.rooms) AS total_rooms, SUM(accomodation_rooms.beds * accomodation_rooms.rooms) AS total_persons FROM accomodation_rooms GROUP BY accomodation_id你有关于 accomodation_id 和房间的索引吗? hmm 如果我添加 EXPLAIN 它显示:您的 SQL 语法有错误;查看与您的 MySQL 服务器版本相对应的手册,了解在“EXPLAIN SELECT accomodation_id, SUM(accomodation_rooms.rooms) AS to”附近使用的正确语法 【参考方案1】:
SELECT 
   accomodation.id, 
   accomodation.aid, 
   accomodation.title_en, 
   accomodation.title_url_en, 
   accomodation.address, 
   accomodation.zip, 
   accomodation.stars, 
   accomodation.picture, 
   accomodation.valid_from, 
   accomodation.valid_to, 
   accomodation.latitude, 
   accomodation.longitude, 
   accomodation.city_id AS accomodation_city_id, 
   db_cities.id AS city_id, 
   db_cities.title_en AS city, 
   db_cities.title_url AS city_url, 
   db_countries.title_en AS country_title, 
   db_countries.title_url_en AS country_url, 
   accomodation_type.class AS accomodation_type_class, 
   accomodation_review_value_total.value AS review_total, 
   MIN(accomodation_price.price) AS price_from, 
   accomodation_rooms.total_persons 
FROM 
   (SELECT aid, MAX(info_date_add) AS max_info_date_add FROM accomodation GROUP BY aid) accomodation_max 
INNER JOIN accomodation 
   ON 
      accomodation_max.aid = accomodation.aid AND 
      accomodation_max.max_info_date_add = accomodation.info_date_add 
LEFT JOIN db_cities 
   ON 
      ( db_cities.id = accomodation.city_id OR (((acos(sin((db_cities.latitude*pi()/180)) * sin((accomodation.latitude*pi()/180)) + cos((db_cities.latitude*pi()/180)) * cos((accomodation.latitude*pi()/180)) * cos(((db_cities.longitude - accomodation.longitude)*pi()/180))))*180/pi())*60*1.1515*1.609344) < '20') 
JOIN db_countries 
   ON db_countries.id = accomodation.country_id 
LEFT JOIN accomodation_review_value_total 
   ON accomodation_review_value_total.accomodation_aid = accomodation.aid 
LEFT JOIN accomodation_type_value 
   ON accomodation_type_value.accomodation_id = accomodation.id 
LEFT JOIN accomodation_type 
   ON accomodation_type.id = accomodation_type_value.accomodation_type_id 
LEFT JOIN accomodation_price 
   ON 
      ( accomodation_price.accomodation_aid = accomodation.aid AND 
      accomodation_price.accomodation_price_type_id = '1' AND 
      accomodation_price.accomodation_price_cat_id = '1' ) 
LEFT JOIN accomodation_season 
   ON 
      ( accomodation_season.accomodation_aid = accomodation.aid AND 
      accomodation_season.id = accomodation_price.accomodation_season_id AND 
      ( '2013-09-25' >= accomodation_season.start_date AND 
      accomodation_season.end_date >= '2013-09-25' OR '2013-09-26' >= accomodation_season.start_date AND 
      accomodation_season.end_date >= '2013-09-26' ) ) 
INNER JOIN 
      (SELECT accomodation_id, SUM(accomodation_rooms.rooms) AS total_rooms, 
      SUM(accomodation_rooms.beds * accomodation_rooms.rooms) AS total_persons 
   FROM accomodation_rooms 
   GROUP BY accomodation_id) accomodation_rooms 
ON 
   accomodation_rooms.accomodation_id = accomodation.id AND 
   accomodation_rooms.total_persons >= '4' 
WHERE 
   db_countries.title_url_en LIKE '%country%' AND 
   db_cities.title_url LIKE '%city%' AND 
   total_rooms >= '2' AND 
   db_cities.id = '2416' 
GROUP BY accomodation.aid 
ORDER BY CASE 
   WHEN 
      accomodation.valid_to>=NOW() AND 
      accomodation.valid_from<=NOW() THEN 0 
   WHEN 
      NOW()>accomodation.valid_to AND 
      accomodation.valid_to!='0000-00-00' THEN 1 ELSE 2 END, 
review_total DESC, accomodation.title_en LIMIT 10 OFFSET 0

【讨论】:

这个查询应该是什么?这是对原始问题的回答吗?简短的描述会很好地使其对其他人有用。【参考方案2】:

我已经修改了您的查询,删除了额外的连接并将它们组合起来试试这个。

SELECT
  accomodation.id,
  accomodation.aid,
  accomodation.title_en,
  accomodation.title_url_en,
  accomodation.address,
  accomodation.zip,
  accomodation.stars,
  accomodation.picture,
  accomodation.valid_from,
  accomodation.valid_to,
  accomodation.latitude,
  accomodation.longitude,
  MAX(info_date_add)                    AS max_info_date_add,
  accomodation.city_id                  AS accomodation_city_id,
  db_cities.id                          AS city_id,
  db_cities.title_en                    AS city,
  db_cities.title_url                   AS city_url,
  db_countries.title_en                 AS country_title,
  db_countries.title_url_en             AS country_url,
  accomodation_type.class               AS accomodation_type_class,
  accomodation_review_value_total.value AS review_total,
  MIN(accomodation_price.price)         AS price_from,
  accomodation_rooms.total_persons,
  (((acos(sin((db_cities.latitude * pi()/180)) * sin((accomodation.latitude * pi()/180)) + cos((db_cities.latitude * pi()/180)) * cos((accomodation.latitude * pi()/180)) * cos(((db_cities.longitude - accomodation.longitude) * pi()/180)))) * 180/pi()) * 60 * 1.1515 * 1.609344) as Calculation
FROM accomodation
LEFT JOIN db_cities ON db_cities.id = accomodation.city_id
LEFT JOIN db_countries ON db_countries.id = accomodation.country_id
LEFT JOIN accomodation_review_value_total ON accomodation_review_value_total.accomodation_aid = accomodation.aid
LEFT JOIN accomodation_price ON (accomodation_price.accomodation_aid = accomodation.aid AND accomodation_price.accomodation_price_type_id = '1' AND accomodation_price.accomodation_price_cat_id = '1')
INNER JOIN (SELECT
                accomodation_id,
                SUM(accomodation_rooms.rooms)          AS total_rooms,
                SUM(accomodation_rooms.beds * accomodation_rooms.rooms) AS total_persons
              FROM accomodation_rooms
              GROUP BY accomodation_id) accomodation_rooms
    ON accomodation_rooms.accomodation_id = accomodation.id
      AND accomodation_rooms.total_persons >= '4'
WHERE db_countries.title_url_en LIKE '%country%'
    AND db_cities.title_url LIKE '%city%'
    AND total_rooms >= '2'
    AND db_cities.id = '2416'
GROUP BY accomodation.aid
HAVING Calculation < 20
ORDER BY CASE WHEN accomodation.valid_to >= NOW()
    AND accomodation.valid_from <= NOW()THEN 0 WHEN NOW() > accomodation.valid_to
    AND accomodation.valid_to != '0000-00-00'THEN 1 ELSE 2 END, review_total DESC, accomodation.title_en
LIMIT 10 OFFSET 0

编辑:

您仍然可以使用自己的加入方式

SELECT
  accomodation.id,
  accomodation.aid,
  accomodation.title_en,
  accomodation.title_url_en,
  accomodation.address,
  accomodation.zip,
  accomodation.stars,
  accomodation.picture,
  accomodation.valid_from,
  accomodation.valid_to,
  accomodation.latitude,
  accomodation.longitude,
  MAX(info_date_add)                    AS max_info_date_add,
  accomodation.city_id                  AS accomodation_city_id,
  db_cities.id                          AS city_id,
  db_cities.title_en                    AS city,
  db_cities.title_url                   AS city_url,
  db_countries.title_en                 AS country_title,
  db_countries.title_url_en             AS country_url,
  accomodation_type.class               AS accomodation_type_class,
  accomodation_review_value_total.value AS review_total,
  MIN(accomodation_price.price)         AS price_from,
  accomodation_rooms.total_persons,
  SUM(IFNULL(accomodation_rooms.rooms,0,1)) AS total_rooms,
  SUM(IFNULL(accomodation_rooms.beds * accomodation_rooms.rooms,0,1)) AS total_persons
FROM accomodation
  LEFT JOIN db_cities
    ON db_cities.id = accomodation.city_id
  LEFT JOIN db_countries
    ON (db_cities.id = accomodation.city_id
         OR (((acos(sin((db_cities.latitude * pi()/180)) * sin((accomodation.latitude * pi()/180)) + cos((db_cities.latitude * pi()/180)) * cos((accomodation.latitude * pi()/180)) * cos(((db_cities.longitude - accomodation.longitude) * pi()/180)))) * 180/pi()) * 60 * 1.1515 * 1.609344) < '20')
  LEFT JOIN accomodation_review_value_total
    ON accomodation_review_value_total.accomodation_aid = accomodation.aid
  LEFT JOIN accomodation_price
    ON (accomodation_price.accomodation_aid = accomodation.aid
        AND accomodation_price.accomodation_price_type_id = '1'
        AND accomodation_price.accomodation_price_cat_id = '1')
  LEFT JOIN accomodation_rooms
    ON accomodation_rooms.accomodation_id = accomodation.id
WHERE db_countries.title_url_en LIKE '%country%'
    AND db_cities.title_url LIKE '%city%'
    AND total_rooms >= '2'
    AND db_cities.id = '2416'
GROUP BY accomodation.aid
HAVING accomodation_rooms.total_persons >= '4'
ORDER BY CASE WHEN accomodation.valid_to >= NOW()
    AND accomodation.valid_from <= NOW()THEN 0 WHEN NOW() > accomodation.valid_to
    AND accomodation.valid_to != '0000-00-00'THEN 1 ELSE 2 END, review_total DESC, accomodation.title_en
LIMIT 10 OFFSET 0

这里的重要一点是您可以使用左连接而不是内连接,我认为这会更快。将 where 条件放在 HAVAING 子句中。另请注意,我在选择时应用了 IF 条件,因此如果出现 null,则总和为 0,否则为 1。

【讨论】:

嗯,当我选择计算然后我使用拥有时,它看起来真的更快。但我还有一个问题。我怎样才能修改它以显示没有指定纬度和经度的结果,所以计算不可用?在我的选择中,JOIN db_cities 有 2 个条件 - 按 ID 或按 DISTANCE 你可以在have子句中加入or条件。 Having Calculation &lt; 20 OR Calculation IS NULL。我猜只有在计算不可用时才会出现 NULL 好的,它正在工作,但我没有注意到另一个问题......现在它只加入表 db_cities ON id,但我还需要按距离范围加入城市...... 嗯好的,但这不是更快...问题仍然存在于 SUM 的 INNER JOIN 中 - 我认为 SUM 是问题 @general666 查看我修改的第二个查询。试试看它是否更快。

以上是关于更快的相当于 mysql 内连接与 sum的主要内容,如果未能解决你的问题,请参考以下文章

SQL内连接与外连接用法与区别

相当于 MySQL 多表内连接的 Google BigQuery

将内连接与 sum 一起使用时出现问题

MySQL 内连接与外连接

MySQL连接查询 内连接和外连接的区别

MySQL 的内连接、左连接、右连接有啥区别?