Использование объединений для устранения нескольких циклов SELECT в цикле - PullRequest
3 голосов
/ 20 марта 2012

Я пытаюсь научить себя PHP / mysql, создав клон целей Джо , если хотите.

Обычно у каждого пользователя есть несколько целей, и каждый день они записывают, сколько раз происходило определенное событие. Например, скажем, моя цель - пить только 1 чашку кофе в день. Если бы у меня было 3 сегодня (упс!), Я бы записал 3 «проверки» на сегодня. Я использую таблицу под названием «чеки» для хранения количества чеков на каждый день.

У меня есть следующие таблицы и образцы вставок:

CREATE TABLE `users` (
  `user_id` int(5) NOT NULL AUTO_INCREMENT,
  `user_email` varchar(50) NOT NULL,
  `user_name` varchar(25) NOT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;

-- Dumping data for table `users`
INSERT INTO `users` VALUES (1, 'xxx@xxx.com', 'xxx');
INSERT INTO `users` VALUES (2, 'some@guy.com', 'SomeGuy');

CREATE TABLE `goal_settings` (
  `goal_id` int(5) NOT NULL AUTO_INCREMENT,
  `user_id` int(5) NOT NULL,
  `goal_description` varchar(100) NOT NULL,
  PRIMARY KEY (`goal_id`),
  KEY `user_id` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

-- Dumping data for table `goal_settings`
INSERT INTO `goal_settings` VALUES (1, 1, 'Run 1k');
INSERT INTO `goal_settings` VALUES (2, 1, 'Read 20 pages');
INSERT INTO `goal_settings` VALUES (3, 2, 'Cups of Coffee');

CREATE TABLE `checks` (
  `check_id` int(40) NOT NULL AUTO_INCREMENT,
  `goal_id` int(5) NOT NULL,
  `check_date` date NOT NULL,
  `check_count` int(3) NOT NULL,
  PRIMARY KEY (`check_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

-- Dumping data for table `checks`
INSERT INTO `checks` VALUES (6, 1, '2012-03-02', 3);
INSERT INTO `checks` VALUES (2, 1, '2012-03-01', 2);
INSERT INTO `checks` VALUES (3, 2, '2012-03-01', 1);
INSERT INTO `checks` VALUES (5, 1, '2012-02-29', 1);

В выводе, который я хотел бы, есть goal_ids в виде строк и диапазон дат в виде столбцов (например, календарь просмотра недели).

goal_id   |  2012-03-01 | 2012-03-02 | 2012-03-03 | ... 2012-03-08 |
--------------------------------------------------------------------
1            2             3           0            ... 0
2            1             0           0            ... 0   

Обратите внимание, что, если в данный день не существует проверок для данной цели, возвращается 0 вместо NULL.

Мне удалось заставить его работать плохо, используя PHP. Усеченный код, но я надеюсь, что он в основном показывает то, что я пробовал: [$ goal_ids - это массив, содержащий все цели, связанные с пользователем. $ num_days - это количество дней (то есть столбцов), которые будут отображаться, а $ goal_days - это массив, используемый для хранения дней, для которых мы ищем информацию].

$mysqli = new mysqli('xxx','xxx','xxx','goals');
$stmt = $mysqli->stmt_init();
$stmt = $mysqli->prepare("SELECT checks.check_count AS check_count 
FROM `checks` WHERE goal_id = ? AND check_date = ?");

for($i=0; $i<=$goal_count - 1; $i++){  
  echo '<tr id="'.$goalid.'">';
      for($j=0; $j <=$num_days; $j++){
          $checkdate = $goal_days[$j];
          $goalid = (integer) $goal_ids[$i];
            if (!$stmt->bind_param("ii", $goalid, $checkdate)) {
              echo "Binding parameters failed: (" . $stmt->errno . ") " . $stmt->error;
            }
            if (!$stmt->execute()) {
              echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
              }
              $stmt->bind_result($check_count);
                if($stmt->fetch()){
              echo "<td>".$check_count."</td>";
             }
             else{
              echo '<td>0</td>';
             }
          }
   echo "</tr>";
} 
echo "</table>";
$stmt->close();

Это, очевидно, неэффективно, потому что для m целей и n дней он делает m x n выбранных операторов.

Из чтения кажется, что я в основном пытаюсь сделать сводную таблицу, но я читал, что они также неэффективны, и я предполагаю, что то, что я делаю, лучше обрабатывается PHP, чем сводная таблица?

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

Надеюсь, я достаточно ясно. Любая помощь или указатели в правильном направлении будет принята с благодарностью.

Спасибо!

Ответы [ 2 ]

1 голос
/ 20 марта 2012

Если я правильно понимаю вашу проблему, я бы посоветовал вам сделать регулярный выбор, при котором каждая комбинация goal_id и check_date будет получать запись в наборе результатов, а затем на стороне клиента вы будете создавать столбец для каждой check_date.скажем, имея массив массивов и вставив в него контрольный счет.

Это должно быть как минимум быстрее, чем использование операторов выбора mxn.

Для большей эффективности ваш sql может отсортировать его по target_id иcheck_date, это приведет к тому, что записи будут сгруппированы вместе.

Вот пример оператора sql:

SELECT check_date, goal_id, check_count FROM checks ORDER BY goal_id, check_date 

Вот пример кода PHP, при условии, что у вас есть массив массивов "$ array_of_arrays "(инициализируется нулем, чтобы избежать проблемы с нулем) с внешним ключом, являющимся goal_id, а внутренним ключом, являющимся check_date:

while ($row = mysqli_fetch_result($result)){
     $row_goal_id = $row["goal_id"];
     $row_check_date = $row["check_date"];
     $array_of_arrays[$row_goal_id][$row_check_date] = $row["check_count"];
}

И затем вы можете использовать массив массивов, чтобы делать то, что выкак, скажем, если вы хотите вывести в качестве примера таблицы HTML, затем объедините внутренний массив с

и tВнешний массив с .

Пример создания и инициализации массива "$ array_of_arrays" может быть следующим (при условии, что у вас есть массив $ goal, содержащийвсе цели и массив $ даты, содержащий все даты, если вы не знаете, то заранее вы можете получить их из таблицы проверок, выполнив SELECT DISTINCT)

 $array_of_arrays = array();   
 foreach ($goals as $key=>$value){
         $array_of_arrays[$value] = array();
         foreach ($checks as $key1=>$value1){
                  $array_of_arrays[$value][$value1] = 0;
         }
   }

Аналогичный подход может бытьиспользуется для генерации окончательной таблицы HTML следующим образом:

$final_array = array();
foreach ($array_of_arrays as $key=>$value){ 
    $final_array[$key] = implode("</td><td>", $value);
}
$final_str = implode("</td></tr><tr><td>", $final_array);
$table_str = "<table><tr><td>" . $final_str . "</td></tr></table>";         
1 голос
/ 20 марта 2012

Попробуйте добавить таблицу дней (или создать временную таблицу во время выполнения), содержащую только последовательные даты или даты, которые вам нужны. Затем вы можете получить хороший список чеков с помощью одного запроса:

SELECT g.goal_id, d.day, COALESCE(c.check_count,0) as check_count 
FROM
  goal_settings g 
JOIN 
  days d
LEFT JOIN 
  checks c 
ON c.goal_id = g.goal_id AND c.check_date = d.day
WHERE 
  g.user_id = 1 
  AND d.day BETWEEN '2012-03-01' AND '2012-03-03'
ORDER BY g.goal_id, d.day

, что приводит к набору строк, например:

goal_id  | day        | check_count
    1    | 2012-03-01 |      2
    1    | 2012-03-02 |      3
    1    | 2012-03-03 |      0
    2    | 2012-03-01 |      1
    2    | 2012-03-02 |      0
    2    | 2012-03-03 |      0

А затем извлекайте эти строки в цикле с помощью php, чтобы создать красивую HTML-таблицу - если goal_id изменился, вывести новую строку и т. Д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...