2

The problem is whenever I execute the stored procedured "usp_Execute100K" in the Query Windows simultaneously the server could not locate the record and it creating a new record even though the record is already there. You have to run the "usp_Execute100K" multiple windows simultaneously to see this problem.

What I want is to make sure the server only create 1 record per day, if the record does exist then only update the "StatisticTotal" column to the next number.

This problem is taught.

The below are 1 table and two stored procedures

Statistic /Table name/

usp_Statistic_InsertOrUpdate /this will update the "StatisticTotal" column or insert if the record does not exist/

usp_Execute100K /* this will execute the usp_Statistic_InsertOrUpdate 100 thousand times*/

PLEASE COPY THE SCRIPT BELOW TO RECREATE THE TABLE AND STORED PROCEDURES.

GO
/****** Object:  StoredProcedure [dbo].[usp_Execute100K]    Script Date: 09/07/2011 15:37:29 ******/
DROP PROCEDURE [dbo].[usp_Execute100K]
GO
/****** Object:  StoredProcedure [dbo].[usp_Statistic_InsertOrUpdate]    Script Date: 09/07/2011 15:37:29 ******/

DROP PROCEDURE [dbo].[usp_Statistic_InsertOrUpdate]
GO
/****** Object:  Table [dbo].[Statistic]    Script Date: 09/07/2011 15:37:28 ******/
DROP TABLE [dbo].[Statistic]
GO
/****** Object:  Table [dbo].[Statistic]    Script Date: 09/07/2011 15:37:28 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Statistic](
    [StatisticID] [uniqueidentifier] NOT NULL,
    [StatisticAccount] [uniqueidentifier] NOT NULL,
    [StatisticTotal] [float] NOT NULL,
    [StatisticCreatedDate] [datetime] NOT NULL,
    [DebugDate] [datetime] NULL,
 CONSTRAINT [PK_Statistics] PRIMARY KEY CLUSTERED 
(
    [StatisticID] 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
/****** Object:  StoredProcedure [dbo].[usp_Statistic_InsertOrUpdate]    Script Date: 09/07/2011 15:37:29 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[usp_Statistic_InsertOrUpdate](
@StatisticAccount  uniqueidentifier
)
AS
SET NOCOUNT OFF; --this can be turn on or off, it has no effects
DECLARE @NowDate date
SET     @NowDate=CONVERT(datetime, CONVERT(char, GETDATE(), 106))--remove all time.
--UPDATE ONLY IF IT HAS THE SAME DATE
UPDATE TOP (1) Statistic SET   StatisticTotal =StatisticTotal+1 WHERE (StatisticAccount=@StatisticAccount AND StatisticCreatedDate = @NowDate)
if @@ROWCOUNT=0--If the above statement return no effects then create a new record
BEGIN
    INSERT TOP (1)INTO Statistic(StatisticID, StatisticAccount, StatisticTotal, StatisticCreatedDate,DebugDate)     VALUES (NEWID(),@StatisticAccount,1,@NowDate,@NowDate)
END
GO
/****** Object:  StoredProcedure [dbo].[usp_Execute100K]    Script Date: 09/07/2011 15:37:29 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[usp_Execute100K]
AS
SET NOCOUNT OFF; --this can be turn on or off, it has no effects
declare @current float
set @current=0
while (@current <100000)    
begin
--INSERT THIS STATEMENT for 100 thousand times
    exec usp_Statistic_InsertOrUpdate'4c34eea5-fe17-4b11-8e06-0039577e7421'
    set @current = @current + 1
 end
GO
David Cook
  • 25
  • 2
  • 1
    +1 For providing useful DDL with your question. [Related](http://stackoverflow.com/questions/3407857/only-inserting-a-row-if-its-not-already-there) – Martin Smith Sep 07 '11 at 21:33
  • 1
    Should this really be tagged [tag:sql-server-2005]? You're using the `DATE` data type, which is not valid in SQL Server 2005. – Aaron Bertrand Sep 07 '11 at 21:33

2 Answers2

1

1) create a unique index on Statistic.StatisticAccount + Statistic.StatisticCreatedDate or just Statistic.StatisticCreatedDate what ever your business rules dictate.

2) wrap your INSERT WITH:

BEGIN TRY
    INSERT ...
END TRY
BEGIN CATCH
END CATCH

it will just ignore/throw away the error generated by the failed duplicate insert.

KM.
  • 101,727
  • 34
  • 178
  • 212
1

Have you considered wrapping your UPDATE/INSERT combo in a transaction? While the constraint and TRY/CATCH will certainly prevent a second row, it will discard the attempt as well. If you wrap it in a transaction, and use TABLOCK (I assume you are potentially doing a lot of writes to this table, but not a ton of reads), you hamper concurrency slightly but you can better ensure that one user will only see the state of the table after the other user is done with it. I could get two rows reliably as is, but I couldn't get two rows when I used this:

ALTER PROCEDURE [dbo].[usp_Statistic_InsertOrUpdate]
    @StatisticAccount UNIQUEIDENTIFIER
AS
BEGIN
    SET NOCOUNT ON; -- this should always be on

    DECLARE @NowDate DATE = CURRENT_TIMESTAMP; 
    -- by definition, you don't need to convert to remove time from date

    BEGIN TRANSACTION;

    --UPDATE ONLY IF IT HAS THE SAME DATE
    UPDATE dbo.Statistic WITH (TABLOCK)
      SET StatisticTotal += 1 
      WHERE StatisticAccount = @StatisticAccount 
      AND StatisticCreatedDate = @NowDate;

    IF @@ROWCOUNT = 0 
    -- If the above statement return no effects then create a new record
    BEGIN
        INSERT dbo.Statistic
        (
          StatisticID,
          StatisticAccount,
          StatisticTotal,
          StatisticCreatedDate,
          DebugDate
        )
        SELECT
          NEWID(),
          @StatisticAccount,
          1,
          @NowDate,
          @NowDate;
    END

    COMMIT TRANSACTION;
END
GO

Error trapping, rollback, constraints etc. I'll leave as an exercise.

Aaron Bertrand
  • 272,866
  • 37
  • 466
  • 490