Mengapa UPDATE ini gagal dengan pelanggaran batasan kunci yang unik?

11

Saya seorang "kebetulan" DBA, relatif tidak berpengalaman dan bingung dengan masalah ini.

Menjalankan MS SQL Server 2012. Masalahnya adalah dengan pernyataan UPDATE ini:

UPDATE dbo.tAccts SET
       Ticket               = 'ARP.ExGE'
       , Method             = 'smtp'
       , AcctOwner          = 'r00417819'
       , DisplayName = '~AppLight HBSFax-Inactive'
       , Destination = '[email protected]'
       , UpdatedBy          = SYSTEM_USER
       , UpdatedOn          = CAST(GetDate() AS DATE)
FROM dbo.vReclaimable
WHERE OHR_EmpStatus <> 'A'

Yang harus memperbarui hanya baris dalam tabel tAccts yang dikembalikan oleh tampilan vReclaimable.

Tampilan vReclaimable didasarkan pada tabel tAccts dan mengembalikan sebagian dari baris dalam tAccts.

Ketika saya menjalankannya, gagal dengan kesalahan kunci unik:

(0 row(s) affected)
Msg 2627, Level 14, State 1, Line 67
Violation of UNIQUE KEY constraint 'UQ__tAccounts_DNIS.Method.Destination.Phones'. Cannot insert duplicate key in object 'dbo.tAccts'. The duplicate key value is (68497, smtp, r00417819@mail.ad.ge.com, 800-905-8793, none).
The statement has been terminated.

Cukup adil, tabel tAccts memang memiliki batasan kunci yang unik:

CONSTRAINT [UQ__tAccounts_DNIS.Method.Destination.Phones] UNIQUE NONCLUSTERED 
(
                [DNIS] ASC,[Method] ASC,[Destination] ASC,[Phone_TF] ASC,[Phone_Local] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) 

Tapi ini yang aneh. Jika saya menjalankan dua pertanyaan ini:

select 'tAccts table', dnis, method, destination, phone_tf, phone_local from tAccts where dnis=68497
select 'vReclaimable view', dnis, method, destination, phone_tf, phone_local, daysidle from vReclaimable where dnis=68497

Yang pertama mengembalikan dua baris (seperti yang diharapkan):

(No column name)     dnis   method destination   phone_tf      phone_local
tAccts table  68497  ftp    ftp://faxuser@ap1plm02cige/appliances    800-905-8793  none
tAccts table  68497  unc    \\\\for4as01applge\\cfs_portfolio\\cfs_faxdocs  800-905-8793  none

dan yang kedua mengembalikan 0 baris (seperti yang diharapkan).

Jika “FROM vReclaimable WHERE OHR_EmpStatus <> 'A'” mengembalikan 0 baris, mengapa UPDATE mencoba memperbarui baris di mana DNIS = 68497?

(Saya harap saya telah menggambarkan ini dengan memadai. Saya merasa saya kehilangan sesuatu yang jelas)

USE [TEST-GEAFax_arley_NEW]
GO

/****** Object:  Table [dbo].[tAccts]    Script Date: 12/9/2015 1:39:41 PM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[tAccts](
    [Ticket] [varchar](30) NOT NULL,
    [Method] [varchar](15) NOT NULL,
    [AcctOwner] [varchar](15) NOT NULL,
    [DisplayName] [varchar](75) NOT NULL,
    [Destination] [varchar](75) NOT NULL,
    [DNIS] [varchar](20) NOT NULL,
    [DNIS2] [varchar](20) NULL,
    [Phone_TF] [varchar](30) NOT NULL,
    [Phone_Local] [varchar](30) NOT NULL,
    [Phone_PBX] [varchar](255) NOT NULL,
    [UpdatedBy] [varchar](50) NOT NULL,
    [UpdatedOn] [date] NOT NULL,
    [FaxNotes] [varchar](255) NULL,
    [TelcomNotes] [varchar](255) NULL,
    [AcctID] [int] IDENTITY(0,1) NOT NULL,
 CONSTRAINT [PK__tAccounts_AcctID] PRIMARY KEY CLUSTERED 
(
    [AcctID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
 CONSTRAINT [UQ__tAccounts_DNIS.Method.Destination.Phones] UNIQUE NONCLUSTERED 
(
    [DNIS] ASC,
    [Method] ASC,
    [Destination] ASC,
    [Phone_TF] ASC,
    [Phone_Local] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

---------------------------------------------------------------------------------

USE [TEST-GEAFax_arley_NEW]
GO

/****** Object:  View [dbo].[vReclaimable]    Script Date: 12/9/2015 1:39:57 PM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO


/***********************************************************************
* Written By    : N. Arley Dealey (200018252
* Written On    :
* Updated By    :
* Updated On    :
* Description   : Returns data from tAccts, vRxAl, vWLT_AllGE
* Notes         :
***********************************************************************/
CREATE VIEW [dbo].[vReclaimable] AS
SELECT
        a.Ticket
        , a.Method
        , a.AcctOwner
        , a.DisplayName
        , a.Destination
        , a.DNIS
        , a.DNIS2
        , a.Phone_TF
        , a.Phone_Local
        , a.Phone_PBX
        , a.UpdatedBy
        , a.UpdatedOn
        , a.FaxNotes
        , a.TelcomNotes
        , a.AcctID
        , COUNT(jt.JobID) AS 'FaxesRcvd'
        , CAST(MIN(jt.TimeStamp_UTC) AS DATE) AS 'FirstRcvd'
        , CAST(MAX(jt.TimeStamp_UTC) AS DATE) AS 'LastRcvd'
        , DATEDIFF(dd, MAX(jt.TimeStamp_UTC), GETDATE()) AS 'DaysIdle'
        , o.OHR_EmpSSO
        , o.OHR_EmpStatus
        , o.OHR_EmpName
        , o.OHR_EmpTitle
        , o.OHR_BizIndustryGroup
        , o.OHR_BizSegment
        , o.OHR_BizUnit
        , o.OHR_BizDept
        , o.OHR_BizDomain
FROM
    dbo.tAccts AS a
    LEFT OUTER JOIN dbo.tAccts_Retain AS r ON (a.AcctID = r.AcctID)
    LEFT OUTER JOIN dbo.vWLT_AllGE AS o ON (a.AcctOwner = o.OHR_EmpSSO)
    LEFT OUTER JOIN dbo.vRxAll AS jt ON (a.DNIS = jt.DNIS)
    WHERE ( 1                                               -- place holder, has no effect
            AND r.RetainID IS NULL                          -- out of scope: in Retain table
            AND a.Method = 'smtp'                           -- out of scope: ftp, unc, cifs, printers
            AND a.Phone_Local NOT LIKE '216-%'              -- out of scope: NELA numbers
            AND a.AcctOwner <> 'r00417819'                  -- out of scope: reclaimed numbers
            AND a.AcctOwner <> 'r00336832'                  -- out of scope: never assigned numbers
            AND a.AcctOwner <> 'r00971729'                  -- out of scope: invalid numbers
            AND a.Destination NOT LIKE 'g%@mail.ad.ge.com'  -- out of scope: distribution lists
            AND a.Destination NOT LIKE 'r%@mail.ad.ge.com'  -- out of scope: shared mailboxes
        )
    GROUP BY
        a.DNIS
        -- remaining columns are just for syntax reasons
        , a.Ticket, a.Method, a.AcctOwner, a.DisplayName, a.Destination, a.DNIS2, a.Phone_TF, a.Phone_Local, a.Phone_PBX, a.UpdatedBy, a.UpdatedOn, a.FaxNotes, a.TelcomNotes, a.AcctID
        , o.OHR_EmpSSO, o.OHR_EmpStatus, o.OHR_EmpName, o.OHR_EmpTitle
        , o.OHR_BizIndustryGroup, o.OHR_BizSegment, o.OHR_BizUnit, o.OHR_BizDept, o.OHR_BizDomain

GO
ArleyD
sumber
tunjukkan pada kami CREATE VIEWpernyataannya.
ypercubeᵀᴹ
Dan apakah OHR_EmpStatuskolom dari tabel, tampilan atau keduanya?
ypercubeᵀᴹ
1
Saya hanya ASTONISHED pada jumlah respon cepat & sangat baik untuk pertanyaan saya. Seperti yang saya katakan, itu mungkin sesuatu yang jelas bahwa saya telah mengabaikan atau salah paham tetapi saya tidak berpikir itu karena saya telah berakhir dengan cross-join. Saya seharusnya memposting definisi untuk tabel tAccts dan tampilan vReclaimable. Saya akan meninjau semua jawaban yang diposting sejauh ini dan, jika saya masih berpikir mereka tidak tepat sasaran, saya akan menambahkan definisi tersebut ke pertanyaan. Sementara itu, terima kasih BESAR untuk semua orang yang telah merespons.
ArleyD
Menambahkan CREATE pernyataan untuk tAccts dan vReclaimable, seperti yang diminta oleh ypercube
ArleyD

Jawaban:

18

Itu bermuara pada apa UPDATEyang dilakukan pernyataan. Tidak sepenuhnya jelas tetapi pernyataan Anda setara dengan yang ini:

UPDATE upd SET
         Ticket             = 'ARP.ExGE'
       , Method             = 'smtp'
       , AcctOwner          = 'r00417819'
       , DisplayName = '~AppLight HBSFax-Inactive'
       , Destination = '[email protected]'
       , UpdatedBy          = SYSTEM_USER
       , UpdatedOn          = CAST(GetDate() AS DATE)
FROM 
    dbo.tAccts AS upd 
  CROSS JOIN
    dbo.vReclaimable AS v
WHERE OHR_EmpStatus <> 'A' ;

Karena tidak ada penyebutan dbo.tAcctstabel di dalam FROMdan tidak ada gabungan atau kondisi di mana antara tabel dan tampilan, itu menghasilkan CROSSgabungan dan upaya untuk memperbarui semua baris tabel (dan bukan hanya dari tampilan), dan mungkin beberapa kali juga!


Anda dapat menambahkan kondisi bergabung (atau di mana) dengan:

UPDATE upd SET
         Ticket             = 'ARP.ExGE'
       , Method             = 'smtp'
       , AcctOwner          = 'r00417819'
       , DisplayName = '~AppLight HBSFax-Inactive'
       , Destination = '[email protected]'
       , UpdatedBy          = SYSTEM_USER
       , UpdatedOn          = CAST(GetDate() AS DATE)
FROM 
    dbo.tAccts AS upd 
  JOIN
    dbo.vReclaimable AS v
      ON v.PK = upd.PK              -- whatever the PK column is
WHERE OHR_EmpStatus <> 'A' ;

atau (menggunakan versi Anda):

UPDATE dbo.tAccts SET
       Ticket               = 'ARP.ExGE'
       , Method             = 'smtp'
       , AcctOwner          = 'r00417819'
       , DisplayName = '~AppLight HBSFax-Inactive'
       , Destination = '[email protected]'
       , UpdatedBy          = SYSTEM_USER
       , UpdatedOn          = CAST(GetDate() AS DATE)
FROM dbo.vReclaimable
WHERE OHR_EmpStatus <> 'A'
  AND vReclaimable.PK = tAccts.PK ;

Atau, Anda dapat (mungkin) cukup memperbarui tampilan. Agar ini berfungsi, tampilan harus sesuai dengan batasan tentang "Tampilan yang Dapat Diupdate" . Lihat paragraf yang relevan dokumentasi MSDN: CREATE VIEW, Views Updatable :

UPDATE dbo.vReclaimable SET
       Ticket               = 'ARP.ExGE'
       , Method             = 'smtp'
       , AcctOwner          = 'r00417819'
       , DisplayName = '~AppLight HBSFax-Inactive'
       , Destination = '[email protected]'
       , UpdatedBy          = SYSTEM_USER
       , UpdatedOn          = CAST(GetDate() AS DATE)

WHERE OHR_EmpStatus <> 'A' ;
ypercubeᵀᴹ
sumber
2

tampaknya Anda tidak memiliki gabungan antara tabel dalam kueri pembaruan Anda.

UPDATE dbo.tAccts SET
       Ticket               = 'ARP.ExGE'
       , Method             = 'smtp'
       , AcctOwner          = 'r00417819'
       , DisplayName = '~AppLight HBSFax-Inactive'
       , Destination = '[email protected]'
       , UpdatedBy          = SYSTEM_USER
       , UpdatedOn          = CAST(GetDate() AS DATE)
FROM dbo.vReclaimable
WHERE OHR_EmpStatus <> 'A'

di sini harus ada sesuatu yang cocok dengan baris antara tabel seperti di mana tAccts.id = vReclaimable.id

alk
sumber
2

Cara lain untuk mengatakan ini:

Masalahnya adalah keyakinan Anda bahwa pernyataan "harus memperbarui hanya baris dalam tabel tAccts yang dikembalikan oleh tampilan vReclaimable".

Bukan itu masalahnya. Ini memperbarui semua baris dari tAccts(tabel yang disebutkan setelah UPDATE) yang cocok OHR_EmpStatus <> 'A'(kondisi di WHERE). Mungkin menggunakan data dari vReclaimabledalam melakukannya (tetapi Anda tidak membuat referensi apa pun untuk itu).

Jika Anda ingin membatasi untuk baris yang ada vReclaimable, selain opsi lain yang disajikan, Anda bisa menggunakan subquery:

UPDATE dbo.tAccts SET
       Ticket               = 'ARP.ExGE'
       , Method             = 'smtp'
       , AcctOwner          = 'r00417819'
       , DisplayName = '~AppLight HBSFax-Inactive'
       , Destination = '[email protected]'
       , UpdatedBy          = SYSTEM_USER
       , UpdatedOn          = CAST(GetDate() AS DATE)
WHERE OHR_EmpStatus <> 'A' AND tAccts.key IN (SELECT key FROM vReclaimable)
jcaron
sumber
0

Jika kueri di bawah ini mengembalikan lebih dari satu baris:

select 'tAccts table', dnis, method, destination, phone_tf, phone_local 
from tAccts
where OHR_EmpStatus <> 'A'

maka Anda mencoba untuk memperbarui beberapa baris dengan nilai yang sama sehingga melanggar batasan unik.

infiniteLoop
sumber
Mungkin juga layak untuk melihat ini: stackoverflow.com/questions/2648445/…
infiniteLoop
0

Pilihan lain adalah: Anda tidak perlu

DARI dbo.vReclaimable

karena Anda tidak menggunakan nilai apa pun dari tabel ini dalam pernyataan pembaruan Anda.

alk
sumber
Sebagai catatan: dalam hal ini, join to vReclaimabletidak diragukan lagi dimaksudkan untuk memfilter tabel yang sedang diperbarui. Meskipun tidak diperlukan untuk SETklausa, itu secara efektif bagian dari WHEREklausa.
Jon of All Trades