SQL - 根据多行选择条件

时间:2015-01-18 15:16:33

标签: sql

我有这两个表:

actions 
action_data

action_data属于操作,并且包含以下列:action_idnamevalue

内容可能如下所示:

Actions

id | 
-----
178| 
179|

action_data

action_id |   name   | value
-------------------------------------
178       |  planet  | earth
178       |  object  | spaceship_a
179       |  planet  | earth
179       |  object  | building

现在我想选择动作,其中action_data中有planet = earth and object = spaceship_a

如何使用SQL实现这一目标?如果你只有一个条件就可以这样工作:

SELECT DISTINCT 
    actions.* 
FROM 
    actions 
INNER JOIN 
    action_data ON actions.id = action_data.action_id 
WHERE 
    (action_data.name = 'planet' AND action_data.value = 'earth');

但我需要action_data中的两个或更多条件。

有什么想法吗?

5 个答案:

答案 0 :(得分:1)

如果您需要特定于DBMS的语法,则可以使用自动加入。

我会这样做:

SELECT DISTINCT action_id
FROM action_data a1 JOIN action_data a2 USING(action_id)
WHERE 
    a1.name = 'planet' AND a1.value = 'earth' AND
    a2.name = 'object' AND a2.value = 'spaceship_a';

这适用于 2个条件,但可以使用FROM子句中的数据表的更多副本以及相应的比较条件将其扩展为3个或更多。

在这种情况下,a1副本用于第一个条件(planet - earth),a2副本用于第二个条件(object - spaceship_a)。

JOIN允许我们在所有可能的组合中搜索匹配(N行给出N ^ 2个组合)。

这可能不是最好和最有效的方法,但是可靠且不依赖于平台。

演示如下:

mysql> select * from action_data;
+-----------+--------+-------------+
| action_id | name   | value       |
+-----------+--------+-------------+
|       178 | planet | earth       |
|       178 | object | spaceship_a |
|       179 | planet | earth       |
|       179 | object | building    |
+-----------+--------+-------------+
4 rows in set (0.02 sec)

mysql> SELECT DISTINCT action_id
    -> FROM action_data a1 JOIN action_data a2 USING (action_id)
    -> WHERE 
    ->     a1.name = 'planet' AND a1.value = 'earth' AND
    ->     a2.name = 'object' AND a2.value = 'spaceship_a';
+-----------+
| action_id |
+-----------+
|       178 |
+-----------+
1 row in set (0.00 sec)

答案 1 :(得分:1)

由于您不知道要搜索的元数据的数量,因此我不建议使用未知/无限数量的joins

而是使用group concatenation

select * from actions 
  join (
    select action_id,
      group_concat(name,'=',value order by name separator ',') as csv // MySQL
//    string_agg(name || '=' || value, ',' order by name) as csv // PostgreSQL
    from meta 
    where name in ('planet', 'object') 
    group by action_id
  ) meta 
  on actions.id = meta.action_id 
  where csv = 'object=building,planet=earth'

我很高兴听到关于性能的SQL专业人士,我认为,如果要找到3+以上的数据会更好。

答案 2 :(得分:0)

我使用group byhaving来解决这些问题,因为这是一种适用于许多条件的非常通用的方法。

在你的情况下:

select ad.action_id
from action_data ad
group by ad.action_id
having sum(case when name = 'planet' and value = 'earth' then 1 else 0 end) > 0 and
       sum(case when name = 'object' and value = 'spaceship_a' then 1 else 0 end) > 0;

having子句中的每个条件都会计算匹配的行数。 > 0表示至少有一个。

如果需要,您可以join返回actions表格以获取更多列。

答案 3 :(得分:0)

如果条件数量恒定,则可以使用join,这比使用sums和case进行分组要快得多。

如果有2个条件,您可以像这样加入:

    declare @t TABLE(id int, name NVARCHAR(MAX), value NVARCHAR(MAX))

    INSERT INTO @t VALUES(1, 'planet', 'earth')
    INSERT INTO @t VALUES(1, 'object', 'spaceship_a')
    INSERT INTO @t VALUES(1, 'destination', 'mars')

    SELECT * FROM @t t1
    JOIN @t t2 ON t1.ID = t2.id
    WHERE t1.name = 'planet' AND t1.value = 'earth' 
    AND t2.name = 'object' AND t2.value = 'spaceship_a'

当然,如果你有3个条件,那么你需要加入2次并添加新的过滤器:

    SELECT * FROM @t t1
    JOIN @t t2 ON t1.ID = t2.id
    JOIN @t t3 ON t1.ID = t3.id
    WHERE t1.name = 'planet' AND t1.value = 'earth' 
    AND t2.name = 'object' AND t2.value = 'spaceship_a'
    AND t3.name = 'destination' AND t3.value = 'mars'

答案 4 :(得分:0)

还有一些选择:

1)使用exists

select *
from actions a
where exists (select 1 from action_data ad 
    where ad.action_id = a.id and ad.name = 'planet' and ad.value = 'earth')
and exists (select 1 from action_data ad 
    where ad.action_id = a.id and ad.name = 'object' and ad.value = 'spaceship_a');

2)使用with

with q1 as (
    select action_id
    from action_data
    where name = 'planet' and value = 'earth'
),
q2 as (
    select action_id
    from action_data
    where name = 'object' and value = 'spaceship_a'
)
select *
from q1 inner join q2 on q1.action_id = q2.action_id;