2013-03-25 3 views
0

Мне нужно запустить этот скрипт каждый день (cron), чтобы обновить общий объем отзывов за 1 месяц (по крайней мере, это то, что я разработал сейчас). Вот код, который у меня есть. Есть ли у кого-нибудь лучшее представление о том, как мне это нужно? Может быть, меняются по пути, я об этом или оптимизирую свой скрипт updateMonthlyFeedback.php?MySQL PHP Query Optimization - отзывы

updateMonthlyFeedback.php

session_start(); 
include("db.php"); 

$sql="SELECT MAX(uid) as maxUID FROM users"; 
$result = mysql_query($sql) or die(mysql_error()); 
$row = mysql_fetch_array($result); 
$maxUID = $row['maxUID']; 

for($i=0;$i<$maxUID;$i++){ 
    $sql="SELECT COUNT(*) as negativeCount FROM feedbacks WHERE date_created >= (CURDATE() - INTERVAL 30 DAY) AND type = -1 AND uid = '$i'"; 
    $result = mysql_query($sql) or die(mysql_error()); 
    $row = mysql_fetch_array($result); 
    $negativeCount = $row['negativeCount']; 
    $sql="SELECT COUNT(*) as neutralCount FROM feedbacks WHERE date_created >= (CURDATE() - INTERVAL 30 DAY) AND type = 0 AND uid = '$i'"; 
    $result = mysql_query($sql) or die(mysql_error()); 
    $row = mysql_fetch_array($result); 
    $neutralCount = $row['neutralCount']; 
    $sql="SELECT COUNT(*) as positiveCount FROM feedbacks WHERE date_created >= (CURDATE() - INTERVAL 30 DAY) AND type = 1 AND uid = '$i'"; 
    $result = mysql_query($sql) or die(mysql_error()); 
    $row = mysql_fetch_array($result); 
    $positiveCount = $row['positiveCount']; 
    $sql = "UPDATE feedback_totals SET negativeCount = '$negativeCount', neutralCount = '$neutralCount', positiveCount = '$positiveCount' WHERE uid = '$i'"; 
    $result=mysql_query($sql) or die(mysql_error()); 
} 

MySQL Таблицы

CREATE TABLE feedback_totals (
    uid      VARCHAR(40), 
    negativeCount   int, 
    neutralCount   int,     
    positiveCount   int, 
    halfStarCount   int, 
    oneStarCount   int, 
    oneHalfStarCount  int, 
    twoStarCount   int, 
    twoHalfStarCount  int, 
    threeStarCount   int, 
    threeHalfStarCount  int, 
    fourStarCount   int, 
    fourHalfStarCount  int, 
    fiveStarCount   int, 
    PRIMARY KEY    (uid) 
    #FOREIGN KEY   (uid) REFERENCES users(uid) ON DELETE CASCADE 
); 

CREATE TABLE feedback_last_month (
    uid      VARCHAR(40), 
    negativeCount   int, 
    neutralCount   int,     
    positiveCount   int, 
    halfStarCount   int, 
    oneStarCount   int, 
    oneHalfStarCount  int, 
    twoStarCount   int, 
    twoHalfStarCount  int, 
    threeStarCount   int, 
    threeHalfStarCount  int, 
    fourStarCount   int, 
    fourHalfStarCount  int, 
    fiveStarCount   int, 
    PRIMARY KEY    (uid) 
    #FOREIGN KEY   (uid) REFERENCES users(uid) ON DELETE CASCADE 
); 

CREATE TABLE feedback (
    feedback_id    INT NOT NULL AUTO_INCREMENT, 
    uid      VARCHAR(40),INDEX (uid), 
    sender_id    VARCHAR(40), 
    type     int,    #-1 = neg, 0 = neutral, 1 = positive 
    starCount    VARCHAR(40), 
    description    VARCHAR(80), 
    date_created   timestamp DEFAULT CURRENT_TIMESTAMP, 
    fromType    VARCHAR(40), # buyer or seller 
    fromUsername   VARCHAR(40), 
    PRIMARY KEY    (feedback_id) 
    #FOREIGN KEY   (uid) REFERENCES users(uid) ON DELETE CASCADE 
); 
+1

Ну, очевидным первым ответом будет удаление цикла 'for' и вместо ограничения запроса на uid,' group on' uid. –

+0

Вы действительно хотите выбрать COUNT() из * users * вместо подсчета количества строк * обратной связи? – Hazzit

+0

Да, я обновил это. Просто написал быстрый прототип и еще не тестировал. Просто хотел иметь что-то, а не ничего, чтобы объяснить мой вопрос. – Toosick

ответ

1

ОК, как и все остальные, используйте PDO/MYSQLI. Однако, используя код, который у вас уже есть, вот два метода, которые могут работать и работать лучше.

Сначала используются коррелированные подзапросы для получения отрицательных/положительных/нейтральных значений. Это хорошо, потому что оно короткое, однако оно никоим образом не идеально. Вы по-прежнему выполняете тонну запросов в базе данных (3 для каждого uid + начального обновления). Однако вы отправляете только один запрос на сервер с php и позволяете базе данных выполнять всю оставшуюся работу. Это может сработать для нескольких пользователей, но через некоторое время у него появятся проблемы с производительностью.Этот запрос будет обновлять все строки в отзывах. Однако, если в feedback_totals нет строки, она не вставляет новую строку для любых новых uids.

//one query, this is it. updates it all. 
$sql = "UPDATE `feedback_totals` 
     SET 
      `negativeCount`=(SELECT COUNT(*) FROM `users` WHERE `uid`=`feedback_totals`.`uid` AND `date_created` >= (CURDATE() - INTERVAL 30 DAY) AND `type`=-1), 
      `positiveCount`=(SELECT COUNT(*) FROM `users` WHERE `uid`=`feedback_totals`.`uid` AND `date_created` >= (CURDATE() - INTERVAL 30 DAY) AND `type`=1), 
      `neutralCount`=(SELECT COUNT(*) FROM `users` WHERE `uid`=`feedback_totals`.`uid` AND `date_created` >= (CURDATE() - INTERVAL 30 DAY) AND `type`=0)"; 
$result=mysql_query($sql) or die(mysql_error()); 

Во-вторых, вероятно, лучше в долгосрочной перспективе. Запросить все данные, необходимые в одном запросе. сверните этот результат, форматируя его в php, перебирая его и делая обновления. Вероятно, это будет работать лучше, потому что вы выполняете гораздо меньшее количество запросов (1 для получения данных + 1 для каждого uid).

//query for all the data 
$sql="SELECT 
      `uid`, 
      `type`, 
      COUNT(*) AS cnt 
     FROM `users` 
     WHERE `date_created` >= (CURDATE() - INTERVAL 30 DAY) 
     GROUP BY `uid`,`type`"; 
$result=mysql_query($sql) or die(mysql_error()); 

$data = array(); 
//loop through the result 
while($row=mysql_fetch_assoc($result)){ 
    //if the uid is not in $data 
    if(!isset($data[$row['uid']])){ 
     //add it with a blank array 
     $data[$row['uid']] = array('negativeCount'=>0,'neutralCount'=>0,'positiveCount'=>0); 
    } 
    //add to the data for this uid depending on type 
    if($row['type']==-1){ 
     $data[$row['uid']]['negativeCount']=$row['cnt']; 
    } elseif($row['type']==1){ 
     $data[$row['uid']]['positiveCount']=$row['cnt']; 
    } else { 
     $data[$row['uid']]['neutralCount']=$row['cnt']; 
    } 
} 

//now loop through the data and update the table 
foreach($data as $uid=>$cnt){ 
    $sql = "UPDATE `feedback_totals` 
      SET 
       `negativeCount`={$cnt['negativeCount']}, 
       `positiveCount`={$cnt['positiveCount']}, 
       `neutralCount`={$cnt['neutralCount']} 
      WHERE `uid`=$uid"; 
    $result=mysql_query($sql) or die(mysql_error()); 
} 
+0

Хорошо, я понимаю. Но из-за пределов кода у меня уже есть. Будет ли лучший способ обойти это? FYI. Итоговые значения обратной связи инициализируются при создании пользователя. – Toosick

+0

Код, который я дал, - это всего лишь переконфигурация вашего кода. Он должен работать как прямая замена. Оба * должны * работать (у меня нет вашей базы данных для тестирования) и все еще лучше, чем у вашего кода. –

+0

Как я должен обновлять эти итоги каждый день или есть лучший способ? – Toosick

-1

сначала попробуйте изменить Mysql для Mysqli

просто изменить db.php для:

$mysqli = new mysqli("localhost", "XXX", "XXX", "XXX"); 

if ($mysqli->connect_errno) { 
    printf("Connect failed: %s\n", $mysqli->connect_error); 
    exit(); 
} 

И изменить код:

session_start(); 
include("db.php"); 
$sql="SELECT MAX(uid) as maxUID FROM users; "; 

for($i=0;$i<$maxUID;$i++){ 
    $sql.="SELECT COUNT(*) as negativeCount FROM users WHERE date_created >= (CURDATE() - INTERVAL 30 DAY) AND type = -1 AND uid = '$i'; "; 
    $sql.="SELECT COUNT(*) as neutralCount FROM users WHERE date_created >= (CURDATE() - INTERVAL 30 DAY) AND type = 0 AND uid = '$i'; "; 
    $sql.="SELECT COUNT(*) as positiveCount FROM users WHERE date_created >= (CURDATE() - INTERVAL 30 DAY) AND type = 1 AND uid = '$i'; "; 
    $sql.="UPDATE feedback_totals SET negativeCount = '$negativeCount', neutralCount = '$neutralCount', positiveCount = '$positiveCount' WHERE uid = '$i'; "; 
} 

if ($mysqli->multi_query($sql)) { 
    do { 
     $rows=array(); 
     if ($result = $mysqli->store_result()) { 
      while($rows[] = mysqli_fetch_assoc($result)); 
      array_pop($rows); 
      $result->free(); 
     } 
     $data[]=$rows; 
    } while ($mysqli->next_result()); 
    print_r($data); 
} else 
    echo "Error with SQL"; 

Это будет onlye сделать один пакетное подключение к БД, и будет печатать все данные в массиве.

+0

, если вы все равно собираетесь менять API БД, вы также можете пойти с PDO; это лучше, чем mysqli. – Spudley

1

Здесь очевидно много повторений. Большинство из них могут быть удалены путем рефакторинга кода, но в качестве отправной точки даже в текущем процессе вы могли бы заставить его работать лучше, используя лучший API баз данных.

Итак, первое, что я рекомендую, - это прекратить использование функций mysql_xxx() и вместо этого переключиться на использование библиотеки PDO. Старые функции mysql в любом случае устарели, поэтому не рекомендуется использовать их, если это вообще возможно, но в этом случае есть определенная причина для использования PDO, поскольку это имеет значительные преимущества в производительности по сравнению с старыми функциями.

PDO позволяет использовать функцию, называемую Prepared Queries, позволяет более эффективно кэшировать запросы к базе данных, если вы повторно вызываете подобные запросы повторно.

Во-вторых, сами вопросы. Да, это определенно можно было бы упростить. Три запроса в цикле можно объединить в один запрос, используя GROUP BY. Запрос будет выглядеть следующим образом:

SELECT COUNT(*) FROM users 
WHERE date_created >= (CURDATE() - INTERVAL 30 DAY) 
AND uid = :uid 
AND type = -1 OR type = 1 OR type = 1 
GROUP BY type 

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

Есть еще немного, что вы могли бы сделать, но это хорошее начало. Я уверен, что вы получите другие ответы, которые помогут вам в дальнейшем.

Надеюсь, что это поможет.

0

Я бы запустить три запроса, чтобы получить информацию вам нужно:

SELECT uid, COUNT(*) as negativeCount FROM users 
WHERE date_created >= (CURDATE() - INTERVAL 30 DAY) AND type = -1 
GROUP BY uid ORDER BY uid ASC"; 

SELECT uid, COUNT(*) as neutralCount FROM users 
WHERE date_created >= (CURDATE() - INTERVAL 30 DAY) AND type = 0 
GROUP BY uid ORDER BY uid ASC"; 

SELECT uid, COUNT(*) as positiveCount FROM users 
WHERE date_created >= (CURDATE() - INTERVAL 30 DAY) AND type = 1 
GROUP BY uid ORDER BY uid ASC"; 

И тогда я шел бы результаты, приращение строки, как они выстроились с текущей жидкостью. Один сложный бит с возвращенными результатами состоит в том, что отсутствие uid указывает количество 0. Но они упорядочены, поэтому вы знаете, что нужно увеличивать возвращаемый результат (либо с помощью используемого вами fetch_row), либо индивидуальный индекс для каждого результатов.

Это будет выглядеть примерно так:

// Have to load up the first result 
$p_row = mysql_fetch_array($positive_result); 
$neu_row = mysql_fetch_array($neutral_result); 
$neg_row = mysql_fetch_array($negative_result); 

for($i = 0; $i < $maxUID; $i++){ 
    $positive = $neutral = $negative = 0; 
    if($p_row[0] == $i){ 
    $positive = $p_row[1]; 
    $p_row = mysql_fetch_array($positive_result); 
    } 
    if($neu_row[0] == $i){ 
    $neutral = $neu_row[1]; 
    $neu_row = mysql_fetch_array($neutral_result); 
    } 
    if($neg_row[0] == $i){ 
    $negative = $neg_row[1]; 
    $neg_row = mysql_fetch_array($negative_result); 
    } 
    $sql = "UPDATE feedback_totals SET negativeCount = '$negative', neutralCount = '$neutral', positiveCount = '$positive' WHERE uid = '$i'"; 
    mysql_query($sql) or die(mysql_error()); 
} 
0

Все это может быть достигнуто с помощью одного запроса SQL:

INSERT INTO `feedback_totals` (uid,negativeCount,positiveCount,neutralcount) 
SELECT users.uid, 
    COUNT(neg.feedback_id) as negativeCount, 
    COUNT(pos.feedback_id) as positiveCount, 
    COUNT(neut.feedback_id) AS neutralCount 
FROM users 
LEFT JOIN feedback neg ON neg.uid =users.uid AND neg.type=-1 AND neg.date_created >=(CURDATE() - INTERVAL 30 DAY) 
LEFT JOIN feedback pos ON pos.uid =users.uid AND pos.type=1 AND pos.date_created >=(CURDATE() - INTERVAL 30 DAY) 
LEFT JOIN feedback neut ON neut.uid=users.uid AND neut.type=0 AND neut.date_created>=(CURDATE() - INTERVAL 30 DAY) 
GROUP BY uid 
ON DUPLICATE KEY UPDATE 
    negativeCount=VALUES(negativeCount), 
    positiveCount=VALUES(positiveCount), 
    neutralCount=VALUES(neutralCount); 

ON DUPLICATE KEY UPDATE позволит запрос для добавления новых строк, а также обновить существующие. Вы можете играть с этим на SQL Fiddle