1

以下查询用于进行成员搜索,在此示例中,仅使用姓氏。如果搜索完全匹配的名称,查询会在几秒钟内返回;但如果:LastName = 'S',则查询需要 12 秒以上才能返回。

我怎样才能加快这个查询?如果我能在一秒钟内用两个查询完成它,我不应该只用一个查询就可以,同样快吗?由于插件和其他方法,我最容易将其作为一个查询,因此是我的问题。

这张Member桌子容纳了我们曾经拥有的每一个成员。该表有一些我们没有任何注册的成员,因此它们只存在于该表中,而不存在于Registrationor中Registration_HistoryRegistration_History有关于我想显示的大多数成员的额外信息。Registration与 RH 有大部分相同的信息(RH 有一些 Reg 没有的字段),但有时它有 RH 没有的成员,这就是它在这里加入的原因。编辑:成员在注册中可以有多行。我想填写 Registration_History 中的列,但是,一些旧成员仅存在于 Registration 中。与其他成员不同,这些遗留成员在注册中只有 1 行,所以我不需要担心注册是如何排序的,只需从那里抓取 1 行。

带有示例数据库设计的 SQL Fiddle

MemberID在所有 3 个表中都有索引。在我放入SELECT RHSubSelect.rehiId子查询之前,这个查询需要将近一分钟才能返回。

如果我将查询拆分为 2 个查询,请执行以下操作:

SELECT
    MemberID
FROM
    Member
WHERE 
    Member.LastName LIKE CONCAT('%', :LastName, '%')

然后将这些MemberIDs 放入一个数组并将该数组传递给RHSubSelect.MemberID IN ($theArray)(而不是 Member 子查询),结果很快就会回来(大约一秒钟)。

完整查询:(为简洁起见,完整的 SELECT 语句在 FiddleSELECT *中)

SELECT
    *
FROM
 Member
    LEFT JOIN
        Registration_History FORCE INDEX (PRIMARY)
            ON
                Registration_History.rehiId = (
                                                SELECT
                                                    RHSubSelect.rehiId
                                                FROM
                                                    Registration_History AS RHSubSelect
                                                WHERE
                                                    RHSubSelect.MemberID IN (
                                                                                SELECT
                                                                                    Member.MemberID
                                                                                FROM
                                                                                    Member
                                                                                WHERE 
                                                                                    Member.LastName LIKE CONCAT('%', :LastName, '%')
                                                                            )                                                                   
                                                ORDER BY 
                                                    RHSubSelect.EffectiveDate DESC
                                                LIMIT 0, 1
                                            )                                   
    LEFT JOIN
        Registration FORCE INDEX(MemberID)
            ON
                Registration.MemberID = Member.MemberID
WHERE 
    Member.LastName LIKE CONCAT('%', :LastName, '%') 
GROUP BY
    Member.MemberID
ORDER BY 
    Relevance ASC,LastName ASC,FirstName asc 
LIMIT 0, 1000

MySQL解释,FORCE INDEX()在查询中:

(如果带有说明的图片没有显示,也在这里: http: //oi41.tinypic.com/2iw4t8l.jpg

4

5 回答 5

1

您似乎要检查的主要内容是带有前导 % 的姓氏。这会使该列上的索引无用,并且您的 SQL 正在搜索它两次。

我不是 100% 确定你想要做什么。您的 SQL 似乎将所有名称匹配的成员与所需的成员相匹配,然后获取这些成员的最后一个 registration_history 记录。你得到的可能来自任何一个匹配的成员,这似乎很奇怪,除非你只期望得到一个成员。

如果是这种情况,下面的小整理(删除和 IN 并将其更改为 JOIN)可能会稍微改善一些事情。

SELECT
    COALESCE(NULLIF(Registration_History.RegYear, ''), NULLIF(Registration.Year, '')) AS RegYear,
    COALESCE(NULLIF(Registration_History.RegNumber, ''), NULLIF(Registration.RegNumber, ''), NULLIF(Member.MemberID, '')) AS RegNumber,
    Member.MemberID,
    Member.LastName,
    Member.FirstName,
    CASE
        WHEN Member.LastNameTrimmed = :LastName
        THEN 1
        WHEN Member.LastNameTrimmed LIKE CONCAT(:LastName, '%')
        THEN 2
        ELSE 3
    END AS Relevance 
    FROM Member
    LEFT JOIN Registration_History FORCE INDEX (PRIMARY)
    ON Registration_History.rehiId = 
    (
        SELECT RHSubSelect.rehiId
        FROM Registration_History AS RHSubSelect
        INNER JOIN Member 
        ON RHSubSelect.MemberID = Member.MemberID
        WHERE Member.LastName LIKE CONCAT('%', :LastName, '%')
        ORDER BY RHSubSelect.EffectiveDate DESC
        LIMIT 0, 1
    )                                   
    LEFT JOIN Registration FORCE INDEX(MemberID)
    ON  Registration.MemberID = Member.MemberID
    WHERE Member.LastName LIKE CONCAT('%', :LastName, '%') 
    GROUP BY Member.MemberID
    ORDER BY Relevance ASC,LastName ASC,FirstName asc 
    LIMIT 0, 1000

但是,如果这不是您想要的,那么可能会进行进一步的更改。

多一点清理,消除一个带有前导通配符的 LIKE:-

SELECT
    COALESCE(NULLIF(Sub2.RegYear, ''), NULLIF(Registration.Year, '')) AS RegYear,
    COALESCE(NULLIF(Sub2.RegNumber, ''), NULLIF(Registration.RegNumber, ''), NULLIF(Member.MemberID, '')) AS RegNumber,
    Member.MemberID,
    Member.LastName,
    Member.FirstName,
    CASE
        WHEN Member.LastNameTrimmed = :LastName
        THEN 1
        WHEN Member.LastNameTrimmed LIKE CONCAT(:LastName, '%')
        THEN 2
        ELSE 3
    END AS Relevance 
FROM Member
LEFT OUTER JOIN Registration 
ON  Registration.MemberID = Member.MemberID
LEFT OUTER JOIN
(
    SELECT Registration_History.MemberID, Registration_History.rehiID, Registration_History.RegYear, Registration_History.RegNumber
    FROM Registration_History
    INNER JOIN
    (
        SELECT RHSubSelect.MemberID, MAX(RHSubSelect.EffectiveDate) AS EffectiveDate
        FROM Registration_History AS RHSubSelect
        GROUP BY RHSubSelect.MemberID
    ) Sub1
    ON Registration_History.MemberID = Sub1.MemberID AND Registration_History.EffectiveDate = Sub1.EffectiveDate
) Sub2
ON  Sub2.MemberID = Member.MemberID
WHERE Member.LastName LIKE CONCAT('%', :LastName, '%') 
GROUP BY Member.MemberID
ORDER BY Relevance ASC,LastName ASC,FirstName asc 
LIMIT 0, 1000

这将获取具有匹配名称的所有成员、匹配的注册记录以及具有最新 EffectiveDate 的registration_history 记录。

我不认为最后一个 GROUP BY 是必要的(假设成员和注册之间存在 1 对 1 的关系,如果不是,您可能想要使用 GROUP BY 以外的其他东西),但我现在把它留在了。

害怕没有表声明和一些相同的数据,我无法真正测试它。

编辑 - 有点戏,试图减少它在选择早期处理的数量: -

SELECT
    COALESCE(NULLIF(Registration_History.RegYear, ''), NULLIF(Sub1.Year, '')) AS RegYear,
    COALESCE(NULLIF(Registration_History.RegNumber, ''), NULLIF(Sub1.RegNumber, ''), NULLIF(Sub1.MemberID, '')) AS RegNumber,
    Sub1.MemberID,
    Sub1.LastName,
    Sub1.FirstName,
    CASE
        WHEN Sub1.LastName = :LastName
        THEN 1
        WHEN Sub1.LastName LIKE CONCAT(:LastName, '%')
        THEN 2
        ELSE 3
    END AS Relevance 
FROM
(
    SELECT 
        Member.MemberID,
        Member.LastName,
        Member.FirstName,
        Registration.Year,
        Registration.RegNumber,
        MAX(Registration_History.EffectiveDate) AS EffectiveDate
    FROM Member
    LEFT OUTER JOIN Registration 
    ON  Registration.MemberID = Member.MemberID
    LEFT OUTER JOIN Registration_History 
    ON Registration_History.MemberID = Member.MemberID
    WHERE Member.LastName LIKE CONCAT('%', :LastName, '%') 
    GROUP BY Member.MemberID,
        Member.LastName,
        Member.FirstName,
        Registration.Year,
        Registration.RegNumber
) Sub1
LEFT OUTER JOIN Registration_History
ON Registration_History.MemberID = Sub1.MemberID AND Registration_History.EffectiveDate = Sub1.EffectiveDate
ORDER BY Relevance ASC,LastName ASC,FirstName asc 
LIMIT 0, 1000

再次编辑。

试试这个。您正在排序的项目都来自成员表,因此在子选择中尽早排除可能是有意义的。

SELECT
    COALESCE(NULLIF(Registration_History2.EffectiveDate, ''), NULLIF(Registration2.Year, '')) AS RegYear,
    COALESCE(NULLIF(Registration_History2.RegNumber, ''), NULLIF(Registration2.RegNumber, ''), NULLIF(Member.MemberID, '')) AS RegNumber,
    Member.MemberID,
    Member.LastName,
    Member.FirstName,
    Member.Relevance 
    FROM
    (
        SELECT Member.MemberID,
                Member.LastName,
                Member.FirstName,
                CASE
                    WHEN Member.LastName = :LastName
                    THEN 1
                    WHEN Member.LastName LIKE CONCAT(:LastName, '%')
                    THEN 2
                    ELSE 3
                END AS Relevance 
        FROM Member
        WHERE Member.LastName LIKE CONCAT('%', :LastName, '%')
        ORDER BY Relevance ASC,LastName ASC,FirstName asc 
        LIMIT 0, 1000
    ) Member
    LEFT OUTER JOIN 
    (
        SELECT MemberID, MAX(EffectiveDate) AS EffectiveDate
        FROM Registration_History 
        GROUP BY MemberID
    ) Registration_History
    ON Registration_History.MemberID = Member.MemberID
    LEFT OUTER JOIN Registration_History Registration_History2
    ON Registration_History2.MemberID = Registration_History.MemberID
    AND Registration_History2.EffectiveDate = Registration_History.EffectiveDate
    LEFT OUTER JOIN 
    (
        SELECT MemberID, MAX(Year) AS Year
        FROM Registration 
        GROUP BY MemberID
    ) Registration
    ON Registration.MemberID = Member.MemberID
    LEFT OUTER JOIN 
    (
        SELECT MemberID, Year, MAX(RegNumber) AS RegNumber
        FROM Registration 
        GROUP BY MemberID, Year
    ) Registration2
    ON Registration2.MemberID = Member.MemberID
    AND Registration2.Year = Registration.Year

再次编辑

未测试以下内容,因此这更多是为了了解另一种尝试解决问题的方法,使用 GROUP_CONCAT 的小技巧:-

SELECT
    COALESCE(NULLIF(Registration_History.EffectiveDate, ''), NULLIF(Registration.Year, '')) AS RegYear,
    COALESCE(NULLIF(Registration_History.RegNumber, ''), NULLIF(Registration.RegNumber, ''), NULLIF(Member.MemberID, '')) AS RegNumber,
    Member.MemberID,
    Member.LastName,
    Member.FirstName,
    Member.Relevance 
    FROM
    (
        SELECT Member.MemberID,
                Member.LastName,
                Member.FirstName,
                CASE
                    WHEN Member.LastName = :LastName
                    THEN 1
                    WHEN Member.LastName LIKE CONCAT(:LastName, '%')
                    THEN 2
                    ELSE 3
                END AS Relevance 
        FROM Member
        WHERE Member.LastName LIKE CONCAT('%', :LastName, '%')
        ORDER BY Relevance ASC,LastName ASC,FirstName asc 
        LIMIT 0, 1000
    ) Member
    LEFT OUTER JOIN 
    (
        SELECT MemberID, 
                SUBSTRING_INDEX(GROUP_CONCAT(EffectiveDate ORDER BY EffectiveDate DESC), ",", 1) AS EffectiveDate,
                SUBSTRING_INDEX(GROUP_CONCAT(RegNumber ORDER BY EffectiveDate DESC), ",", 1) AS RegNumber
        FROM Registration_History 
        GROUP BY MemberID
    ) Registration_History
    ON Registration_History.MemberID = Member.MemberID
    LEFT OUTER JOIN 
    (
        SELECT MemberID, 
                SUBSTRING_INDEX(GROUP_CONCAT(Year ORDER BY Year DESC), ",", 1) AS Year,
                SUBSTRING_INDEX(GROUP_CONCAT(RegNumber ORDER BY Year DESC), ",", 1) AS RegNumber
        FROM Registration 
        GROUP BY MemberID
    ) Registration
    ON Registration.MemberID = Member.MemberID
于 2013-07-12T13:42:01.637 回答
1

我的建议是这样的查询:

SELECT *
FROM Member
LEFT JOIN Registration USING (MemberID)
LEFT JOIN Registration_History ON rehiID = (
  SELECT rehiID
  FROM Registration_History AS RHSubSelect
  WHERE RHSubSelect.MemberID = Member.MemberID
  ORDER BY EffectiveDate DESC
  LIMIT 1
)
WHERE Member.LastName LIKE CONCAT('%', :LastName, '%')

它的工作方式是从与LastName匹配的Member表中进行选择。然后,您就可以简单地访问注册表,因为特定成员在该表中最多可以有 1 个条目。最后是带有子选择的Registration_History表。LEFT JOINLEFT JOIN

子选择查找与当前MemberID匹配的最新EffectiveDate并返回该记录的rehiID。然后必须与该rehiID完全匹配。如果该成员的Registration_History中没有条目,则不会加入任何内容。LEFT JOIN

从理论上讲,这应该相对较快,因为您只LIKE在主查询中执行比较。注册连接应该很快,因为表是在MemberID建立索引的。但是,我怀疑您需要额外的Registration_History索引才能获得最佳性能。

您已经获得了主键rehID,它是我们需要的LEFT JOINon rehID 索引。但是,子查询需要匹配子句中的MemberID以及WHEREEffectiveDate排序。为了获得最佳性能,我认为您需要一个结合MemberIDEffectiveDate列的附加索引。

请注意,我的示例查询只是保持简单的最低要求。您显然需要将 替换为*您想要返回的所有字段(与原始查询相同)。此外,您还需要添加您的ORDER BYandLIMIT子句。但是,GROUP BY不应该是必需的。

SQL Fiddle 链接:http ://sqlfiddle.com/#!2/4a947a/1

上面的小提琴显示了完整的查询,除了它的姓氏硬编码。我已经修改了您的原始示例数据以包含更多记录并更改了一些值。我还在Registration_History表上添加了额外的索引。

针对 LIMIT 进行优化

如果您要再次进行计时运行,我很想知道在加入RegistrationRegistration_History表之前,使用Kickstart建议的修改首先对Member表进行子选择时我的查询如何执行。

SELECT
    COALESCE(NULLIF(Registration_History.RegYear, ''), NULLIF(Registration.Year, '')) AS RegYear,
    COALESCE(NULLIF(Registration_History.RegNumber, ''), NULLIF(Registration.RegNumber, ''), NULLIF(Member.MemberID, '')) AS RegNumber,
    Member.MemberID,
    Member.LastName,
    Member.FirstName,
    Member.Relevance
FROM (
  SELECT MemberID, LastName, FirstName,
    CASE
      WHEN Member.LastNameTrimmed = :LastName THEN 1
      WHEN Member.LastNameTrimmed LIKE CONCAT(:LastName, '%') THEN 2
      ELSE 3
    END AS Relevance 
  FROM Member
  WHERE Member.LastName LIKE CONCAT('%', :LastName, '%')
  ORDER BY Relevance ASC,LastName ASC,FirstName ASC
  LIMIT 0, 1000
) Member
LEFT JOIN Registration USING (MemberID)
LEFT JOIN Registration_History ON rehiID = (
  SELECT rehiID
  FROM Registration_History AS RHSubSelect
  WHERE RHSubSelect.MemberID = Member.MemberID
  ORDER BY EffectiveDate DESC
  LIMIT 1
)

使用 LIMIT 时,这应该比我的原始查询执行得更好,因为它不必为 LIMIT 排除的记录执行一堆不必要的连接。

于 2013-07-16T15:44:20.190 回答
0

试试这个查询:

set @lastname = 'Smith1';

-- explain extended
SELECT  
    COALESCE(NULLIF(Registration_History.RegYear, ''), NULLIF(Registration.Year, '')) AS RegYear,
    COALESCE(NULLIF(Registration_History.RegNumber, ''), NULLIF(Registration.RegNumber, ''), NULLIF(Member.MemberID, '')) AS RegNumber,
    Member.MemberID,
    Member.LastName,
    Member.FirstName,
    CASE
      WHEN Member.LastNameTrimmed = 'Smith' THEN 1
      WHEN Member.LastNameTrimmed LIKE CONCAT(@lastname, '%') THEN 2
      ELSE 3
    END AS Relevance 
FROM (
    SELECT  Member.*,
        ( SELECT RHSubSelect.rehiId
            FROM  Registration_History AS RHSubSelect
            WHERE RHSubSelect.MemberID = Member.MemberID                                         
            ORDER BY RHSubSelect.EffectiveDate DESC
            LIMIT 0,1
         ) rh_MemberId
    FROM Member
    WHERE Member.LastName LIKE CONCAT('%', @lastname, '%')
) Member
LEFT JOIN  Registration_History 
    ON Registration_History.rehiId = Member.rh_MemberId
LEFT JOIN Registration -- FORCE INDEX(MemberID)
    ON Registration.MemberID = Member.MemberID
GROUP BY Member.MemberID
ORDER BY Relevance ASC,LastName ASC,FirstName asc 
LIMIT 0, 1000
;
于 2013-07-19T11:28:09.500 回答
0

如果我正确理解了您的问题(您只需要选择特定用户及其最新历史记录 - 是否正确)?如果是,您的问题实际上是每组问题的最大记录的非常简单的变体。不需要任何子查询:

查询 #1

SELECT Member.*, rh1.*
FROM Member
LEFT JOIN Registration_History AS rh1 USING (MemberID)
LEFT JOIN Registration_History AS rh2
    ON rh1.MemberId = rh2.MemberId AND rh1.EffectiveDate < rh2.EffectiveDate
WHERE Member.LastName LIKE CONCAT('%', :LastName, '%') 
    AND rh2.MemberId IS NULL
ORDER BY Relevance ASC,LastName ASC,FirstName ASC
LIMIT 0, 1000

查询 #3

(#2 已被删除,此处采用#3 以避免评论混淆)

SELECT Member.*, max(rh1.EffectiveDate), rh1.*
FROM Member
LEFT JOIN Registration_History AS rh1 USING (MemberID)
WHERE Member.LastName LIKE CONCAT('%', :LastName, '%') 
GROUP BY Member.MemberID
ORDER BY Relevance ASC,LastName ASC,FirstName ASC
LIMIT 0, 1000

查询 #4

这个灵感来自 James 查询,但删除了limitand order by(请注意,您应该在 EffectiveDate 上定义索引,不仅如此,而且所有查询都高效!)

select *
from Member
left join Registration_History AS rh1 on rh1.MemberID = Member.MemberID
    and rh1.EffectiveDate = (select max(rh2.EffectiveDate)
                             from Registration_History as rh2
                             where rh2.MemberID = Member.MemberID)
                        )
WHERE Member.LastName LIKE CONCAT('%', :LastName, '%') 
ORDER BY Relevance ASC,LastName ASC,FirstName ASC
LIMIT 0, 1000

请在您的数据库中发布实际持续时间!

于 2013-07-17T18:19:36.967 回答
0

好的,这是我的照片,我使用了各种各样的作品。一,我不得不从一个中提取“相关性”字段,因为您没有说明如何使其工作。接下来,由于您想要给定成员的注册历史记录中的最新条目(如果它们存在于 R/H 中),因此生效日期似乎与 ReHiID 相关,因此我使用了它,因为这似乎是一个很好的关键为后续的左连接工作。

因此,内部查询仅根据您要查找的名称的标准进行初步传递,应用相关性并限制那里的 1000 个条目。这样,它就不必在外层通过 20,000 个条目并加入……只要有 1000 个符合条件即可。

然后将该结果左连接到其他表,如所示...仅注册单个条目(如果存在)并左连接到成员上的 R/H 和最大 ReHiID。

要应用您要查找的名称,只需更改查询中的 ( select @LookForMe := 'S' ) sqlvars 行...

select *
   from
      ( select
              M.*,
              max( RH.EffectiveDate ) as MaxEffectiveDate,
              max( R.RegNumber ) as MaxRegNumber,
              CASE WHEN M.LastNameTrimmed = @LookForMe THEN 1
              WHEN M.LastNameTrimmed LIKE CONCAT(@LookForMe, '%') THEN 2
              ELSE 3 END AS Relevance 
           from
              ( select @LookForMe := 'S' ) sqlvars,
              Member M
                 LEFT JOIN Registration_History RH
                    on M.MemberID = RH.MemberID
                 LEFT JOIN Registration R
                    on M.MemberID = R.MemberID
           where 
              M.LastName LIKE CONCAT('%', 'S', '%')
           group by
              M.MemberID
           order by
              Relevance, 
              M.LastName,
              M.FirstName
           limit
              0,1000 ) PreQuery
      LEFT JOIN Registration R2
         on PreQuery.MemberNumber = R2.MemberNumber
         AND PreQuery.MaxRegNumber = R2.RegNumber
      LEFT JOIN Registration_History RH2
         ON PreQuery.MemberNumber = RH2.MemberNumber
        AND PreQuery.MaxEffectiveDate = RH2.EffectiveDate

让我们看看您的生产数据运行速度有多快,以及我们有多接近。

于 2013-07-21T01:04:09.153 回答