Warum schlägt dieses UPDATE mit einer Verletzung der eindeutigen Schlüsseleinschränkung fehl?

11

Ich bin ein "zufälliger" DBA, relativ unerfahren und von diesem Problem verwirrt.

Ausführen von MS SQL Server 2012. Das Problem liegt bei dieser UPDATE-Anweisung:

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'

Was sollte aktualisiert werden nur die Zeilen in der tAccts Tabelle , die von der vReclaimable Ansicht zurückgegeben werden.

Die vReclaimable-Ansicht basiert auf der Tabelle tAccts und gibt eine Teilmenge der Zeilen in tAccts zurück.

Wenn ich es ausführe, schlägt es mit einem eindeutigen Schlüsselfehler fehl:

(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.

Fairerweise hat die tAccts-Tabelle eine eindeutige Schlüsselbeschränkung:

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) 

Aber hier ist das Seltsame. Wenn ich diese beiden Abfragen ausführe:

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

Die erste gibt zwei Zeilen zurück (wie erwartet):

(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

und die zweite gibt 0 Zeilen zurück (wie erwartet).

Wenn "FROM vReclaimable WHERE OHR_EmpStatus <> 'A'" 0 Zeilen zurückgibt, warum versucht das UPDATE, die Zeile mit DNIS = 68497 zu aktualisieren?

(Ich hoffe, ich habe dies angemessen beschrieben. Ich habe das Gefühl, dass mir etwas Offensichtliches fehlt.)

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
quelle
Zeigen Sie uns die CREATE VIEWAussage.
Ypercubeᵀᴹ
Und ist OHR_EmpStatuseine Spalte der Tabelle, die Ansicht oder beides?
Ypercubeᵀᴹ
1
Ich bin einfach erstaunt über die Menge an schnellen und ausgezeichneten Antworten auf meine Frage. Wie gesagt, es ist wahrscheinlich etwas Offensichtliches, das ich übersehen oder missverstanden habe, aber ich glaube nicht, dass es daran liegt, dass ich einen Cross-Join habe. Ich hätte die Definitionen für die tAccts-Tabelle und die vReclaimable-Ansicht veröffentlichen sollen. Ich werde alle bisher veröffentlichten Antworten überprüfen und, wenn ich immer noch der Meinung bin, dass sie nicht richtig sind, diese Definitionen zur Frage hinzufügen. In der Zwischenzeit ein RIESIGES Dankeschön an alle, die geantwortet haben.
ArleyD
Hinzufügen von CREATE-Anweisungen für tAccts und vReclaimable, wie von ypercube angefordert
ArleyD

Antworten:

18

Es UPDATEläuft darauf hinaus, was die Aussage bewirkt. Es ist nicht ganz offensichtlich, aber Ihre Aussage entspricht dieser:

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' ;

Da die dbo.tAcctsTabelle in der FROMund keine Verknüpfung oder wo-Bedingung zwischen der Tabelle und der Ansicht nicht erwähnt wird, führt dies zu einer CROSSVerknüpfung und einem Versuch, alle Zeilen der Tabelle (und nicht nur aus der Ansicht) zu aktualisieren , und wahrscheinlich zu mehreren auch mal!


Sie können eine Join-Bedingung (oder eine Where-Bedingung) hinzufügen mit:

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' ;

oder (mit Ihrer Version):

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 ;

Alternativ können Sie die Ansicht (wahrscheinlich) einfach aktualisieren. Damit dies funktioniert, muss die Ansicht den Einschränkungen für "Aktualisierbare Ansichten" entsprechen . Siehe den entsprechenden Abschnitt in der MSDN-Dokumentation : CREATE VIEW, Aktualisierbare Ansichten :

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ᵀᴹ
quelle
2

Es scheint, dass Sie keine Verknüpfung zwischen den Tabellen in Ihrer Aktualisierungsabfrage haben.

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'

Hier muss es etwas geben, das den Zeilen zwischen den Tabellen entspricht, z. B. wo tAccts.id = vReclaimable.id

Alonk
quelle
2

Eine andere Art, dies auszudrücken:

Das Problem ist Ihre Überzeugung, dass die Anweisung "nur die Zeilen in der tAccts-Tabelle aktualisieren sollte, die von der vReclaimable-Ansicht zurückgegeben werden".

Das ist nicht der Fall. Es aktualisiert alle Zeilen aus tAccts(die direkt danach erwähnte Tabelle UPDATE), die übereinstimmen OHR_EmpStatus <> 'A'(die Bedingung in der WHERE). Dabei werden möglicherweise Daten von verwendet vReclaimable(Sie verweisen jedoch überhaupt nicht darauf).

Wenn Sie es vReclaimablezusätzlich zu den anderen angezeigten Optionen auf Zeilen beschränken möchten, die sich in befinden , können Sie eine Unterabfrage verwenden:

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
quelle
0

Wenn die folgende Abfrage mehr als eine Zeile zurückgibt:

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

Dann versuchen Sie, mehrere Zeilen mit denselben Werten zu aktualisieren, wodurch die eindeutige Einschränkung verletzt wird.

Endlosschleife
quelle
Könnte
infiniteLoop
0

Eine andere Option ist: Sie brauchen das nicht

FROM dbo.vReclaimable

weil Sie in Ihrer Update-Anweisung keinen Wert aus dieser Tabelle verwenden.

Alonk
quelle
Für den Datensatz: In diesem Fall sollte der Join to vReclaimablezweifellos die zu aktualisierende Tabelle filtern. Obwohl es für die SETKlausel nicht benötigt wird , ist es effektiv Teil der WHEREKlausel.
Jon of All Trades