我有这两个表:
actions
action_data
action_data
属于操作,并且包含以下列:action_id
,name
,value
内容可能如下所示:
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
中的两个或更多条件。
有什么想法吗?
答案 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 by
和having
来解决这些问题,因为这是一种适用于许多条件的非常通用的方法。
在你的情况下:
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;