MySql - 用于选择第一选择,第二选择,第三选择等的聚合函数?

时间:2010-06-26 18:07:37

标签: mysql

想象一下,我在名为“messages”的表中有以下数据:

message_id | language_id | message
------------------------------------
1            en            Hello
1            de            Hallo
1            es            Hola
2            en            Goodbye
2            es            Adios

(请注意,我没有“Goodbye”的德语翻译。)

我想为说英语和德语但更喜欢德语的用户选择信息。

意思是,我想要一个看起来像的结果集:

message_id | language_id | message
------------------------------------
1            de            Hallo
2            en            Goodbye

但是,嗯,它证明是棘手的。有什么想法吗?

8 个答案:

答案 0 :(得分:2)

select message_id, language_id, message
from
(select if(language_id="de",0,1) as choice, m.*
 from messages m where m.language_id in ("de","en")
 order by choice) z
group by message_id

通过选择中的“if”设置首选项以强制首选语言到结果集的顶部,因此分组依据将选择它。

您也可以这样做,但上面的回答可能更适合您想要使用它的内容。

select *
from messages m where m.language_id = "de" or
 (language_id = "en" and not exists (select 1 from messages n
                                  where n.language_id = "de" and
                                    n.message_id = m.message_id))

继续你的评论。如果您对使用GROUP BY的特定MySQL行为(没有聚合函数)感到不舒服,那么您可以使用这个更标准的代码:

select *
 from messages m where m.language_id in ("de","en")
  and if(m.language_id="de",0,1) <= (select min(if(n.language_id="de",0,1))
 from messages n where n.message_id = m.message_id)

答案 1 :(得分:2)

此查询将完全符合您的需求:

SELECT * FROM (
    SELECT * FROM messages
    WHERE language_id IN ('en', 'de')
    ORDER BY FIELD(language_id, 'en', 'de') DESC
) m
GROUP BY message_id;

FIELD(language_id, 'en', 'de')中的语言应按优先级排序:最新的语言(本例中为“de”)具有更高的优先级,然后是“en”,然后是其他所有语言。

WHERE子句在这里是可选的,只有在“en”和“de”都没有翻译的情况下你不想要任何结果时才需要。

编辑: Sean提到非聚合列上的GROUP BY子句可能会产生不可靠的结果。这可能是真的,至少MySQL手册says so(虽然在实践中,第一个匹配行总是(?)使用)。

无论如何,还有另一个具有相同想法的查询,但没有提到的问题。

SELECT m1.*
FROM messages AS m1
INNER JOIN (
    SELECT message_id, MAX(FIELD(language_id, 'en', 'de')) AS weight
    FROM messages
    WHERE language_id IN ('en', 'de')
    GROUP BY message_id
) AS m2
USING(message_id)
WHERE FIELD(m1.language_id, 'en', 'de') = m2.weight;

答案 2 :(得分:0)

这是一个可能的解决方案:

首先我要设置你的表:

DROP TEMPORARY TABLE IF EXISTS messages;
CREATE TEMPORARY TABLE messages (
  message_id INT,
  language_id INT,
  message VARCHAR(64)
);

INSERT INTO messages VALUES
(1, 1, "Hello"),
(1, 2, "Hellode"),
(1, 3, "Hola"),
(2, 1, "Goodbye"),
(2, 3, "Adios");

并添加了一个新的语言偏好:

DROP TEMPORARY TABLE IF EXISTS user_language_preference;
CREATE TEMPORARY TABLE user_language_preference (
  user_id INT,
  language_id INT,
  preference INT
);

INSERT INTO user_language_preference VALUES
(1, 1, 10), # know english
(1, 2, 100); # but prefers 'de'

查询..

您好:

SET @user_id = 1;
SET @message_id = 1;

# Returns 'Hellode', 'Hello'
SELECT
  m.language_id,
  message
FROM messages AS m, user_language_preference AS l
WHERE message_id=@message_id
  AND m.language_id=l.language_id
  AND user_id=@user_id
ORDER BY preference DESC;

再见:

SET @message_id = 2;

# Returns 'Goodbye' as 'de' doesn't have a message there
SELECT
  m.language_id,
  message
FROM messages AS m, user_language_preference AS l
WHERE message_id=@message_id
  AND m.language_id=l.language_id
  AND user_id=@user_id
ORDER BY preference DESC;

编辑:回复评论:

SELECT
  m.message_id,
  m.language_id,
  message
FROM messages AS m, user_language_preference AS l
WHERE m.language_id=l.language_id
  AND user_id=@user_id
ORDER BY m.message_id, preference DESC;

答案 3 :(得分:0)

使用group-concat技巧在一个查询中获取此内容:

select message_id,
       substring(max(concat(if(language_id='de', 9, if(language_id='en',8,0)), message)),2) as message,
       substring(max(concat(if(language_id='de', 9, if(language_id='en',8,0)), language_id)),2) as language
from messages 
group by message_id;

只需在IF子句中添加条件和适当的优先级即可添加更多后备语言。

答案 4 :(得分:0)

SELECT *
FROM messages
WHERE (message_id,CASE language_id WHEN 'de' THEN 1 WHEN 'en' THEN 2 ELSE NULL END) IN (
    SELECT message_id, MIN(CASE language_id WHEN 'de' THEN 1 WHEN 'en' THEN 2 ELSE NULL END) pref_language_id
    FROM `messages`
    GROUP BY message_id
)

您必须将用户首选语言中的 CASE language_id WHEN'de'THEN 1'EN'TEN 2 ELSE NULL END 更改为用户首选语言。如果他有第三个,只需添加另一个案例,例如。 CASE language_id WHEN'de'那么1当'en'那么2当'es'然后3'结束时

答案 5 :(得分:0)

这是分组最大查询的一个很好的示例。 http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/

这是我想出的。使用与simendsjo相同的数据和架构。

SELECT prefered.message_id, p2.language_id, message FROM
  (SELECT message_id, MAX(preference) AS prefered FROM messages m
  JOIN user_language_preference p ON p.language_id = m.language_id AND p.user_id = 1
  GROUP BY m.message_id) AS prefered
  JOIN user_language_preference p2 ON prefered = p2.preference AND p2.user_id = 1
  JOIN messages m2 ON p2.language_id = m2.language_id AND m2.message_id = prefered.message_id

这是它的工作原理。

  1. 内部查询prefered选择所有邮件,将其加入用户语言首选项,计算每封邮件的最大首选项(GROUP BY m.messsage id)。如果现在有翻译,则最大值将用于下一个首选语言,依此类推......
  2. 外部查询包含两个连接:第一个连接从给定用户的最大首选项(MAX(preference) = prefered = p2.preference)获取语言ID。
  3. 上次加入m2只选择已知首选语言_id和message_id的翻译。
  4. PS。不要忘记更改两次出现的user_id。

答案 6 :(得分:0)

编辑添加一些与问题性质相对应的替代解决方案。 :d
(FWIW:第二选择是我的第一次实施)

第一选择

这个应该能够提供最好的表现,尽管有点难以理解 更重要的是,它可以更好地扩展到包括第4,第5,第6等语言 该解决方案需要一个定义语言优先级的临时表(使用mysql中最好的技术) 解决方案的核心在于'finder'子查询;一旦确定了可用的最佳优先级语言,加入回来获取实际消息是一件简单的事情。

declare @prio table (prio_id int, lid varchar(5))
insert into @prio values(1, 'de')
insert into @prio values(2, 'en')
insert into @prio values(3, 'es')

select  m.*
from    (
        select  message_id, MIN(prio_id) prio_id
        from    @messages m
                inner join @Prio p on
                  p.lid = m.language_id
        group by message_id
        ) finder
        inner join @Prio p
          on p.prio_id = finder.prio_id
        inner join @messages m
          on m.message_id = finder.message_id
         and m.language_id = p.lid

第二选择

以下查询结构应该很容易遵循 每个联合添加到结果集中的任何消息ID都不在结果集中 UNION ALL足够,因为每个后续查询都不保证重复 (language_id,message_id)上的索引应该提供最佳性能(特别是如果它是聚类的)。

select  message_id, language_id, message
from    messages
where   language_id = 'de'
union all
select  message_id, language_id, message
from    messages
where   language_id = 'en' 
    and message_id not in (select message_id from messages where language_id in ('de'))
union all
select  message_id, language_id, message
from    messages
where   language_id = 'es' 
    and message_id not in (select message_id from messages where language_id in ('de', 'en'))

第三选择

这是一个使用COALESCE功能的有趣信息 但是,我不认为它会在大量数据上表现良好。

select  *,
        COALESCE(
        (select language_id from @messages where message_id = m.message_id and language_id = 'de'),
        (select language_id from @messages where message_id = m.message_id and language_id = 'en'),
        (select language_id from @messages where message_id = m.message_id and language_id = 'es')
        ) language_id,
        COALESCE(
        (select message from @messages where message_id = m.message_id and language_id = 'de'),
        (select message from @messages where message_id = m.message_id and language_id = 'en'),
        (select message from @messages where message_id = m.message_id and language_id = 'es')
        ) message
from    (
        select  distinct message_id
        from    @messages
        ) m

答案 7 :(得分:-2)

本文描述了我发现的最快的解决方案,它提供了我所追求的结果集:

http://onlamp.com/pub/a/mysql/2007/03/29/emulating-analytic-aka-ranking-functions-with-mysql.html