【问题标题】:How can deal with lists and ranges embedded in a column?如何处理嵌入在列中的列表和范围?
【发布时间】:2011-02-10 19:16:31
【问题描述】:

我无法控制我尝试处理的数据的格式。当然,我可以使用脚本语言来处理数据库之外的以下问题,但我想避免这种情况,因为我要处理的数据量很大,而且我想消除手动操作的必要性步骤。

简而言之,我有一个列表表。一个列表可能包含一个 3 位字符串、多个 3 位字符串、一系列 3 位字符串,例如012-018,或多个 3 位字符串和 3 位字符串范围。例如:

drop table list;
drop table lists;

create table lists (id varchar, vals varchar);

insert into lists values('A', '001,003-005');
insert into lists values('B', '008-007');
insert into lists values('C', '010, 011, 012');
insert into lists values('D', '011-013, 016-018, 020');

我知道我知道

我想把它变成下表:

create table list (id varchar, val varchar);
A 001 003 004 005 B 008 B 007 C 010 C 011 C 012 D 011 D 012 013 D 016 D 017 D 018 D 020

有没有办法在 SQL 中做到这一点?

【问题讨论】:

  • 我实际上是在 SAS 的 PROC SQL 中这样做的。
  • 如果您的问题是,是否可以使用纯 SQL 来完成,我会说不,不现实。 SQL 大师能否将其变成 codegolf 并编写一个 whizbang、难以理解的 SQL 语句来完成它……可能。但我不想维护它。是否可以使用您的特定品种支持的任何程序语言在数据库端完成...绝对。
  • @Stephanie:这也是我的感觉,但是我知道的 SQL 不够多,无法知道我不知道的东西 ;-)

标签: sql denormalized


【解决方案1】:

由于您没有使用特定的 RDBMS 标记您的问题,我将不得不笼统地回答。

SQL 本身不提供您正在寻找的基本操作,这基本上是一个字符串拆分。这意味着您必须自己编写,或使用已在线发布的众多中的一种。

不过,您在数据中拥有的范围使事情变得有些复杂。这意味着您的程序将如下所示:

  1. 将数据插入临时/内存表并按程序迭代(或者,使用游标来执行相同操作)
  2. 对于集合中的每条记录,提取非规范化字符串数据并按',' 拆分。
  3. 对于拆分数据中的每个元素,您必须将 that 拆分为 '-'(对于非范围元素,应该返回一个结果)。
  4. 如果您的第二次拆分(在'-' 上)产生一个结果,那么您可以将它插入到最终目的地中。如果它产生两个结果,那么它是一个范围,您必须从头到尾迭代(使用该拆分的元素 1 和 2)并将记录插入到最终目的地

评论后编辑

很遗憾,我对 PROC SQL 或 SAS 并不熟悉,因此我无法为此提供具体的解决方案。我可以在 SQL Server T-SQL 中发布以下内容,希望能帮助您入门。

declare @results table (idx int identity(1, 1), id varchar(5), data varchar(max))
declare @elements table (idx int identity(1, 1), element varchar(25))
declare @range table (idx int identity(1, 1), element varchar(25))

insert into @results (id, data)
select
    your_id,
    your_data

from your_source

declare @i int
declare @cnt int

declare @j int
declare @cnt2 int

declare @element varchar(25)

declare @first int
declare @second int

declare @start int
declare @end int

declare @id varchar(5)
declare @data varchar(max)

select @i = min(idx) - 1, @cnt = max(idx) from @results

while @i < @cnt
begin
    select @i = @i + 1

    select @id = id, @data = data from @results where idx = @i

    delete @elements

    insert into @elements (element) 
    select
        element

    from split(@data, ',')

    select @j = min(idx) - 1, @cnt2 = max(idx) from @elements

    while @j < @cnt2 
    begin
        select @j = @j + 1

        select @element = element from @elements where idx = @j

        delete @range

        insert into @range (element)
        select
            element

        from split(@element, '-')

        select @first = min(idx), @second = max(idx) from @range

        if @first = @second --single element
            insert into final_destination (id, value)
            select
                @id,
                element

            from @range
        else if @second - @first = 1 -- two elements, as desired
        begin
            select @start = convert(int, element) - 1 from @range where idx = @first
            select @end = convert(int, element) from @range where idx = @second

            while @start < @end
            begin
                select @start = @start + 1

                insert into final_destination (id, value)
                values (@id, @start)
            end
        end
        else -- error condition, bad input
    end
end

【讨论】:

  • 这比我自己想出的任何东西都要好。在我放弃之前,我将看看我是否可以按照这个逻辑实现一些东西。 ;-)
猜你喜欢
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多