6

我正在尝试编写一个 SQL 查询,该查询从包含数据的表中返回行:

表结构如下:

CREATE TABLE person(
    id INT PRIMARY KEY,
    name TEXT,
    operation TEXT);

我想返回所有未被“取消”的唯一名称行。如果操作是“插入”或“删除”,并且存在另一行具有相反操作的同名行,则该行被视为“取消”。

例如,如果我有以下行

id   name   operation
1    bob    insert
2    bob    delete
3    bob    insert

前 2 行相互“取消”,因为它们具有相同的名称和相反的操作。所以查询应该返回第 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;
4

3 回答 3

4

一种方法是比较操作计数。由于您还需要获取与 InsertCount - deleteCount 或 InsertCount - deleteCount 对应的 INSERTS 或 DELETES 的数量,并且由于 PostgreSQL 支持窗口函数,因此您应该能够使用 row_number()。

注意:我没有对此进行过测试,但根据此PostgreSQL 手册 Chapter 3. Advanced Features, 3.5 Window functions您可以在内联查询中引用 Window Function

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')
于 2012-01-11T17:13:52.437 回答
1

最佳速度和最短答案:问题可以简化为

  1. 计算每个名称的删除操作(cnt_del)
  2. 忽略第一个 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

如果以前在 postgres 中不起作用(AFAIK,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)
于 2012-01-11T20:39:48.197 回答
0

测试显示我的原始查询比@Conrad 的出色查询要慢。谦虚地,我尝试了一些方法,并提出了一个实际上简单、更快的查询。

测试设置

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

它最终与@Conrad 的查询类似,并进行了一些简化/改进。关键是要消除在游戏早期被取消的名字。

于 2012-01-12T06:21:00.877 回答