SQL查询在表中查找min_numbers和max_number之间的漏洞

时间:2009-03-08 21:32:02

标签: sql mysql

SQL专家的快速提问。

我有一个表,其中包含两列--min_number和max_number 我一直试图写一个查询,找不到最小和最大数字之间 n 大小的第一个洞

实施例

     min    max
1.   100    200
2.   250    300
3.   330    400

如果我想找到一个大小为50的洞,那么第1行的最大值为200(在该行和第2行的最小值之间有一个50的洞),一个20的洞将返回第2行,最多为300等。 如果没有合适尺寸的孔,则返回最后一个(400)。

由于

6 个答案:

答案 0 :(得分:1)

已编辑:最终答案位于底部。

为什么这么多SQL问题忘记了表名?

-- Buggy: should reference (lo.max + 1)
SELECT lo.max + 1 AS min_range
    FROM example lo, example hi
    WHERE hi.min - (lo.max - 1) >= 40   -- Example won't work with 50
      AND NOT EXISTS (SELECT * FROM example AS mid
                         WHERE mid.min > lo.max
                           AND mid.max < hi.min
                     )

NOT EXISTS子句至关重要 - 它确保您只考虑相邻范围。

这涉及“存在足够大的差距”案例。

名义上,您可以使用UNION条款处理'没有足够大的差距':

...
UNION
SELECT MAX(max)+1
    FROM example
    WHERE NOT EXISTS(
        SELECT lo.max + 1 AS min_range
            FROM example lo, example hi
            WHERE hi.min - (lo.max - 1) >= 40   -- Example won't work with 50
              AND NOT EXISTS (SELECT * FROM example AS mid
                                 WHERE mid.min > lo.max
                                   AND mid.max < hi.min
                             )
            )

内部SELECT是第一个缩进的直接转录。


上面的SQL未经测试。第一部分工作(尤其是测试数据) - 但可以产生多个答案。所以,它需要修改为(修复,我认为,一个两个错误):

SELECT MIN(lo.max + 1) AS min_range
    FROM example lo, example hi
    WHERE hi.min - (lo.max + 1) >= 40   -- Example won't work with 50
      AND NOT EXISTS (SELECT * FROM example AS mid
                         WHERE mid.min > lo.max
                           AND mid.max < hi.min
                     )

UNION条款给了我一些悲伤......没有产生我期望的答案。

从语法上讲,我不得不将其修改为:

SELECT MIN(lo.max + 1) AS min_range
    FROM example lo, example hi
    WHERE hi.min - (lo.max + 1) >= 40   -- Example won't work with 50
      AND NOT EXISTS (SELECT * FROM example AS mid
                         WHERE mid.min > lo.max
                           AND mid.max < hi.min
                     )
UNION
SELECT MAX(solo.max)+1
    FROM example AS solo
    WHERE NOT EXISTS(
        SELECT MIN(lo.max + 1) AS min_range
            FROM example lo, example hi
            WHERE hi.min - (lo.max - 1) >= 40   -- Example won't work with 50
              AND NOT EXISTS (SELECT * FROM example AS mid
                                 WHERE mid.min > lo.max
                                   AND mid.max < hi.min
                             )
            )

这避免了将关键字MAX用作列名的问题(我可能写了example.max而不是solo.max。但它并没有产生我期望的答案。


UNION相当于OR,当然在这种情况下,这个查询似乎产生了我想要的答案:

SELECT MIN(lo.max + 1) AS min_range
    FROM example lo, example hi
    WHERE (hi.min - (lo.max + 1) >= 40
           AND NOT EXISTS (SELECT * FROM example AS mid
                              WHERE mid.min > lo.max
                                AND mid.max < hi.min
                          )
          )
       OR lo.max = (SELECT MAX(solo.max) FROM Example AS Solo)
;

OR条款引用lo.max而非hi.max至关重要;否则,你会得到错误的答案。


好的 - UNION版本注定失败,因为SQL错误定义了MIN的行为。具体来说,如果没有匹配的行,则MIN返回值为NULL的单行,而不是不返回任何行。这意味着当没有找到行时,UNION的第一个子句返回NULL;通过在NOT EXISTS中省略SELECT中的MIN可以“修复”第二个子句,但是你仍然会从语句中得到两行(一个NULL和正确的值),这是不可接受的。因此,OR版本是要使用的版本 - 并且SQL再次使用NULL值进行咬合。

可以通过在FROM子句中的表表达式中构造UNION来严格避免空值。这最终会稍微简单一些:

SELECT MIN(min_range)
    FROM (SELECT (lo.max + 1) AS min_range
              FROM example lo, example hi
              WHERE hi.min - (lo.max + 1) >= 49
                AND NOT EXISTS (SELECT * FROM example AS mid
                                   WHERE mid.min > lo.max
                                     AND mid.max < hi.min
                               )
          UNION
          SELECT MAX(solo.max + 1) AS min_range
              FROM example AS solo
         );

UNION的前半部分可以返回任意数量的时隙,包括零;第二个总是返回一个值(只要表中有任何行)。外部查询然后选择这些值中的最低值。

当然,此版本可用于分配行:

INSERT INTO Example(min, max)
    SELECT MIN(min_range) AS min, MIN(min_range) + (50 - 1) AS max
        FROM (SELECT (lo.max + 1) AS min_range
                  FROM example lo, example hi
                  WHERE hi.min - (lo.max + 1) >= 50
                    AND NOT EXISTS (SELECT * FROM example mid
                                       WHERE mid.min > lo.max
                                         AND mid.max < hi.min
                                   )
              UNION
              SELECT MAX(solo.max + 1) AS min_range
                  FROM example AS solo
             );

答案 1 :(得分:1)

SELECT
     MIN(T1.max_value)
FROM
     My_Table T1
LEFT OUTER JOIN My_Table T2 ON
     T2.min_value BETWEEN (T1.max_value + 1) AND (T1.max_value + @range)
WHERE
     T2.id IS NULL

我猜测,因为您正在寻找要分配的ID,所以您希望一系列值完全排除max_value和min_value。

您还可以使用NOT EXISTS子句执行上述查询。尝试两者,看看哪个表现更好。

需要考虑的另一件事是,您真的需要重复使用ID吗?您的ID值是否会变得如此之高且您的范围可用得如此之低以至于您需要这样做?我不知道你的系统的具体细节,但似乎你可能会花费很多精力然后使用大量的额外处理来解决一个不存在的问题。

答案 2 :(得分:1)

select min(n+1) from myTable where n+1 NOT IN  (select n from myTable)
  • R Doherty

答案 3 :(得分:0)

就个人而言,我不会尝试在SQL中执行此操作 - AIUI很难在不同的行中执行分析,而不必在最坏的情况下有效地扫描O(n ^ 2)中的表。但是,使用存储过程可能会更容易。

我的解决方案,如果您能够,将更改您的数据库架构和代码,以便每次插入新行时,前一行将更新为该行的最大值与该行的最小值之间的差异。新行,该差值存储在自己的列中。

搜索间隙足够大的第一行会变得相对微不足道。

答案 4 :(得分:0)

拥有MySQL model clause?如果是,您可以使用它进行查询。

答案 5 :(得分:0)

“一个20洞的洞将返回第2行最多300等” 我没有遵循你的逻辑 - 第2行(300)的最大值和第3行(330)的最小值之间的差距为30(如果包括最小值或最大值,则为29,否则为29)。这是否意味着您正在寻找“大于或等于”指定值的第一个间隙,或者差距是否必须完全匹配?如果它是“大于或等于”那么肯定返回的第一个匹配将是第1行,其具有间隙&gt;在它和第2行之间20?

无论如何,如果您的表具有某种行ID,如示例所示,那么您可以尝试这样的事情(假设一个表MyTable,其中列RowID,MinVal和MaxVal填充了示例中的数据) ):

SELECT TOP 1
        a.RowID,
        a.MinVal,
        a.MaxVal, -- this is the value you want to return
        ISNULL(b.MinVal, 9999) AS MinVal_NextRow,
        ISNULL(b.MinVal, 9999) - a.MaxVal AS Diff
FROM    MyTable a
        LEFT JOIN MyTable b ON a.RowID = ( b.RowID - 1 )
WHERE   ( ISNULL(b.MinVal, 9999) - a.MaxVal ) = 20

此示例选择间隙正好为20的第一行。如果您要查找至少为20的第一个间隙,则可以将WHERE子句更改为:

WHERE   ( ISNULL(b.MinVal, 9999) - a.MaxVal ) >= 20

当行是最后一行时,查询替换任意大数(9999) - 如果没有合适大小的间隙,则返回最后一个(最大)MaxVal。您需要将此数字调整为对数据有意义的数字(即大于数据中任何可能的值)。