2013-08-29 28 views
0

我有一个简单的用户偏好表看起来像这样:仅当非空值不存在时才选择空位?

id | user_id | preference_name | preference_value 

是什么让这个独特的表虽然是如果USER_ID字段为空,这意味着它是针对偏好的默认值。我试图获得用户的所有首选项,并且只有在没有为该用户指定实际值的情况下才使用默认值。

所以基本上我需要:

SELECT * FROM user_preferences WHERE user_id = {userIdVar} OR user_id IS NULL; 

,但我想抛出了一个user_id是空的结果,如果没有在结果与同PREFERENCE_NAME和user_id的值设置另一行。

有没有办法做到这一点与单个SQL查询,或者我应该这样做的代码?

回答

4

使用NOT EXISTS

SELECT up1.* 
FROM user_preferences up1 
WHERE (NOT EXISTS(SELECT 1 
        FROM user_preferences up2 
        WHERE user_id = {userIdVar}) 
     AND user_id IS NULL) 
     OR (user_id = {userIdVar}); 
1

有你可以做到这一点不同的方式,但如果所有的喜好有一个默认值,或者你有其他地方的偏好的完整列表,我会做这样的:

select 
    default_preferences.preference_name, 
    coalesce(
    real_user_preferences.preference_value, 
    default_preferences.preference_value) as preference_value 
from 
    (select * from user_preferences where user_id is null) 
    as default_preferences 
left join 
    (select * from user_preferences where user_id = @user_id) 
    as real_user_preferences 
on 
    real_user_preferences.preference_name = default_preferences.preference_name 

您已经在MySQL和SQL Server上标记了你的问题,我不知道你在找哪种方言。我知道SQL Server接受这种语法,但它可能需要对MySQL进行一些调整。

编辑:funkwurm指出子查询使得这可能在MySQL上表现不佳。如果这原来是一个问题,它可以在没有子查询改写为

select 
    default_preferences.preference_name, 
    coalesce(
    real_user_preferences.preference_value, 
    default_preferences.preference_value) as preference_value 
from 
    user_preferences as default_preferences 
left join 
    user_preferences as real_user_preferences 
on 
    real_user_preferences.preference_name = default_preferences.preference_name 
    and real_user_preferences.user_id = @user_id 
where 
    default_preferences.user_id is null 

编辑2:如果有没有默认值的偏好,第一个版本可以被修改为使用full join代替的left join,并采取preference_name从任一默认值或用户特定的喜好,就像preference_value。但是,第二个版本不太容易修改。

+0

在MySQL子查询往往是资源密集型的,也许SQL-Server有优化,使得该子查询是首选。我会选择在一个子查询代替直接选择'FROM user_preferences'和走样,所以你可以用自己加入它的默认值。 – funkwurm

+0

@funkwurm谢谢,会编辑。 – hvd

+0

这会返回错误的结果!看到我的答案和sql小提琴 – amaster

0

这应该选择的第一行是NULL或设置在那里如果同时设置user_ID的变量优先停留,然后显示每个PREFERENCE_NAME只有一次user_ID的变量。

SELECT 
    * 
FROM 
    (
    SELECT 
     * 
    FROM 
     user_preferences 
    WHERE 
     user_id = {userIdVar} OR 
     user_id IS NULL 
    ORDER BY 
     CASE WHEN user_id IS NULL THEN 1 ELSE 0 END 
) sub_query 
GROUP BY 
    preference_name 

SQL FIDDLE

+0

聪明的主意,但这并不能保证在MySQL中工作,并且会在SQL Server和大多数其他数据库系统中造成硬性错误。从[扩展到GROUP BY]引用(https://dev.mysql.com/doc/refman/5.7/en/group-by-extensions.html):“服务器可以自由选择每个组的任何值,所以除非它们是相同的,否则所选的值是不确定的。此外,每个组中的值的选择不会受到添加ORDER BY子句的影响。“ – hvd

+0

@ hvd我使用类似于MySQL的方式,它始终工作。我的理解是,MySQL会分组并使用第一个实例。如果子查询的顺序正确,那么查询将始终返回预期的结果......现在在sql-fiddle上工作 – amaster

+0

完全可能的是,对于特定版本的MySQL,对于特定的表,它总是会给你想要的结果,因为您获得的查询计划会导致您的查询以您期望的方式执行。但是,如果文档明确指出它将不确定地选择一个值,并且您的ORDER BY'不足以使此查询可靠,那么我会非常谨慎地在其他数据库或其他版本的MySQL上尝试此操作。 – hvd