有没有办法针对SQL-CSV-Antipattern编写“好”的查询?

时间:2017-05-23 14:59:50

标签: sql oracle csv

TL; DR在底部。

我继承了一个设计得很差的表,该表包含对第三方Web服务的请求记录。此表包含一个时间戳,两个名为metadata_1和metadata_2的字段,用于标识用户,以及一个名为TEXT的非常长的VARCHAR2字段。

此TEXT字段包含实际的请求信息,表示为以逗号分隔的值列表。隐藏在此列表中的某处是两个相关的信息位,我将其称为“请求类型”和“请求密钥”。我试图找出每个用户不成功的请求与每天总请求和请求密钥的比率。然后将该值与当天的全局错误率进行比较,以识别在与Web服务通信时遇到问题的设备。 (元数据只标识用户,因此访问他们的设备需要额外的努力。)一个非成功的请求有一个我称之为“错误类型”的字段,它在请求类型之前插入。

正如您可能从最后一句话中猜到的那样,这个以逗号分隔的值列表的格式不一致。特别是,逗号分隔列表中KEY字段的位置取决于请求类型,其位置又取决于请求是否成功。所有这些字段都是可变长度的。

所以TEXT字段可能如下所示:

"2017-04-05T07:21:00.569Z,html_error:403,get_status,80,asdf2k,1,0,KEY_123,hunter2"

"2017-04-05T07:21:01.529Z,html_error:403,get_status,80,asdf2k,1,0,KEY_123,hunter2"

但它也可能看起来像这样:

"2017-04-05T07:23:46.459Z,send_events,80,qwert-8,2,1,KEY_123,foobar,1,1,false,114,11838"

我的问题是:您如何解决这个问题?有一个查询在测试环境中工作,下面再现,但其性能极差。必须有一个更好的方法来做到这一点。假设这一切都必须在单个查询中完成,并且我对环境或数据库设计没有影响。 (请注意,这里还有一些额外的过滤。其他任何看似奇怪的事情都可能是我在匿名化过程中犯的错误。这更多是关于我正在使用的策略 - 我不是要求你为我编写查询。 )

期望的结果:对于每个用户,日期和请求密钥,成功和不成功请求的数量,与当天成功请求的比率相比。

当前结果:如上所述,但具有不可接受的性能。

select a.*, b.success_rate_day from (select device.serial_id, rql.date, rql.KEY, rql.requests_ok, rql.requests_error
from device, user, 
    (select request_log.meta_1, request_log.meta_2, to_char(request_log.created_timestamp, 'DDD') AS date,
        (select count(b.TEXT) 
            FROM request_log b 
            where b.meta_1 = request_log.meta_1 
            and b.meta_2 = request_log.meta_2 
            and b.text NOT LIKE '%error%' 
            and to_char(request_log.created_timestamp, 'DDD') = to_char(b.created_timestamp, 'DDD') 
            and (CASE 
                    WHEN b.TEXT LIKE '%html_error%' THEN SUBSTR(b.TEXT, INSTR(b.TEXT, ';', -1, 2)+1, (INSTR(b.TEXT, ';', -1, 1)-INSTR(b.TEXT, ';', -1, 2)-1)) 
                    WHEN b.TEXT LIKE '%get_status%' THEN SUBSTR(b.TEXT, INSTR(b.TEXT, ';', -1, 4)+1, (INSTR(b.TEXT, ';', -1, 3)-INSTR(b.TEXT, ';', -1, 4)-1)) 
                    WHEN b.TEXT LIKE '%send_events%' THEN SUBSTR(b.TEXT, INSTR(b.TEXT, ';', 1, 6)+1, INSTR(b.TEXT, ';', 1, 7)-INSTR(b.TEXT, ';', 1, 6)-1) 
                    ELSE 'Error' 
                END) = (CASE 
                    WHEN request_log.TEXT LIKE '%html_error%' THEN SUBSTR(request_log.TEXT, INSTR(request_log.TEXT, ';', -1, 2)+1, (INSTR(request_log.TEXT, ';', -1, 1)-INSTR(request_log.TEXT, ';', -1, 2)-1)) 
                    WHEN request_log.TEXT LIKE '%get_status%' THEN SUBSTR(request_log.TEXT, INSTR(request_log.TEXT, ';', -1, 4)+1, (INSTR(request_log.TEXT, ';', -1, 3)-INSTR(request_log.TEXT, ';', -1, 4)-1)) 
                    WHEN request_log.TEXT LIKE '%send_events%' THEN SUBSTR(request_log.TEXT, INSTR(request_log.TEXT, ';', 1, 6)+1, INSTR(request_log.TEXT, ';', 1, 7)-INSTR(request_log.TEXT, ';', 1, 6)-1) 
                    ELSE 'Error' 
                END)
        ) AS requests_ok, 
        (select count(b.TEXT) 
            FROM request_log b 
            where b.meta_1 = request_log.meta_1 
            and b.meta_2 = request_log.meta_2 
            and b.text LIKE '%error%' 
            and to_char(request_log.created_timestamp, 'DDD') = to_char(b.created_timestamp, 'DDD') 
            and (CASE 
                    WHEN b.TEXT LIKE '%html_error%' THEN SUBSTR(b.TEXT, INSTR(b.TEXT, ';', -1, 2)+1, (INSTR(b.TEXT, ';', -1, 1)-INSTR(b.TEXT, ';', -1, 2)-1)) 
                    WHEN b.TEXT LIKE '%get_status%' THEN SUBSTR(b.TEXT, INSTR(b.TEXT, ';', -1, 4)+1, (INSTR(b.TEXT, ';', -1, 3)-INSTR(b.TEXT, ';', -1, 4)-1)) 
                    WHEN b.TEXT LIKE '%send_events%' THEN SUBSTR(b.TEXT, INSTR(b.TEXT, ';', 1, 6)+1, INSTR(b.TEXT, ';', 1, 7)-INSTR(b.TEXT, ';', 1, 6)-1) 
                    ELSE 'Error' 
                END) = (CASE 
                    WHEN request_log.TEXT LIKE '%html_error%' THEN SUBSTR(request_log.TEXT, INSTR(request_log.TEXT, ';', -1, 2)+1, (INSTR(request_log.TEXT, ';', -1, 1)-INSTR(request_log.TEXT, ';', -1, 2)-1)) 
                    WHEN request_log.TEXT LIKE '%get_status%' THEN SUBSTR(request_log.TEXT, INSTR(request_log.TEXT, ';', -1, 4)+1, (INSTR(request_log.TEXT, ';', -1, 3)-INSTR(request_log.TEXT, ';', -1, 4)-1)) 
                    WHEN request_log.TEXT LIKE '%send_events%' THEN SUBSTR(request_log.TEXT, INSTR(request_log.TEXT, ';', 1, 6)+1, INSTR(request_log.TEXT, ';', 1, 7)-INSTR(request_log.TEXT, ';', 1, 6)-1) 
                    ELSE 'Error' 
                END)
        ) AS requests_error,
        (CASE 
            WHEN TEXT LIKE '%html_error%' THEN SUBSTR(TEXT, INSTR(TEXT, ';', -1, 2)+1, (INSTR(TEXT, ';', -1, 1)-INSTR(TEXT, ';', -1, 2)-1)) 
            WHEN TEXT LIKE '%get_status%' THEN SUBSTR(TEXT, INSTR(TEXT, ';', -1, 4)+1, (INSTR(TEXT, ';', -1, 3)-INSTR(TEXT, ';', -1, 4)-1)) 
            WHEN TEXT LIKE '%send_events%' THEN SUBSTR(TEXT, INSTR(TEXT, ';', 1, 6)+1, INSTR(TEXT, ';', 1, 7)-INSTR(TEXT, ';', 1, 6)-1) 
            ELSE 'Error' 
        END) AS KEY
    from request_log 
    where request_log.meta_1 <= 99999
    and extract(hour from request_log.created_timestamp) BETWEEN 5 AND 23 
    group by request_log.meta_1, 
            request_log.meta_2,
            to_char(request_log.created_timestamp, 'DDD'), 
            (CASE 
                    WHEN TEXT LIKE '%html_error%' THEN SUBSTR(TEXT, INSTR(TEXT, ';', -1, 2)+1, (INSTR(TEXT, ';', -1, 1)-INSTR(TEXT, ';', -1, 2)-1)) 
                    WHEN TEXT LIKE '%get_status%' THEN SUBSTR(TEXT, INSTR(TEXT, ';', -1, 4)+1, (INSTR(TEXT, ';', -1, 3)-INSTR(TEXT, ';', -1, 4)-1)) 
                    WHEN TEXT LIKE '%send_events%' THEN SUBSTR(TEXT, INSTR(TEXT, ';', 1, 6)+1, INSTR(TEXT, ';', 1, 7)-INSTR(TEXT, ';', 1, 6)-1) 
                    ELSE 'Error' 
            END)
    ) rql
where (device.user_ID = user.id)
and (user.meta_1 = rql.meta_1)
and (user.meta_2 = rql.meta_2)) a,
    (select rql_global.date, rql_global.success_rate_day
from (select to_char(request_log.created_timestamp, 'DDD') AS date, ROUND(
        (select count(b.TEXT) FROM request_log b where b.meta_1 <= 99999 and extract(hour from b.created_timestamp) BETWEEN 5 AND 23 and b.text NOT LIKE '%error%' and to_char(request_log.created_timestamp, 'DDD') = to_char(b.created_timestamp, 'DDD'))
        /
        GREATEST((select count(c.TEXT) FROM request_log c where c.meta_1 <= 99999 and extract(hour from c.created_timestamp) BETWEEN 5 AND 23 and to_char(request_log.created_timestamp, 'DDD') = to_char(c.created_timestamp, 'DDD')), 1), 4) *100
    AS success_rate_day
    from request_log 
    group by to_char(request_log.created_timestamp, 'DDD')
    ) rql_global) b
where a.date = b.date
order by serial_id ASC, a.date ASC, KEY ASC;

1 个答案:

答案 0 :(得分:1)

我认为这可以改写如下:

WITH log_info AS (SELECT meta_1,
                         meta_2,
                         to_char(created_timestamp, 'DDD') dt,
                         CASE 
                           WHEN TEXT LIKE '%html_error%' THEN SUBSTR(TEXT, INSTR(TEXT, ';', -1, 2)+1, (INSTR(TEXT, ';', -1, 1)-INSTR(TEXT, ';', -1, 2)-1)) 
                           WHEN TEXT LIKE '%get_status%' THEN SUBSTR(TEXT, INSTR(TEXT, ';', -1, 4)+1, (INSTR(TEXT, ';', -1, 3)-INSTR(TEXT, ';', -1, 4)-1)) 
                           WHEN TEXT LIKE '%send_events%' THEN SUBSTR(TEXT, INSTR(TEXT, ';', 1, 6)+1, INSTR(TEXT, ';', 1, 7)-INSTR(TEXT, ';', 1, 6)-1) 
                           ELSE 'Error' 
                         END key_val
                  FROM   request_log 
                  where  request_log.meta_1 <= 99999
                  and    extract(hour from request_log.created_timestamp) BETWEEN 5 AND 23),
           li AS (SELECT meta_1,
                         meta_2,
                         dt,
                         key_val,
                         COUNT(CASE WHEN text NOT LIKE '%error%' THEN 1 END) requests_ok,
                         COUNT(CASE WHEN text LIKE '%error%' THEN 1 END) requests_error,
                         COUNT(*) total_requests
                  FROM   log_info
                  GROUP BY meta_1,
                           meta_2,
                           dt,
                           key_val),
           rl AS (SELECT meta_1,
                         meta_2,
                         dt,
                         key_val,
                         requests_ok,
                         requests_error,
                         SUM(requests_error) OVER (PARTITION BY dt) requests_error_by_ddd,
                         SUM(total_requests) OVER (PARTITION BY dt) total_requests_by_ddd
                  FROM   li)
SELECT d.serial_id,
       rql.date,
       rql.key,
       rql.requests_ok,
       rql.requests_error,
       ROUND(100 * reqests_error_by_ddd/greatest(total_requests_by_ddd, 1), 2) success_rate_day
FROM   device d
       INNER JOIN usr u ON d.user_id = u.id
       INNER JOIN rl ON u.meta_1 = rl.meta_1
                        AND u.meta_2 = rl.meta_2;

但你必须检查我是否设法让逻辑正确。

首先,你在整个地方重复密钥的case语句,所以我把它拉到一个单独的子查询中(log_info)。

接下来,看起来你想做一个条件计数,所以我没有使用单独的标量子查询来获取计数,而是使用case来计算我想要计算的行数(空值不能得到)包括在计数中)。这是在li子查询中完成的。

然后,似乎你需要每天获得总请求和总错误请求,所以我使用了一个解析sum()函数在同一天为所有行提取信息 - 这是在{{{ 1}}子查询。

然后,在最后的查询中,我将连接到其他表加上success_rate_day计算。请注意,我将连接从旧式语法转换为ANSI连接语法。

假设我的逻辑确实正确,那么这应该比你当前的查询更高效。如果我的逻辑错误,希望你能够相应地修改我的查询。

我还将我的声音添加到合唱中,表明该表已经过优化,将信息拉出到他们自己的独立列中* {; - )