SQL:筛选行
我试图编写一个SQL查询,从包含数据的表中返回行: 表格结构如下:SQL:筛选行,sql,postgresql,postgresql-9.1,Sql,Postgresql,Postgresql 9.1,我试图编写一个SQL查询,从包含数据的表中返回行: 表格结构如下: CREATE TABLE person( id INT PRIMARY KEY, name TEXT, operation TEXT); 我想返回所有未取消的唯一名称行。 如果操作是insert或delete,并且存在另一个具有相同名称且具有相反操作的行,则认为该行已取消 例如,如果我有以下行 id name operation 1 bob insert 2 bob de
CREATE TABLE person(
id INT PRIMARY KEY,
name TEXT,
operation TEXT);
我想返回所有未取消的唯一名称行。
如果操作是insert或delete,并且存在另一个具有相同名称且具有相反操作的行,则认为该行已取消
例如,如果我有以下行
id name operation
1 bob insert
2 bob delete
3 bob insert
前两行相互抵消,因为它们具有相同的名称,并且具有相反的操作。因此,查询应该返回第3行
下面是另一个例子:
id name operation
1 bob insert
2 bob delete
3 bob insert
4 bob delete
在这种情况下,第1行和第2行取消,第3行和第4行取消。因此,查询不应返回任何行
最后一个例子:
id name operation
1 bob insert
2 bob insert
在这种情况下,第1行和第2行不会取消,因为操作不是相反的。因此,查询应该同时返回两行
下面的查询处理前两个场景,但不处理最后一个场景
有人对可以处理所有3种情况的查询有什么建议吗
SELECT MAX(id),name
FROM person z
WHERE operation IN ('insert','delete')
GROUP BY name
HAVING count(1) % 2 = 1;
一种方法是比较操作计数。由于您还需要获取与InsertCount-deleteCount或InsertCount-deleteCount相对应的插入或删除的数量,并且由于PostgreSQL支持,因此您应该能够使用row_number 注意:我没有对此进行测试,但根据这一点,您可以在内联查询中引用窗口函数
SELECT
id, name
FROM
(
SELECT
row_number() over (partition by p.name, p.operation order by p.id desc) rn ,
id,
p.Name,
p.operation,
operationCounts.InsertCount,
operationCounts.deleteCount
FROM
Person p
INNER JOIN (
SELECT
SUM(CASE WHEN operation = 'insert' then 1 else 0 END) InsertCount,
SUM(CASE WHEN operation = 'delete' then 1 else 0 END) deleteCount,
name
FROM
person
GROUP BY
name ) operationCounts
ON p.name = operationCounts.name
WHERE
operationCounts.InsertCount <> operationCounts.deleteCount) data
WHERE
(rn <= (InsertCount - deleteCount)
and operation = 'insert')
OR
(rn <= (deleteCount - InsertCount)
and operation = 'delete')
最佳速度和最短答案: 这个问题可以归结为 统计每个名称的删除操作 忽略第一个cnt_del插入 这可以用这种方式一次完成:不知道这个查询是否一切正常
select * from(
SELECT id, name,
row_number() over (partition by name order by case
when operation = 'insert'
then id
else null end
nulls last ) rnk_insert,
count(case
when operation='delete' then 1
else null
end) over (partition by name) as cnt_del
FROM person z
WHERE operation IN ('insert','delete')
)
where rnk_insert > cnt_del
如果以前的解决方案在postgresAFAIK中不起作用,Oracle可以处理它。解决方案可以以更轻松的方式实施:
select i.id, i.name
from
(select id, name,
row_number over (partition by name order by id) as rnk_insert
from person z
where operation='insert') i
left join
(select name, count(*) as cnt_del
from person z
where operation='delete') d
on d.name = i.name
where rnk_insert > coalesce(cnt_del, 0)
测试显示我最初的查询比@Conrad的优秀查询慢。我很谦卑,尝试了一些事情,提出了一个实际上更简单、更快的查询 测试设置 查询:
它最终与@Conrad的查询相似,只是做了一些简化/改进。关键的一点是要消除在游戏早期被取消的名字。顺序重要吗?如果你有INSERT,INSERT,DELETE哪个INSERT应该被保留?@Martin-最新的INSERT行是最相关的,所以我猜是第二行。这是很有趣的,但仍然缺少一些东西。您只提供了maxp.id,您应该提供更多,请参见问题“示例3。我在运行查询时遇到以下错误:错误:列引用名称不明确第6行:按名称划分的行数,操作…@Jin对此表示抱歉。我忘记了第_行中的表格别名。“我修好了。”康拉德谢谢你的帮助。不过,我现在遇到了一个不同的错误:错误:表操作计数第27行的子句条目中缺少:rn@Jin doh复制/粘贴错误再次触发。我修好了。在这种情况下,我们不需要别名引用,但如果需要,它将是数据
INSERT INTO person
SELECT i
,'name' || (random() * 500)::int::text
,CASE WHEN random() >= 0.5 THEN 'insert' ELSE 'delete' END
FROM generate_series(1,10000) AS i;
SELECT id, name, operation
FROM (
SELECT row_number() OVER (PARTITION BY name, operation ORDER by id) AS rn
,id
,name
,operation
,y.cancel
FROM (
SELECT name
,least(ct_del, ct_all - ct_del) AS cancel
FROM (
SELECT name
,count(*) AS ct_all
,count(NULLIF(operation, 'insert')) AS ct_del
FROM person
GROUP BY 1
) x
WHERE (ct_all - ct_del) <> ct_del
) y
JOIN person USING (name)
) p
WHERE rn > cancel