detecting whether some records are excluded from an INSERT INTO operation - sql

I have a composite primary key on 2 columns in the table I am INSERTing into. I come from working with SQL Server, and I know that if I attempted to insert a duplicate key value into a PK table, it would throw an error.
My problem is, my code is not throwing this kind of error. Can you look at it and see if it's a problem with the code? Or does Access not throw errors for this kind of violation?
[Edit]
I guess I'm looking for a way to just acknowledge that duplicate records are attempted to be inserted. I want the current functionality to remain (dups are tossed; valid records are inserted). I don't want the entire INSERT to get rolled back.
My code is below.
Function InsertData(Ignore As String)
' define file path of CSV to be imported
Dim CurrentDate As String
Dim CurrentYear As String
CurrentDate = Format(Date, "yyyymmdd")
CurrentYear = Format(Date, "yyyy")
Dim Exfile As String
Exfile = iPath + "\" + CurrentYear + "\" + "FileName" + CurrentDate + ".txt"
'this calls a saved import routine
DoCmd.RunSavedImportExport "tbl_TEMP"
'merge data with that already existing in tbl_Perm.
'the clustered PK on product_ID and As_of_Date prevents dup insertion
Dim dbs As Database
Dim errLoop As Error
Set dbs = OpenDatabase(iPath + "\ExDatabase.mdb")
dbs.Execute " INSERT INTO tbl_Perm (Col1,Col2,Date_Created) " _
& "SELECT ColA + ColB, ColC, Format$(Now(),'Short Date')" _
& "FROM tbl_TEMP;"
' Trap for errors, checking the Errors collection if necessary.
On Error GoTo Err_Execute
'delete temp table
dbs.Execute "DROP TABLE tbl_TEMP;"
dbs.Close
Err_Execute:
' Notify user of any errors that result from
' executing the query.
If DBEngine.Errors.Count > 0 Then
For Each errLoop In DBEngine.Errors
MsgBox "Error number: " & errLoop.Number & vbCr & _
errLoop.Description
Next errLoop
End If
Resume Next
End Function

From the Microsoft DAO Doc (here):
In a Microsoft Access workspace, if you provide a syntactically
correct SQL statement and have the appropriate permissions, the
Execute method won't fail — even if not a single row can be modified
or deleted. Therefore, always use the dbFailOnError option when using
the Execute method to run an update or delete query. This option
generates a run-time error and rolls back all successful changes if
any of the records affected are locked and can't be updated or
deleted.
So add the dbFailOnError option to your call.

If you want to allow the INSERT to proceed and determine whether any duplicates were rejected then you could do something like this
Dim cdb As DAO.Database, qdf As DAO.QueryDef, rst As DAO.Recordset
Dim sqlSelect As String, sourceRecords As Long
Set cdb = CurrentDb
sqlSelect = _
"SELECT ColA + ColB, ColC, Format$(Now(),'Short Date') " & _
"FROM tbl_TEMP"
Set rst = cdb.OpenRecordset("SELECT COUNT(*) AS n FROM (" & sqlSelect & ")", dbOpenSnapshot)
sourceRecords = rst!n
rst.Close
Set rst = Nothing
Set qdf = cdb.CreateQueryDef("", _
"INSERT INTO tbl_Perm (Col1,Col2,Date_Created) " & sqlSelect)
qdf.Execute
If qdf.RecordsAffected < sourceRecords Then
Debug.Print sourceRecords - qdf.RecordsAffected & " record(s) not inserted"
End If
Set qdf = Nothing
Set cdb = Nothing

The line
On Error GoTo Err_Execute
is after you execute your SQL statement. The Err_Execute block will - in this case - be called every time because there is not statement like Exit Function before the label. I am not sure what happens to the errors if you close the connection before evaluating the error collection.

Related

Microsoft Access 2016 Summarize and Merge Table with SQL

I have a table that I need to format for use in a manual upload process. The table is thousands of rows so I need to use a repeatable process to quickly fix the way the data is given to me into what it needs to be. I have zero ability to control the way the data comes to me today. But, I have to format it to use it due to a system limitation. My current table is 4 columns, I need to output it as 3 columns. I have to group up by field names: "brand" and "promotion". Field name: "skus" I need to take and merge them into one continuous string by a single "brand" and "promotion" combination.
There are duplicate "promotion" by a given brand since they are created at a product level. But, the system they go into need to be "brand", "promotion", "skus".
Not really sure if I need to use VBA to do some of this inside Access. Or I can do this in two different queries.
You will need to use a bit of VBA to do this. The VBA will need to loop a recordset of data from the table that is filtered on brand and promotion and build up the sku string. Something like this perhaps:
Function fJoinData(strBrand As String, strPromotion As String) As String
On Error GoTo E_Handle
Dim db As DAO.Database
Dim rsData As DAO.Recordset
Dim strSQL As String
Set db = DBEngine(0)(0)
strSQL = "SELECT skus FROM Table1 " _
& " WHERE Brand='" & strBrand & "' " _
& " AND Promotion='" & strPromotion & "';"
Set rsData = db.OpenRecordset(strSQL)
If Not (rsData.BOF And rsData.EOF) Then
Do
fJoinData = fJoinData & ", " & rsData!skus
rsData.MoveNext
Loop Until rsData.EOF
End If
If Left(fJoinData, 2) = ", " Then fJoinData = Mid(fJoinData, 3)
fExit:
On Error Resume Next
rsData.Close
Set rsData = Nothing
Set db = Nothing
Exit Function
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "fJoinData", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume fExit
End Function
Rather than inserting into a table, I would suggest just creating a query which can then be exported:
SELECT DISTINCT
T.Brand,
T.Promotion,
fJoinData(T.Brand,T.Promotion) AS skus
FROM Table1 AS T
Regards,

ACCESS SQL Adding Multiple Columns If They Don't Exist

I'm trying to add several new columns to a table that already exists in Access 2007 or 2010 with a query. Right now my code looks like this (yes, I'm terribly new to this):
ALTER TABLE AC_PROPERTY
ADD JAB_1 double,
JAB_2 double,
JAB_3 double;
This correctly adds the three columns when none of them already exist, but if any of them exist I get an error message and the query fails to run. I need it to add each columns only if it does not exist. Can anyone please help with what my code should look like to check if each column exists before trying to add it?
NOTE: I would just do 3 queries for each column, but I actually need to add 20+ columns. This is just a simple example of my actual use.
Thanks a billion!
Here is some old code.... it would be better to just check all fields in the same subroutine rather than opening/closing the DB, TDF, etc.
Option Compare Database
Option Explicit
Function Check_If_Exists()
Dim strStatus As String
' Add calls for the fields you want to append
strStatus = Add_Field("MyFLd2", "Double")
If strStatus = "Exists" Then
Debug.Print "field present"
ElseIf strStatus = "Added" Then
Debug.Print "field added"
End If
End Function
Function Add_Field(strFN, strType) As String
Dim db As DAO.Database
Dim td As DAO.TableDef
Dim fd As DAO.Field
Dim strSQL As String
On Error GoTo Error_Trap
Set db = CurrentDb
Set td = db.TableDefs("Table1")
' ' List all field names
' For Each fd In td.Fields
' Debug.Print fd.Name
' Next fd
If IsNull(td.Fields(strFN)) Then
Add_Field = "Exists"
End If
Set fd = Nothing
Set td = Nothing
Set db = Nothing
Exit Function
Error_Trap:
Debug.Print Err.Number & vbTab & Err.Description
If Err.Number = 3265 Then
Add_Field = "Added"
strSQL = "alter table Table1 ADD " & strFN & " " & strType & ";"
db.Execute strSQL
db.TableDefs.Refresh
End If
Exit Function
Resume
End Function

Too few parameters Expected 1, recordset issue

I'm having a problem getting a recordset to run from a query I created in an MS Access 2010 database. here is t he code I want to run:
Private Sub Command192_Click()
Dim recs As String
Dim param As Integer
Dim rs As Recordset
Set rs = CurrentDb.OpenRecordset("UnitRec_Qry", dbOpenDynaset)
With rs
.MoveLast
.MoveFirst
While Not .EOF
recs = recs & vbNewLine & !Spara & " - " & !Rec
.MoveNext
Wend
End With
MsgBox (recs)
End Sub
What this should output is a message box with a number of records from the query in a list. I do this so I can gather this and a number of other records into a text file for copying and pasting into a separate system. At the moment, I'm running this code so I can place it all into a string variable.
My problem is that I'm getting the dreaded "Too Few parameters expected 1" error.
The query works, I've saved it into the database and tested it and I get the expected results.
I tried running the recordset with SQL:
Set rs = CurrentDb.OpenRecordset("SELECT UnitRecommend_tbl.URecID, UnitRecommend_tbl.Spara," _
& " UnitRecommend_tbl.Rec, UnitRecommend_tbl.SvyID" _
& " FROM UnitRecommend_tbl" _
& " WHERE ((UnitRecommend_tbl.SvyID) = [Forms]![SurveyRegister_frm]![SurveyID])" _
& " ORDER BY UnitRecommend_tbl.Spara;", dbOpenDynaset)
I get the same error
I ran it again but removed the "WHERE" statement and the code ran just fine, but gave me every record in the table. Not what I wanted.
So, the fields are OK because the data runs. When I debug the text the parameter in the SQL does show up as the right parameter, in this case, the number 4 which is an integer.
So I'm at a loss here, I've searched through the other posts here and I have tried these possible solutions (unless I missed something).
I also tried using dbopensnapshot as well, still no joy. Wondering if I'm using the right code here now.
Any help would be great.
Cheers
A parameter like [Forms]![SurveyRegister_frm]![SurveyID] doesn't get evaluated automatically if you open a recordset in VBA.
Use this function:
Public Sub Eval_Params(QD As DAO.QueryDef)
On Error GoTo Eval_Params_Err
Dim par As DAO.Parameter
For Each par In QD.Parameters
' This is the key line: Eval "evaluates" the form field and gets the value
par.Value = Eval(par.Name)
Next par
Eval_Params_Exit:
On Error Resume Next
Exit Sub
Eval_Params_Err:
MsgBox Err.Description, vbExclamation, "Runtime-Error " & Err.Number & " in Eval_Params"
Resume Eval_Params_Exit
End Sub
with a QueryDef object like this:
Dim QD As QueryDef
Dim RS As Recordset
Set QD = DB.QueryDefs("UnitRec_Qry")
Call EVal_Params(QD)
Set RS = QD.OpenRecordset(dbOpenDynaset)
Alternatively, you can run it with SQL in the VBA code by moving the parameter outside of the SQL string:
Set rs = CurrentDb.OpenRecordset("SELECT UnitRecommend_tbl.URecID, UnitRecommend_tbl.Spara," _
& " UnitRecommend_tbl.Rec, UnitRecommend_tbl.SvyID" _
& " FROM UnitRecommend_tbl" _
& " WHERE ((UnitRecommend_tbl.SvyID) = " & [Forms]![SurveyRegister_frm]![SurveyID] & ")" & _
& " ORDER BY UnitRecommend_tbl.Spara;", dbOpenDynaset)

Adding field to MS Access Table using VBA

I need to add a calculated field to an existing table. I am aware of two ways to do this and I'm wondering if anyone has any input on which is best and how to make them work:
Using TableDef.CreateField, then TableDef.Fields.Append
Using a DDL Alter Table ADD COLUMN statement
I tried using the first method, but I keep getting a 3211 error because Access could not lock the table. I don't have the table open. However, I am calling CreateField from a form that has accessed which fields currently exist in the table.
Here's the code for calling CreateField:
`
Public Sub AddFieldToTable(strTable As String, strField As String, nFieldType As Integer)
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim fld As DAO.Field
On Error GoTo ErrorHandler
Set db = CurrentDb
Set tdf = db.TableDefs(strTable)
Set fld = tdf.CreateField(strField, nFieldType)
tdf.Fields.Append fld
MsgBox "The field named [" & strField & "] has been added to table [" & strTable & "]."
Set tdf = Nothing
Set db = Nothing
Exit Sub
ErrorHandler:
MsgBox "An error has occurred. Number: " & Err.Number & ", description: " & Err.Description
Exit Sub
End Sub
`
I get the error on the tdf.fields.append line. Would executing an ALTER TABLE statement be better? What are the tradeoffs?
You can use DDL to create fields:
Long:
CurrentDb.Execute "ALTER TABLE t ADD COLUMN a Long not null", dbFailOnError
(tack on NOT NULL IDENTITY(1,1) for an autonumber)
CurrentDb.Execute "ALTER TABLE t ADD COLUMN b text(100)", dbFailOnError
Boolean:
CurrentDb.Execute "ALTER TABLE t ADD COLUMN c Bit not null", dbFailOnError
DateTime:
CurrentDb.Execute "ALTER TABLE t ADD COLUMN d datetime null", dbFailOnError
Memo:
CurrentDb.Execute "ALTER TABLE t ADD COLUMN e memo null", dbFailOnError
Obviously, this lends itself well to functionalization, and you could just pass in your own eternal enum, combined with a Select, to construct the string and execute it:
Public Sub AddFieldToTable(TableName as string, FieldName as string, _
FieldType as Long, FieldLen as Long, FieldAllowsNull as Boolean)
Dim FieldText as String
Select Case(FieldType)
Case 0:
FieldText = "Long"
Case 1:
FieldText = "text(" & FieldLen & ")"
Case 2:
FieldText = "bit"
Case 3:
FieldText = "datetime"
Case 4:
FieldText = "memo"
End Select
Dim Sql as string
Sql = "ALTER TABLE " & TableName & " ADD COLUMN " & FieldName & " " & FieldText
If FieldAllowsNull then
Sql = Sql & " NULL"
Else
Sql = Sql & " NOT NULL"
End If
CurrentDb.Execute Sql, dbFailOnError
End Sub
I got the code working with either the CreateField or the ALTER TABLE statement. The key here was that I had used a recordset to access the table's data (I needed to check whether the field already existed and/or contained data before I ran the AddField method). I moved the rst.close statement up to before I edited the table structure and it worked! No more 3211.
`
Set db = CurrentDb
Set rst = db.OpenRecordset(strTable)
bFieldExists = Field_Exists(rst, strOutputField) ' Custom field_exists in table function
If bFieldExists then nFieldType = rst(strOutputField).Type
If CheckFieldHasValues(strTable, strOutputField) = True Then ' custom CheckField function
If MsgBox("The output field has values in it. Proceed?", vbYesNo) = vbNo Then Exit Sub
End If
rst.Close ' Recordset must release the table data before we can alter the table!
If bFieldExists = False Then
AddFieldToTable strTable, strOutputField, dbCurrency
End If
Set db = Nothing
I just did the following in a module and it works fine
Sub AddTableFields()
Dim db As DAO.Database
Dim t As DAO.TableDef
Dim f As DAO.Field
Set db = CurrentDb
Set t = db.TableDefs("tl_LongTermStat")
Dim intY As Integer
Dim intQ As Integer
For intY = 2012 To 2018
For intQ = 1 To 4
Set f = t.CreateField("Y" & intY & "Q" & intQ, dbText, 10)
t.Fields.Append f
Next
Next
Debug.Print "AddTableFields() done"
End Sub

Check if access table exists

I want to log web site visits' IP, datetime, client and refferer data to access database but I'm planning to log every days log data in separate tables in example logs for 06.06.2010 will be logged in 2010_06_06 named table. When date is changed I'll create a table named 2010_06_07. But the problem is if this table is already created.
Any suggestions how to check if table exists in Access?
You can use the hidden system table MSysObjects to check if a table exists:
If Not IsNull(DlookUp("Name","MSysObjects","Name='TableName'")) Then
'Table Exists
However, I agree that it is a very bad idea to create a new table every day.
EDIT: I should add that tables have a type 1, 4 or 6 and it is possible for other objects of a different type to have the same name as a table, so it would be better to say:
If Not IsNull(DlookUp("Name","MSysObjects","Name='TableName' And Type In (1,4,6)")) Then
'Table Exists
However, it is not possible to create a table with the same name as a query, so if you need a look up to test for a name, it may be best to add 5, that is query, to the Type list.
Here's another solution, will be a bit faster than looping over all of the tables.
Public Function doesTableExist(strTableName As String) As Boolean
Dim db As DAO.Database
Dim td As DAO.TableDef
Set db = CurrentDb
On Error Resume Next
Set td = db.TableDefs(strTableName)
doesTableExist = (Err.Number = 0)
Err.Clear
End Function
I tested various methods for finding out if a table exists several years ago. Here is the code for all of them as I implemented, including my simple test routine.
Public Function TableExists(strTableName As String, Optional ysnRefresh As Boolean, Optional db As DAO.Database) As Boolean
' Originally Based on Tony Toews function in TempTables.MDB, http://www.granite.ab.ca/access/temptables.htm
' Based on testing, when passed an existing database variable, this is the fastest
On Error GoTo errHandler
Dim tdf As DAO.TableDef
If db Is Nothing Then Set db = CurrentDb()
If ysnRefresh Then db.TableDefs.Refresh
Set tdf = db(strTableName)
TableExists = True
exitRoutine:
Set tdf = Nothing
Exit Function
errHandler:
Select Case Err.Number
Case 3265
TableExists = False
Case Else
MsgBox Err.Number & ": " & Err.Description, vbCritical, "Error in mdlBackup.TableExists()"
End Select
Resume exitRoutine
End Function
Public Function TableExists2(strTableName As String, Optional ysnRefresh As Boolean, Optional db As DAO.Database) As Boolean
On Error GoTo errHandler
Dim bolCleanupDB As Boolean
Dim tdf As DAO.TableDef
If db Is Nothing Then
Set db = CurrentDb()
bolCleanupDB = True
End If
If ysnRefresh Then db.TableDefs.Refresh
For Each tdf In db.TableDefs
If tdf.name = strTableName Then
TableExists2 = True
Exit For
End If
Next tdf
exitRoutine:
Set tdf = Nothing
If bolCleanupDB Then
Set db = Nothing
End If
Exit Function
errHandler:
MsgBox Err.Number & ": " & Err.Description, vbCritical, "Error in mdlBackup.TableExists1()"
Resume exitRoutine
End Function
Public Function TableExists3(strTableName As String, _
Optional db As DAO.Database) As Boolean
' Based on testing, when NOT passed an existing database variable, this is the fastest
On Error GoTo errHandler
Dim strSQL As String
Dim rs As DAO.Recordset
If db Is Nothing Then Set db = CurrentDb()
strSQL = "SELECT MSysObjects.Name FROM MSysObjects "
strSQL = strSQL & "WHERE MSysObjects.Name=" & Chr(34) & strTableName & Chr(34)
strSQL = strSQL & " AND MSysObjects.Type=6;"
Set rs = db.OpenRecordset(strSQL)
TableExists3 = (rs.RecordCount <> 0)
exitRoutine:
If Not (rs Is Nothing) Then
rs.Close
Set rs = Nothing
End If
Exit Function
errHandler:
MsgBox Err.Number & ": " & Err.Description, vbCritical, _
"Error in TableExists1()"
Resume exitRoutine
End Function
Public Sub TestTableExists(strTableName As String, intLoopCount As Integer)
Dim dteStart As Date
Dim i As Integer
Dim bolResults As Boolean
dteStart = Now()
For i = 0 To intLoopCount
bolResults = TableExists(strTableName, , CurrentDB())
Next i
Debug.Print "TableExists (" & intLoopCount & "): " & Format(Now() - dteStart, "nn:ss")
dteStart = Now()
For i = 0 To intLoopCount
bolResults = TableExists2(strTableName, , CurrentDB())
Next i
Debug.Print "TableExists2 (" & intLoopCount & "): " & Format(Now() - dteStart, "nn:ss")
dteStart = Now()
For i = 0 To intLoopCount
bolResults = TableExists3(strTableName, CurrentDB())
Next i
Debug.Print "TableExists3 (" & intLoopCount & "): " & Format(Now() - dteStart, "nn:ss")
End Sub
I have found querying system tables or tabledefs to be unreliable and introduce unpredictable behaviour in scripts where tables get regularly created and dropped.
Based on my results, my hypothesis is that these tables aren't necessarily updated at the exact instant a CREATE or DROP is executed, or that concurrency issues are preventing me from getting an accurate result.
I've found the following method to be more reliable:
Public Function TableExists(theDatabase As Access.Application, _
tableName As String) As Boolean
' Presume that table does not exist.
TableExists = False
' Define iterator to query the object model.
Dim iTable As Integer
' Loop through object catalogue and compare with search term.
For iTable = 0 To theDatabase.CurrentData.AllTables.Count - 1
If theDatabase.CurrentData.AllTables(iTable).Name = tableName Then
TableExists = True
Exit Function
End If
Next iTable
End Function
There should be no runtime issue iterating unless there is an staggeringly enormous collection of tables.
This question is quite old but I found that no answer is satisfying, because:
they do not handle the case of "bad" linked tables, where the linked table points to a non existing db or table.
since linked tables are potentially huge, we must be able to check them with a fast query.
So here is my simple but more complete solution:
Function isTableOk(tblName As String) As Boolean
'works with local or linked tables
Dim db As DAO.Database, rs As DAO.Recordset
Dim sSql As String
sSql = "SELECT TOP 1 ""xxx"" AS Expr1 FROM [" & tblName & "]"
On Error Resume Next
Err.Clear
Set db = CurrentDb
Set rs = db.OpenRecordset(sSql)
isTableOk = (Err.Number = 0)
rs.Close
End Function
You can even check table in an external Access db with this version:
Function isTableOk(tblName As String, Optional dbName As String) As Boolean
'works with local or linked tables, or even tables in external db (if dbName is provided)
Dim db As DAO.Database, rs As DAO.Recordset
Dim sSql As String
Set db = CurrentDb
sSql = "SELECT TOP 1 'xxx' AS Expr1 FROM [" & tblName & "]"
If Len(dbName) > 0 Then 'external db
sSql = sSql & " IN '" & dbName & "'"
End If
Err.Clear
On Error Resume Next
Set rs = db.OpenRecordset(sSql)
isTableOk = (Err.Number = 0)
rs.Close
End Function