3

PHP/MySQL でチケット システムを設計しています。各チケットは、'tickets' テーブルの 1 つの行を表します。ユーザーは、各チケットに複数の「コメント」を追加できます。

チケットを表示するとき、「コメント」テーブルで SQL クエリを実行して、対応するチケット ID にリンクされたすべてのコメントを選択します。

しかし、今度は検索ページを設計する必要があります。このページでは、一致するすべてのチケットが返され、リンクされたコメントがすぐに表示されます (最後の 5 件などの一部)。

単一のチケット ビュー ページで使用したのと同じ SQL クエリを実行することでこれを機能させますが、検索クエリで一致する行ごとにこれを繰り返します。したがって、1500 の一致する行がある場合、それは「comments」テーブルに対する 1500 の SQL クエリを意味します。はい、あまり効率的ではありません...

今、私は疑問に思っていました: コメント テーブルをチケット テーブルに結合する方法はありますが、コメント テーブルの複数の行を 1 つの列に「圧縮」する方法はありますか?

コメント行に対して CONCAT() を実行し、コンマ区切りの値として返すことを考えていました。これを php で再び爆発させて、各チケットのすべてのコメントを含む配列を取得することができました。しかし、これは最も効果的な方法ですか、それとももっと良い方法がありますか?

編集:コメントがまだないチケットも、クエリによって返される必要があることに注意してください

4

3 に答える 3

2

そうそう、多くの人が遭遇しがちな「セットのグループ化」の典型的な問題...

複数のレコード (コメント) を 1 つの行 (イベント) に結合することについて話すとき、MySQL を使用して確実に (さまざまな量の行を考慮する場合のように) これを行う唯一の方法は、GROUP_CONCAT()1 つまたは複数の行にまたがる情報を単一の区切り文字列。

と呼ばれる機能を使用して、情報をさまざまな量の実際のPIVOTにグループ化することもできますが、残念ながら、PIVOTMySQL では使用できません。

いずれにしても、各チケットのコメントのサブセットをフォーマットして表示するには、いくつかのアプリケーション ロジック (展開など) が必要になります。

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

SELECT...の 4 番目の列を除いて、ここにあるものはすべてかなり簡単です。

最も内側には、 がありCONCAT()ます。これにより、各コメントのフィールドが連結され、各コメントの複数の属性 (日付、実際のテキスト、おそらく ID など) を取得できるようになります。

aloneの後CONCAT()、単一のコメントは次のようになります。

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

は、各属性を区切るために使用:::する区切り文字の 1 つです。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最初の 5 つのコメントのみが選択されます。コメントを最新順に並べたので、基本的には、各チケットで最新の 5 つのコメントのみを選択することになります。

次に、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
            }
        }
    }

タイトルなどのフィールドにカンマが存在する可能性があり、スクリプトで文字列を間違った場所で区切らないようにするため、これらの特定の区切り記号を使用します。この誤った分離の可能性は、 を使用する場合の主な欠点の 1 つであるGROUP_CONCAT()ため、フィールド値内に区切り文字が存在する可能性が低いことに基づいて、どの区切り文字を使用するのが最適かを決定する必要があります。

于 2012-06-21T10:23:44.343 に答える
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%';

これは、コメントをチケットにリンクし直す必要があるため、アプリケーション側でさらに作業が必要になることを意味します。

私のアドバイス: オールインワン クエリを使用してください。

于 2012-06-21T11:38:23.667 に答える
1

コメントを関連付けるには、コメントにチケット ID のレコードが必要なので、チケットから ID を取得し、JOIN を使用してコメントを取得できるようにする必要があります。

私はWordpress DBで非常に似たようなことをしているので、そのレイアウトしかできません。wordpress では、投稿にはコメントに関連する ID があります。

したがって、1 つのクエリで実行できるはずです。それは、テーブルの設計がこのタイプの関連付けを許可している場合です。

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>";

    }
}
于 2012-06-21T09:39:12.740 に答える