在SQL数据库中存储范围/多个范围

时间:2017-04-26 09:41:26

标签: oracle rdbms sqldatatypes

我想将范围存储在SQL表列中。例如[4-10],[14-18],然后使用类似

的查询过滤行

select * from table_name where range_column contains 16;

我对oracle更感兴趣,但也想知道其他任何数据库是否都有允许这样的数据类型。

我知道这可以使用多行和两列range_beginrange end来完成,但我想知道是否有一种数据类型可以使用单个列执行此操作。

2 个答案:

答案 0 :(得分:3)

使用本机数据类型没有一种简单的内置方法。

这不会以您显示的形式保留两个单独的范围,但您可以使用包含范围内所有值的嵌套表:

create type range_type as table of number
/

create table table_name (id number, range_column range_type)
nested table range_column store as range_tab;

显然,对于大范围,存储要求将高于仅存储低值和高值。

然后,一个重要的问题就变成了如何从原始范围数据填充它,以及如何维护它 - 因为原始范围的划分将丢失。对于一组连续值,您无法判断集合是从单个范围构建还是构建多个连续甚至重叠的范围。

你可以像这样设置你的范围,但是你真正做到这一点取决于你如何获得范围的低/高值:

insert into table_name (id, range_column)
values (1, cast(multiset(
   select level + 3 from dual connect by level <= 7
) as range_type));

insert into table_name (id, range_column)
values (2, cast(multiset(
   select level + 25 from dual connect by level <= 5
) as range_type));

insert into table_name (id, range_column)
values (3, cast(multiset(
   select level + 3 from dual connect by level <= 7
   union select level + 13 from dual connect by level <= 5
) as range_type));

您的查询将是:

select * from table_name where 16 member of range_column;

        ID RANGE_COLUMN                                                
---------- ------------------------------------------------------------
         3 RANGE_TYPE(4, 5, 6, 7, 8, 9, 10, 14, 15, 16, 17, 18)        

如果你想保持范围可以单独识别,你可以使用一个对象类型来保存低值和高值,然后使用那些嵌套表,尽管查询变得更复杂:

create type range_object as object(low number, high number)
/

create type range_type as table of range_object;
/

create table table_name (id number, range_column range_type)
nested table range_column store as range_tab;

insert into table_name (id, range_column)
values (1, range_type(range_object(4, 10)));

insert into table_name (id, range_column)
values (2, range_type(range_object(25, 5)));

insert into table_name (id, range_column)
values (3, range_type(range_object(4, 10), range_object(14, 18)));

select t.* from
table_name t
cross join table(t.range_column) r
where 16 between r.low and r.high;

        ID RANGE_COLUMN(LOW, HIGH)
---------- -----------------------------------------------------
         1 RANGE_TYPE(RANGE_OBJECT(4, 10), RANGE_OBJECT(14, 18))

与具有低/高值的单独表格相比,似乎都没有特别吸引人;既不存储范围的字符串表示,也不具有确定给定数字是否在其中的函数,尤其是验证范围的格式将是一种痛苦。

答案 1 :(得分:-1)

Oracle 12.1引入了一种新语法,可以更轻松地处理范围。不幸的是,它仅适用于日期,但它可以扭曲以使用数字。

通常我不建议将数据存储为错误类型的任何解决方案。只有在迫切需要更方便的范围语法时才使用它。

使用日期和PERIOD FOR子句创建表:

--drop table table_name;
create table table_name
(
    id number,
    range_begin date,
    range_end   date,
    period for table_name_period(range_begin, range_end)
);

插入数据,将其转换为'J'格式模型的日期。这使用朱利安日期格式,该格式计算公元前4712年的天数。日期仅转到9999年,这意味着此解决方案仅适用于0到5373484之间的数字。

insert into table_name values (1, to_date(4, 'J'), to_date(10, 'J'));
insert into table_name values (2, to_date(14, 'J'), to_date(18, 'J'));
commit;

现在可以简化比较条件。

select id, to_number(to_char(range_begin, 'J')) begin, to_number(to_char(range_end, 'J')) end
from table_name
as of period for table_name_period to_date(16, 'J');

ID   BEGIN   END
--   -----   ---
2       14    18

上面的代码中有很多丑陋的转换。它们可以通过一些简单的函数进行简化,以进行数字转换。

--Date-to-number.
create or replace function d2n(p_date date) return number is
begin
    return to_number(to_char(p_date, 'J'));
end;
/

--Number-to-date.
create or replace function n2d(p_number number) return date is
begin
    return to_date(p_number, 'J');
end;
/

使用这些函数,代码看起来更好。

select id, d2n(range_begin) begin, d2n(range_end) end
from table_name
as of period for table_name_period n2d(16);

这仍然是一团糟,可能只适用于某些极端情况。希望Oracle的未来版本将扩展范围语法以使用数字。