自我加入而不是合并

时间:2016-10-05 14:49:44

标签: sql query-optimization self-join

我需要编写一个查询扫描2个表的查询,如果它们具有一定条件,则在一个表中自连接后的记录不应出现在第二个表中。

如果收到订单,我们会插入到UniqueReference表中,该表对参考组合(data1,data2,reference,identifier)有唯一约束 但可能是订单被拒绝或取消,下一个订单可能会带有相同的参考组合。我必须确保我仍然允许它。

我在下面举例说明测试数据:

UniqueReference Table   
-------
sequence  data1  data2  key   reference  identifier  
1         XYZB   ABCD   234   Reference1   ID2
2         XYZB   DCBF   456   Reference2   ID2
3         XYZB   null   678   Reference3   null
4         XYZB   ABCD   980   Reference1   ID2 

Order Table
--------
sequence  key   status 
 1        234   Created  
 2        456   Rejected
 3        789   Processed 
 4        980   Cancelled 
 5        678   Processing 

现在,当我收到一个带有参考组合(XYZB,ABCD,Reference1,ID2)的订单(比如说密钥980)时,上面有两个数据集,那么系统不应该允许该订单(它是重复的)因为我们之前收到过这样的订单使用其状态未被拒绝的键(234)。但是如果我们收到组合的新订单(XYZB,DCBF,Reference2,ID2),那么我们应该允许这个订单,因为我们确实在系统中有该订单,但其状态为Rejected。

我需要像

这样的东西
Select count(*) from Order o where o.status <> 'Rejected' and o.key in 
(select <self-join> on reference combination on key for newly received order)

2 个答案:

答案 0 :(得分:0)

我认为你大部分都对此设置感到困惑。 至于UniqueReference表。如果你说组合data1, data2, reference, identifier是独一无二的,那么它应该只是:UNIQUE。无所谓也不应该关心其他地方发生的事情。

备注:我假设key字段也是唯一的,如果是,我想知道你为什么不简单地使用autonumber / identity / ...分配到sequence列,它没有真正的价值恕我直言。现在我不知道你是如何产生这些key值的。

现在对于Order表,您可以预见到这样的逻辑:如果我们还没有此key的先前记录,则允许INSERT操作。如果有前一个,找到最新的一个,如果它显示为“已取消”,则允许INSERT。否则我们总是阻止INSERT。

我不确定你是怎么想这样做的,但我想在这种情况下存储过程是有意义的。

PS:这个代码远非完美(对于初学者,它没有经过测试!),没有错误处理,可能会进行相当多的优化(例如通过合并某些操作,减少分支等)。但是我觉得它的冗长使它更容易阅读,因为过早的优化是所有邪恶的根源,你可能想要先解决这个问题,并在结果太慢时根据需要进行优化。

(因为你没有提到RDBMS,我使用的是T-SQL语法,但我认为它足够通用,可以在大多数系统上运行并进行微调......)

CREATE PROCEDURE p_insert_order (
                                  @data1        varchar(100), 
                                  @data2        varchar(100),
                                  @reference    varchar(100),
                                  @identifier   varchar(100),
                                  @status       varchar(100)
                                )
AS

    DECLARE @key          int,
            @is_new_key   bit,
            @allow_insert bit,
            @last_status  varchar(100)

    SELECT @key = NULL,
           @is_new_key = 0,
           @allow_insert = 0,
           @last_status = NULL


    BEGIN TRANSACTION

        -- do we already have a key for this combination?
        SELECT @key = [key]
          FROM [UniqueReference] WITH (UPDLOCK, HOLDLOCK)
         WHERE data1      = @data1      
           AND data2      = @data2     
           AND reference  = @reference 
           AND identifier = @identifier

        IF @key IS NULL
            BEGIN
                SELECT @is_new_key = 1,
                       @key = 0 -- ??? -- I have no clue how you come up with these so I'm going to leave it open

                INSERT [UniqueReference] (data1, data2, reference, identifier, [key])
                VALUES (@data1, @data2, @reference, @identifier, @key)
            END

        -- do we allow insert?
        SELECT @allow_insert = @is_new_key

        IF @allow_insert = 0
            BEGIN
                SELECT TOP 1 @last_status = [status]
                  FROM [Order]  WITH (UPDLOCK, HOLDLOCK)
                 WHERE [key] = @key
                 ORDER BY sequence DESC

                SELECT @allow_insert = 1
                 WHERE @last_status IS NULL
                    OR @last_status = 'Rejected'
            END

        -- do we do insert?
        IF @allow_insert = 1
            BEGIN

                INSERT [Order] ([key], [status])
                VALUES (@key, @status)

            END

    COMMIT TRANSACTION

    Return(0)

PS:尽量避免使用保留字Orderkeystatus,...

答案 1 :(得分:0)

首先,您必须将key表中的UniqueReference列添加到唯一约束中,以便它(data1,data2,reference,identifier,key)

然后您可以使用此查询

;with
old_orders as (
    select distinct data1, data2, [reference], [identifier]
    from UniqueReference u
    where not exists(select top 1 1 from Order o where u.[key] = o.[key] and o.status in ('Cancelled','Rejected'))
)
insert into @UniqueReference (data1, data2, [reference], [identifier], [key])
select @data1, @data2, @reference, @identifier, @key
where exists (
    select @data1, @data2, @reference, @identifier
    except
    select * from old_orders o
)

使用这些值,插入将被拒绝

declare 
    @data1 varchar(4)='XYZB',
    @data2 varchar(4)='ABCD', 
    @key int = 980, 
    @reference varchar(10)='Reference1',
    @identifier varchar(4)='ID2'

使用这些值将接受插入

declare 
    @data1 varchar(4)='XYZB',
    @data2 varchar(4)='DCBF', 
    @key int = 999, 
    @reference varchar(10)='Reference2',
    @identifier varchar(4)='ID2'