【问题标题】:Mysql joining multiple totals - making query efficientMysql加入多个总计 - 使查询高效
【发布时间】:2012-05-12 21:07:12
【问题描述】:

我正在为员工创建一个打卡/打卡系统。

有一个 tbl_clockins 包含每个打卡/打卡时段的记录,其中包含关于每个时段是否有报酬、员工参加该时段的迟到时间或他们加班多少等信息。

还有一个名为 tbl_user_work_settings 的表,经理可以在其中设置员工休假的日期,或因病休假等。

我正在创建一些需要每位员工总计的报告,例如每个员工在给定日期范围内休假的总天数。我有一个很长的查询,它实际上获取了所有必需的信息,但它很大而且效率很低。有没有办法让它更小/更高效?任何帮助表示赞赏。

// get total days worked, unpaid days, bank holidays, holidays, sicknesses
// and absences within given date range for given users            
$sql = "SELECT us.username, daysWorked, secondsWorked,
            unpaidDays, bankHolidays, holidays, sicknesses, absences
FROM
  (SELECT username FROM users WHERE clockin_valid='1') us
  LEFT JOIN (
    SELECT   username, selectedDate, count(isUnpaid) AS unpaidDays
    FROM     tbl_user_work_settings
    WHERE    isUnpaid = '1'
         AND selectedDate>='$startDate'
         AND selectedDate<='$endDate'
    GROUP BY username
  ) u ON us.username=u.username
  LEFT JOIN (
    SELECT   username, count(isBankHoliday) AS bankHolidays
    FROM     tbl_user_work_settings
    WHERE    isBankHoliday='1'
         AND selectedDate>='$startDate'
         AND selectedDate<='$endDate'
    GROUP BY username
  ) bh ON us.username=bh.username
  LEFT JOIN (
    SELECT   username, count(isHoliday) AS holidays
    FROM     tbl_user_work_settings
    WHERE    isHoliday='1'
         AND selectedDate>='$startDate'
         AND selectedDate<='$endDate'
    GROUP BY username
  ) h ON us.username=h.username
  LEFT JOIN (
    SELECT   username, count(isSickness) AS sicknesses
    FROM     tbl_user_work_settings
    WHERE    isSickness='1'
         AND selectedDate>='$startDate'
         AND selectedDate<='$endDate'
    GROUP BY username
  ) s ON us.username=s.username
  LEFT JOIN (
    SELECT   username, count(isOtherAbsence) AS absences
    FROM     tbl_user_work_settings
    WHERE    isOtherAbsence='1'
         AND selectedDate>='$startDate'
         AND selectedDate<='$endDate'
    GROUP BY username
  ) a ON us.username=a.username
  LEFT JOIN (
    SELECT   username, count(DISTINCT DATE(in_time)) AS daysWorked,
                SUM(seconds_duration) AS secondsWorked
    FROM     tbl_clockins
    WHERE    DATE(in_time)>='$startDate'
         AND DATE(in_time)<='$endDate'
    GROUP BY username
  ) dw ON us.username=dw.username";

if(count($selectedUsers)>0)
  $sql .= " WHERE (us.username='"
       .  implode("' OR us.username='", $selectedUsers)."')";

$sql .= " ORDER BY us.username ASC";

【问题讨论】:

    标签: mysql sql performance join


    【解决方案1】:

    您可以在使用tbl_user_work_settings 表时使用SUM(condition)

    // get total days worked, unpaid days, bank holidays, holidays, sicknesses
    // and absences within given date range for given users            
    $sql = "
      SELECT      users.username,
                  SUM(ws.isUnpaid      ='1')       AS unpaidDays,
                  SUM(ws.isBankHoliday ='1')       AS bankHolidays,
                  SUM(ws.isHoliday     ='1')       AS holidays,
                  SUM(ws.isSickness    ='1')       AS sicknesses,
                  SUM(ws.isOtherAbsence='1')       AS absences,
                  COUNT(DISTINCT DATE(cl.in_time)) AS daysWorked,
                  SUM(cl.seconds_duration)         AS secondsWorked
      FROM        users
        LEFT JOIN tbl_user_work_settings           AS ws
               ON ws.username = users.username
              AND ws.selectedDate  BETWEEN '$startDate' AND '$endDate'
        LEFT JOIN tbl_clockins                     AS cl
               ON cl.username = users.username
              AND DATE(cl.in_time) BETWEEN '$startDate' AND '$endDate'
      WHERE       users.clockin_valid='1'";
    
    if(count($selectedUsers)>0) $sql .= "
              AND users.username IN ('" . implode("','", $selectedUsers) . "')";
    
    $sql .= "
      GROUP BY    users.username
      ORDER BY    users.username ASC";
    

    顺便说一下(也许更多的是为了其他读者的利益),我真的希望您通过在将 PHP 变量插入 SQL 之前正确转义它们来避免 SQL 注入攻击。理想情况下,您根本不应该这样做,而是将这些变量作为准备好的语句的参数传递给 MySQL(不会针对 SQL 进行评估):阅读有关 Bobby Tables 的更多信息。

    另外,顺便说一句,您为什么将整数类型作为字符串处理(通过将它们括在单引号字符中)?这是不必要的,而且在 MySQL 中必须执行不必要的类型转换是一种资源浪费。确实,如果isUnpaid等各个列都是0/1,可以改上面的去掉相等性测试,直接用SUM(ws.isUnpaid)等即可。

    【讨论】:

    • 从 where 子句中删除“isUnpaid = '1'”。
    • @Romil:谢谢,我也发现了!
    • 您好,非常感谢您的回复和建议。我采用了您在上面提供的解决方案,并且必须对其进行稍微修改才能使其正常工作,但是我现在有了一个可行的解决方案。我已将其包含在下面供您参考
    • 看来我有字数限制,所以无法将其粘贴到一条评论中。不用担心。我不得不在 FROM 之前删除一个额外的逗号。我还必须使用 ON 而不是 USING 因为我需要将日期过滤器作为“AND”ed 标准来加入 tbl_users 和 tbl_user_work_settings 的条件..否则我有时不会得到任何结果..而我想要所有用户的行(使用NULL 值)在必要时
    • @RomanAli:很公平。我已更新我的答案以包含这些更改。
    【解决方案2】:

    将要加入的每个表放入一个临时表... 然后在临时表的可连接字段上创建索引... 并使用临时表进行查询。

    例子:

    SELECT   username, selectedDate, count(isUnpaid) AS unpaidDays
    INTO     #TempTable1
    FROM     tbl_user_work_settings
    WHERE    isUnpaid = '1'
         AND selectedDate>='$startDate'
         AND selectedDate<='$endDate'
    GROUP BY username
    create clustered index ix1 on #TempTable1 (username)
    

    【讨论】:

    • 此外,虽然临时表在优化查询方面非常有用,但它们也可以创造更多的工作。在您的示例中,您不会重新使用任何临时表,因此只会用比需要更多的数据填充 Temp DB(除非 $SelectedUsers 是所有用户)。关于添加索引,给永久表添加索引tbl_user_work_settings会是更好的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2011-09-03
    • 1970-01-01
    • 2019-07-08
    • 1970-01-01
    • 2011-12-21
    • 1970-01-01
    • 1970-01-01
    • 2018-05-09
    相关资源
    最近更新 更多