根据有关查询数据的元数据动态生成sql语句

时间:2019-05-22 22:54:41

标签: sql oracle dynamic-sql

我想通过定义如何读取和拆分字符串来动态查询分段为长字符串的数据。

所以我可以使用以下元素定义数据

FIELD_NAME              VARCHAR2(30)          NOT NULL,
DATA_TYPE               VARCHAR2(20)          NOT NULL,
COLUMN_ID               NUMBER                NOT NULL,
FIELD_START_POS         NUMBER,
FIELD_END_POS           NUMBER,
FIELD_LEN               NUMBER,
ROW_TYPE                VARCHAR2(10),
DATE_MASK               VARCHAR2(12)

此表中的示例数据

enter image description here

我可以利用该信息来创建一个类似于

的选择吗?
SELECT CASE cd.data_type
           WHEN 'DATE'
           THEN
               TO_DATE (SUBSTR (sd.source_text, cd.field_start_pos, cd.field_len), cd.date_mask)
           WHEN 'NUMBER'
           THEN
               TO_NUMBER (SUBSTR (sd.source_text, cd.field_start_pos, cd.field_len))
           ELSE
               TRIM (SUBSTR (sd.source_text, cd.field_start_pos, cd.field_len))
       END
           AS cd.field_name
  FROM staged_data sd, column_definitions cd

我很难将2个绑在一起。

我知道我可以像这样将定义中的列名旋转:

SELECT *
  FROM column_definitions 
  PIVOT (max(field_name) FOR column_id IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20))

但这仍然会导致很多行

我的目标是生成此语句,以便可以通过EXECUTE IMMEDIATE运行该语句,以便仅通过定义如何读取字符串即可将其用于许多不同的文件。

我还需要读取不同的行类型,因此row_type列将为同一文件定义,但具有自己的列顺序和列。


因此,我能够基于有关暂存文件的元数据生成一个字符串,该字符串是我要查找的内容,如下所示:

DECLARE
    select_items   VARCHAR2 (4000);
BEGIN
    FOR c IN (  SELECT *
                  FROM column_definitions
                 WHERE file_pk = 1 AND row_type = 1
              ORDER BY column_id)
    LOOP
        IF c.data_type = 'NUMBER'
        THEN
            select_items :=
                   select_items
                || 'CASE WHEN is_number(SUBSTR(row_data,'
                || c.field_start_pos
                || ','
                || c.field_len
                || ')) = ''TRUE'' THEN TO_NUMBER(SUBSTR(row_data,'
                || c.field_start_pos
                || ','
                || c.field_len
                || ')) ELSE NULL END AS '
                || c.field_name
                || ',';
        ELSIF c.data_type = 'DATE'
        THEN
            select_items :=
                   select_items
                || 'CASE WHEN ISDATE(SUBSTR(row_data,'
                || c.field_start_pos
                || ','
                || c.field_len
                || '))=''true'' THEN TO_DATE(SUBSTR(row_data,'
                || c.field_start_pos
                || ','
                || c.field_len
                || '),'''
                || c.date_mask
                || ''') ELSE NULL END AS '
                || c.field_name
                || ',';
        ELSE
            select_items :=
                   select_items
                || 'TRIM(SUBSTR(row_data,'
                || c.field_start_pos
                || ','
                || c.field_len
                || ')) AS '
                || c.field_name
                || ',';
        END IF;
    END LOOP;

    select_items := SUBSTR (select_items, 1, LENGTH (select_items) - 1);

    select_items :=
           'SELECT '
        || select_items
        || ' FROM STAGED_FILE where row_type=1 AND rownum <= 1000;';

    DBMS_OUTPUT.PUT_LINE (select_items);
END;

这会吐出这样的东西:

SELECT CASE
           WHEN is_number (SUBSTR (row_data, 1, 1)) = 'TRUE'
           THEN
               TO_NUMBER (SUBSTR (row_data, 1, 1))
           ELSE
               NULL
       END
           AS REC_TYPE_IND,
       SUBSTR (row_data, 11, 4)   AS SRVC_LOC,
       CASE
           WHEN ISDATE (SUBSTR (row_data, 15, 8)) = 'true'
           THEN
               TO_DATE (SUBSTR (row_data, 15, 8), 'YYYYMMDD')
           ELSE
               NULL
       END
           AS BEGIN_DT,
       CASE
           WHEN ISDATE (SUBSTR (row_data, 23, 8)) = 'true'
           THEN
               TO_DATE (SUBSTR (row_data, 23, 8), 'YYYYMMDD')
           ELSE
               NULL
       END
           AS END_DT,
       SUBSTR (row_data, 31, 50)  AS ID,
       SUBSTR (row_data, 101, 2)  AS COUNTY_CD,
       SUBSTR (row_data, 103, 30) AS ADDR_LN_1,
       SUBSTR (row_data, 133, 30) AS ADDR_LN_2,
       SUBSTR (row_data, 163, 18) AS CITY,
       SUBSTR (row_data, 181, 2)  AS STATE_CD,
       CASE
           WHEN is_number (SUBSTR (row_data, 183, 5)) = 'TRUE'
           THEN
               TO_NUMBER (SUBSTR (row_data, 183, 5))
           ELSE
               NULL
       END
           AS ZIP_CD,
       CASE
           WHEN is_number (SUBSTR (row_data, 188, 4)) = 'TRUE'
           THEN
               TO_NUMBER (SUBSTR (row_data, 188, 4))
           ELSE
               NULL
       END
           AS ZIP_CD4,
       CASE
           WHEN is_number (SUBSTR (row_data, 192, 10)) = 'TRUE'
           THEN
               TO_NUMBER (SUBSTR (row_data, 192, 10))
           ELSE
               NULL
       END
           AS PHONE_NUM
  FROM staged_FILE
 WHERE row_type = 1 AND ROWNUM <= 1000;

现在开始解决如何动态创建关联数组以将数据填充到数据中或使用另一种方式处理数据的问题。

1 个答案:

答案 0 :(得分:0)

在您的示例中,您使用了CASE语句。您的第一个表达式具有DATE数据类型,第二个表达式具有NUMBER,第三个表达式是VARCHAR2。从文档中:

  

对于简单的CASE表达式,expr和所有compare_expr值   必须具有相同的数据类型,或者必须全部具有   数字数据类型。

基本上,您无法执行此操作,因为无法在编译时知道field_name列的数据类型。

这不是要解决的简单问题,因为直到运行时您都不知道数据类型是什么。即使您获得了动态SQL语句,您仍将选择哪种变量将数据放入

我认为您基本上必须:

  1. 使用column_definitions,构造一个字符串,其中包含适用于所讨论数据类型的SQL语句。
  2. 创建一个TYPE,其中包含所有可能的结果数据类型的成员。
  3. 使用EXECUTE IMMEDIATEDBMS_SQL解析并执行该字符串,然后将结果提取到该类型的实例中。

实际上最好不要完全通过SQL执行此操作。相反,我可能会执行以下操作:

  1. column_definitions获取感兴趣的数据类型。
  2. 使用SUBSTR从staged_data中的字符串中提取感兴趣的区域。
  3. 执行类似操作:

l_token := SUBSTR (sd.source_text, cd.field_start_pos, cd.field_len);
IF l_datatype = 'DATE' THEN
    l_date := TO_DATE( l_token, 'yyyy-mm-dd' );
ELSIF l_datatype = 'NUMBER' THEN
    l_number := TO_NUMBER( l_token);
....
END IF;

我不希望这种方法具有高性能。