Microsoft Access find field in table with known value - sql

We have a fairly large Oracle database that we are able to connect via Microsoft Access and ODBC with read-only access. We work with a front-end system that does not match the structure behind the scenes and often I need to query the system via Microsoft Access. The problem is that we are not provided any documentation as to structure and the structure needs serious attention. Searching for the field that I need is very time consuming.
With our front end, I'm able to view the values that I want to query, and I know the key fields, but I need to find the field that contains the known value.
If I have a record where I know the value of field "A", and have the value of field "X", is it possible to query field "X"?
Front end shows
Student ID: 12345678
Payments: 23456
Back end
TechID: 12345678
???: 23456
Can I query "???"

You can do this by iterating over the collection of tables, and for each table, the collection of fields.
Open Database
Get all Tables
For Each Table
Get all Fields
For Each Field
If Field type is text ... and
If Field size is not TOO Long ...
Search for string
If found, write to a results bucket
Next
Next
Here is an example of code for cataloging tables (source here)
Public Function GenerateDataDictionary(aDataDictionaryTable As String)
'*** Usage: GenerateDataDictionary("MyDataDictionaryTable")
'*** Extracts the information about the tables for the data dictionary
'*** and inserts it to a table named aDataDictionaryTable
Dim tdf As TableDef, fldCur As Field, colTdf As TableDefs
Dim rstDatadict As Recordset
Dim i As Integer, j As Integer, k As Integer
Set rstDatadict = CurrentDb.OpenRecordset(aDataDictionaryTable)
Set colTdf = CurrentDb.TableDefs
'Go through the database and get a tablename
For Each tdf In CurrentDb.TableDefs
'Do what you want with the table names here.
rstDatadict.AddNew
rstDatadict.Update
rstDatadict.AddNew
rstDatadict![Table] = tdf.NAME
rstDatadict![Field] = "----------------------------"
rstDatadict![Display] = "----------------------------"
rstDatadict![Type] = ""
rstDatadict.Update
rstDatadict.AddNew
rstDatadict![Table] = "Table Description:"
For j = 0 To tdf.Properties.Count - 1
If tdf.Properties(j).NAME = "Description" Then
rstDatadict![Field] = tdf.Properties(j).Value
End If
Next j
rstDatadict.Update
rstDatadict.AddNew
rstDatadict.Update
For i = 0 To tdf.Fields.Count - 1
Set fldCur = tdf.Fields(i)
rstDatadict.AddNew
rstDatadict![Table] = tdf.NAME
rstDatadict![Field] = fldCur.NAME
rstDatadict![Size] = fldCur.Size
Select Case fldCur.Type
Case 1
FieldDataType = "Yes/No"
Case 4
FieldDataType = "Number"
Case 8
FieldDataType = "Date"
Case 10
FieldDataType = "String"
Case 11
FieldDataType = "OLE Object"
Case 12
FieldDataType = "Memo"
Case Else ' Other values.
FieldDataType = fldCur.Type
End Select
rstDatadict![Type] = FieldDataType
For j = 0 To tdf.Fields(i).Properties.Count - 1
If fldCur.Properties(j).NAME = "Description" Then
rstDatadict![DESCRIPTION] = fldCur.Properties(j).Value
End If
If fldCur.Properties(j).NAME = "Caption" Then
rstDatadict![Display] = fldCur.Properties(j).Value
End If
If fldCur.Properties(j).NAME = "Rowsource" Then
rstDatadict![LookupSQL] = fldCur.Properties(j).Value
End If
Next j
rstDatadict.Update
Next i
Debug.Print " " & tdf.NAME
Next tdf
End Function
You can catalog your findings in Access by making a table of field-names which joins to a table of table-names. Then your searches are based on the catalog instead of raw collections.
I reverse-engineered the schema for MAS 90 (with JobOps add-in) this way. There's no map, but I had a read-only ODBC connection which I used in precisely the way you propose. The purchasing accountant would give me a distinctive Product Number and I'd run it through this comprehensive engine. Over time I succeeded in distilling 700 tables comprising 18k fields down to 20 tables and a few hundred fields. That allowed us to export our data.

The answer to your question is simple. No, you cannot do that.
There are two solutions that I can think of. The first is to manually concatenate all the values together and then look for the row that contains the value. This is imperfect, but might work:
select *
from (select t.*, ('|'""col1||'|'||col2+'|' . . .||'|') as allcols
from t
) t
where instr('|23456|', allcols) > 0
This would find any row that has that value in a column. Probably close enough for what you want.
The second is to use UNPIVOT to do essentially the same thing.
I would strongly suggest that you invest a little bit of time to find the mapping between the fields, and then create a view in Oracle that has the field names as seen in the application. It sounds like this would save you a lot of effort in the medium term.

Related

Most efficient way to query 1 of 4 large Access tables based on condition (using VBA module)

For the past couple weeks I've been working on a very unconventional solution to a problem for my job. I'm almost there, but I need to know the most efficient way to do the last step. I will dumb it down so I don't have to write an essay describing the insane nature of the problem I've been working on.
I have four large local tables in MS Access with a total of over 500,000 records.
Each table represents a different type of product.
The productID for table1 always starts with "9"
The productID for table2 always starts with "8"
The productID for table3 always starts with "4"
The productID for table4 always starts with "3"
I have a vba procedure written that does exactly what I need it to do except I have it querying information using only table1 thus far. Basically, a user inputs a productID and the procedure searches the table for that record and sends the information to a sharepoint list. Speed of execution is HIGHLY important in my situation. So, what is the fastest way to have it run? Should I write a statement that says "If the ID starts with 9 then search this table, ElseIF ..... and so on." Or, should I combine all the tables into one and not have it look at the first digit of the ID?
I know this sounds like a simple issue but trust me, this is a wild over simplification of the real issue and it would take 2,000 words to explain how ridiculous it actually is; I'm not kidding. However, I am fairly confident that the answer to the above question will give me all the information I need to finish this project successfully. I have come so far and all I have left is to figure out the most efficient way to apply it to the 3 other tables.
Thanks!
As the tables are local, use the Seek method which is extremely fast:
Recordset.Seek method (DAO)
If the tables were linked, you could still using Seek by opening the backend database. This is an example:
Function SeekTable()
Const cstrTable As String = "tblValue01"
Const cstrAttached As String = ";DATABASE="
Dim wks As Workspace
Dim dbs As Database
Dim tdf As TableDef
Dim rst As Recordset
Dim strConnect As String
Dim strTablename As String
Set wks = DBEngine(0)
Set dbs = wks(0)
Set tdf = dbs.TableDefs(cstrTable)
strConnect = tdf.Connect
strTablename = tdf.SourceTableName
Set tdf = Nothing
If InStr(1, strConnect, cstrAttached, vbBinaryCompare) = 1 Then
strConnect = Mid(strConnect, Len(cstrAttached) + 1)
' Open database shared and read-only.
Set dbs = wks.OpenDatabase(strConnect, False, True)
Set rst = dbs.OpenRecordset(strTablename)
'
' Perform Seek operation. Example.
rst.Index = "ID"
rst.Seek "=", 10010
Debug.Print rst!Value
'
rst.Close
Set rst = Nothing
End If
dbs.Close
Set dbs = Nothing
Set wks = Nothing
End Function

Setting listboxes in MS access form - row by row

I have a requirement in MS Access where a table is displayed as several rows in the form. I have created one form detail record(several fields) that will repeat for each row in the Table1. Lets say I have five columns in the Table1. Based on Column3 value, I would like to have a list of value for Column4 and Column5 during form_load. I have also created a separate Table2 to establish relationship between Column3, Column4 and Column5. I have set up Event procedure to populate the values using sub function. The challenge I have is, not being able to set up different listbox 'value list' for different rows. Any tips on populationg form fields IMRecomExIns and AmendReasonExIns by processing each row in Table1 would be a great help.
Private Sub IMRecomExIns_Click()
Dim CoverType As String
Dim ListRecomm As String
Dim ListAmend As String
Dim db As DAO.Database
Dim tablevar As Recordset
Set db = CurrentDb
Set tablevar = db.OpenRecordset("Table2")
CoverType = "*" & Me.CoverTypeExIns.Value & "*"
ListRecomm = ""
ListAmend = ""
If tablevar.EOF = False And tablevar.BOF = False Then
tablevar.MoveFirst
Do Until tablevar.EOF
If tablevar!CoverType Like CoverType Then
ListRecomm = tablevar!Recommendation
ListAmend = tablevar!AmendReason
tablevar.MoveLast
End If
tablevar.MoveNext
Loop
End If
Me.IMRecomExIns.RowSourceType = "Value list"
Me.IMRecomExIns.RowSource = ListRecomm
Me.AmendReasonExIns.RowSourceType = "Value list"
Me.AmendReasonExIns.RowSource = ListAmend
End Sub
1) I have stored all the value list in a single cell. For example tablevar!Recommendation will have all the values for Me.IMRecomExIns.RowSource, which means the output is will look like "Rec1";"Rec2";"Rec3";etc... Same applies for tablevar!AmendReason "AR1";"AR2';"AR3";ETC... Understand this is not the normalized form of storing data. I want to POC to work before building a full solution with normalized tables.
2) Answered earlier.. the rowsource will be set with all the possible values at the first match, so no point in going all the way to the end of the table
3) CoverTypeExIns is a string, Table 2 have many different possibilities such as "Mortgage Income" and "Family Income", however the Recommendation and Amendreason are same for all "Income" category that comes from Table1. Thats why the wildcard search.
My problem is not with setting the RowSource for a single row, but setting up RowSource for multiple occurrence in of the same IMRecommmendation and AmendReason in MS Access screen.
Here is the design view of the form. This form is linked to MS Access table. For multiple rows the Detail record will repeat itself as many times.
An example of two rows displayed in the screen.
I'm not sure exactly what you are asking/trying to do here.
I can see at several problems with the code that you have:
You are using tablevar.MoveLast in the loop, whic would automatically take you to the end of the recordset.
Also, you are not concatenating (joining together) ListRecomm/ListAmend, you are just setting them equal to a value, so each loop that matches will overwrite any previous value.
Finally, I am not sure what you are doing with trying to find CoverTypeExIns - you are using LIKE, which would indicate that it is text, but not wrapping the value in single quotes. If it is a numeric value, then you should be using "=".
However, rather than opening a recordset, looping it and checking for a match to build up a list of values, it is better to just set the RowSource of listboxes equal to a SQL string (effectively a query).
Something like (assuming CoverType is numeric):
Private Sub IMRecomExIns_Click()
Dim strSQL As String
strSQL = "SELECT Recommendation FROM Table2 WHERE CoverType=" & Me!CoverTypeExIns
Me!AmendReasonExIns.RowSource = strSQL
End Sub
I prefer to declare a string to hold the SQL statement rather than setting the .RowSource directly, as it makes troubleshooting easier.
Regards,
Based on the new information given, below is some VBA code that opens up a recordset based on the information entered in "Cover", and then sets the .RowSource property of the two combo boxes to be the value lists. In my example, don't bother setting the .RowSourceType, as this should be done at design time:
Private Sub IMRecomExIns_Click()
Dim db As DAO.Database
Dim rsData As DAO.Recordset
Dim strSQL As String
Set db = DBEngine(0)(0)
strSQL = "SELECT Recommendation, AmendReason FROM Table2 WHERE CoverType LIKE '*" & Me!cboCover & "*';"
Set rsData = db.OpenRecordset(strSQL)
If Not (rsData.BOF And rsData.EOF) Then
Me!IMRecomExIns.RowSource = rsData!Recommendation
Me!AmendReasonExIns.RowSource = rsData!AmendReason
End If
rsData.Close
Set rsData = Nothing
Set db = Nothing
End Sub
As I have previously stated, you should really normalize the design of your database now, rather than getting in so far that it requires a major re-write.
Regards,

loop through records and add record or create if none exists to new table depending on multiple criteria

I'm new to access/vba and trying to set up a project database. I have a table ("Updates") that is generated when changes are made to certain fields on a form (used for project updates by the end user). It has the primary key UpdateID, foreign key ProjectID as well as UTimeStamp, OldValue, NewValue, History. I use the history key to identify which type of update was made (for example for Status, History=1). I want to then count the number of projects for each status at the end of each month, keeping historical data to allow users to track the changes from month to month (or even compare data from months apart). I'm trying to write a code (in VBA for access) that would take into account that there are sometimes multiple status updates in each month and I don't want them to get counted twice, also some months no updates are made but I still want them included in the count (using the last updated status before that month as the status).
I was thinking of using a combination of looping through the records and checking to see if a value exists for that specific ProjectID and month and inserting the last value (most recent) into a new table "StatusTracking" and if no record exists then using the INSERT INTO function to add a new record. "StatusTracking" will have the fields ID, ValueMonth, ValueYear, (since ideally I want to track over the course of more than a year) Status, ProjectID. However, I am very new to this and am having trouble getting started as I'm not sure the best way to loop through both the months and ProjectID.
Public Function getStatus()
Dim varMonth As Integer
Dim ReportStatus As String
Dim RS As DAO.Recordset
Dim db As DAO.Database
Dim sqlStr As String
sqlStr = "SELECT Updates.ProjectID, Format(Month([UTimeStamp])) AS UpdateMonth, ProjectList.Status, Updates.NewValue, Updates.UTimeStamp" & _
"FROM Updates RIGHT JOIN ProjectList ON Updates.ProjectID = ProjectList.ProjectID" & _
"WHERE (((Updates.History) = 1))" & _
"GROUP BY Updates.ProjectID, Format(Month([UTimeStamp])), ProjectList.Status, Updates.NewValue, Updates.UTimeStamp"
Set db = CurrentDb
Set RS = db.OpenRecordset(sqlStr)
With RS
.MoveLast
.MoveFirst
While (Not .EOF)
'Cycle through each month
For varMonth = 1 To 12 Step 1
ReportStatus = DLast("NewValue", RS, "UpdateMonth = " & varMonth)
RS.Fields ("Status") <> RS.Fields("NewValue")
End Function
Any help is appreciated!
DCount is useful for counting unique occurrences in a specified field. The link I provided should put you on the right track there. Note that that only returns the count itself, and not the records. If you need to populate a recordset, you can use SELECT DISTINCT in your query to return only the records that have, well, distinct values in your criteria.
I recently worked on a project that involved building a history table for tracking purposes. Since it was based around forms, I opted for using .addnew and .update rather than INSERT INTO. Of course, use what's best for the situation at hand; I used .addnew and .update for the main reason that I had a lot of controls in my form and it was simpler in my mind to do it that way. There's lots of ways to do it, this worked best for me. I've also provided a snippet of the code I wrote for that project as another example.
Hope this helps!
'Example
'Assuming recordset and database variables are already declared
'rec = recordset, db = currentdb
set rec = db.openrecordset(<source table, name of existing query, or SQL query>)
if <condition is met> then
'populate table with values in form controls
rec.addnew
rec("Destination Table Field") = Me.Controls("Name of Form Control").Value
.
.
.
rec.update
set rec = nothing
set db = nothing
'clearing rec and db after done using
end if
Code from my project:
Set db = CurrentDb
Set rec = db.OpenRecordset("Select * from tbl_maintenanceOrders")
Set recHist = db.OpenRecordset("Select * from tbl_umoHistory")
msgConfirm = MsgBox("Correct values confirmed?", vbYesNo, "Continue")
'enter record in maintenance order table
If msgConfirm = vbYes Then
rec.AddNew
recHist.AddNew
rec("openTimestamp") = Now()
rec("openedBy") = Me.Controls("cbo_originator").Value
rec("assetID") = Me.Controls("cbo_asset").Value
rec("assetDesc") = Me.Controls("txt_assetDesc").Value
rec("priority") = Me.Controls("cbo_priority").Value
rec("umoProblemDesc") = Me.Controls("txt_issueDesc").Value
rec("umoSpecifics") = Me.Controls("txt_issueDetails").Value
rec("umoState") = "open"
rec("umoStatus") = "new"
rec.Update
'add UMO history entry
recHist("umoID") = rec("orderID")
recHist("activity") = "opened"
recHist("umoState") = "open"
recHist("umoStatus") = "new"
recHist("activityDesc") = "UMO Requested"
recHist("initiatorID") = Me.Controls("cbo_originator").Value
recHist("timeStamp") = Now()
recHist("updater") = Me.Controls("cbo_originator").Value
recHist.Update
End If
'cleanup
Set rec = Nothing
Set recHist = Nothing
Set db = Nothing

Problems with field names and appending files in Access SQL

Okay, so I have nearly 200 tables in an Access database. The tables are of plant species abundance data, and I would like to combine them into a master data file. Each table contains basically the same columns of species; however, many are spelled slightly differently.
When I run an SQL query in MS Access it won't let me append the tables with each other because of the field names being spelled just a little different.
Any thoughts that would help?
The query I am running is an append query:
INSERT INTO masterTable SELECT * FROM siteTable
and, as an example, the differences in field names are pretty minor
(e.g. "Spp.A" vs "SppA" or "SpeciesOne" vs "Species1")
Thanks for any help,
Paul
You'll need to use vba for this, you'll also need to change the column names I'm using in the masterTable, which in my example are just column1, column2 & column3, and to set the maximum column index in a couple of places (I've stuck some comments in, so you can see what needs to be changed).
If you dont usually use vba, Create a form with a button, and a click event for the button & put this code in it, then open the form and click the button.
Dim db As Database
Dim tdf As TableDef
Dim ii As Long
dim sql as String
Set db = CurrentDb()
docmd.setwarnings false
For Each tdf In db.TableDefs
'change column list as required:
sql = "INSERT INTO masterTable (Column1, Column2, Column3) SELECT "
'change 2 to maximum column number - 1:
for ii = 0 to 2
sql = sql & tdf.Fields(ii).Name
'change 2 to maximum column number - 1 again:
if ii < 2 then
sql = sql & ","
end if
next
sql = sql & ")"
docmd.runsql sql
Next
docmd.setwarnings true
This should work I think. (I'm hoping there's no syntax errors, as I havent tested it, but the logic isnt exactly rocket science)
Hope this helps

Query creating an identifier for each packet

Sorry the title is not very descriptive but it is a tricky problem to word.
I have some data, about 200 or more rows of it, and each row has a PacketID, so several rows belong in the same packet. What I need to do, is convert all the PacketIDs from (Example - BDFD-2) to just a number (Example - 1) so all the entries with a packet identifier x need to have a packet identifier of say 3. Is there an SQL query that can do this? Or do I just have to go through manually.
You asked about a query. I wrote a quick VBA procedure instead just because it was so easy. But I'm unsure whether it is appropriate for your situation.
I created tblPackets with a numeric column for new_PacketID. I hoped that will make it clearer to see what's going on. If you truly need to replace PacketID with the new number, you can alter the procedure to store CStr(lngPacketID) to that text field. So this is the sample data I started with:
PacketID new_PacketID packet_data
BDFD-2 a
R2D2-22 aa
BDFD-2 b
R2D2-22 bb
EMC2-0 aaa
EMC2-0 bbb
And this is the table after running the procedure.
PacketID new_PacketID packet_data
BDFD-2 1 a
R2D2-22 3 aa
BDFD-2 1 b
R2D2-22 3 bb
EMC2-0 2 aaa
EMC2-0 2 bbb
And the code ...
Public Sub RenumberPacketIDs()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim lngPacketID As Long
Dim strLastPacketID As String
Dim strSql As String
strSql = "SELECT PacketID, new_PacketID" & vbCrLf & _
"FROM tblPackets" & vbCrLf & _
"ORDER BY PacketID;"
Set db = CurrentDb
Set rs = db.OpenRecordset(strSql)
With rs
Do While Not .EOF
If !PacketID <> strLastPacketID Then
lngPacketID = lngPacketID + 1
strLastPacketID = !PacketID
End If
.Edit
!new_PacketID = lngPacketID
.Update
.MoveNext
Loop
.Close
End With
Set rs = Nothing
Set db = Nothing
End Sub
I think an approach like that could be fine for a one-time conversion. However if this is an operation you need to perform repeatedly, it could be more complicated ... especially if you need each PacketID replaced with the same number from one run to the next ... eg. BDFD-2 was replaced by 1 the first time, so must be replaced by 1 every time you run the procedure.
If you only have a few packet IDs, you can just use update:
UPDATE table_name
SET PacketID =
(
CASE PacketID
WHEN 'BDFD-2' THEN 3
WHEN 'ABCD-1' THEN 5
ELSE 2
END
)
The ELSE is optional.
I am not sure why you even want to convert the packet ids to a number, they seem perfectly fine as they are. You could create a table of packets as follows
SELECT DISTINCT TableOfRows.Packet_id AS PacketId INTO Packets FROM TableOfRows;
You can then use this to select the packet you are interested in and display the corresponding rows