1

I had to add another table into my database and now I need to go back and update a page that allows inserts into more than one table. I didn't write this page, so I'm trying to clean everything up, but there are some parts that I don't really understand. Now I have broken the page and it only inserts into one table. The very first one that I insert into.

ProductName: goes into the Product table

Description: goes into the Picklist table as Data.....it also is supposed to generate an insert into the marketing table based on the PicklistID which is an identity column. The marketing table tells the Picklist table that it is looking for a description.

Price: goes into Product table

Category: goes into the CategoryLink table which also inserts the recently generated ProductID.

Company: goes into the CompanyLink table which also inserts the recently generated ProductID.

Target audience: goes into the TargetLink table which also inserts the recently generated ProductID.

Status: goes into the Product table

enter image description here

Protected Sub submitButton_Click(ByVal sender As Object, 
ByVal e As System.EventArgs) Handles submitButton.Click
    Dim CategoryID As String = txtCategoryID.Value
    Dim CompanyIDs As New ArrayList
    'Get selected companies-----
    Dim CompanyCount As Integer = CompanyCheckbox.Items.Count
    For i As Integer = 0 To CompanyCount - 1
        If CompanyCheckbox.Items(i).Selected Then
            CompanyIDs.Add(CompanyCheckbox.Items(i).Value)
        End If
    Next
    'Get selected targets---
    Dim TargetIDs As New ArrayList
    Dim TargetCount As Integer = TargetCheckbox.Items.Count
    For i As Integer = 0 To TargetCount - 1
        If TargetCheckbox.Items(i).Selected Then
            TargetIDs.Add(TargetCheckbox.Items(i).Value)
        End If
    Next
    'Get Status---
    Dim Status As String = Nothing
    If StatusCheckbox.Checked Then
        Status = "1"
    Else
        Status = "0"
    End If
    'SQL Insert: Product Table
    Dim sqlInsertProduct As String = "IF NOT EXISTS (SELECT * FROM Product 
    WHERE ProductName= @ProductName) 
    BEGIN INSERT INTO Product 
    (ProductName, Status, CreateDate, ModifyDate, CreateUser, ModifyUser, Price) 
    VALUES (@ProductName, @Status ,getdate(),getdate(), @CreateUser, @ModifyUser,
    @Price) END;
    INSERT INTO Picklist (Title, Data) VALUES ('About this product', @Data);"

    'Create SQL Connection
    Using cn As New 
    SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings
    ("LocalSqlServer").ConnectionString)

        Using cmd As New SqlCommand(sqlInsertProduct, cn)

            cmd.Parameters.Add(New SqlParameter("@ProductName", 
            txtNewProductName.Text))
            cmd.Parameters.Add(New SqlParameter("@Status", StatusCheckbox.Checked))
            cmd.Parameters.Add(New SqlParameter("@Price", txtPrice.Text))
            cmd.Parameters.Add(New SqlParameter("@Data", txtProductDesc.Text))
            cmd.Parameters.Add(New SqlParameter("@CreateUser",
            System.Web.HttpContext.Current.User.Identity.Name))
            cmd.Parameters.Add(New SqlParameter("@ModifyUser",
            System.Web.HttpContext.Current.User.Identity.Name))
            cn.Open()
            cmd.ExecuteNonQuery()
        End Using
        'Get the productID of the newly inserted product
        Dim sqlGetID As String = "SELECT @@IDENTITY"
        Dim ProductID As String = Nothing
        Dim cmdGetID As New SqlCommand(sqlGetID, cn)
        Dim myReader As SqlDataReader = cmdGetID.ExecuteReader()
        While myReader.Read
            If IsDBNull(myReader(0)) Then
                ProductID = ""
            Else
                ProductID = myReader(0)
            End If
        End While
        myReader.Close()
        cn.Close()

        'SQL Insert: Marketing Table
        Dim sqlInsertMarketing As String = "INSERT INTO Marketing (ProductID, 
        MarketingTypeID, MarketingTitle, MarketingData) VALUES ('" & ProductID & "', 2,
        'Description', scope_identity())"
        'SQL Insert: Category Table
        If CategoryID <> Nothing Then
            Dim sqlInsertCategory As String = "INSERT INTO CategoryLink (CategoryID,
            ProductID) VALUES (@CategoryID,'" & ProductID & "')"
            Using cmdInsertCategory As New SqlCommand(sqlInsertCategory, cn)
                cmdInsertCategory.Parameters.Add(New SqlParameter("@CategoryID",
                txtCategoryID.Value))
                cn.Open()
                cmdInsertCategory.ExecuteNonQuery()
            End Using
            cn.Close()
        End If
        If CompanyIDs.Count > 0 Then
            For i = 0 To CompanyIDs.Count - 1
                Dim sqlInsertCompany = "INSERT INTO CompanyLink (CompanyID, ProductID)
                VALUES ('" & CompanyIDs(i) & "','" & ProductID & "')"
                Using cmdInsertCompany As New SqlCommand(sqlInsertCompany, cn)
                    cn.Open()
                    cmdInsertCompany.ExecuteNonQuery()
                End Using
                cn.Close()
            Next

        End If
        If TargetIDs.Count > 0 Then
            For i = 0 To TargetIDs.Count - 1
                Dim sqlInsertTarget = "INSERT INTO TargetLink (TargetID, ProductID) 
                VALUES ('" & TargetIDs(i) & "','" & ProductID & "')"
                Using cmdInsertTarget As New SqlCommand(sqlInsertTarget, cn)
                    cn.Open()
                    cmdInsertTarget.ExecuteNonQuery()
                End Using
                cn.Close()
            Next
        End If

    End Using

Response.Write("<script type='text/javascript'>{ alert('Product added successfully'); 
document.location.href = 'AddProduct.aspx'; }</script>")
End Sub
End Class

Like I said before, only the insert that goes into the Product table works. Before I added the Picklist table and tried to reconnect everything, this entire page worked. The code was a lot sloppier and didn't have parameters, so that could also be where I messed up since I am still trying to learn how to use them. If I need to include additional information I can do that. I don't know how detailed to make this post. Thanks

UPDATE:

I have gotten everything to INSERT except for the INSERT to the Marketing table. I would really appreciate someone who can help me get that PicklistID to insert into the MarketingData column of the Marketing table

enter image description here

Jamie
  • 1,579
  • 8
  • 34
  • 74
  • This is not your problem now but note that use @@IDENTITY to get the last inserted value is a bad idea. I think that IDENT_CURRENT is really best. Read this: http://msdn.microsoft.com/en-us/library/ms175098.aspx – DavidEG Sep 29 '11 at 20:02
  • @DavidEG Yeah I'm not sure how to use IDENTITY when there are 2 different IDs being created that need to be inserted into the database. I was under the impression that IDENTITY was only for the last ID that was created? I'm still new to that... – Jamie Sep 29 '11 at 20:04
  • Yes, is the last one. But I think never should be inserted. Suppose you need to audit `some_table` so you create `audit_table` and a trigger to fill `audit_table` after any insert, update or delete on `some_table`. If you were using `@@IDENTITY` to get the last value of `some_table` everything is going to crash, because you are going to get the last value of `audit_table`. To avoid this you should use `IDENT_CURRENT('some_table')`. – DavidEG Sep 29 '11 at 20:19
  • so would this work? `Dim sqlGetID As String = "SELECT IDENT_CURRENT('Product')"` It is just the table name that goes into the parentheses right? – Jamie Sep 29 '11 at 20:20
  • Yes, it's that simple. But you should check the OUTPUT clause that @KM. pointed, it's seems to me that is the real safe way to do it. (By the way sqlGetID shouldn't be as String) – DavidEG Sep 29 '11 at 20:30
  • I will look into OUTPUT. Would it be better to have sqlGETID As Int or just get rid of the As.... all together? – Jamie Sep 29 '11 at 20:36
  • @DavidEG, NEVER use ident_current to get the last value you inserted, it will give the last value anyone inserted. Use Scope_identity() or preferably the OUTPUT clause. Using ident_current is almost a guarantee of data integrity problems in a high transaction system. Problems that are extremely difficult to find and fix and which no one may be aware of for months after the problem occurred. If you are using this in any of your systems, you need to fix it ASAP! – HLGEM Sep 30 '11 at 14:12
  • 1
    @HLGEM scope_identity() is only for one identity value isn't it? I was told the only way to get 2 identity values like I am asking is to use an OUTPUT clause. Never having used one before, I am completely in the dark about it. – Jamie Sep 30 '11 at 14:33
  • @HLGEM Scope_identity will fail for sure in a table with triggers, so I think I never should use it. – DavidEG Sep 30 '11 at 14:51
  • @DavidEG, no @@identity fails with triggers, scope _identity does not. – HLGEM Sep 30 '11 at 15:16
  • @jlg, yes for multiples OUTPUT is the way to go – HLGEM Sep 30 '11 at 15:16
  • OUTPUT it is. I will look it up – Jamie Sep 30 '11 at 15:18
  • @HLGEM I'm not very sure of that: http://stackoverflow.com/questions/908257/instead-of-trigger-in-sql-server-loses-scope-identity – DavidEG Sep 30 '11 at 15:19

2 Answers2

3

If you only have one identity value to get use SCOPE_IDENTITY(), never use @@IDENTITY. The problem with @@IDENTITY is that it always returns the last identity value, even if it was from another "scope". For example, you insert into your table and an identity value is generated, a trigger then fires that inserts in to a log table with an identity. You then call @@IDENTITY, guess what you get the log table identity value, it was last. SCOPE_IDENTITY() gives you the last within your local scope.

HOWEVER, if you need to capture multiple identity values because you insert multiple rows in one statement, you must use the OUTPUT clause. It works on INSERT, UPDATE and DELETE, but here is an example on an UPDATE: view the changed values after an update statement

Community
  • 1
  • 1
KM.
  • 101,727
  • 34
  • 178
  • 212
  • I have 2 identity columns to get and insert into 2 different tables. The ProductID that gets created is an identity column and the PicklistID that gets created is also an identity column. – Jamie Sep 29 '11 at 20:23
  • when I say `If you only have one identity value to get` I talking about row count in a single operation. if you insert 2 rows into a table with a single insert command, that will generate two identity values, so use `OUTPUT`. No matter how many inserts commands you issue, if you insert a single row you can use `SCOPE_IDENTITY()` (right after each) to get the identity. – KM. Sep 30 '11 at 15:50
1

This is one of the drawbacks of surrogates i.e. harder to perform bulk-creation of entities. I avoid using surrogates myself but often come up against this problem. My general approach is to use a staging table to map system-generated surrogates to their natural keys.

Assuming this somewhat simplified structure:

CREATE TABLE Products 
(
 ProductName VARCHAR(20) NOT NULL UNIQUE,  -- natural key
 ProductID INTEGER IDENTITY(1, 1) NOT NULL UNIQUE  -- surrogate
);

CREATE TABLE PickLists
(
 PicklistDescription VARCHAR(30) NOT NULL UNIQUE,  -- natural key
 PicklistID INTEGER IDENTITY(50, 50) NOT NULL UNIQUE  -- surrogate
);

CREATE TABLE Marketing
(
 ProductID INTEGER NOT NULL UNIQUE REFERENCES Products (ProductID), 
 PicklistID INTEGER NOT NULL UNIQUE REFERENCES PickLists (PicklistID), 
 MarketingComment VARCHAR(40) NOT NULL
);

the following is a basic outline using vanilla Standard SQL-92 (IDENTITY keyword excepted!):

(note: natural keys are those generated outside of the DBMS and surrogates are DBMS-generated values)

CREATE TABLE StagingMarketing
(
 ProductName VARCHAR(20) NOT NULL UNIQUE, 
 PicklistDescription VARCHAR(30) NOT NULL UNIQUE, 
 MarketingComment VARCHAR(40) NOT NULL
);

-- Bulk insert staging table using natural key values
INSERT INTO StagingMarketing (ProductName, PicklistDescription, MarketingComment)
   VALUES ('Widget55', 'Stuff22', 'Prototype');
INSERT INTO StagingMarketing (ProductName, PicklistDescription, MarketingComment)
   VALUES ('Widget99', 'Stuff152', 'Research');

-- Update referenced tables
INSERT INTO Products (ProductName)
   SELECT ProductName FROM StagingMarketing;
INSERT INTO PickLists (PicklistDescription)
   SELECT PicklistDescription FROM StagingMarketing;

-- Finally, update referencing table
INSERT INTO Marketing (ProductID, PicklistID, MarketingComment)
   SELECT P.ProductID, L.PicklistID, S.MarketingComment
     FROM StagingMarketing S
          INNER JOIN Products P
             ON P.ProductName = S.ProductName
          INNER JOIN PickLists L
             ON L.PicklistDescription = S.PicklistDescription;

-- Cleanup
DELETE FROM StagingMarketing;

SELECT * FROM Marketing;

p.s. if the designer chose to use IDENTITY as an artificial key in absence of a natural key then you are up the creek.

onedaywhen
  • 55,269
  • 12
  • 100
  • 138
  • I added a picture of the database structure in the original post. I don't know if that will help you see what I'm working with at all. I'm not sure I understand what you are talking about in your post. I've never seen sql like it before. – Jamie Sep 30 '11 at 14:41
  • @jlg: I purposely chose a simplified three-table structure loosely based on your schema for illustrative purposes. I don't think extending it to fit your full and exact schema will aid your comprehension of my simple SQL ;) – onedaywhen Sep 30 '11 at 15:15
  • Okie dokie. Not entirely awake yet :) – Jamie Sep 30 '11 at 15:17