使用列上的函数对 Oracle table 进行分区

Partitioning a Oracle table using a function over a column

由于我不想创建新列,因此我想在列上使用函数对 table 进行分区。我的 table 示例有一个列 DATE_VARCHAR(格式 'YYYY-MM-DD'),我想根据日期的月份对其进行分区,但我遇到了一些问题。

  1. 我无法更改我的 table 来添加分区(Oracle 要求),所以我创建了一个备份 table 来备份数据。
  2. 我无法在新结构中添加列(客户要求),所以如果我想在 DATE_VARCHAR 上使用子字符串函数进行分区,我会收到语法错误:

    CREATE TABLE PRUEBA(
    DATE_VARCHAR VARCHAR2(10),
    SOME_COLUMNS VARCHAR(50)
    )
    
    PARTITION BY LIST (SUBSTR(DATE_VARCHAR,6,2))
    (PARTITION p1 VALUES ('01','05','09'),
    PARTITION p2 VALUES ('02','06','10'),
    PARTITION p3 VALUES ('03','07','11'),
    PARTITION p4 VALUES ('04','08','12'));
    

    由于它需要一个标识符列(列名),尽管我收到以下错误:ORA-00907 缺少右括号.

  3. 如果我创建一个包含月份值的列:

    CREATE TABLE Example(
    DATE_VARCHAR VARCHAR2(10),
    SOME_COLUMNS VARCHAR(50),
    MONTH VARCHAR2(2) GENERATED ALWAYS AS
        (SUBSTR(DATE_VARCHAR,6,2)) VIRTUAL
    )
    
    PARTITION BY LIST (MONTH)
    (PARTITION p1 VALUES ('01','05','09'),
    PARTITION p2 VALUES ('02','06','10'),
    PARTITION p3 VALUES ('03','07','11'),
    PARTITION p4 VALUES ('04','08','12'));
    

我收到以下错误:ORA-12899 列 %s 的值太大(实际值:%s,最大值:%s),由于新列仅长度 2(源列长度为 10);尽管我正在做一个长度为 2 的子串。

做到这一点的唯一方法是 3?是否存在另一种解决方法?

编辑: 如果我执行下一步,它对我有用:

CREATE TABLE Example(
DATE_VARCHAR VARCHAR2(10),
SOME_COLUMNS VARCHAR(50),
MONTH VARCHAR2(**10**) GENERATED ALWAYS AS
    (SUBSTR(DATE_VARCHAR,6,2)) VIRTUAL
)

PARTITION BY LIST (MONTH)
(PARTITION p1 VALUES ('01','05','09'),
PARTITION p2 VALUES ('02','06','10'),
PARTITION p3 VALUES ('03','07','11'),
PARTITION p4 VALUES ('04','08','12'));

但我不明白为什么列的长度需要等于 10,而不是 2,并计算子字符串。

编辑:Oracle 版本

脚本执行时出错:

编辑:通过 sqlplus 执行:

SQL> CREATE TABLE tabx(
  2      DATE_VARCHAR VARCHAR2(10),
    SOME_COLUMNS VARCHAR(50),
    MONTH VARCHAR2(2) GENERATED ALWAYS AS (SUBSTR(DATE_VARCHAR,6,2)) VIRTUAL
)
PARTITION BY LIST (MONTH)
(PARTITION p1 VALUES ('01','05','09'),
PARTITION p2 VALUES ('02','06','10'),
PARTITION p3 VALUES ('03','07','11'),
PARTITION p4 VALUES ('04','08','12'),
-- need default in case of bad data format
partition others values (default)
);

-- NOTE: this WON'T work now after adding VIRTUAL column
--insert into tabx values ('2015-12-01', 'ABC');

-- but this will (must specify columns)
  3    4    5    6    7    8    9   10   11   12   13  insert into tabx(date_varchar,some_columns) values ('2016-01-01', 'XYZ');
    MONTH VARCHAR2(2) GENERATED ALWAYS AS (SUBSTR(DATE_VARCHAR,6,2)) VIRTUAL
    *
ERROR at line 4:
ORA-12899: value too large for column "MONTH" (actual: 2, maximum: 8)


SQL> SQL> SQL> SQL> SQL> SQL> insert into tabx(date_varchar,some_columns) values ('2016-01-01', 'XYZ')
            *
ERROR at line 1:
ORA-00942: table or view does not exist

以下是对我有用的方法。它的情况 3(虚拟列)。它在技术上违反了您的客户要求,即不创建新列,尽管是虚拟的,但它仍然是一个列。总之:

CREATE TABLE tabx(
    DATE_VARCHAR VARCHAR2(10),
    SOME_COLUMNS VARCHAR(50),
    MONTH VARCHAR2(2) GENERATED ALWAYS AS (SUBSTR(DATE_VARCHAR,6,2)) VIRTUAL
)
PARTITION BY LIST (MONTH)
(PARTITION p1 VALUES ('01','05','09'),
PARTITION p2 VALUES ('02','06','10'),
PARTITION p3 VALUES ('03','07','11'),
PARTITION p4 VALUES ('04','08','12'),
-- need default in case of bad data format
partition others values (default)
);

-- NOTE: this WON'T work now after adding VIRTUAL column
--insert into tabx values ('2015-12-01', 'ABC');

-- but this will (must specify columns)
insert into tabx(date_varchar,some_columns) values ('2016-01-01', 'XYZ');

commit;

请注意,默认分区也已创建。

在此处添加脚本输出

运行 上面的 11.2 实例(加上来自 table 的附加 select)给出:

SQL> set lines 500
SQL> drop table tabx
Table dropped.
SQL> CREATE TABLE tabx(
    DATE_VARCHAR VARCHAR2(10),
    SOME_COLUMNS VARCHAR(50),
    MONTH VARCHAR2(2) GENERATED ALWAYS AS (SUBSTR(DATE_VARCHAR,6,2)) VIRTUAL
)
PARTITION BY LIST (MONTH)
(PARTITION p1 VALUES ('01','05','09'),
PARTITION p2 VALUES ('02','06','10'),
PARTITION p3 VALUES ('03','07','11'),
PARTITION p4 VALUES ('04','08','12'),
-- need default in case of bad data format
partition others values (default)
)
Table created.
SQL> -- NOTE: this WON'T work now after adding VIRTUAL column
SQL> --insert into tabx values ('2015-12-01', 'ABC');
SQL> -- but this will (must specify columns)
SQL> insert into tabx(date_varchar,some_columns) values ('2016-01-01', 'XYZ')
1 row created.
SQL> commit
Commit complete.
SQL> select * from tabx partition(p1)

DATE_VARCHAR SOME_COLUMNS                                       MONTH
------------ -------------------------------------------------- -----
2016-01-01   XYZ                                                01   
1 row selected.

编辑:

只是一个猜测,但如果以上方法对您不起作用,则问题可能出在多字节编码上(这就是为什么我要询问您在客户端和服务器上的 NLS_LANG 设置)。无论如何,如果您将 MONTH 列指定为:

会发生什么
MONTH VARCHAR2(2 CHAR) GENERATED ALWAYS AS (SUBSTR(DATE_VARCHAR,6,2)) VIRTUAL

这里我指定了 2 个字符而不是 2 个字节。同样,只是一个猜测,但很容易检查。

不确定您的数据库是否已转到 12c。但是,如果你有那么你可以使用一个虚拟的不可见列来实现你想要做的而不影响数据库。

CREATE TABLE example
(
    date_varchar    VARCHAR2(10)
  , some_columns    VARCHAR(50)
  , month           NUMBER(2, 0) INVISIBLE GENERATED ALWAYS AS(EXTRACT(MONTH FROM TO_DATE(date_varchar, 'YYYY-MM-DD'))) VIRTUAL
)
PARTITION BY LIST(month)
(
    PARTITION p1 VALUES (1, 5, 9)
  , PARTITION p2 VALUES (2, 6, 10)
  , PARTITION p3 VALUES (3, 7, 11)
  , PARTITION p4 VALUES (4, 8, 12)
);
No rows affected (0.023 seconds)

因为列是不可见的,所以不需要插入。

INSERT INTO example VALUES ('2015-12-01', 'ABC');
1 row affected (0.056 seconds)

然后该行被放入正确的分区。

SELECT * FROM example PARTITION(p4);
+--------------+--------------+
| DATE_VARCHAR | SOME_COLUMNS |
+--------------+--------------+
| 2015-12-01   | ABC          |
+--------------+--------------+
1 row selected (0.016 seconds)