Sql server 时间历史表中的重复项

Sql server 时间历史表中的重复项,sql-server,sql-server-2016,temporal-tables,Sql Server,Sql Server 2016,Temporal Tables,我们有JCCfeed将数据从Oracle传输到sqlserver2016。由于某些未知原因,历史记录表中存在具有相同开始时间和结束时间的重复项。怎么会这样?我试图用条件SET Column=Column更新记录。在这种情况下,我有两条记录,其中所有字段都相同,但开始和结束时间不同。怎么会有相同的日期时间呢 更新: DDL: 更新2: 我不能提供真实的数据,但是这是我用来获取重复数据的查询。请注意,我按表中的所有列进行分组,因此这些列是明确的重复项: SELECT LEASE_NUMBER

我们有
JCC
feed将数据从
Oracle
传输到
sqlserver2016
。由于某些未知原因,历史记录表中存在具有相同开始时间和结束时间的重复项。怎么会这样?我试图用条件
SET Column=Column
更新记录。在这种情况下,我有两条记录,其中所有字段都相同,但开始和结束时间不同。怎么会有相同的日期时间呢

更新

DDL:

更新2

我不能提供真实的数据,但是这是我用来获取重复数据的查询。请注意,我按表中的所有列进行分组,因此这些列是明确的重复项:

SELECT LEASE_NUMBER
     , SysStart
     , SysEnd
     , cnt
  FROM
       (   SELECT *
                , COUNT(*) cnt
             FROM dbo.LEASES_HISTORY AS l
            GROUP BY l.LEASE_NUMBER
                   , l.CREDIT_DECISION_CODE
                   , l.LEASE_APPLICATION
                   , l.ACCOUNT_NUMBER
                   , l.CELLULAR_NUMBER
                   , l.DEALER_CODE
                   , l.USERNAME
                   , l.LEASE_DATE
                   , l.NEW_USED_FLAG
                   , l.MANUFACTURER_CODE
                   , l.MODEL
                   , l.SERIAL_NUMBER_ELECTRONIC
                   , l.SERIAL_NUMBER_MECHANICAL
                   , l.CONTROL_HEAD
                   , l.LEASE_TERM
                   , l.LESSEE_CITY
                   , l.LESSEE_ADDRESS_1
                   , l.LESSEE_ADDRESS_2
                   , l.LESSEE_STATE
                   , l.LESSEE_ZIP_CODE
                   , l.LESSEE_NAME
                   , l.KEY_NAME
                   , l.BASE_PAYMENT
                   , l.MONTHLY_SALES_TAX
                   , l.INSURANCE
                   , l.MONTHLY_PAYMENT
                   , l.SECURITY_DEPOSIT
                   , l.INVOICES_GENERATED_COUNT
                   , l.DATE_LAST_INVOICED
                   , l.DATE_LAST_LATE_FEE
                   , l.SECURITY_DEPOSITS_INVOICED
                   , l.SECURITY_DEPOSITS_REFUNDED
                   , l.ADVANCE_RENT
                   , l.ADVANCE_SALES_TAX
                   , l.TOTAL_ADVANCE_PAYMENT
                   , l.AUTO_LEASE_EXPIRATION_DATE
                   , l.PAYMENTS_REMAINING
                   , l.PV_PAYMENTS_REMAINING
                   , l.TAX_RATE
                   , l.TAX_STATE
                   , l.LEASE_FACTOR
                   , l.AMOUNT_FINANCED
                   , l.REMARKS
                   , l.VOUCHER_NUMBER
                   , l.BILL_METHOD_ADVANCE
                   , l.FINANCING_PACKAGE
                   , l.BUYOUT_AMOUNT
                   , l.BUYOUT_DATE
                   , l.DEPRECIATION_MONTHS
                   , l.SALVAGE_VALUE
                   , l.LAST_DEPRECIATION_DATE
                   , l.LAST_DEPRECIATION_AMOUNT
                   , l.ACCUMULATED_DEPRECIATION
                   , l.BILL_METHOD_BUYOUT
                   , l.BUYOUT_INVOICED
                   , l.RECEIVED_DATE
                   , l.LEASE_PROGRAM
                   , l.PAYMENTS_INCLUDED_ADVANCE
                   , l.SALESPERSON_CODE
                   , l.UNGUARANTEED_RESIDUAL_VALUE
                   , l.UNEARNED_INCOME
                   , l.DIRECT_COST
                   , l.AMORTIZABLE_UNEARNED_INCOME
                   , l.AMORTIZED_FLAG
                   , l.RESIDUAL_VALUE_PERCENTAGE
                   , l.MINIMUM_LEASE_PAYMENTS
                   , l.IMPLICIT_MONTHLY_INTEREST_RATE
                   , l.AP_POSTED_FLAG
                   , l.AP_POSTED_DATE
                   , l.CAPITALIZED_LEASE_FLAG
                   , l.LEASE_STATUS
                   , l.GROSS_INVESTMENT
                   , l.ADVANCE_BILLED_FLAG
                   , l.AP_VOUCHER_NUMBER
                   , l.BANK_PACKAGE
                   , l.INSURANCE_BINDER
                   , l.CURRENT_BUYOUT
                   , l.LEASE_AGE_YEARS
                   , l.GUARANTOR_NAME
                   , l.GUARANTOR_ADDRESS_LINE_1
                   , l.GUARANTOR_ADDRESS_LINE_2
                   , l.GUARANTOR_CITY
                   , l.GUARANTOR_STATE
                   , l.GUARANTOR_ZIP
                   , l.GUARANTOR_TELEPHONE
                   , l.GUARANTOR_SS_NUMBER
                   , l.GUARANTOR
                   , l.BILL_CYCLES_DEFER
                   , l.REVENUE_ACCOUNT
                   , l.INVOICE_TYPE
                   , l.CORRESPONDENCE_FLAG
                   , l.DOWN_PAYMENT
                   , l.ADVANCE_INSURANCE
                   , l.ORIGINAL_EQUIPMENT_COST
                   , l.SERVICING_DEALER_CODE
                   , l.DEALER_BUYOUT_DATE
                   , l.LEASE_OWNER_CODE
                   , l.LEASE_OWNER_DATE
                   , l.VENDOR_CODE
                   , l.SPLIT_FUNDING_COUNT
                   , l.DEALER_AMOUNT
                   , l.VENDOR_AMOUNT
                   , l.SALESPERSON_AMOUNT
                   , l.DEALER_OFFICE
                   , l.ASSESSMENT_YEAR
                   , l.PROPERTY_TAX_RATE
                   , l.ASSESSMENT_FACTOR
                   , l.MONTHLY_PROPERTY_TAX
                   , l.MANAGER_CODE
                   , l.DEALER_BUYOUT_PROGRAM
                   , l.SHARED_RESID_METHOD
                   , l.SHARED_RESID_AMOUNT
                   , l.SHARED_RESID_PERCENT
                   , l.SHARED_RESID_L_AND_D
                   , l.SHARED_RESID_COLLECTION_TYPE
                   , l.SHARED_RESID_MONTHS_OVERDUE
                   , l.ORIGINAL_LEASE_TERM
                   , l.ORIGINAL_LEASE_DATE
                   , l.ORIGINAL_BASE_PAYMENT
                   , l.ORIGINAL_MINIMUM_PAYMENTS
                   , l.NEW_PAYMENT_PLAN_FLAG
                   , l.NEW_PAYMENT_PLAN_OFFSET
                   , l.NEW_PAYMENT_PLAN_DATE
                   , l.NEW_MINIMUM_PAYMENTS
                   , l.BILLING_PERIOD_NUMBER
                   , l.BILLING_PERIOD_1_INVOICES
                   , l.BILLING_PERIOD_1_PAYMENT
                   , l.BILLING_PERIOD_2_INVOICES
                   , l.BILLING_PERIOD_2_PAYMENT
                   , l.BILLING_PERIOD_3_INVOICES
                   , l.BILLING_PERIOD_3_PAYMENT
                   , l.BILLING_PERIOD_4_INVOICES
                   , l.BILLING_PERIOD_4_PAYMENT
                   , l.BILLING_PERIOD_5_INVOICES
                   , l.BILLING_PERIOD_5_PAYMENT
                   , l.BILLING_PERIOD_6_INVOICES
                   , l.BILLING_PERIOD_6_PAYMENT
                   , l.EQUIPMENT_AMOUNT
                   , l.SERVICE_AMOUNT
                   , l.MONTHS_OF_RECOURSE
                   , l.VENDOR_PAID_FLAG
                   , l.SALESPERSON_PAID_FLAG
                   , l.AP_ACCOUNT
                   , l.GL_AP_ACCOUNT
                   , l.GL_OWNER_COMPANY
                   , l.GL_BRANCH
                   , l.GL_DEPARTMENT
                   , l.GL_EQUIPMENT
                   , l.GL_STATE
                   , l.DEALER_RECOURSE_PERCENT
                   , l.VENDOR_RECOURSE_PERCENT
                   , l.SALESPERSON_RECOURSE_PERCENT
                   , l.NUMBER_OF_UNITS
                   , l.INVOICES_SUBJECT_TO_DISCOUNT
                   , l.ORIGINAL_LEASE_PROGRAM
                   , l.MISSING_PAYMENT_COUNT
                   , l.BROKER_CODE
                   , l.REVENUE_SHARING_CODE
                   , l.REVENUE_SHARING_FACTOR
                   , l.REVENUE_SHARING_FLAG
                   , l.DEALER_SHARING_CODE
                   , l.DEALER_SHARING_FACTOR
                   , l.DEALER_SHARING_FLAG
                   , l.CHARGEBACK_RESERVE_FLAG
                   , l.FUNDING_FEE
                   , l.FUNDING_FEE_INVOICED_FLAG
                   , l.LEASE_REFERENCE
                   , l.ORIGINAL_LEASE_NUMBER
                   , l.ORIGINAL_ACCOUNT_NUMBER
                   , l.BASE_PAYMENT_TAXABLE
                   , l.DAY_TO_CHARGE_BACK
                   , l.LESSEE_FED_ID_NUMBER
                   , l.ORIGINAL_PURCHASE_PRICE
                   , l.ORIGINAL_PURCHASE_DATE
                   , l.ACQUISITION_PURCHASE_PRICE
                   , l.ACQUISITION_PURCHASE_DATE
                   , l.BROKER_REV_SHAR_AMT_OVERRIDE
                   , l.DEALER_REV_SHAR_AMT_OVERRIDE
                   , l.UPFRONT_TAX_FLAG
                   , l.UPFRONT_TAX_AMOUNT
                   , l.UPFRONT_TAX_BILLED
                   , l.SysStart
                   , l.SysEnd
           HAVING COUNT(*) > 1
       ) a;

更新3:好的,这是我能得到的新信息。我们试图捕捉
rpc_完成的事件,以查看实际发生的情况。跟踪文件有7条不同的语句,它们的
事件顺序不同。据我所知,这意味着这些语句是在不同的事务中执行的。稍后我将尝试准备更详细的更新,但目前的语句如下(除最后一个语句外,所有语句都是使用
sp_prepexec
存储过程执行的):

这一行实际上有3种不同的状态,我们称它们为
x,y,z

  • x
    状态通过PK更新记录——此时数据库中没有这样的记录
  • 通过PK使用
    y
    状态更新记录——此时数据库中没有这样的记录
  • 插入状态为
    x
    的记录
  • 插入状态为
    y
    的记录--此操作因PK约束冲突而失败
  • 将记录更新为
    x
    状态——这实际上没有改变任何行,因此实际记录没有改变
  • 将记录更新为
    y
    状态——实际记录已更改
  • 使用
    sp_execute
    过程将记录更新到
    z
    状态,其中句柄id与上次更新相同(更新到
    z
    状态),但通过参数传递的值不同
因此,在这些行动结束时,我们有:

原始表处于
z
状态,历史记录表类似于此(日期时间是实际值):

  • x
    状态栏,2017-11-01 16:55:31.3358248,2017-11-01 16:55:31.3358248
  • x
    状态栏,2017-11-01 16:55:31.3358248,2017-11-01 16:55:31.3358248
  • y
    state columns,2017-11-01 16:55:31.33582482017-11-01 16:55:41.9296659

时态表使用事务的开始时间作为使用的时间戳(请参见插入、更新和删除部分)。通过在事务中执行多个更新,或者在同一事务中插入后执行更新,您将获得具有相同开始/结束时间的给定主键值的多个记录

该链接摘录如下:

更新:在更新时,系统存储行的上一个值 并将SysEndTime列的值设置为 当前事务的开始时间(UTC时区中)基于 在系统时钟上

下面是一个重现这种行为的示例。请注意,
ProductId=2
ProductsHistory
表中有两个相同的小部件条目。还要注意的是,虽然
ProductId=1
的更新没有更改该值,但它们仍然创建了相同的历史记录表记录

create table test.Products (
        ProductId       int not null primary key,
        ProductName     nvarchar(100) not null,
        EffectiveDate   datetime2(7) generated always as row start hidden not null,
        EndingDate      datetime2(7) generated always as row end hidden not null,
        period for system_time (EffectiveDate, EndingDate)
        )
        with (system_versioning = on (history_table = test.ProductsHistory));

go

begin tran

insert  test.Products (ProductId, ProductName) values
        (1, N'Widgets'),
        (2, N'Miniature Widgets');

update  test.Products set ProductName = 'Tiny Widgets' where ProductId = 2;
update  test.Products set ProductName = 'Miniature Widgets' where ProductId = 2;
update  test.Products set ProductName = 'Tiny Widgets' where ProductId = 2;
update  test.Products set ProductName = 'Widgets' where ProductId = 1;
update  test.Products set ProductName = 'Widgets' where ProductId = 1;

commit tran

select  p.ProductId, p.ProductName, p.EffectiveDate, p.EndingDate
from    test.Products for system_time all p;

select  *
from    test.ProductsHistory;

我将添加另一种可能性作为单独的答案,尽管我认为这种可能性要小得多——服务器时钟已经改变了。我见过服务器/PC时间发生变化的时间重叠实例(文本日志文件多于SQL,但仍有可能)。当夏令时开始/结束时可能会发生这种情况(尽管在这种情况下不太可能,因为开始/结束字段是UTC)。更遥远的可能性是服务器时钟漂移。我怀疑你会在服务器上看到这种情况——我们在单核工业PC上看到这种情况,运行时很热,CPU经常出现最大值。即使每天同步时钟,我们也可能在一天结束时缩短1分钟或2分钟。

您确定没有人直接将数据插入历史记录表?您似乎没有提到这一点-您所有的副本似乎都有相同的开始和结束时间。这似乎是“有效的”,因为如果在同一“瞬间”内多次更新一行,您希望在历史记录表中看到什么?您是否有任何
update
语句使用MS的扩展形式,使用
FROM
子句?如果同一行可以多次更新,并且不能保证它产生的实际行为,则该表单不会出错。@Damien_the_unsiever和@SMM已步入正轨。以下是MSDN的相关引用:“对于SYSTEM_TIME,筛选出有效期为零的行(SysStartTime=SysEndTime)。如果在同一事务中对同一主键执行多个更新,则将生成这些行。在这种情况下,临时查询只显示事务之前的行版本和事务之后实际的行版本。“-取自。解决方案是对系统时间使用
我不认为事件序列显示事务边界(请参阅)。你能提供一些真实的例子吗?我无论如何也不能重新制作它。我可以为不同的记录获得相同的开始/结束时间,但我无法获得绝对重复。您应该能够通过更新一个字段,然后将其设置为新值,然后在同一事务中重新设置来创建精确的重复项。您是否查看了一个特定数据的采样,并检索了该租约号的所有记录
create table test.Products (
        ProductId       int not null primary key,
        ProductName     nvarchar(100) not null,
        EffectiveDate   datetime2(7) generated always as row start hidden not null,
        EndingDate      datetime2(7) generated always as row end hidden not null,
        period for system_time (EffectiveDate, EndingDate)
        )
        with (system_versioning = on (history_table = test.ProductsHistory));

go

begin tran

insert  test.Products (ProductId, ProductName) values
        (1, N'Widgets'),
        (2, N'Miniature Widgets');

update  test.Products set ProductName = 'Tiny Widgets' where ProductId = 2;
update  test.Products set ProductName = 'Miniature Widgets' where ProductId = 2;
update  test.Products set ProductName = 'Tiny Widgets' where ProductId = 2;
update  test.Products set ProductName = 'Widgets' where ProductId = 1;
update  test.Products set ProductName = 'Widgets' where ProductId = 1;

commit tran

select  p.ProductId, p.ProductName, p.EffectiveDate, p.EndingDate
from    test.Products for system_time all p;

select  *
from    test.ProductsHistory;