草庐IT

java - 算法或 SQL : to find where conditions for a set of columns which ensures result set has value in a particular column always > 0

coder 2024-03-10 原文

我正在从事一个基于 java-oracle 的项目,在这个项目中我遇到了一个问题,在我看来这个问题需要一个分析解决方案。 我正在寻找基于 SQL 查询或任何算法或任何免费分析工具的解决方案,我可以按照这些工具获得所需的结果。

问题陈述: 假设我有下面的表,其中 A-D 列和最后一列作为 Score,我想为每个列找到一个值标准,当在 SQL where 子句中组合时,该标准将始终为 Score 列提供正值。那么基本上 A-D 列的哪种组合总能给我正分?

columnA|columnB|columnC|columnD|Score
  1      40      10       3     -20
  0      40      2        3      10
  0      10      3        3      20
  1      15      3        3     -5
  0      10      2        2     -15
  0      15      6        3     -10

上述数据集的预期结果:- 上述数据集的视觉解释给出了条件:“ColumnA =0 and columnB >10 and columnC <5 will="" ensure="" score="" always="">0”。 (从视觉上看它的clear columnD没有作用)。

请注意以上数据集是为了简单起见。实际上,我的项目包含大约 40 列和近 2500 行。可以肯定的是,每一列的值范围都是有限的。


以下信息从以下 OP 答案中复制

这是我开始使用的算法(如果有人认为我的方向正确,则需要输入以进一步完善它):

准备:创建一个包含所有可能表达式的列表,例如 A=0、B>10、C<5(对于 40="" 列,我最终确定了大约="" 150="">

让我们称它为“表达式”变量。

第一次运行的算法:

  1. set totalPositiveRows= select count(*) from my tables where score>0;

    设置 totalNegativeRows= select count(*) from my tables where score<>

  2. 对于表达式中的每个expr,计算以下三个变量 设置 positivePercentage= 找到满足此 expr 的 totalPositiveRows 的百分比;//如果总计 100 行中有 60 行得分>0 满足 expr,则 positivePercentage=60%

    set negativePercentage= find percentage of totalNegativeRows which satisfy this expr; //like if 40 rows out of total 100 rows  having score<0 satisfy expr , then negativePercentage=40%
    
    set diffPercentage=positivePercentage-negativePercentage;
    
  3. Set initialexpr=选择具有最大值 diffPercentage 的 expr set initalPositivePercentage=选择相应的positivePercentage值; set initalNegativePercentage=选择相应的negativePercentage值; 我的想法是,我现在需要继续扩展 initalexpr,直到 initalNegativePercentage 变为 0。

后续运行的算法,直到 initalNegativePercentage 变为 0:-

  1. 对于表达式中的每个expr,计算以下三个变量
    设置 newexpr=initialexpr+"和 "+expr; set positivePercentage= 找到满足 newexpr 的 totalPositiveRows 的百分比; set negativePercentage=求出totalNegativeRows中满足newexpr的百分比;

    //计算它减少了多少负百分比? 设置 positiveReduction=initalPositivePercentage-positivePercentage; 设置 negativeReduction=initalNegativePercentage-negativePercentage; 如果(负减少>=正减少) //记下来 别的 //丢弃它

  2. 选择给出最大负减少的表达式,成为新的初始表达式。 Set initialexpr=选择具有最大值 negativeReduction 的 expr set initalPositivePercentage=选择相应的值; set initalNegativePercentage=选择相应的值;

  3. 重复上面的算法。

请评论。

最佳答案

下面是一个蛮力解决方案。对于理论计算机科学站点,这也可能是一个很好的问题。我认为这是一个类似于 Boolean satisfiability 的 NP 完全问题,但这只是一个疯狂的猜测。可能有更聪明的方法来解决这个问题,但我认为我找不到。

基本思想是将每个表达式与列的每个不同值交叉连接,然后交叉连接所有列。将使用每个表达式列表查询该表,并为正分和负分生成计数。如果表达式返回预期数量的正分数并且没有负分数,则它是有效的。

这假设您只使用表达式 > , < , 和 = .每个新列或表达式都会使这个问题呈指数级变慢。

测试架构

drop table table1;
create table table1(a number, b number, c number, d number, score number);

insert into table1
select  1,      40,      10,       3,     -20 from dual union all
select  0,      40,      2,        3,      10 from dual union all
select  0,      10,      3,        3,      20 from dual union all
select  1,      15,      3,        3,     -5  from dual union all
select  0,      10,      2,        2,     -15 from dual union all
select  0,      15,      6,        3,     -10 from dual;

代码墙

declare
    v_inline_view varchar2(32767);
    v_inline_views clob;
    v_inline_view_counter number := 0;
    v_and_expression varchar2(32767);
    v_query clob;

    v_sqls sys.odcivarchar2list;
    v_dynamic_query_counter number := 0;
begin
    --#1: Create inline views.
    --One for every combination of expression and distinct value, per column.
    for inline_views in
    (
        --Inline view for every possible expression for each column.
        select
            replace(q'[
            (
                select *
                from
                (
                    --Possible expressions.
                    select distinct
                        case
                            when operator is null then null
                            else ' AND %%COLUMN%% '||operator||' '||%%COLUMN%%
                        end %%COLUMN%%_expression
                    from
                    --All operators.
                    (
                        select '>'  operator from dual union all
                        select '<'  operator from dual union all
                        select '='  operator from dual union all
                        select null operator from dual
                    )
                    --All distinct values.
                    cross join
                    (
                        select distinct %%COLUMN%% from table1
                    )
                )
                --where %%COLUMN%%_expression is null or %%COLUMN%%_expression %%EXPRESSION_PERFORMANCE_EXCLUSIONS%%
            )
            ]', '%%COLUMN%%', column_name) inline_view
        from user_tab_columns
        where table_name = 'TABLE1'
            and column_name <> 'SCORE'
        order by column_name
    ) loop
        --Assign to temorary so it can be modified.
        v_inline_view := inline_views.inline_view;

        --#1A: Optimize inline view - throw out expressions if they don't return any positive results.
        declare
            v_expressions sys.odcivarchar2list;
            v_expressions_to_ignore varchar2(32767);
            v_has_score_gt_0 number;
        begin
            --Gather expressions for one column.
            execute immediate v_inline_view bulk collect into v_expressions;

            --Loop through and test each expression.
            for i in 1 .. v_expressions.count loop
                --Always keep the null expression.
                if v_expressions(i) is not null then
                    --Count the number of rows with a positive score.
                    execute immediate 'select nvl(max(case when score > 0 then 1 else 0 end), 0) from table1 where '||replace(v_expressions(i), ' AND ', null)
                    into v_has_score_gt_0;

                    --If the expression returns nothing positive then add it to exclusion.
                    if v_has_score_gt_0 = 0 then
                        v_expressions_to_ignore := v_expressions_to_ignore||','''||v_expressions(i)||'''';
                    end if;
                end if;
            end loop;

            --Convert it into an IN clause.
            if v_expressions_to_ignore is not null then
                --Remove comment, replace placeholder with expression exclusions.
                v_inline_view := regexp_replace(v_inline_view, '(.*)(--where)( .* )(%%EXPRESSION_PERFORMANCE_EXCLUSIONS%%)(.*)', '\1where\3 not in ('||substr(v_expressions_to_ignore, 2)||')');
            end if;
        end;

        --Aggregate and count inline views.
        if v_inline_view_counter <> 0 then
            v_inline_views := v_inline_views||'cross join';
        end if;

        v_inline_views := v_inline_views||v_inline_view;

        v_inline_view_counter := v_inline_view_counter + 1;
    end loop;

    --#2: Create an AND expression to combine all column expressions.
    select listagg(column_name||'_expression', '||') within group (order by column_name)
    into v_and_expression
    from user_tab_columns
    where table_name = 'TABLE1'
        and column_name <> 'SCORE';


    --#3: Create a that will create all possible expression combinations.
    v_query := 
    replace(replace(q'[
        --8281 combinations
        select '
            select
                '''||expressions||''' expression,
                nvl(sum(case when score > 0 then 1 else 0 end), 0) gt_0_score_count,
                nvl(sum(case when score <= 0 then 1 else 0 end), 0) le_0_score_count
            from table1
            where 1=1 '||expressions v_sql
        from
        (
            --Combine expressions
            select %%AND_EXPRESSION%% expressions
            from
            %%INLINE_VIEWS%%
        ) combined_expressions
    ]', '%%AND_EXPRESSION%%', v_and_expression), '%%INLINE_VIEWS%%', v_inline_views);

    --TEST: It might be useful to see the full query here.
    --dbms_output.put_line(v_query);

    --#4: Gather expressions.
    --With larger input you'll want to use a LIMIT
    execute immediate v_query
    bulk collect into v_sqls;

    --#5: Test each expression.  
    --Look for any queries that return the right number of rows.
    for i in 1 .. v_sqls.count loop
        declare
            v_expression varchar2(4000);
            v_gt_0_score_count number;
            v_le_0_score_count number;
        begin
            execute immediate v_sqls(i) into v_expression, v_gt_0_score_count, v_le_0_score_count;
            v_dynamic_query_counter := v_dynamic_query_counter + 1;

            --TODO: Dynamically generate "2".
            if v_gt_0_score_count = 2 and v_le_0_score_count = 0 then
                dbms_output.put_line('Expression: '||v_expression);
            end if;

        exception when others then
            dbms_output.put_line('Problem with: '||v_sqls(i));
        end;
    end loop;

    dbms_output.put_line('Queries executed: '||v_dynamic_query_counter);

end;
/

结果

结果看起来是正确的。它们与您的略有不同,因为“columnB > 10”不正确。

Expression:  AND A = 0 AND C < 6 AND D = 3
Expression:  AND A = 0 AND C < 6 AND D > 2
Expression:  AND A < 1 AND C < 6 AND D = 3
Expression:  AND A < 1 AND C < 6 AND D > 2
Queries executed: 441

问题

这种蛮力方法至少在两个方面效率极低。即使对于这个简单的例子,它也需要 6370 次查询。结果可能包括重复项,这些重复项对于减少来说非常重要。或者,也许您会很幸运,并且解决方案太少以至于您可以目睹它们。

您可以采取一些措施来提高查询性能。最简单的方法是单独检查每个条件,如果它没有“获得”任何计数,则将其丢弃。

优化

不返回任何正结果的单个表达式被排除在外。对于示例数据,这将查询执行次数从 6370 次减少到 441 次。

并行运行该过程也可以将性能提高一个数量级。它可能需要一个并行流水线函数。

但即使是 100 倍的性能提升也可能无助于解决 NP 完全问题。您可能需要根据样本数据找到一些额外的“捷径”。

通过取消注释其中一个 dbms_output.put_line 可能有助于打印出生成测试查询的查询声明。添加 count(*)查看将执行多少查询并使用较小的数据集运行,以估计需要多长时间。

如果估计是 10 亿年,而你想不出任何让蛮力法更快工作的技巧,可能是时候在 https://cstheory.stackexchange.com/ 上提问这个问题了。

关于java - 算法或 SQL : to find where conditions for a set of columns which ensures result set has value in a particular column always > 0,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28208530/

有关java - 算法或 SQL : to find where conditions for a set of columns which ensures result set has value in a particular column always > 0的更多相关文章

随机推荐