Возвращает первый результат, где CASE result = X?

#mysql #database #greatest-n-per-group

#mysql #База данных #наибольшее число пользователей на группу

Вопрос:

Я пытаюсь вернуться, чтобы написать запрос, который возвращает только первый результат, где someid (результат первого случая) =X — и игнорирует последующие результаты, где someid =X. Как бы мне изменить следующий запрос для достижения этого?

Вот что я сейчас использую:

 SELECT DISTINCT CASE WHEN $userid != senderid THEN senderid ELSE GROUP_CONCAT(receivers.id SEPARATOR ', ') END someid,   
            CASE WHEN $userid != senderid THEN senders.username ELSE GROUP_CONCAT(receivers.username SEPARATOR ', ') END somename,
            messages.body, 
            messages.time
    FROM messages 
    LEFT JOIN messages_recipients AS recipients ON messages.id = recipients.messageid
    LEFT JOIN users AS senders ON messages.senderid = senders.id
    LEFT JOIN users AS receivers ON recipients.userid = receivers.id
    WHERE recipients.userid = $userid
    OR messages.senderid = $userid
    GROUP BY messages.id
    ORDER BY messages.time DESC
  

Комментарии:

1. .... LIMIT 1 ? извлекает только первую строку?

2. В основном приведенный выше запрос извлекает все сообщения, с которыми я связан как пользователь (которые я либо отправил, либо получил). Некоторые из этих сообщений могут быть между мной и «Джоном», а другие могут быть между мной и «Кейт». Я хочу вернуть последнее сообщение между ‘John’ и мной, а также последнее сообщение между ‘Kate’ и мной, чтобы использовать их в качестве текста «предварительного просмотра» в представлении потоков.

3. Поскольку между пользователями может быть только один поток, я хочу вернуть первое сообщение только для того, чтобы сказать «хорошо, здесь есть поток».

4. Похоже, что между отправителем и получателями существует отношение 1-n. Итак, допустим, вы отправляете сообщение ‘A’ Джону и Кейт; затем Кейт отправляет сообщение ‘B’ как вам, так и Джону. Вы хотите вернуть сообщение ‘A’ между вами и Джоном? Вы также хотите вернуть сообщение ‘B’ между вами и Кейт? Если предыдущее сообщение было только между вами и Кейт (не Джоном) за три дня до этих двух сообщений, исключено ли оно из результатов?

5. Я хочу возвращать только самые новые (в хронологическом порядке) сообщения между Кейт и Джоном

Ответ №1:

Основываясь на информации из ваших комментариев, я предпринял еще одну попытку в этом направлении.

Я очень практичен, поэтому, хотя я могу концептуализировать, что произойдет, я не знал наверняка, пока не начал возиться.

Итак, давайте начнем с данных. Основываясь на том, о чем мы говорили, я создал три таблицы:

Пользователи

 id  user_name
1   Walker    
2   John      
3   Kate      
  

Сообщения

 id  senderid    body    time
1   1   ignored 1                                           2010-04-01 00:00:00.000
2   1   ignored 2                                           2010-04-02 00:00:00.000
3   3   ignored 3                                           2010-04-03 00:00:00.000
4   1   msg A to john and kate                              2010-04-10 00:00:00.000
5   3   msg b from kate to walker and john                  2010-04-11 00:00:00.000
  

*messages_recipients*

 id  messageid   userid
1   1   2
2   1   3
3   2   2
4   3   1
5   4   2
6   4   3
7   5   1
8   5   2
  

Данные настроены таким образом, что вы (Walker) отправили и получили сообщения в течение апреля с Джоном и Кейт.

Вы можете просмотреть список этих сообщений, выполнив следующую инструкцию sql:

 SELECT 
    u2.user_name AS Sender, 
    u1.user_name AS Receiver, 
    m.body, 
    m.time
FROM 
    messages m
JOIN 
    messages_recipients mr ON m.id = mr.messageid
JOIN 
    users u1 ON mr.userid = u1.id
JOIN 
    users u2 ON m.senderid = u2.id
ORDER BY 
    time DESC 
  

Теперь, когда у нас есть тестовый сценарий, сложная часть: возвращает самое последнее сообщение связи между вами (Walker) и Джоном и Кейт. У меня получилось довольно длинное SQL-выражение, и, по общему признанию, я не лучший в их создании, но я думаю, что это все равно сработает:

 BEGIN
    DECLARE @UserId INT = 1

    --A.  Main Query
    SELECT
        CASE 
            WHEN mtemp.senderid = 1 --@UserId 
                THEN 
                    CONCAT('Message To:  ', receivers.user_name)
                ELSE 
                    CONCAT('Message From:  ' , senders.user_name)
                END AS MessageType, 
        mtemp.body, 
        mtemp.time 
    FROM 
        messages mtemp 
        INNER JOIN users senders ON 
            mtemp.senderid = senders.id 
        INNER JOIN 
            (
            --B.  Inner Query determining most recent message (based on time) 
            --    between @UserID and the person @UserID 
            --    Communicated with (either as sender or receiver)
            select userid,max(maxtime) as maxmaxtime from
                (
                    --C.1.  First part of Union Query Aggregating sent/received messages on passed @UserId
                    SELECT 
                        m2.body,
                        kk.* 
                    FROM 
                        `messages` m2 INNER JOIN
                            (
                                SELECT DISTINCT
                                    userid,
                                    MAX(m.time) AS MaxTime
                                FROM
                                    messages m INNER JOIN
                                        messages_recipients mr  ON m.id = mr.messageid AND
                                        m.senderid = 1 --@UserId
                                GROUP BY
                                    mr.userid
                            ) kk on m2.time = kk.MaxTime and m2.senderid = 1 --@UserId

                    UNION
                    --C.2.  Second part of Union Query Aggregating sent/received messages on passed @UserId
                    SELECT 
                        m1.body,
                        jj.* 
                    FROM 
                        `messages` m1 INNER JOIN
                        ----C.2a.  Inner most query of users users who sent message to userid
                        (SELECT DISTINCT
                                senderid as userid,
                                MAX(m.time) AS MaxTime
                            FROM
                                messages m INNER JOIN
                                    messages_recipients mr  ON m.id = mr.messageid AND
                                    mr.userid = 1 --@UserId
                            GROUP BY
                                m.senderid) jj on m1.time = jj.MaxTime and m1.senderid = jj.userid 
                 )       MaximumUserTime
             group by 
                MaximumUserTime.userid 
             ) AggregatedData on mtemp.time = AggregatedData.maxmaxtime 
                    INNER JOIN users receivers on AggregatedData.userid = receivers.id 
            ORDER BY `time` DESC
END
  

Для тестирования в phpMyAdmin вам также придется удалить комментарии и инструкции begin / end declare. Я просто хотел опубликовать это так, как если бы это выглядело в процедуре.

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

Когда я запускаю этот запрос, я получаю следующие результаты:

 MessageType body    time
Message From:  Kate         msg b from kate to walker and john                  2010-04-11 00:00:00.000
Message To:  John       msg A to john and kate                              2010-04-10 00:00:00.000
  

Это самые последние сообщения о Walker среди всех тех пользователей, которые общались с Walker.

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

Ответ №2:

В этом случае может сработать вложенный запрос (хотя его производительность может не сделать его отличным вариантом):

 SELECT DISTINCT CASE WHEN $userid != senderid THEN senderid ELSE GROUP_CONCAT(receivers.id SEPARATOR ', ') END someid,   
            CASE WHEN $userid != senderid THEN senders.username ELSE GROUP_CONCAT(receivers.username SEPARATOR ', ') END somename,
            messages.body, 
            messages.time
    FROM messages 
    INNER JOIN
       (
        SELECT m.senderId, m.receiverId, MAX(m.time) AS MyMaxTime FROM messages GROUP BY m.senderId, m.receiverId
       ) myLittleQuery
            ON messages.senderid = myLittleQuery.senderid AND messages.senderid = myLittleQuery.receiverId AND messages.time = myLittleQuery.MyMaxTime
    LEFT JOIN messages_recipients AS recipients ON messages.id = recipients.messageid
    LEFT JOIN users AS senders ON messages.senderid = senders.id
    LEFT JOIN users AS receivers ON recipients.userid = receivers.id
    WHERE recipients.userid = $userid
    OR messages.senderid = $userid
    GROUP BY messages.id
    ORDER BY messages.time DESC
  

Прошло некоторое время с тех пор, как я был в области MySQL, поэтому мой синтаксис, скорее всего, отключен. Тем не менее, суть этого есть.

Кроме того, в зависимости от того, что вы пытаетесь извлечь, вы могли бы также отказаться от операторов DISTINCT и CASE .

Комментарии:

1. Привет, Рэй, я просто пытаюсь создать список всех разговоров, которые имел пользователь ($ userid). Между двумя пользователями может быть только один разговор, и он не обязательно должен быть взаимным (если я отправил сообщение и не получил ответа, это все еще разговор, и если мне было отправлено сообщение, и я не ответил, это все еще разговор). Есть идеи?