SQL - 在这种情况下可以避免使用游标吗?

时间:2014-07-31 02:51:47

标签: mysql sql sql-server recursion netezza

如果对以下内容很重要,将使用Netezza后端+ SPSS Modeler和/或高级查询工具进行查询。我无法访问CLI。我试图了解是否有必要使用游标和遍历已排序的表来处理以下内容:

想象一下有两列的表,第一列是非唯一ID,第二列是日期。任何给定的ID可能会在表中多次出现一个或多个日期。

我的目标是从此表中选择日期间隔不少于固定天数的行,例如90.例如:

| ID |  DATE      |
===================
  X    2014-01-01
  X    2014-02-01
  X    2014-07-01
  Y    2014-02-01
  Y    2014-06-01
  Y    2014-07-01

在上面的示例中,我想为X选择的行将是1月1日和7月1日(不包括2月1日,因为从1月1日起不到90天),Y的行将是2月1日和6月1日(不包括7月1日,因为它是在先前案件的90天内。

实际上,表格中可能有超过100M的行。没有游标可以做到这一点吗?最佳方法是什么?

提前感谢任何建议!

编辑:在此扩展测试表数据。 SQL Fiddle

在上面编辑的示例中,所需的输出将是

| ID |  DATE      |
===================
  X    2014-01-01
  X    2014-04-01
  X    2014-10-01
  Y    2014-01-15
  Y    2014-04-15
  Y    2014-10-15
  Z    2014-01-01
  Z    2014-04-01
  Z    2014-10-01

2 个答案:

答案 0 :(得分:0)

我找到了一个成功的SPSS / Netezza原生迭代解决方案。 SPSS支持可以提前读取或后退的@OFFSET(字段,整数)函数。我曾尝试使用此函数,但在尝试使用" field"时遇到了与递归相关的错误。等于相同的字段,函数的结果与负整数一起读取前一个结果。

今天在另一个项目中我发现@OFFSET()的文档很差,虽然我相信它从第一行到最后一行的顺序w /表示预读的正整数,但实际上是倒退。它实际上解决了最后一行到第一行和正整数偏移实际上意味着读取前一行。使用我的原始方法重试并校正整数偏移上的符号消除了递归错误并解决了问题。

实际的解决方案可以这样。为了清楚起见,这种描述过于冗长,实际上大多数这些项目可以压缩到同一步骤中,并且它忽略了ID具有相同日期的多个实例的可能性(这是不难处理的)在比较中使用额外的逻辑,但对我的需求来说并不是必需的)。

  1. 在ID上选择一个有序集,然后选择日期。
  2. 通过@OFFSET(ID,1)定义新列PR_ID以存储上一行的ID
  3. 定义一个名为LST_CNT_DT的新列(最后一个可数 如果PR_ID<>,则包含当前行的日期ID。除此以外, 比较当前行与日期之间的天数差异 @OFFSET(LST_CNT_DT,1)[即相同字段的前一行的值],如果差值> = 90,则存储当前行的日期。否则存储@OFFSET(LST_CNT_DT,1)。
  4. 从这个新集合中选择LST_CNT_DT = DT的所有行(其中DT是当前行的日期)。
  5. 不像MsSQL中提供的CTE递归方法那么优雅,但可以完全在SPSS v15中构建,并在几分钟内在完整的表上执行。

答案 1 :(得分:-1)

如果您接受在SQL Server中有效的内容,则以下代码将起作用:

With CTE as (
    select A.ID, A.DATA, MIN(B.DATA) DATA1 
    from Table1 A
    inner join Table1 B
      on A.ID = B.ID
      and DATEADD(DAY, 90, A.DATA) <= B.DATA
    GROUP BY A.ID, A.DATA
), REC AS (
   SELECT ID, MIN(DATA) DATA
   FROM Table1
   GROUP BY ID
   UNION ALL
   SELECT A.ID, B.DATA1
   FROM REC A
   INNER JOIN CTE B
     ON A.ID = B.ID
     AND A.DATA = B.DATA
)

SELECT *
FROM REC
ORDER BY ID, DATA

CTE的用户递归。通过选择每个ID的最小日期,后面的递归始终是最大日期,大于90天。但这只适用于SQL Server。

SQL Fiddle

<强>更新

当您在其他地方获得实施的想法时,有多种方法来实现它是很有趣的。在SQL Server中也可以在TSQL中实现这种方式:

DECLARE @TABLE1 TABLE (ID VARCHAR(1), DATA DATE, DATA1 DATE)

  INSERT INTO @TABLE1
    select A.ID, A.DATA, MIN(B.DATA) DATA1 
    from (
       SELECT ID, MIN(DATA) DATA
       FROM Table1
       GROUP BY ID
    ) A
    inner join Table1 B
      on A.ID = B.ID
      and DATEADD(DAY, 90, A.DATA) <= B.DATA
    GROUP BY A.ID, A.DATA

DECLARE @AUX INT = 0

WHILE (SELECT COUNT(*) FROM @TABLE1) <> @AUX
BEGIN

  SELECT @AUX = COUNT(*) FROM @TABLE1

  INSERT INTO @TABLE1
    select *
    from ( 
       select A.ID, A.DATA, MIN(B.DATA) DATA1 
       from @Table1 A
       inner join Table1 B
         on A.ID = B.ID
         and DATEADD(DAY, 90, A.DATA1) <= B.DATA
       GROUP BY A.ID, A.DATA    
    ) A
    where not exists (
        SELECT 1
        FROM @TABLE1
        WHERE ID = A.ID
         AND DATA = A.DATA
         AND DATA1 = A.DATA1
    )

END

SELECT ID, DATA
FROM @TABLE1
UNION
SELECT ID, DATA1
FROM @TABLE1
UNION
SELECT ID, MIN(DATA) DATA
FROM Table1
GROUP BY ID

SQL Fiddle