2012-06-21 2 views
3

Я разрабатываю систему билетов в PHP/MySQL. Каждый билет представляет собой одну строку в таблице «билетов». Пользователи могут добавлять несколько «комментариев» к каждому билету.MySQL: Свяжите несколько записей из таблицы B, на одну запись таблицы A в одной строке

При просмотре билет я просто сделать запрос SQL на столе «Комментарии», чтобы выбрать все комментарии, связанные с corresponsing TicketID

Однако, теперь я разработать страницу поиска, которая возвращает любые подходящие билеты и немедленно показывают (часть, например, последние 5) свои связанные комментарии.

У меня это работает, выполняя тот же SQL-запрос, который я использую на странице просмотра одного билета, но повторяю это для каждой соответствующей строки в поисковом запросе. Поэтому, если у вас есть 1500 подходящих строк, это означает 1500 SQL-запросов в таблице комментариев, да, не очень эффективно ...

Теперь мне было интересно: есть ли способ присоединиться к таблице комментариев к таблице билетов , но имеют несколько строк из таблицы комментариев, «сжатых» в один столбец?

Я думал о выполнении CONCAT() в строках комментариев, возвращая их как разделенные запятыми значения, которые я мог бы затем взорвать() снова в php, чтобы получить массив, содержащий все мои комментарии для каждого билета. Но это самый эффективный способ или что-то лучше?

EDIT: Имейте в виду, что билеты, которые не имеют каких-либо комментариев, должны мы также возвращаемый запросом

+0

Не могли бы вы разместить структуру ваших двух таблиц и какие поля в таблице 'comments' вы хотели бы получить для каждого билета? –

ответ

2

Zane Bien ответил на вопрос CONCAT, я не буду комментировать его, потому что добавить нечего, но вы также спросили, является ли это самым эффективным способом, поэтому я пойду за этим.

Мне действительно не нравится решение CONCAT. Это не философия SQL, и вы никогда не знаете, что пользователи будут писать в области комментариев.

Что касается SQL, то, правильный способ сделать это:

SELECT 
    tickets.*, 
    comments.* 
FROM tickets 
LEFT JOIN comments ON comments.ticket_id = tickets.id 
WHERE tickets.title LIKE '%whatever%'; 

Тогда в PHP вы просто перебрать результирующую и построить выход. Обратите внимание, что LEFT JOIN будет по праву возвращать билеты без комментариев.

Если вы чувствуете себя немного неловко - , но не должны! - имея информацию о билетах на каждой линии можно разделить запрос в 2:

SELECT 
    tickets.* 
FROM tickets 
WHERE tickets.title LIKE '%whatever%'; 

И

SELECT 
    comments.* 
FROM comments 
INNER JOIN tickets ON comments.ticket_id = tickets.id 
WHERE tickets.title LIKE '%whatever%'; 

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

Мой совет: перейдите к запросу «все-в-одном».

+0

Вау, вот что я искал! Огромное спасибо! Я действительно пойду за все-в-одном запросе и отсортирую несколько строк билета в PHP. – Alex

+1

Это альтернативное решение, которое я собирался предложить. Это связано с еще большей логикой приложения при обнаружении изменений в 'ticket_id', когда вы перебираете результат. Преимущество состоит в том, что вам не нужно беспокоиться о рисках разграничения. Недостатком является то, что даже если вам нужны только 5 последних комментариев, вам придется перебирать ** все ** комментарии каждого билета в вашем коде приложения. Вы можете заказывать комментарии по дате курса, но после того, как вы получите первые пять, вам придется «продолжать» до тех пор, пока значение «ticket_id» не изменится. –

+0

Возможно, некоторые билеты содержат более 800 комментариев? Это может немного замедлить вас. Я точно ответил на вопрос в соответствии с заголовком («несколько записей в одну строку»), но я бы сказал, что это решение лучше и было бы правильным путем. Преимущество полного устранения риска взлома приложения на определенный вклад перевешивает недостаток в * потенциально * немного больше времени рендеринга. –

1

Комментарии должны иметь запись билета ID для них, чтобы быть связано, чтобы вы должен иметь возможность захватить идентификатор из билета и использовать JOIN для захвата комментариев.

Я делаю что-то очень похожее с Wordpress DB, поэтому я могу только пойти по этому макету. В wordpress сообщение имеет идентификатор, который относится к комментарию.

Итак, вы должны быть в состоянии сделать это одним запросом, я думаю. То есть, если дизайн таблиц допускает такой тип ассоциации, что и должно быть.

SELECT tickets.ID, tickets.title, tickets.content, comments.content 
FROM tickets 
INNER JOIN comments ON comments.ticket_id = tickets.ID 
WHERE tickets.content LIKE '%the search term%' 
ORDER BY comments.comment_date 

У меня есть только функциональное понимание SQL, так что другие ребята на здесь может исправить меня в правильном направлении, чтобы сделать это, но я думаю, что он должен работать. Фактический элемент поиска этого запроса просто для примера, я не знаю, что именно вы на самом деле ищете.

После доработки:

$query = "SELECT tickets.ID ticket_id, tickets.title title, tickets.content content 
      FROM tickets 
      WHERE tickets.content LIKE '%the search term%' 
      AND tickets.title LIKE '%the search term%'"; 

$tickets = $db->fetch_array($query); 


foreach ($tickets as $ticket) { 
    echo "<h1>$ticket['title']</h1>"; 
    echo "<h1>$ticket['content']</h1>"; 

    $query = "SELECT comments.title title, comments.content content 
       FROM comments 
       WHERE comments.ID = {$ticket['ticket_id']} 
       ORDER BY comments.date DESC 
       LIMIT 5"; 

    $comments = $db->fetch_array($query); 

    if ($comments) { 

     echo "<div class="comments">"; 

     foreach($comments as $comment) { 
      echo "<div class="comment">"; 
      echo "<h1>$comment['title']</h1>"; 
      echo "<h1>$comment['content']</h1>"; 
      echo "</div>"; 
     } 

     echo "</div>"; 

    } 
} 
+0

Только что понял, что я тяну билет несколько раз без причины ... Оливер имеет правильную идею, разбить запрос, получив билет первым, а затем захватить комментарии, используя идентификатор билетов. Если вам не нужно также искать содержание комментариев! –

+0

Если вы это сделаете, то отобразится только 1 комментарий. Содержимое поля, содержащее только один комментарий. – Alex

+0

Первый запрос неверен. Я просто оставил его, чтобы показать выработку и мою ошибочную логику. –

2

Ах да, типичная проблема «группировка взаимозачетов», что многие люди, как правило, сталкиваются с ...

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

Вы можете также информацию о группе в переменный размер фактических столбцов используя функцию под названием PIVOT, но, к сожалению, PIVOT не доступен в MySQL.

В любом случае вам понадобится логика приложения (взрывающаяся и т. Д.).) для форматирования и отображения подмножества комментариев для каждого билета.

Что касается SQL, вы можете сделать что-то вроде следующего:

SELECT 
    a.ticket_id, 
    a.ticket_title, 
    a.date_created, 
    SUBSTRING_INDEX(GROUP_CONCAT(CONCAT(LEFT(b.comment_txt, 150), '...', ':::', b.date_posted) ORDER BY b.date_posted DESC SEPARATOR '|||'), '|||', 5) AS comment_list 
FROM 
    tickets a 
LEFT JOIN 
    comments b ON a.ticket_id = b.ticket_id 
WHERE 
    a.ticket_title LIKE '%search_term%' 
GROUP BY 
    a.ticket_id 

Все здесь довольно просто для 4-го столбца в SELECT кроме ... так что давайте разбить его:

В большинстве случаев у нас есть CONCAT(). Это означает, что он объединяет поля каждого комментария вместе, чтобы вы могли получать несколько атрибутов каждого комментария (например, дату, фактический текст и, возможно, идентификатор и т. Д.).

После одной только CONCAT(), один комментарий может выглядеть примерно так:

Lorem Ipsum dolor sit amet consecteur...:::2012-06-21 00:00:00 

::: является одним из разделителей, которые вы бы explode() на отделить каждый атрибут.

Перемещение наружу, то GROUP_CONCAT() затем сцепляет каждый строку вместе. На данный момент мы в основном объединяем конкатенации. Кроме того, самые последние комментарии появляются в начале строки из-за ORDER BY b.date_posted внутри функции.

Комментарий список может выглядеть следующим образом:

Lorem Ipsum dolor sit amet consecteur...:::2012-06-21 00:00:00|||Cras aliquam neque quam, eget facilisis nulla...:::2012-06-18 00:00:00 

||| является разделителем вы бы использовать, чтобы отделить каждый комментарий.

Переходя далее, SUBSTRING_INDEX выбирает только первые пять комментариев. Поскольку мы заказывали комментарии по последним данным, это, по сути, только выбор пяти последних комментариев в каждом билете.

Затем в PHP коде, вы могли бы сделать примерно:

foreach($tickets as $ticket) 
    { 
     // First check if the ticket has comments. Value will be NULL if not. 
     if(!empty($ticket['comment_list'])) 
     { 
      foreach(explode('|||', $ticket['comment_list']) as $comment) 
      { 
       $attributes = explode(':::', $comment); 

       $comment_preview = $attributes[0]; // Get first attribute 
       $date_posted = $attributes[1]; // Get second attribute 
      } 
     } 
    } 

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

+0

Спасибо, что вы предлагаете Зейн, это то, о чем я говорил в своем вопрос, но спасибо, что написал его более чистым. Суть в том, что эта система билетов должна использоваться для взаимодействия между разработчиками и столами поддержки высокого уровня, поэтому комментарии должны содержать фрагменты кода здесь и там. Я думаю, что это всего лишь вопрос времени, пока кто-то не добавит строку разделителя в комментарий. Я буду ждать, если кто-нибудь придумает более приятное решение, если нет, я соглашусь с этим ответом. Спасибо! – Alex

+1

Вы можете сохранить ваши текстовые значения комментариев как Base64, что означает, что никогда не будет столкновения с разделителями, если вы используете. который не является допустимым символом Base64. Вы также можете свернуть свою собственную кодировку, которая отфильтровывает какой бы ограничитель вы ни использовали. –