web-dev-qa-db-fra.com

Violation de la contrainte PRIMARY KEY. Impossible d'insérer une clé en double dans un objet - SQL Server

J'ai hérité d'un projet et je rencontre une erreur SQL que je ne sais pas comment résoudre ce problème. 

Sur un site de commerce électronique, le code insère les informations d'expédition de la commande dans une autre table de base de données. 

Voici le code qui insère les informations dans la table:

string sql = "INSERT INTO AC_Shipping_Addresses   
(pk_OrderID, FullName, Company, Address1, Address2, City, Province, PostalCode, CountryCode, Phone, Email, ShipMethod, Charge_Freight, Charge_Subtotal)  
VALUES (" + _Order.OrderNumber;
sql += ", '" + _Order.Shipments[0].ShipToFullName.Replace("'", "''") + "'";
if (_Order.Shipments[0].ShipToCompany == "")
{
  sql += ", '" + _Order.Shipments[0].ShipToFullName.Replace("'", "''") + "'";
}
else
{
  sql += ", '" + _Order.Shipments[0].ShipToCompany.Replace("'", "''") + "'";
}
sql += ", '" + _Order.Shipments[0].Address.Address1.Replace("'", "''") + "'";
sql += ", '" + _Order.Shipments[0].Address.Address2.Replace("'", "''") + "'";
sql += ", '" + _Order.Shipments[0].Address.City.Replace("'", "''") + "'";
sql += ", '" + _Order.Shipments[0].Address.Province.Replace("'", "''") + "'";
sql += ", '" + _Order.Shipments[0].Address.PostalCode.Replace("'", "''") + "'";
sql += ", '" + _Order.Shipments[0].Address.Country.Name.Replace("'", "''") + "'";
sql += ", '" + _Order.Shipments[0].Address.Phone.Replace("'", "''") + "'";
if (_Order.Shipments[0].ShipToEmail == "")
{
  sql += ",'" + _Order.BillToEmail.Replace("'", "''") + "'";
}
else
{
  sql += ",'" + _Order.Shipments[0].ShipToEmail.Replace("'", "''") + "'";
}
sql += ", '" + _Order.Shipments[0].ShipMethod.Name.Replace("'", "''") + "'";
sql += ", " + shippingAmount;
sql += ", " + _Order.ProductSubtotal.ToString() + ")";
bll.dbUpdate(sql);

Il fonctionne correctement, mais génère également l'erreur SQL suivante:

Violation de la contrainte PRIMARY KEY 'PK_AC_Shipping_Addresses'. Impossible d'insérer clé en double dans l'objet 'dbo.AC_Shipping_Addresses'. La valeur de la clé en double est (165863).

En lisant des questions similaires, il semble que je devrais déclarer l'ID dans la déclaration. 

Est-ce exact? Comment pourrais-je ajuster le code pour résoudre ce problème?

Toute aide est grandement appréciée!

7
Justin Hollender

Assez sûr que pk_OrderID est la clé de commande de AC_Shipping_Addresses 

Et vous essayez d'insérer un duplicata via le _Order.OrderNumber 

Fait une 

select * from AC_Shipping_Addresses where pk_OrderID = 165863;

ou sélectionnez le compte (*) ....

Je suis sûr que vous obtiendrez une ligne. 

Cela vous dit que vous utilisez déjà pk_OrderID = 165863 et que vous ne pouvez pas avoir une autre ligne avec cette valeur. 

si vous voulez ne pas insérer s'il y a une rangée 

insert into table (pk, value) 
select 11 as pk, 'val' as value 
where not exists (select 1 from table where pk = 11) 
3
paparazzo

J'obtenais la même erreur sur une base de données restaurée lorsque j'ai essayé d'insérer un nouvel enregistrement à l'aide de EntityFramework. Il s'est avéré que Indentity/Seed était en train de tout gâcher.

L'utilisation d'une commande de réensemencement a corrigé le problème.

DBCC CHECKIDENT ('[Prices]', RESEED, 4747030);GO
7
Ger Groot

Quelle est la valeur que vous transmettez à la clé primaire (probablement "pk_OrderID")? Vous pouvez le configurer pour l'incrémentation automatique, puis la duplication de la valeur ne devrait plus poser aucun problème - la base de données s'en chargera. Si vous devez spécifier une valeur vous-même, vous devez écrire du code pour déterminer la valeur maximale de ce champ, puis l'incrémenter. 

Si vous avez une colonne nommée "ID" ou une autre colonne qui n'apparaît pas dans la requête, c'est très bien tant qu'elle est configurée pour l'auto-incrémentation - mais ce n'est probablement pas le cas, sinon vous ne devriez pas recevoir cette erreur. En outre, vous feriez mieux d'écrire une requête plus facile et d'utiliser des paramètres. Comme l'indique le garçon de neuf ans, vous laissez votre base de données ouverte aux attaques par injection SQL si vous ajoutez simplement des valeurs entrées par l'utilisateur. Par exemple, vous pourriez avoir une méthode comme celle-ci:

internal static int GetItemIDForUnitAndItemCode(string qry, string unit, string itemCode)
{
    int itemId;
    using (SqlConnection sqlConn = new SqlConnection(ReportRunnerConstsAndUtils.CPSConnStr))
    {
        using (SqlCommand cmd = new SqlCommand(qry, sqlConn))
        {
            cmd.CommandType = CommandType.Text;
            cmd.Parameters.Add("@Unit", SqlDbType.VarChar, 25).Value = unit;
            cmd.Parameters.Add("@ItemCode", SqlDbType.VarChar, 25).Value = itemCode;
            sqlConn.Open();
            itemId = Convert.ToInt32(cmd.ExecuteScalar());
        }
    }
    return itemId;
}

... qui s'appelle comme si:

int itemId = SQLDBHelper.GetItemIDForUnitAndItemCode(GetItemIDForUnitAndItemCodeQuery, _unit, itemCode);

Vous n'êtes pas obligé, mais je stocke la requête séparément:

public static readonly String GetItemIDForUnitAndItemCodeQuery = "SELECT PoisonToe FROM Platypi WHERE Unit = @Unit AND ItemCode = @ItemCode";

Vous pouvez vérifier que vous n'êtes pas sur le point d'insérer une valeur existante par (pseudocode):

bool alreadyExists = IDAlreadyExists(query, value) > 0;

La requête ressemble à "SELECT COUNT FROM TABLE WHERE BLA = @CANDIDATEIDVAL" et la valeur correspond à l'ID que vous êtes potentiellement sur le point d'insérer:

if (alreadyExists) // keep inc'ing and checking until false, then use that id value

Justin veut savoir si cela fonctionnera:

string exists = "SELECT 1 from AC_Shipping_Addresses where pk_OrderID = " _Order.OrderNumber; if (exists > 0)...

Ce qui semble fonctionner pour moi est:

string existsQuery = string.format("SELECT 1 from AC_Shipping_Addresses where pk_OrderID = {0}", _Order.OrderNumber); 
// Or, better yet:
string existsQuery = "SELECT COUNT(*) from AC_Shipping_Addresses where pk_OrderID = @OrderNumber"; 
// Now run that query after applying a value to the OrderNumber query param (use code similar to that above); then, if the result is > 0, there is such a record.
2
B. Clay Shannon

Pour empêcher l'insertion d'un enregistrement qui existe déjà. Je vérifierais si la valeur d'ID existe dans la base de données. Pour l'exemple d'une table créée avec une clé IDENTITY PRIMARY:

CREATE TABLE [dbo].[Persons] (    
    ID INT IDENTITY(1,1) PRIMARY KEY,
    LastName VARCHAR(40) NOT NULL,
    FirstName VARCHAR(40)
);

Quand JANE DOE et JOE BROWN existent déjà dans la base de données. 

SET IDENTITY_INSERT [dbo].[Persons] OFF;
INSERT INTO [dbo].[Persons] (FirstName,LastName)
VALUES ('JANE','DOE'); 
INSERT INTO Persons (FirstName,LastName) 
VALUES ('JOE','BROWN');

SORTIE DE LA BASE DE DONNÉES de TABLE [dbo]. [Personnes] sera:

ID    LastName   FirstName
1     DOE        Jane
2     BROWN      JOE

Je vérifierais si je devrais mettre à jour un enregistrement existant ou en insérer un nouveau. Comme exemple Java suivant: 

int NewID = 1;
boolean IdAlreadyExist = false;
// Using SQL database connection
// STEP 1: Set property
System.setProperty("Java.net.preferIPv4Stack", "true");
// STEP 2: Register JDBC driver
Class.forName("com.Microsoft.sqlserver.jdbc.SQLServerDriver");
// STEP 3: Open a connection
try (Connection conn1 = DriverManager.getConnection(DB_URL, USER,pwd) {
    conn1.setAutoCommit(true);
    String Select = "select * from Persons where  ID = " + ID;
    Statement st1 = conn1.createStatement();
    ResultSet rs1 = st1.executeQuery(Select);
    // iterate through the Java resultset
    while (rs1.next()) {
        int ID = rs1.getInt("ID");
        if (NewID==ID) {
            IdAlreadyExist = true;
        }

    }
    conn1.close();
} catch (SQLException e1) {
    System.out.println(e1);
}
if (IdAlreadyExist==false) {
    //Insert new record code here
} else {
    //Update existing record code here
}
1
QA Specialist

Cela pourrait être causé par plusieurs facteurs, et cela dépend en partie de ce que vous avez configuré dans votre base de données.

Premièrement, vous pourriez utiliser une PK dans la table qui est aussi une FK vers une autre table, créant ainsi la relation 1-1. Dans ce cas, vous devrez peut-être effectuer une mise à jour plutôt qu'un insert. Si vous ne pouvez vraiment avoir qu'un seul enregistrement d'adresse pour une commande, c'est peut-être ce qui se passe.

Ensuite, vous pourriez utiliser une sorte de processus manuel pour déterminer l'identifiant à l'avance. Le problème avec ces processus manuels est qu’ils peuvent créer des conditions de concurrence dans lesquelles deux enregistrements comparent le même dernier identifiant et l’incrémentent de un, puis le second ne peut pas être inséré. 

Troisièmement, vous interrogez comme il est envoyé à la base de données peut créer deux enregistrements. Pour déterminer si tel est le cas, exécutez Profiler pour voir exactement le code SQL que vous envoyez et si ti est une clause select au lieu d'une clause values, puis exécutez-la et voyez si, en raison des jointures, certains enregistrements ont été dupliqués. . Dans tous les cas, même lorsque vous créez du code à la volée, la première étape de dépannage consiste TOUJOURS à exécuter Profiler pour voir si ce qui a été envoyé correspond à ce que vous espériez recevoir.

0
HLGEM