4

目前我们正在使用三个嵌套的 foreach 循环来获取运行批处理的信息。但是,我相当确定我们可以使用带有连接和子查询的单个 MySQL 语句来获取信息。

我们有大约 30 个类别,有 2000 个用户。我们的目标是大约 100 个类别和 100000 个用户,但显然 foreach 循环并不理想(即使现在它们需要大约一分钟才能运行)。

情况: 用户希望在某个区域有可用于交易的工作时收到通知

目标: 要放入发件箱的批处理(每日、每周等)通知

技术: PHP、MySQL

到目前为止我所拥有的:

数据库:

 "table.notification_options" : [id][user_id][category]
 "table.user" : [id][user_id][method_of_contact][contact_frequency][center_of_work_area_long][center_of_work_area_lat][distance_from_center]
 "table.work" : [id][post_date][longitude][latitude][category]

代码:

foreach user{
    foreach category tracked{
        foreach job in category posted <> $current_date-$batch_frequency{
            if job inside workspace{
                notify_user(job);
            }
        }
   }
}

期望的结果是一个 job_ids 数组,其中 user_id 作为键 [user_id]=>{jobs}

例如

    {
        [user1]{
                 job1,
                 job4,
                 job28
               },
        [user34]{
                 job3,
                 job4,
                 job34,
                 job78
                }
     {

编辑:

我可以更高效地为一个用户选择所有作业。但它仍然需要一个 foreach 用户。

   $category_id = get_category_from_notification_options($userid);
   $user_distance = get_user_work_distance($userid);
    "SELECT DISTINCT work.ID as workID, ( 6371 * acos( cos( radians(-46.409939) ) * cos( radians( jobs.lat ) ) * cos( radians( jobs.lng ) - radians(168.366180) ) + sin( radians(-46.409939) ) * sin( radians( jobs.lat ) ) ) ) 
        AS distance 
        FROM work,user
        WHERE work.categoryID == $category_id
        HAVING distance < $user_distance
        ORDER BY distance";
4

2 回答 2

1

在我看来,您获取的距离似乎是从用户表中获取的(distance_from_center 字段?)

SELECT DISTINCT ser.user_id, work.ID as workID, ( 6371 * acos( cos( radians(-46.409939) ) * cos( radians( jobs.lat ) ) * cos( radians( jobs.lng ) - radians(168.366180) ) + sin( radians(-46.409939) ) * sin( radians( jobs.lat ) ) ) ) AS distance 
FROM notification_options
INNER JOIN jobs ON notification_options.category = jobs.category
INNER JOIN user ON notification_options.user_id = user.user_id
HAVING distance < user.distance_from_center
ORDER BY distance

编辑-如果您只想按距离顺序为每个用户列出工作列表(如果需要,您可以分解为一个数组以在 php 中进行处理-尽管可能更容易使用上述查询来构建数组),那么您可以使用像这样的东西: -

SELECT user_id, GROUP_CONCAT(workID ORDER BY distance)
FROM (
SELECT DISTINCT ser.user_id, work.ID as workID, ( 6371 * acos( cos( radians(-46.409939) ) * cos( radians( jobs.lat ) ) * cos( radians( jobs.lng ) - radians(168.366180) ) + sin( radians(-46.409939) ) * sin( radians( jobs.lat ) ) ) ) AS distance 
FROM notification_options
INNER JOIN jobs ON notification_options.category = jobs.category
INNER JOIN user ON notification_options.user_id = user.user_id
HAVING distance < user.distance_from_center) Sub1
于 2013-03-18T14:45:19.270 回答
1

我认为您应该以相反的方式进行操作以提高效率。下面我将向您展示我用来创建查询的过程。所以只有最后的查询是你需要的。但我解释了这些步骤,所以它可能会在未来帮助你。

First I would select all jobs. Most likely there are a lot less jobs then users if your goal is 100.000 users.

select JOB.id, JOB.category
FROM table.work JOB

Now we have all the jobs, lets see which users want to be notified about it.

select JOB.id, JOB.category, NOTIFY.user_id
FROM table.work JOB
LEFT JOIN table.notification_options NOTIFY
ON JOB.category=NOTIFY.category
WHERE NOTIFY.user_id IS NOT NULL

This creates a list with for each job, all the userID's that want to be notified about it. I added the WHERE clause to remove all jobs from the list nobody wants to see. Now we can JOIN the users table to get user details aswell.

select JOB.id
     , JOB.post_date
     , JOB.longitude
     , JOB.latitude
     , USR.user_id
     , USR.method_of_contact
     , USR.contact_frequency
     , USR.center_of_work_area_long
     , USR.center_of_work_area_lat
     , USR.distance_from_center
     , ((ACOS(SIN(USR.center_of_work_area_lat * PI() / 180) * SIN(JOB.latitude * PI() / 180) + COS(USR.center_of_work_area_lat * PI() / 180) * COS(JOB.latitude * PI() / 180) * COS((USR.center_of_work_area_long – JOB.longitude) * PI() / 180)) * 180 / PI()) * 60 * 1.1515) AS `distance`
FROM table.work JOB
LEFT JOIN table.notification_options NOTIFY
ON JOB.category=NOTIFY.category
LEFT JOIN table.user USR
ON NOTIFY.user_id=USR.user_id
WHERE NOTIFY.user_id IS NOT NULL
HAVING `distance`<=USR.distance_from_center
ORDER BY USR.user_id ASC, distance ASC

I included the distance in the query. Notice that I use HAVING to check if the distance is smaller then the user supplied. If you would add it to the WHERE clause you would get an error saying distance is an unknown column. I also added the ORDER BY class to first sort it on user ID and then on distance. This will make it easier to create the array you want in PHP.

Now there are a lot of ways to implement the daily/weekly intervals. One of them is to create seperate scripts for each interval and only select the users that set it. For example, you could create a script 'daily.php' which you run each day and have the following query

select JOB.id
     , JOB.post_date
     , JOB.longitude
     , JOB.latitude
     , USR.user_id
     , USR.method_of_contact
     , USR.contact_frequency
     , USR.center_of_work_area_long
     , USR.center_of_work_area_lat
     , USR.distance_from_center
     , ((ACOS(SIN(USR.center_of_work_area_lat * PI() / 180) * SIN(JOB.latitude * PI() / 180) + COS(USR.center_of_work_area_lat * PI() / 180) * COS(JOB.latitude * PI() / 180) * COS((USR.center_of_work_area_long – JOB.longitude) * PI() / 180)) * 180 / PI()) * 60 * 1.1515) AS `distance`
FROM table.work JOB
LEFT JOIN table.notification_options NOTIFY
ON JOB.category=NOTIFY.category
LEFT JOIN table.user USR
ON NOTIFY.user_id=USR.user_id
WHERE NOTIFY.user_id IS NOT NULL
AND USR.contact_frequency = 'daily'
HAVING `distance`<=USR.distance_from_center
ORDER BY USR.user_id ASC, distance ASC

Now we have the query, lets create the PHP code for it. We can loop trough all the rows and create the array. Obviously instead of creating the array you could also directly process the result. Because if you create an array first, you do need to loop trough that array again afterwards.

<?php
$arNotify = array();
foreach ($queryresult as $row) {
  $userid = $row->user_id;
  $jobid = $row->id;

  //check if there is an entry for the user in the database, else create it
  if (!array_key_exists($userid, $arNotify))
    $arNotify[$userid] = array();

  //and then push the job
  $arNotify[$userid][] = $jobid;

  //the array is being created, but I still like to process the job directly
  //notify_user($userid, $jobid);

}

var_dump($arNotify);
?>

There you go, the array as you want with the jobs sorted on closest first.

于 2013-03-21T09:59:15.020 回答