2009-02-27 24 views
7

Dường như một vấn đề đủ phổ biến này, nhưng hầu hết các giải pháp liên quan đến việc ghép nối nhiều lệnh SQL, một cái gì đó mà tôi tin rằng không thể được thực hiện với ADO/VBA (Tôi sẽ được vui mừng được hiển thị sai trong vấn đề này tuy nhiên).Làm cách nào để lấy id của bản ghi vừa được chèn bằng VBA Excel?

Tôi hiện đang chèn bản ghi mới của mình, sau đó chạy truy vấn chọn sử dụng (Tôi hy vọng) đủ trường để đảm bảo rằng chỉ bản ghi mới được chèn mới có thể được trả lại. Cơ sở dữ liệu của tôi hiếm khi được truy cập bởi nhiều người cùng một lúc (rủi ro không đáng kể của việc chèn khác xảy ra giữa các truy vấn) và do cấu trúc của các bảng, việc xác định bản ghi mới thường khá dễ dàng.

Tôi hiện đang cố cập nhật bảng không có nhiều phạm vi cho tính duy nhất, ngoài khóa chính giả tạo. Điều này có nghĩa là có rủi ro là bản ghi mới có thể không phải là duy nhất và tôi không muốn thêm một trường chỉ để buộc tính duy nhất.

Cách tốt nhất để chèn bản ghi vào bảng Access sau đó truy vấn khóa chính mới từ Excel trong trường hợp này là gì?

Cảm ơn bạn đã trả lời. Tôi đã cố gắng để có được @@IDENTITY hoạt động, nhưng điều này luôn trả về 0 bằng cách sử dụng mã bên dưới.

Private Sub getIdentityTest() 
    Dim myRecordset As New ADODB.Recordset 
    Dim SQL As String, SQL2 As String 

    SQL = "INSERT INTO tblTasks (discipline,task,owner,unit,minutes) VALUES (""testDisc3-3"",""testTask"",""testOwner"",""testUnit"",1);" 
    SQL2 = "SELECT @@identity AS NewID FROM tblTasks;" 

    If databaseConnection Is Nothing Then 
     createDBConnection 
    End If 

    With databaseConnection 
     .Open dbConnectionString 
     .Execute (SQL) 
     .Close 
    End With 

    myRecordset.Open SQL2, dbConnectionString, adOpenStatic, adLockReadOnly 

    Debug.Print myRecordset.Fields("NewID") 

    myRecordset.Close 

    Set myRecordset = Nothing 
End Sub 

Mọi thứ đều chịu trách nhiệm?

Tuy nhiên, do những lời nhắc thận trọng do Renaud cung cấp (bên dưới) có vẻ như có nhiều rủi ro khi sử dụng @@IDENTITY như với bất kỳ phương pháp nào khác, vì vậy tôi đã sử dụng SELECT MAX bây giờ. Để tham khảo trong tương lai mặc dù tôi sẽ quan tâm để xem những gì là sai với nỗ lực của tôi ở trên.

+0

Sử dụng 'Max' có thể phức tạp ... nếu ai đó đang chèn hồ sơ, nó có thể cung cấp cho bạn ID của bản ghi khác. Nếu bạn là người duy nhất chèn bản ghi, hãy tiếp tục. –

Trả lời

12

Về câu hỏi của bạn:

Tôi bây giờ cố gắng cập nhật một bảng không hav e nhiều phạm vi cho tính độc đáo, ngoại trừ trong khóa chính nhân tạo . Điều này có nghĩa là có rủi ro là kỷ lục mới có thể không phải là duy nhất và tôi không thích thêm trường chỉ để buộc tính duy nhất.

Nếu bạn sử dụng một AutoIncrement cho khóa chính của bạn, sau đó bạn có tính độc đáo và bạn có thể sử dụng SELECT @@Identity; để có được giá trị của ID autogenerated cuối cùng (xem hãy cẩn thận dưới đây).

Nếu bạn là không sử dụng autoincrement, và bạn đang chèn các bản ghi từ truy cập nhưng bạn muốn lấy người cuối cùng từ Excel:

  • đảm bảo khóa chính của bạn là sắp xếp được, vì vậy bạn có thể được người cuối cùng sử dụng một truy vấn như một trong những:

    SELECT MAX(MyPrimaryField) FROM MyTable; 
    SELECT TOP 1 MyPrimaryField FROM MyTable ORDER BY MyPrimaryField DESC; 
    
  • hoặc, nếu sắp xếp lĩnh vực chính của bạn sẽ không cung cấp cho bạn một cuối cùng, bạn sẽ cần phải thêm một lĩnh vực DateTime (nói InsertedDate) một nd lưu ngày hiện tại và thời gian mỗi khi bạn tạo ra một kỷ lục mới trong bảng đó, do đó bạn có thể nhận được người cuối cùng như thế này:

    SELECT TOP 1 MyPrimaryField FROM MyTable ORDER BY InsertedDate DESC; 
    

Trong cả hai trường hợp này, tôi nghĩ rằng bạn sẽ tìm thấy thêm một AutoIncrement khóa chính như là dễ dàng hơn nhiều để đối phó với:

  • Nó sẽ không cho bạn chi phí nhiều

  • Nó sẽ đảm bảo bạn độc đáo của hồ sơ của bạn mà không cần phải để suy nghĩ về điều này

  • Việc này sẽ giúp bạn dễ dàng hơn trong việc chọn hồ sơ gần đây nhất, bằng cách sử dụng @@Identity hoặc thông qua sắp xếp theo khóa chính hoặc nhận được Max().

Từ Excel

Để có được dữ liệu vào Excel, bạn có một vài lựa chọn:

  • tạo một liên kết dữ liệu sử dụng một truy vấn, vì vậy bạn có thể sử dụng kết quả trực tiếp trong Ô hoặc dải ô.

  • truy vấn từ VBA:

    Sub GetLastPrimaryKey(PrimaryField as string, Table as string) as variant 
        Dim con As String 
        Dim rs As ADODB.Recordset 
        Dim sql As String 
        con = "Provider=Microsoft.ACE.OLEDB.12.0;" & _ 
          "Data Source= ; C:\myDatabase.accdb" 
        sql = "SELECT MAX([" & PrimaryField & "]) FROM [" & MyTable & "];" 
        Set rs = New ADODB.Recordset 
        rs.Open sql, con, adOpenStatic, adLockReadOnly 
        GetLastPrimaryKey = rs.Fields(0).Value 
        rs.Close 
        Set rs = Nothing 
    End Sub 
    

Lưu ý về @@Identity

Bạn phải careful of the caveats khi sử dụng @@Identity trong cơ sở dữ liệu truy cập chuẩn (*):

  • Nó chỉ hoạt động với các trường Identity AutoIncrement.

  • Đó là chỉ có sẵn nếu bạn sử dụng ADO và chạy SELECT @@IDENTITY;

  • Nó trả về truy cập sử dụng mới nhất, but that's for all tables. Bạn không thể sử dụng nó để trả về bộ đếm cho một bảng cụ thể trong MS Access (theo như tôi biết, nếu bạn chỉ định một bảng sử dụng FROM mytable, nó chỉ bị bỏ qua).
    Tóm lại, giá trị trả lại có thể không hoàn toàn giống như bạn mong đợi.

  • Bạn phải truy vấn ngay sau INSERT để giảm thiểu rủi ro nhận được câu trả lời sai.
    Điều đó có nghĩa là nếu bạn đang chèn dữ liệu cùng một lúc và cần lấy ID cuối cùng tại một thời điểm khác (hoặc một địa điểm khác), nó sẽ không hoạt động.

  • Cuối cùng nhưng không kém phần quan trọng, biến được đặt chỉ khi các bản ghi được chèn qua mã lập trình.
    Điều này có nghĩa là bản ghi đã được thêm vào thông qua giao diện người dùng, @@IDENTITY sẽ không được đặt.

(*): chỉ để được rõ ràng, @@IDENTITY cư xử khác nhau, và trong một cách tiên đoán hơn, nếu bạn sử dụng ANSI-92 chế độ SQL cho cơ sở dữ liệu của bạn.
Vấn đề mặc dù ANSI 92 có cú pháp hơi khác so với hương vị ANSI 89 được Access hỗ trợ và có nghĩa là tăng khả năng tương thích với SQL Server khi Access được sử dụng làm giao diện người dùng.

+1

"SELECT MAX" có mùi thực sự xấu. Trong khi nó sẽ làm việc trong các trường hợp được mô tả, như trong mọi trường hợp mọi thứ thay đổi theo thời gian, và trước khi bạn biết nó mã này sẽ thất bại! Không làm cho mã mùi, suy nghĩ về các chàng trai tiếp theo – TFD

+1

@ TFD: đã đồng ý về nguyên tắc, nhưng không có nhiều cách để đạt được những gì người đăng muốn, ít nhất là trong MS Access. Nó sẽ luôn là một sự cân bằng. Max() sẽ hoạt động OK cho hầu hết các kiểu dữ liệu, bao gồm cả chuỗi, mặc dù tôi đồng ý rằng có thể có những trường hợp bạn không thể nhận được những gì bạn muốn. –

+0

Cảm ơn bạn đã trả lời chi tiết, và cho những lời khuyên hữu ích về @@ IDENTITY - Tôi sẽ bỏ phiếu nếu tôi có thể. – Lunatik

7

Nếu khóa nhân tạo là số tự động, bạn có thể sử dụng @@ identity.

Lưu ý rằng với cả hai ví dụ này, giao dịch được phân lập từ các sự kiện khác, do đó danh tính được trả về là thông tin được chèn vào. Bạn có thể kiểm tra điều này bằng cách tạm dừng mã tại Debug.Print db.RecordsAffected hoặc Debug.Print lngRecs và chèn một bản ghi theo cách thủ công vào Bảng 1, tiếp tục mã và lưu ý rằng danh tính được trả về không phải là bản ghi được chèn theo cách thủ công, mà trước đó bản ghi được chèn vào bằng mã.

DAO Ví dụ

'Reference: Microsoft DAO 3.6 Object Library ' 
Dim db As DAO.Database 
Dim rs As DAO.Recordset 

Set db = CurrentDb 

db.Execute ("INSERT INTO table1 (field1, Crdate) " _ 
      & "VALUES (46, #" & Format(Date, "yyyy/mm/dd") & "#)") 
Debug.Print db.RecordsAffected 
Set rs = db.OpenRecordset("SELECT @@identity AS NewID FROM table1") 
Debug.Print rs.Fields("NewID") 

ADO Ví dụ

Dim cn As New ADODB.Connection 
Dim rs As New ADODB.Recordset 

Set cn = CurrentProject.Connection 

cn.Execute ("INSERT INTO table1 (field1, Crdate) " _ 
      & "VALUES (46, #" & Format(Date, "yyyy/mm/dd") & "#)"), lngRecs 
Debug.Print lngRecs 
rs.Open "SELECT @@identity AS NewID FROM table1", cn 
Debug.Print rs.Fields("NewID") 
+0

Xin cảm ơn, hãy thử. – Lunatik

+0

Với điều này tôi nhận được lỗi 3001 "Các đối số sai loại, nằm ngoài phạm vi có thể chấp nhận được hoặc đang xung đột với nhau". Tôi cũng không thể tìm thấy "OpenRecordset" trong trình duyệt đối tượng, điều này có thể giải thích một chút. Tôi có thư viện ADO 2.6 được đặt làm tham chiếu, nếu đó là bất kỳ trợ giúp nào. – Lunatik

+0

Mã này dành cho DAO, thường là tốt nhất cho MS Access. Tôi sẽ có thể chạy một cái gì đó trong ADO, nếu bạn cần nó. – Fionnuala

0

Hãy thử sau vĩ mô code.First thêm một nút lệnh để bảng từ hộp điều khiển và dán mã sau trong cửa sổ mã

Private Sub CommandButton1_Click() 
    MsgBox GetLastPrimaryKey 
End Sub 

Private Function GetLastPrimaryKey() As String 
Dim con As String 
Dim cn As ADODB.Connection 
Dim rs As ADODB.Recordset 
Dim sql As String 
con = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\myaccess.mdb;Persist Security Info=False" 
sql = "SELECT MAX(id) FROM tblMyTable" 

Set cn = New ADODB.Connection 
Set rs = New ADODB.Recordset 
cn.Open con 
rs.Open sql, cn, 3, 3, 1 
If rs.RecordCount <> 0 Then 
    GetLastPrimaryKey = rs.Fields(0).Value 
End If 
rs.Close 
cn.Close 
Set rs = Nothing 
Set cn = Nothing 
End Function 
3

Re: "Tôi đã cố gắng để có được @@ IDENTITY làm việc, nhưng điều này luôn trả về 0 bằng cách sử dụng mã bên dưới. "

Mã của bạn gửi SQLSQL2 thông qua các đối tượng kết nối khác nhau. Tôi không nghĩ rằng @@identity sẽ trả lại bất kỳ thông tin nào khác không phải trừ khi bạn yêu cầu từ cùng kết nối nơi bạn đã thực hiện tuyên bố INSERT của mình.

Cố gắng thay đổi này:

myRecordset.Open SQL2, dbConnectionString, adOpenStatic, adLockReadOnly 

tới:

myRecordset.Open SQL2, databaseConnection, adOpenStatic, adLockReadOnly 
+0

D'oh, đó là những gì tôi nhận được để tái sử dụng bit mã từ các dự án khác nhau! Tôi đang ở một dự án khác vào lúc này, nhưng đề xuất của bạn có ý nghĩa hoàn hảo và tôi sẽ thử nó khi tôi có cơ hội. – Lunatik

0

Đây là giải pháp của tôi mà không sử dụng @@ chỉ mục hoặc MAX.

Const connectionString = "Provider=SQLOLEDB; Data Source=SomeSource; Initial Catalog=SomeDB; User Id=YouIDHere; Password=YourPassword" 
Const RecordsSQL = "SELECT * FROM ThatOneTable" 

Private Sub InsertRecordAndGetID() 
    Set connection = New ADODB.connection 
    connection.connectionString = connectionString 
    connection.Open 
    Set recordset = New ADODB.recordset 
    recordset.Open SQL, connection, adOpenKeyset, adLockOptimistic 

    With recordset 
     .AddNew 
     !Field1 = Value1 
     !Field2 = Value2 
    End With 

    recordset.MoveLast 
    ID = recordset.Fields("id") 

End Sub 

Tận hưởng!

0

8 năm sau bữa tiệc ... Sự cố bạn đang gặp phải là bạn đang sử dụng dbConnectionString để tạo kết nối mới mới. @@ danh tính cụ thể cho kết nối bạn đang sử dụng.

Thứ nhất, không đóng kết nối ban đầu

'.Close 

thay

myRecordset.Open SQL2, dbConnectionString, adOpenStatic, adLockReadOnly 

với kết nối trước đó bạn sử dụng để chèn

myRecordset.Open SQL2, databaseConnection, adOpenStatic, adLockReadOnly 

và bạn muốn đã tất cả các thiết lập.Trên thực tế, bạn thậm chí không cần chỉ định bảng:

SQL2 = "SELECT @@identity AS NewID"