8

每天,请求变得越来越奇怪。

我被要求组合一个查询来检测表中的哪些列包含所有行的相同值。我说“这需要通过程序来完成,这样我们就可以在表格的一遍而不是 N 遍中完成它。”

我被否决了。

长话短说。我有一个非常简单的查询来演示这个问题。它对测试集进行了 4 次通过。我正在寻找不涉及在每一列上添加索引、编写程序或花费人类一生来运行的 SQL Magery 的想法。

叹息 它需要能够在任何桌子上工作。

提前感谢您的建议。

WITH TEST_CASE AS
(
    SELECT 'X' A, 5 B, 'FRI' C, NULL D FROM DUAL UNION ALL
    SELECT 'X' A, 3 B, 'FRI' C, NULL D FROM DUAL UNION ALL
    SELECT 'X' A, 7 B, 'TUE' C, NULL D FROM DUAL 
),
KOUNTS AS 
(
    SELECT SQRT(COUNT(*)) S, 'Column A' COLUMNS_WITH_SINGLE_VALUES
    FROM TEST_CASE P, TEST_CASE Q
    WHERE P.A = Q.A OR (P.A IS NULL AND Q.A IS NULL)

    UNION ALL

    SELECT SQRT(COUNT(*)) S, 'Column B' COLUMNS_WITH_SINGLE_VALUES
    FROM TEST_CASE P, TEST_CASE Q
    WHERE P.B = Q.B OR (P.B IS NULL AND Q.B IS NULL)

    UNION ALL

    SELECT SQRT(COUNT(*)) S, 'Column C' COLUMNS_WITH_SINGLE_VALUES
    FROM TEST_CASE P, TEST_CASE Q
    WHERE P.C = Q.C OR (P.C IS NULL AND Q.C IS NULL)

    UNION ALL

    SELECT SQRT(COUNT(*)) S, 'Column D' COLUMNS_WITH_SINGLE_VALUES
    FROM TEST_CASE P, TEST_CASE Q
    WHERE P.D = Q.D OR (P.D IS NULL AND Q.D IS NULL)
)
SELECT COLUMNS_WITH_SINGLE_VALUES
FROM KOUNTS
WHERE S = (SELECT COUNT(*) FROM TEST_CASE)
4

4 回答 4

10

你的意思是这样的吗?

WITH 
TEST_CASE AS
(
    SELECT 'X' A, 5 B, 'FRI' C, NULL D FROM DUAL UNION ALL
    SELECT 'X' A, 3 B, 'FRI' C, NULL D FROM DUAL UNION ALL
    SELECT 'X' A, 7 B, 'TUE' C, NULL D FROM DUAL 
)
select case when min(A) = max(A) THEN 'A'
            when min(B) = max(B) THEN 'B'
            when min(C) = max(C) THEN 'C'
            when min(D) = max(D) THEN 'D'
            else 'No one'
       end 
from TEST_CASE

编辑 这个作品:

WITH 
TEST_CASE AS
(
    SELECT 'X' A, 5 B, 'FRI' C, NULL D FROM DUAL UNION ALL
    SELECT 'X' A, 3 B, 'FRI' C, NULL D FROM DUAL UNION ALL
    SELECT 'X' A, 7 B, 'TUE' C, NULL D FROM DUAL 
)
select case when min(nvl(A,0)) = max(nvl(A,0)) THEN 'A ' end ||
       case when min(nvl(B,0)) = max(nvl(B,0)) THEN 'B ' end ||
       case when min(nvl(C,0)) = max(nvl(C,0)) THEN 'C ' end ||
       case when min(nvl(D,0)) = max(nvl(D,0)) THEN 'D ' end c

from TEST_CASE

奖励:我还添加了对空值的检查,所以现在的结果是:A 和 D

以及为您准备的SQLFiddle 演示

于 2013-10-31T16:32:59.727 回答
8

优化器统计信息可以轻松识别具有多个不同值的列。收集统计信息后,对数据字典的简单查询将几乎立即返回结果。

如果您使用 ESTIMATE_PERCENT = 100,结果将仅在 10g 上准确。如果您使用 ESTIMATE_PERCENT = 100 或 AUTO_SAMPLE_SIZE,结果将在 11g+ 上准确。

代码

create table test_case(a varchar2(1), b number, c varchar2(3),d number,e number);

--I added a new test case, E.  E has null and not-null values.
--This is a useful test because null and not-null values are counted separately.
insert into test_case
SELECT 'X' A, 5 B, 'FRI' C, NULL D, NULL E FROM DUAL UNION ALL
SELECT 'X' A, 3 B, 'FRI' C, NULL D, NULL E FROM DUAL UNION ALL
SELECT 'X' A, 7 B, 'TUE' C, NULL D, 1    E FROM DUAL;

--Gather stats with default settings, which uses AUTO_SAMPLE_SIZE.
--One advantage of this method is that you can quickly get information for many
--tables at one time.
begin
    dbms_stats.gather_schema_stats(user);
end;
/

--All columns with more than one distinct value.
--Note that nulls and not-nulls are counted differently.
--Not-nulls are counted distinctly, nulls are counted total.
select owner, table_name, column_name
from dba_tab_columns
where owner = user
    and num_distinct + least(num_nulls, 1) <= 1
order by column_name;

OWNER     TABLE_NAME   COLUMN_NAME
-------   ----------   -----------
JHELLER   TEST_CASE    A          
JHELLER   TEST_CASE    D          

表现

在 11g 上,这种方法可能与 mucio 的 SQL 语句一样快。诸如此类的选项cascade => false将通过不分析索引来提高性能。

但是这种方法的好处在于它还可以产生有用的统计数据。如果系统已经在定期收集统计数据,则可能已经完成了艰苦的工作。

关于 AUTO_SAMPLE_SIZE 算法的详细信息

AUTO_SAMPLE_SIZE 在 11g 中完全改变了。它不使用抽样来估计不同值的数量 (NDV)。相反,它扫描整个表并使用基于散列的不同算法。该算法不需要大量内存或临时表空间。阅读整个表格比对其中的一小部分进行排序要快得多。Oracle Optimizer 博客在此处对算法进行了很好的描述。有关更多详细信息,请参阅Amit Podder 的此演示文稿。(如果您想在我的下一部分中验证详细信息,则需要扫描该 PDF。)

错误结果的可能性

尽管新算法没有使用简单的采样算法,但它仍然不能 100% 正确计算不同值的数量。很容易找到估计的不同值的数量与实际不同的情况。但是,如果不同值的数量显然不准确,那么如何在这个解决方案中信任它们呢?

潜在的不准确性来自两个来源 - 哈希冲突和概要拆分。概要拆分是不准确的主要来源,但不适用于此处。只有当有 13864 个不同的值时才会发生这种情况。而且它永远不会抛出所有的值,最终的估计肯定会比 1 大得多。

唯一真正关心的是哈希冲突有 2 个不同值的可能性有多大。使用 64 位哈希的机会可能低至 18,446,744,073,709,551,616 分之一。不幸的是,我不知道他们散列算法的细节,也不知道真正的概率。我无法从一些简单的测试和以前的实际测试中产生任何碰撞。(我的一个测试是使用大值,因为一些统计操作只使用前 N 个字节的数据。)

现在还要考虑只有当表中的所有不同值发生冲突时才会发生这种情况。只有两个值碰巧发生冲突的表的可能性有多大?可能比中彩票并同时被陨石击中的机会要小得多。

于 2013-11-03T07:23:48.197 回答
3

如果您可以在一行中接受结果,则应该只扫描一次;

WITH TEST_CASE AS
(
    SELECT 'X' A, 5 B, 'FRI' C, NULL D FROM DUAL UNION ALL
    SELECT 'X' A, 3 B, 'FRI' C, NULL D FROM DUAL UNION ALL
    SELECT 'X' A, 7 B, 'TUE' C, NULL D FROM DUAL
)
SELECT 
  CASE WHEN COUNT(DISTINCT A) + 
            COUNT(DISTINCT CASE WHEN A IS NULL THEN 1 END) = 1
       THEN 1 ELSE 0 END SAME_A,
  CASE WHEN COUNT(DISTINCT B) + 
            COUNT(DISTINCT CASE WHEN B IS NULL THEN 1 END) = 1
       THEN 1 ELSE 0 END SAME_B,
  CASE WHEN COUNT(DISTINCT C) + 
            COUNT(DISTINCT CASE WHEN C IS NULL THEN 1 END) = 1
       THEN 1 ELSE 0 END SAME_C,
  CASE WHEN COUNT(DISTINCT D) + 
            COUNT(DISTINCT CASE WHEN D IS NULL THEN 1 END) = 1
       THEN 1 ELSE 0 END SAME_D
FROM TEST_CASE

一个用于测试的 SQLfiddle

于 2013-10-31T16:47:24.763 回答
2

这将在一次扫描中完成

WITH 
    TEST_CASE AS
    (
        SELECT 'X' A, 5 B, 'FRI' C, NULL D FROM DUAL UNION ALL
        SELECT 'X' A, 3 B, 'FRI' C, NULL D FROM DUAL UNION ALL
        SELECT 'X' A, 7 B, 'TUE' C, NULL D FROM DUAL 
    )
    select decode(count(distinct nvl(A,0)),1,'SINGLE','MULTP') COL_A,
           decode(count(distinct nvl(B,0)),1,'SINGLE','MULTP') COL_B,
           decode(count(distinct nvl(C,0)),1,'SINGLE','MULTP') COL_C,
           decode(count(distinct nvl(D,0)),1,'SINGLE','MULTP') COL_D
    from TEST_CASE
于 2013-11-08T08:54:47.397 回答