First a few general points:
- I would not recommend that the PIC column be the primary key of Items. It is a candidate key, and as such must be uniquely indexed, but a 'surrogate' autonumber ItemID primary key would be more efficient
2. Do not use the generic ID as a column name. I know you'll find it done in some of the Access templates, it is not good practice due to the ambiguity which it creates. Always use more specifically descriptive column names which describe the attribute represented
by the column, in this case UserID. Use the same name for the primary key of the referenced table and any corresponding foreign keys in referencing tables.
3. The use of upper case for column names will make any SQL statements confusing as the usual convention is to use upper case for SQL key words (Access defaults to this). For table and column names I always use CamelCase, or you can represent a space by an
underscore character.
What you have is a many-to-many relationship type between users and items. This is represented by a table which resolves it into two one-to-many relationship types, i.e. your Links table:
Links
….ItemID (FK)
….UserID (FK)
….Fav
This table is 'all key', i.e. its primary key is a composite one of the three columns. In addition to referencing Items it is referencing a Users table.
To return a result table of all of each user's favourite items is merely a matter of joining the tables, e.g.
SELECT Users.UserID, FirstName, LastName, Pic, Title
FROM (Users INNER JOIN Links ON Users.UserID= Links.UserID)
INNER JOIN Items ON Links.ItemID = Items.ItemID
ORDER BY LastName, FirstName, Title;
To return a result table of each user's favourite items ranked as 1 for instance would merely require the query to be restricted on the Fav column:
SELECT Users.UserID, FirstName, LastName, Pic, Title
FROM (Users INNER JOIN Links ON Users.UserID= Links.UserID)
INNER JOIN Items ON Links.ItemID = Items.ItemID
WHERE Fav = 1
ORDER BY LastName, FirstName, Title;
You can of course reference a parameter in place of the literal 1 value.
To return a result table of all of a specific user's favourite items the query would be restricted on the UserID column:
PARAMETERS Forms!YourFormName!cboUser LONG;
SELECT Users.UserID, FirstName, LastName, Pic, Title
FROM (Users INNER JOIN Links ON Users.UserID= Links.UserID)
INNER JOIN Items ON Links.ItemID = Items.ItemID
WHERE Users.UserID = Forms!YourFormName!cboUser
ORDER BY LastName, FirstName, Title;
Where cboUser is an unbound combo box set up as follows:
RowSource: SELECT UserID, FirstName & " " & LastName FROM Users ORDER BY LastName, FirstName;
BoundColumn: 1
ColumnCount: 2
ColumnWidths: 0cm
If your units of measurement are imperial rather than metric Access will automatically convert the unit of the last one to inches. The important thing is that the dimension is zero to hide the first column.
Alternatively, you can concatenate the names so that the last name is first, which would be better with a large list of names as it allows the user to enter the initial characters of the last name and progressively go to the first match as each character is
entered:
RowSource: SELECT UserID, LastName & ", " & FirstName FROM Users ORDER BY LastName, FirstName;
By binding a form to an amended version of the above query:
PARAMETERS Forms!YourFormName!cboUser LONG;
SELECT Pic, Title
FROM Links INNER JOIN Items ON Links.ItemID = Items.ItemID
WHERE Links.UserID = Forms!YourFormName!cboUser
ORDER BY Title;
And placing the cboUser control in the form header, you then merely need to requery the form in the control's AfterUpdate event procedure with:
Me.Requery
An alternative approach, and the one I'd recommend, would be to use a bound parent form, in single form view, based on Users and within it a bound subform, in continuous forms view, based on a query which joins Links and Items, the subform being linked to the
parent form on UserID. The subform would contain a combo box bound to ItemID from Links, set up to hide the bound column and show either the Pic or Title values, whichever you prefer to select an item by. Whichever you use the other can be shown in a bound
text box in the subform, with its Locked property set to True (Yes) and its Enabled property to False (No) to make the control read-only. A form/subform like this would be the obvious set-up for entering users and their favourites.
By placing the cboUser control in the form header you can then navigate to a user with code in its AfterUpdate event procedure:
Const MESSAGETEXT = "No matching record"
Dim ctrl As Control
Set ctrl = Me.ActiveControl
If Not IsNull(ctrl) Then
With Me.RecordsetClone
.FindFirst "UserID = " & ctrl
If Not .NoMatch Then
' go to record by synchronizing bookmarks
Me.Bookmark = .Bookmark
Else
MsgBox MESSAGETEXT, vbInformation, "Warning"
End If
End With
End If
This will take you to the selected user, whose favourites will be shown in the subform.
If you wished to filter such a form to those users who have selected a particular item as one of their favourites you would do as in my demo, but the string expression to filter the form would be as follows:
strFilter = "UserID IN(SELECT UserID " & _
"FROM links WHERE ItemID = " & ctrl & ")"
The control in this case would be an unbound combo box with a RowSource such as:
SELECT ItemID, Title FROM Items ORDER BY Title;
All tables should be on the server. Each user can enter their own favourites simply by navigating to their own record in the form. Separate copies of single front end can be used by each user, without the need to tailor each to the individual user. If you
do not wish users to see other users' records then the form should be based on a query which restricts its result table to the current user's record only. This can be done by giving each user a password, which is stored in a column in Users. In the form's
query this column can then reference as a parameter a control in an unbound log-in form in which the user enters their password. This form should be hidden, not closed, when the bound form is opened.
Be clear, however, that a set-up like this does not constitute 'security'; it merely controls the normal day-to-day operation of the database. Any half-competent user could easily access other users' records if they so wished. If you anything like real security
a lot more would be required, but even then Access is not a suitable product where true security is needed.