web-dev-qa-db-fra.com

Vérifier si une ligne existe, sinon insérer

J'ai besoin d'écrire une procédure stockée T-SQL qui met à jour une ligne dans une table. Si la ligne n'existe pas, insérez-la. Toutes ces étapes sont conditionnées par une transaction.

Ceci est pour un système de réservation, donc il doit être atomique et fiable. Il doit retourner la valeur true si la transaction a été validée et le vol réservé.

Je suis nouveau dans T-SQL, et je ne sais pas trop comment utiliser @@rowcount. C'est ce que j'ai écrit jusqu'à maintenant. Suis-je sur le bon chemin? Je suis sûr que c'est un problème facile pour vous.

-- BEGIN TRANSACTION (HOW TO DO?)

UPDATE Bookings
 SET TicketsBooked = TicketsBooked + @TicketsToBook
 WHERE FlightId = @Id AND TicketsMax < (TicketsBooked + @TicketsToBook)

-- Here I need to insert only if the row doesn't exists.
-- If the row exists but the condition TicketsMax is violated, I must not insert 
-- the row and return FALSE

IF @@ROWCOUNT = 0 
BEGIN

 INSERT INTO Bookings ... (omitted)

END

-- END TRANSACTION (HOW TO DO?)

-- Return TRUE (How to do?)
222
Robert

Jetez un oeil à la commande FUSION . Vous pouvez faire UPDATE, INSERT & DELETE en une seule déclaration.

Voici une implémentation fonctionnelle sur l'utilisation de MERGE
- Il vérifie si le vol est plein avant de faire une mise à jour, sinon fait un insert.

if exists(select 1 from INFORMATION_SCHEMA.TABLES T 
              where T.TABLE_NAME = 'Bookings') 
begin
    drop table Bookings
end
GO

create table Bookings(
  FlightID    int identity(1, 1) primary key,
  TicketsMax    int not null,
  TicketsBooked int not null
)
GO

insert  Bookings(TicketsMax, TicketsBooked) select 1, 0
insert  Bookings(TicketsMax, TicketsBooked) select 2, 2
insert  Bookings(TicketsMax, TicketsBooked) select 3, 1
GO

select * from Bookings

Puis ...

declare @FlightID int = 1
declare @TicketsToBook int = 2

--; This should add a new record
merge Bookings as T
using (select @FlightID as FlightID, @TicketsToBook as TicketsToBook) as S
    on  T.FlightID = S.FlightID
      and T.TicketsMax > (T.TicketsBooked + S.TicketsToBook)
  when matched then
    update set T.TicketsBooked = T.TicketsBooked + S.TicketsToBook
  when not matched then
    insert (TicketsMax, TicketsBooked) 
    values(S.TicketsToBook, S.TicketsToBook);

select * from Bookings
153
Sung M. Kim

J'assume une seule rangée pour chaque vol? Si c'est le cas:

IF EXISTS (SELECT * FROM Bookings WHERE FLightID = @Id)
BEGIN
    --UPDATE HERE
END
ELSE
BEGIN
   -- INSERT HERE
END

Je suppose que ce que j'ai dit, car votre façon de faire peut surcharger un vol car il insérera une nouvelle ligne lorsqu'il y a 10 billets maximum et que vous en réservez 20.

147
Gregory A Beamer

Passez les astuces de mise à jour, de verrouillage de ligne et de verrouillage lors du test de l'existence de la ligne.

begin tran /* default read committed isolation level is fine */

if not exists (select * from Table with (updlock, rowlock, holdlock) where ...)
    /* insert */
else
    /* update */

commit /* locks are released here */

L'indicateur de verrouillage de mise à jour force la requête à verrouiller la ligne si elle existe déjà, empêchant ainsi d'autres transactions de la modifier jusqu'à ce que vous la validiez ou que vous l'annuliez.

L'indicateur de verrouillage oblige la requête à verrouiller une plage, empêchant les autres transactions d'ajouter une ligne correspondant à vos critères de filtre jusqu'à ce que vous validiez ou annuliez.

L'indice de verrouillage de ligne force la précision du verrou au niveau de la ligne au lieu du niveau de page par défaut. Ainsi, votre transaction ne bloquera pas les autres transactions tentant de mettre à jour des lignes non liées dans la même page (mais tenez compte du compromis entre contention réduite et augmentation de la verrouillage des frais généraux - vous devez éviter de prendre un grand nombre de verrous au niveau de la ligne dans une transaction unique).

Voir http://msdn.Microsoft.com/en-us/library/ms187373.aspx pour plus d'informations.

Notez que les verrous sont pris comme les déclarations qui les prennent sont exécutées - l'invocation de begin tran ne vous donne pas l'immunité contre une autre transaction qui bloque les verrous avant quelque chose. Vous devez essayer de factoriser votre code SQL pour qu'il maintienne les verrous le plus rapidement possible en validant la transaction dès que possible (acquisition tardive, libération anticipée).

Notez que les verrous au niveau des lignes peuvent être moins efficaces si votre PC est un bigint, car le hachage interne sur SQL Server est dégénéré pour les valeurs 64 bits (différentes valeurs de clé peuvent avoir le même identifiant de verrou).

64
Cassius Porcus

j'écris ma solution. ma méthode ne tient pas "si" ou "fusion". ma méthode est facile.

INSERT INTO TableName (col1,col2)
SELECT @par1, @par2
   WHERE NOT EXISTS (SELECT col1,col2 FROM TableName
                     WHERE col1=@par1 AND col2=@par2)

Par exemple:

INSERT INTO Members (username)
SELECT 'Cem'
   WHERE NOT EXISTS (SELECT username FROM Members
                     WHERE username='Cem')

Explication:

(1) SELECT col1, col2 FROM TableName WHERE col1 = @ par1 ET col2 = @ par2 Il sélectionne des valeurs recherchées par TableName

(2) SELECT @ par1, @ par2 WHERE NOT EXISTS Cela prend sinon dans la sous-requête (1)

(3) Insère dans les valeurs d'étape TableName (2)

37
Cem

C’est quelque chose que j’ai eu à faire récemment:

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[cjso_UpdateCustomerLogin]
    (
      @CustomerID AS INT,
      @UserName AS VARCHAR(25),
      @Password AS BINARY(16)
    )
AS 
    BEGIN
        IF ISNULL((SELECT CustomerID FROM tblOnline_CustomerAccount WHERE CustomerID = @CustomerID), 0) = 0
        BEGIN
            INSERT INTO [tblOnline_CustomerAccount] (
                [CustomerID],
                [UserName],
                [Password],
                [LastLogin]
            ) VALUES ( 
                /* CustomerID - int */ @CustomerID,
                /* UserName - varchar(25) */ @UserName,
                /* Password - binary(16) */ @Password,
                /* LastLogin - datetime */ NULL ) 
        END
        ELSE
        BEGIN
            UPDATE  [tblOnline_CustomerAccount]
            SET     UserName = @UserName,
                    Password = @Password
            WHERE   CustomerID = @CustomerID    
        END

    END
2
TheTXI

J'ai finalement pu insérer une ligne, à condition qu'elle n'existe pas déjà, en utilisant le modèle suivant:

INSERT INTO table ( column1, column2, column3 )
(
    SELECT $column1, $column2, $column3
      WHERE NOT EXISTS (
        SELECT 1
          FROM table 
          WHERE column1 = $column1
          AND column2 = $column2
          AND column3 = $column3 
    )
)

que j'ai trouvé à:

http://www.postgresql.org/message-id/[email protected]

2
Paul G

Vous pouvez utiliser la fonctionnalité Fusion à atteindre. Sinon, vous pouvez faire:

declare @rowCount int

select @rowCount=@@RowCount

if @rowCount=0
begin
--insert....
1
JoshBerke
INSERT INTO [DatabaseName1].dbo.[TableName1] SELECT * FROM [DatabaseName2].dbo.[TableName2]
 WHERE [YourPK] not in (select [YourPK] from [DatabaseName1].dbo.[TableName1])
0
Almamun

La meilleure approche à ce problème consiste d'abord à rendre la colonne de base de données UNIQUE.

ALTER TABLE table_name ADD UNIQUE KEY

THEN INSERT IGNORE INTO table_name, la valeur ne sera pas insérée s'il en résulte une clé en double/existe déjà dans la table.

0
Maurice Elagu

La solution complète est ci-dessous (y compris la structure du curseur). Merci beaucoup à Cassius Porcus pour le code begin trans ... commit de la publication ci-dessus.

declare @mystat6 bigint
declare @mystat6p varchar(50)
declare @mystat6b bigint

DECLARE mycur1 CURSOR for

 select result1,picture,bittot from  all_Tempnogos2results11

 OPEN mycur1

 FETCH NEXT FROM mycur1 INTO @mystat6, @mystat6p , @mystat6b

 WHILE @@Fetch_Status = 0
 BEGIN

 begin tran /* default read committed isolation level is fine */

 if not exists (select * from all_Tempnogos2results11_uniq with (updlock, rowlock, holdlock)
                     where all_Tempnogos2results11_uniq.result1 = @mystat6 
                        and all_Tempnogos2results11_uniq.bittot = @mystat6b )
     insert all_Tempnogos2results11_uniq values (@mystat6 , @mystat6p , @mystat6b)

 --else
 --  /* update */

 commit /* locks are released here */

 FETCH NEXT FROM mycur1 INTO @mystat6 , @mystat6p , @mystat6b

 END

 CLOSE mycur1

 DEALLOCATE mycur1
 go
0
user2836818