Is there a way to guarantee row order in a table derived from a linked sheet? I seem to occasionally lose row order (which unfortunately has meaning) when I select from a Linked Sheet while looping over a number of sheets & WB.
DoCmd.TransferSpreadsheet acLink, , "linked_sheet ", sfile, False, "some_sheet!A1:U100"
CurrentDb().Execute "select '" & identifier & "' as id1, " & identifier2 & " as id2, i.* into temp_table from linked_sheet i"
CurrentDb().Execute "alter temp_table add column ky counter primary key"
The keys are correctly ordinal, but the rows are not the same as the original spreadsheet.
The problem is intermittent and does not occur on the same Sheet/WB every time.
If I catch the error, then go back and rerun the same code, it will return correct original row order as it appears in the sheet.
The rows are not missing, they are moved to a different location in temp_table.
I'm holding the WB in memory while querying the linked_sheet could this be causing the problem?
To wax philosophically, you should not care about the order of rows inserted into the target table. That is at the db engine's discretion. The only time you should be concerned is upon presentation, when you select from the temp table. This is the most likely reason that "order by" is not supported by this syntax.
May I inquire why it is an issue?
Other databases use a syntax "insert into x from (select * from y order by z)", where the "order by" controls the order in which the rows are presented to the insert statement. But it still remains that the order of data in the db should not be relevant.
Related
First of all, I'm very new with coding, so please bear with me, secondly;
I am currently trying to draw records from another database inside a network into my database. This has proved to be a little troublesome, because I would want to fill all fields in all tables that have the same primary key.
My current code looks like this
varPrimaryKey = InputBox("Specify primary key:")
strPrimaryKey = module1.Item1
If Not varPrimaryKey = "" Then
MsgBox ("Get data from: " & varPrimaryKey)
If Not (tdf.Name Like "MSys*" Or tdf.Name Like "~*") Then
For Each tdf In externalDb.TableDefs
For Each fld In tdf.Fields
db.Execute ("INSERT INTO CurrentDb.fld.Name SELECT fld.Name FROM tdf.Name WHERE fld.Name = 'Value' AND varPrimaryKey = 'Value'")
Next fld
Next tdf
End If
End If
Right now I'm getting a type mismatch error from .CurrentDb , but I have a feeling that there's something else wrong with this code too, just can't put my finger on it. If someone could help, I would really appreciate it!
Reconsider this entire approach. For user application needs, databases should not be copied versions of each other as gathered from above comment:
I meant that I would want to draw ALL records from ALL tables that
have the same primary key ID as the one that the user has entered
inside an input box
One of the central tenets of a relational database is to avoid duplication of data. Plus, if users add/edit data it will reflect live on the system. Data transference between databases should only be run for migration purposes where one is to be archived and other for production.
With that said, consider the following steps to build your user application:
Link all needed tables from network database. The GUI dialog allows a Select All feature or holding SHIFT and/or CTRL for multiple tables.
Build a temp table to hold current user's primary key and insert data value via VBA. This is the only table to hold data (one column/one row structure) in application database. See below steps:
Create a table with code or via Table Design (one row/one column)
CREATE TABLE TmpPrimaryKey (
PrimaryID Long
)
Append using user input as below shows with parameterization with VBA. This code should be run regularly for any change to PK. Ideally, run this at startup of application.
Dim qdef As QueryDef
Dim strSQL As String
Dim varPrimaryKey As Variant
varPrimaryKey = InputBox("Specify primary key:")
' CLEAN OUT TEMP TABLE
CurrentDb.Execute "DELETE FROM TmpPrimaryKey", dbFailOnError
' APPEND TO TEMP TABLE
strSQL = "PARAMETERS [PkParam] LONG;" _
& " INSERT INTO TmpPrimaryKey (PrimaryID) VALUES ([PkParam])"
Set qdef = CurrentDb.CreateQueryDef("", strSQL)
qdef![PkParam] = varPrimaryKey
qdef.Execute dbFailOnError
Set qdef = Nothing
Create a filtered query for every linked table aligning to TmpPrimaryKey (i.e., the target primary key). Doing so, the user will only see such records. Use these queries for forms/reports recordsources or module recordsets.
Queries can use the JOIN, IN, EXISTS clauses such as below examples of separate SQL statements. Tables below would be linked tables from network database (step #1).
Query1
SELECT src.*
FROM [Table1] src
INNER JOIN TmpPrimaryKey tmp ON src.ID = tmp.PrimaryID;
Query2
SELECT src.*
FROM [Table2] src
WHERE src.ID IN (SELECT PrimaryID FROM TmpPrimaryKey);
Query3
SELECT src.*
FROM [Table3] src
WHERE EXISTS
(SELECT 1 FROM TmpPrimaryKey tmp WHERE src.ID = tmp.PrimaryID);
Because you potentially have many tables, build above SQL queries in VBA loop using TableDefs and QueryDefs. NOTE: Below loop routine to create queries should be run only once.
Dim tdef As TableDef
Dim qdef As QueryDef
Dim strSQL As String
' LOOP THROUGH ALL TABLES OF APP DB (I.E., LINKED TABLES)
For Each tdef in CurrentDb.TableDefs
If tdef.Name <> "TmpPrimaryKey" And tdef.Name Not Like "MSys*" Then
' ASSUMING EACH TABLE'S PK IS NAMED ID
strSQL = "SELECT src.* FROM [" & tdef.Name & "] src" _
& " INNER JOIN TmpPrimaryKey tmp ON src.ID = tmp.PrimaryID;"
' NAME EACH QUERY SAME AS TABLE WITH "Q_PK" SUFFIX
Set qdef = CurrentDb.CreateQueryDef(tdef.Name & "Q_PK", strSQL)
Set qdef = Nothing
End If
Next tdef
Set tdef = Nothing
Again, set all needed application objects (forms, reports, modules, etc.) to point to these queries as the data sources and not linked tables, holding all data. Queries should be updateable for user to add/edit records.
From there, distribute copies of this application database to all users in a frontend/backend split architecture, maintaining one centralized and normalized database and many app files that hold no data (except of course, the temp PK value).
You just reference the table you want to INSERT into. What you need is path to the other db. Need to concatenate variables. You don't show declaring and setting variables db, tdf, fld, externalDB. Need to swap the If and first For lines. Your INSERT SELECT will not work the way you think, unless you really want each field inserted into its own record. Consider:
For Each tdf In CurrentDb.TableDefs
If Not (tdf.Name Like "MSys*" Or tdf.Name Like "~*") Then
CurrentDb.Execute ("INSERT INTO [" & tdf.Name & "] SELECT * FROM [" & tdf.Name & _
"] IN 'other db path\filename.accdb' WHERE [Value] = '" & varPrimaryKey & "'")
End If
Next tdf
However, autonumber fields will interfere with this simple INSERT SELECT. Also, fields in both tables must be arranged in same order in table design.
Value is a reserved word and really should avoid using reserved words as names for anything. If Value is a number data type then eliminate the apostrophe delimiters.
Why have that MsgBox?
But then why all this effort anyway and not just link to the backend tables?
My append query fails due to key violation but only one new records, the old records append normally just the new ones that I added fail.
DoCmd.RunSQL "INSERT INTO [Order_Item] ([Order_ID],[Item_ID],[Quantity]) Values (" & orderNumber & ", [Enter Item ID], [Enter Quantity])"
Order_ID and Item_ID are foreign keys
Access will raise an error re. key violation when you insert something into the FK via sql that isn't in the related table. You may also get a similar error for primary key violations.
Here is some code (which you'd need to adapt to your model) to check the keys exists in the related tables and that the 'mapping' does not already exist in the mapping table before you insert.
Select Case True
Case IsNull(DLookup("[ID]", "Table1", "[ID]=" & num1))
MsgBox ("ID does not exist in Table1")
Case IsNull(DLookup("[ID]", "Table2", "[ID]=" & num2))
MsgBox ("ID does not exist in Table2")
Case Not (IsNull(DLookup("[ID1]", "Map", "[ID1]=" & num1 & "And [ID2]=" & num2)))
MsgBox ("Mapping already exists")
Case Else
DoCmd.RunSQL "INSERT INTO [Map] VALUES (" & num1 & ", " & num2 & ")"
End Select
I just resolved a similar issue that was caused by a key violation issue. Two lessons that I learned might be helpful to others with similar issues:
make sure that your relationships diagram shows every table - I didn't (and the non-visible table was linked to a visible one, and that was the problem link). It's too easy to forget the links you've set up!
if you're not sure which field in the query is driving the key violation error, type a valid value into a blank field on the table - if it's the field causing the problem it'll tell you which field needs a related entry.
Of course be extra vigilant if this is a "live" system and remove any "test" data!
I have tried this with ALTER TABLE to create the column followed by INSERT INTO. This kind of works, except each subsequent column starts after the previous column has ended. I guess this is how insert into works, so is there a workaround or another query I can build?
I have been trying with updates but its not working out.
For reference, these were the alter/insert queries i used.
SQL = "ALTER TABLE [results] ADD COLUMN [" & fld.Name & "_result] TEXT(25)"
db.Execute SQL
SQL = "INSERT INTO [results] ([" & fld.Name & "_result]) SELECT [Result] As
[" & fld.Name & "_result] FROM [newtable]"
db.Execute SQL
Your insert statement assumes that the results table has only one column that you need to insert data into. This is unlikely to be true, if the table already had other columns before you executed the ADD COLUMN.
You will need to keep track of the columns in the results table, and provide data (or a default value) for each column.
It is rather unusual to expand a table's structure from inside an application. What are you trying to accomplish? Are you sure you can't accomplish it better by defining fixed tables and then adding data from your application?
UPDATE
Okay, I think I understand what you're describing. On the first iteration, the ALTER TABLE creates the first column. The INSERT adds a bunch of rows that have data in this first column.
On the second interation, the ALTER TABLE creates a second column. The INSERT creates a whole bunch of new rows, but only the second column is populated. The first column is all NULL because you didn't provide values for it. And so on and so forth for the third and subsequent iterations.
If your actual intention is to duplicate the source table and its data, then you should create your results table in a single pass. You know the column structure, right? Use a CREATE TABLE statement. Then write a single INSERT statement somewhat like the following:
INSERT INTO [results]
([field1_result], [field2_result], [field3_result])
SELECT [Result] As
[field1_result, [field2_result], [field3_result]]
FROM [newtable]
Is this what you have in mind?
Before you enter into the loop create your [results] table as
SQL = "CREATE TABLE [results] SELECT [primary_key] FROM [newtable]"
db.Execute SQL
Then at every iteration of the loop execute
SQL = "ALTER TABLE [results] ADD COLUMN [" & fld.Name & "_result] TEXT(25)"
db.Execute SQL
SQL = "UPDATE [results] SET r.[" & fld.Name & "_result] = n.[Result] " &
"FROM [results] r, [newtable] n " &
"WHERE r.[primary_key] = n.[primary_key]"
db.Execute SQL
So, if you had your [newtable] at its first two iterations like
[primary_key] [Results] [primary_key] [Results]
1 A 1 D
2 B 2 E
3 C 3 F
Your [results] table (after the above two iterations) would look like
[primary_key] [fld1_result] [fld2_result]
1 A D
2 B E
3 C F
Each day ~5000 records are uploaded to tblRecordsCurrent, at some point within the next few days when those records have been processed, they need to be moved to tblRecordsHistorical. Each record has a Foreign Key DataSetID that ties it to the date/time it was uploaded (Parent Table).
How, within vba, can I insert a single DataSet of tblRecordsCurrent into the tblRecordsHistorical from tblRecordsCurrent. I cannot insert all columns as both tables contain persisted columns.
I can't put the entire INSERT INTO tblRecordsHistorical A, B, C, D, E, F... as it is too long for access vba.
Any ideas?
If you save the query in your Access database, then you can execute in in VBA the following way:
DoCmd.OpenQuery "yourQueryName", acViewNormal, acEdit
Or
CurrentDb.OpenRecordset("yourQueryName")
Or
CurrentDb.Execute "qryAddLoginfoRow"
This allows you to execute the query without having the query stored in your VBA code. But you are also able to execute this via a query in VBA:
INSERT INTO tblRecordsHistorical (col1, col2...)
SELECT col1, col2...
FROM tblRecordsCurrent
EDIT:
You can create a long SQL string by concatenating the string together:
SQLString = "INSERT INTO tblRecordsHistorical (col1, col2...) " & _
" SELECT ... " & _
" FROM tblRecordsCurrent "
I want to duplicate a row, not the keys of course, without explicity using the field names.
Is there a SQL way or do I have to enumerate the field names through code?
I don't want to explicity use field names because I want to minimize code and db dependencies.
I am going to use it in the Ms Access 2003. I mention it in case that no standard way exists.
INSERT INTO `<table>` (column1, column2, ...) -- Not IDENTITY columns
SELECT column1, column2, ... FROM ...
This will also allow you to insert replacement values for the primary key columns, etc. I've used this, along with a common table expression, to take a set of test data from the month of February, and to pretend they're really from June.
I know you said you want to do it without the field names, but I don't think you can. It's also not a good idea, as it would tie you to the order of the columns.
If you don't have any uniques to worry about:
INSERT INTO <table> (SELECT * FROM <table> WHERE <condition>)
Otherwise, John Saunders' answer is probably your best bet.
If your primary key fields have automatic identifiers then you might well be able to script to interogate the system for fields which are not in the PK, and use the existing values for those that are not and only insert those ones (or to insert null for the PK fields).
Consequently I don't think there is going to be a "standard" way.
I'm not an Access person, but in SQL Server you can choose "Script table as --> Insert into" in SQL Server Management Studio. You can easily modify this to filter the rows you want into an INSERT INTO SELECT statement.
Perhaps something like this exists in Access?
Like folks have stated before me, you can do "INSERT INTO TBL SELECT * FROM TBL WHERE X=Y" and you will get one row. And this will fail if you have a primary key.
If you do not have a PK then you probably have bigger problems.
Is this a linked table? If so, there are no database dependencies, because you are dealing with an ODBC link. In that case, you can easily use this to get a list of columns for the table:
SELECT TOP 0 * FROM TBL (on linked tbl will need a round trip to server)
You get a blank recordset, and you just iterate through the columns.
Ms Access 2003 oriented solution
I have a form where the user can press a button to create a new version of the current record.
That part in Ms Access is easy:
DoCmd.GoToRecord , , acNewRec
Now I need to update all the fields on the form (controls are bind with table fields) except the key, ie "id" field, with data from some other record.
I came up with the below routine, which worked good for me:
Private Sub UpdateRow(tblname As String, key_name As String, key_value As String)
Dim Rst As Recordset
Dim field As field
Set DB = CurrentDb
Set Rst = DB.OpenRecordset("select * from " & tblname & " where " & _
key_name & "=" & key_value, dbOpenDynaset)
For Each field In Rst.Fields
If field.Name <> key_name Then
Form(field.Name) = field
End If
Next field
Rst.Close
Set Rst = Nothing
Set DB = Nothing
End Sub
And I use it like this:
DoCmd.GoToRecord , , acNewRec
UpdateRow "TableName", "KeyName", "some_previous_key_value"
Form.Refresh
You would need to explicitly supply the field names for the keys when you supply replacement values, therefore a 'standard' way is simply not possible.
...unless all you tables have a single key, all with the same name (ID is popular), and each key consists of a single column that has the IDENTITY (autonumber) property, in which case you would in fact have no keys at all, merely a way of using the IDENTITY value to uniquely identify your duplicate rows!