Selectively deleting duplicates from table based on field value in Access - sql

I have the following table (ID column exists but not shown below) :
Email
Course
DateComplete
1#1.com
Running
01/01/2021
1#1.com
Running
1#1.com
Running
2#2.com
Walking
2#2.com
Walking
2#2.com
Walking
I'd like to know if it is possible to delete all duplicate (of Email&Course) records from my table, but also ensuring that no records with a value in DateComplete are deleted.
So after running the query I would have :
Email
Course
DateComplete
1#1.com
Running
01/01/2021
2#2.com
Walking

You just need a query with an aggregation such as
SELECT Email, Course, MAX(DateComplete) AS DateComplete
INTO [dbo].[new_table]
FROM [dbo].[current_table]
GROUP BY Email, Course

Run a loop that deletes the dupes:
Public Function DeleteDupes()
Const Sql As String = "Select * From Course Order By Email, Course, DateComplete Desc"
Dim Records As DAO.Recordset
Dim Values As String
Set Records = CurrentDb.OpenRecordset(Sql)
While Not Records.EOF
If Values <> Records!Email.Value & Records!Course.Value Then
Values = Records!Email.Value & Records!Course.Value
Else
Records.Delete
End If
Records.MoveNext
Wend
Records.Close
End Function

Related

Access claiming duplicate record with no unique fields updated with Update Recordset/Addnew

I'm trying to allow my client to add a bunch of dates in a row with the few fields copied. None of these fields are unique in the table but it keeps flagging them as duplicates records. When I got into the the table the new records are not added to the table despite there not being any duplicates.
This is the code (edited for silly mistakes)
Public Sub Create_Click()
Dim rsEventDayGroup As DAO.Recordset
Dim NextDate As Date
Set rsEventDayGroup = CurrentDb.OpenRecordset("Select * From tblEventDays")
NextDate = Me!Start.Value
While DateDiff("d", NextDate, Me!End.Value) >= 0
rsEventDayGroup.AddNew
rsEventDayGroup!Start.Value = NextDate
rsEventDayGroup!EventID.Value = Forms!frmEvents.Controls!ID.Value
rsEventDayGroup!DayType.Value = Me!DayType.Value
rsEventDayGroup!Period.Value = Me!Period.Value
rsEventDayGroup.Update
NextDate = NextDate + 1
Wend
rsEventDayGroup.Close
Set rsEventDayGroup = Nothing
End Sub
I'm trying to copy fields EventID, DayType, and Period and create records for each day someone inputs. tblEventDays has it's own unique ID, called just "ID" that is an autonumber. I suspect it's the code screwing up somewhere, but as it was working just yesterday and I haven't changed it, I'm not sure what it could be. When I go to debug it highlights "rsEventDayGroup.Update" I've also tried changing Select * from the table with no luck.

How to return random records in a table based on query results?

I have a table named Values with thousands of records (sample is a lot less). The data itself is unique except that some records share the same ID.
So I need a query to return random records depending on the total count of TestQ1. For example, the query has a total of 9 records for ID 120 so there should be 3 random records each time this query is run because that's what the Test table indicates (this table changes the "Test" numbers weekly).
Pictured:
The "Values" table is the raw data.
The "TestQ1" query has a total count for that specific "ID"
and to the right, is the number of records that should be returned.
This is as far as I've been able to get:
SELECT TOP 5 Values.ID, Values.Test, Values.State, Rnd([Values]![ID]) AS [Random No], * FROM [Values] ORDER BY Rnd([Values]![ID]);
TOP N cannot be dynamic in a query object.
Can calculate a group sequence ID and use that in criteria to return a number of records for each ID group. That requires a unique identifier field which can be provided with an autonumber. Set it as random so the selection can be different as records are added to dataset.
Consider SQL:
SELECT Values.PKID, Values.ID, Values.Test, Values.State, DCount("*","Values","ID=" & [Values].[ID] & " AND PKID<" & [PKID])+1 AS GrpSeq
FROM TestQ2 INNER JOIN [Values] ON TestQ2.ID = Values.ID
WHERE (((DCount("*","Values","ID=" & [Values].[ID] & " AND PKID<" & [PKID])+1)<=[TestQ2].[Test]));
However, if you truly need to return randomized set of records with each query run, I expect VBA will be needed. Either to populate a field in Values table with a randomized sequence number for each ID group or to populate a temp table with randomized dataset. Consider VBA to populate field:
Sub RandomSeq()
Dim rs1 As DAO.Recordset, rs2 As DAO.Recordset, x As Integer
Set rs1 = CurrentDb.OpenRecordset("SELECT DISTINCT ID FROM [Values]")
Do While Not rs1.EOF
Set rs2 = CurrentDb.OpenRecordset("SELECT * FROM [Values] WHERE ID=" & rs1!ID & " ORDER BY Rnd([ID]);")
Do While Not rs2.EOF
rs2.Edit
x = x + 1
rs2!GrpSeq = x
rs2.Update
rs2.MoveNext
Loop
rs2.Close
x = 0
rs1.MoveNext
Loop
End Sub
Now run query:
SELECT Values.ID, Values.Test, Values.State, Values.GrpSeq
FROM TestQ2 INNER JOIN [Values] ON TestQ2.ID = Values.ID
WHERE (((Values.GrpSeq)<=[TestQ2].[Test]));
Be aware this will not be practical in a split database with multiple simultaneous users. Then a temp table (located in frontend) approach may be needed.

How to insert every Single date into table [duplicate]

hoping someone can help? I'm fairly new to Access 2016 and have been tasked with building a very simple booking system for our school's breakfast and after school clubs.
I have a table with a list of Children (primary key is ChildID), another table (CLUBS) lists the 5 clubs available, and a third table (BOOKINGS) connects the children to the clubs (ChildID, ClubID, DateRequested)
I have a simple form that enables me to select a child's name from a drop down box, then from a list choose a club, then enter the date required. This saves the record to the Bookings table.
This works fine, however to make it easier to use...I've added unbound Start and End Date fields in the form with a view to being able to quickly book a child in over a term..i.e. rather than having to add each day individually, I enter the child's name, choose a club and then enter start and end dates. Multiple records are created in the booking table with the Child ID, Club ID identical, but the DateRequested field varies.
We do need to store a record in the Bookings table for the child on each date so we can print a register for each day..as well as for invoicing/reporting.
From looking at VBA...I think I need to use the INSERT INTO command? Is the best way to do it? Also I need to make sure that dates within the range that are Sat/Sunday are ignored.
I'd really appreciate any guidance on this and pointers to which commands would work best...
This is where DAO shines. It is so much faster to run a loop adding records than calling a Insert Into multiple times.
Here is how:
Public Function PopulateBokings()
Dim rsBookings As DAO.Recordset
Dim NextDate As Date
Set rsBookings = CurrentDb.OpenRecordset("Select Top 1 * From Bookings")
NextDate = Me!StartDate.Value
While DateDiff("d", NextDate, Me!EndDate.Value) >= 0
If Weekday(NextDate, vbMonday) > 5 Then
' Skip Weekend.
Else
rsBookings.AddNew
rsBookings!ChildrenId.Value = Me!ChildrenId.Value
rsBookings!ClubsId.Value = Me!ClubId.Value
rsBookings!DateRequested.Value = NextDate
rsBookings.Update
End If
NextDate = DateAdd("d", 1, NextDate)
Wend
rsBookings.Close
Set rsBookings = Nothing
End Function
Paste the code into the code module of the form, adjust the field and control names to those of yours, and call the function from the Click event of a button.
Consider populating a separate DateRange table that holds all possible dates, like all of 2017. You can build such a table iteratively with dynamic SQL query calls in VBA. And only run this once.
Then, create a stored query that cross joins Children, Club, and DateRange with filters for all by form parameters. This returns all possible date ranges repeating same Child and Club for table append.
VBA
Public Sub PopulateTime()
Dim i As Integer, StartDate As Date
CurrentDb.Execute "CREATE TABLE DateRange (" _
& " [ID] AUTOINCREMENT PRIMARY KEY," _
& " [BookDate] DATETIME)", dbFailOnError
StartDate = DateSerial(2017, 1, 1)
For i = 0 To 364
CurrentDb.Execute "INSERT INTO DateRange ([BookDate])" _
& " VALUES (#" & DateAdd("d", i, StartDate) & "#);", dbFailOnError
Next i
End Sub
SQL
INSERT INTO Bookings (ChildID, ClubID, DateRequested)
SELECT c.ChildID, b.ClubID, d.BookDate
FROM Children c, Clubs b, DateRange d
WHERE c.ChildID = Forms!myformname!ChildID
AND b.ClubID = Forms!myformname!ClubID
AND d.BookDate BETWEEN Forms!myformname!StartDate
AND Forms!myformname!EndDate
You can use a sequence generator query to repeatedly insert rows into a table between two parameters.
For this example, the maximum number of days inserted is 999, but this can easily be increased to 9999 or even more.
Inspired by this answer by Gustav:
PARAMETERS [StartDate] DateTime, [EndDate] DateTime;
INSERT INTO MyTable(MyDateField)
SELECT DISTINCT [StartDate] - 1+ 100*Abs([Hundreds].[id] Mod 10) + 10*Abs([Tens].[id] Mod 10)+Abs([Ones].[id] Mod 10)+1
FROM MSysObjects As Ones, MSysObjects As Tens, MSysObjects As Hundreds
WHERE [StartDate] - 1+ 100*Abs([Hundreds].[id] Mod 10) + 10*Abs([Tens].[id] Mod 10)+Abs([Ones].[id] Mod 10)+1 Between [StartDate]-1 And [EndDate]
Performance won't be good, but there are multiple advantages using a non-VBA solution.

Find changes made in PGSQL table using SQL in Excel VBA

Is it possible to create an SQL query to compare a field within a single table to see if a change has been made and if possible list the before and after?
I have the following SQL query written in Excel 2010 VBA, which connects to an Oracle PostGreSQL database
Dim au As String
au = "SELECT id, priority, flag, code " _
& "FROM hist WHERE ( aud_dt >= '18/05/2020' AND aud_dt <='18/05/2020' ) " _
Set rs = conn.Execute(au)
With ActiveSheet.QueryTables.Add(Connection:=rs, Destination:=Range("A1"))
.Refresh
End With
Where fields include:
"priority" is the field that I'd like to check for changes which will
be a single number between 0-9
"code" is the record that has been
assigned the priority and is a mixture of numbers and letters up to 7
characters
"flag" shows a 1 as the active record, and 2 as an edited
record
"id" refers to the user account
I'd ideally like to end up with something like: id | priority | flag | priority_old | flag_old | code
Which should show the before and after changes to the priority. If the record shows priority=3 and flag=2 and code=Ab12, there must also be record with a 1 flag, as that is now the active record. If it has the same priority number for the code I'm not interested in it as that just means something else was changed instead as I have not listed all the column fields.
If the active record now shows priority=4, flag=1 and code=Ab12, that would be exactly the record I need to see.
Consider a self-join query (possibly you need to adjust date filter in WHERE depending on when items change):
SELECT h1.id, h1.priority, h1.flag,
h2.priority AS priority_old, h2.flag AS flag_old, h1.code
FROM hist h1
LEFT JOIN hist h2
ON h1.code = h2.code
AND h1.priority <> h2.priority
AND h1.flag = 1
AND h2.flag <> 1
WHERE (aud_dt >= '2020-05-18' AND aud_dt <='2020-05-18')

Multiple 'AND' conditions on same column

I'm using an Access 2007 database in ASP.Net where I need to extract data from a table if an item meets multiple 'AND' clauses.
For example:
Select * from tableA
where tableA.column1 = myvalue1
and (tableA.column2 = myvalue2
and tableA.column2 = myvalue3)
(The '(' and ')' parenthesis around the 'and' clauses are an Access requirement)
myvalue1 exists multiple times in column1 because column2 stores many different pieces of data for the column1 value.
However, I only want the results to show those column1 values which meet all of the column2 search criteria.
For example, column1 contains employee surnames, column2 contains the qualifications of the employee.
One employee in column1 may hold many qualifications in column2.
Searching with the 'OR' clause for employees who hold all the required qualifications for an assignment would produce a table with multiple rows for each employee.
What is required is a single row for each employee who holds every one of the required qualifications.
Let's say employee A has qualifications X, Y and Z. The employee's name will be in a row in column 1 each time a qualification is added to column2, so the employee's name appears in column1 3 times. Assume now employee B also has qualifications X, Y and Z but employee C has W, X and Y. If an assignment needs qualifications X,Y and Z I don't want employee C appearing in the results of a search as employee C won't hold the relevant qualifications.
When I use the above 'select' command with just one 'and' clause the search works fine, but with two 'and' clauses nothing is returned, even though I know there are items in column1 that meet both of the 'and' criteria.
What am I missing in the concatenation of the 'and' clauses?
The following solution will work, but performance will suffer. This will return 1 row per person, I can rewrite it to return both rows.
Select * from tableA
where tableA.column1 = myvalue1
and (tableA.column2 = myvalue2
and DCount("column2", "TableA", "column1 = """ & column1 & """ AND column2 = """ & myvalue3 & """" ) <> 0)
Your fundamental problem is that a select query without a group by or pivot clause only evaluates 1 row at a time. I'd probably pivot the data, concatenate it, and then evaluate it for better performance in the end. But that's more complicated, and requires more information on the table structure and what you want to do.
Based on new information, I'll submit this answer:
My gut feeling is that you can't do this in one simple query. This sounds cludgy, but I think you're going to need one query created that's a SELECT DISTINCT query, and that query will pull one record for each employee:
Select DISTINCT Column1 from tableA
Call this query something like qryEmployees
Next you're going to need to create a new table. Call it tblQualifications. It will contain 2 fields; EName and EQualifications. We're going to fill that in a minute.
Next you're going to need to loop through each record in qryEmployees and write the employee name and their qualifications into tblQualifications.
'Run through every Employee name
Do While qryEmployees.EOF = False
'Set up a temp variable, MyQual, and set it equal to Empty
MyQual = ""
'Pull all qualifications from TableA for the current Employee
Set rec = db.OpenRecordset ("Select Column1, column2 from TableA WHERE Column1 = '" & qryEmployees.Column1 & "'")
'Looping through the qualifications
Do While rec.EOF = False
'Add the qualification to the MyQual string
MyQual = MyQual & rec("Column2") & ", "
rec.MoveNext
Loop
'Now that we have all the qualifications in a string, trim off the last ", "
MyQual = left(MyQual, len(MyQual)-2)
'Open up tblQualifications and fill it with the Employee and a string of all of their qualifications
tblQualifications.AddNew
tblQualifications(0) = qryEmployees.Column1
tblQualifications(1) = MyQual
tblQualifications.Update
'Move on to the next Employee and repeat the process
qryEmployees.MoveNext
Loop
Now you have one table with Employee Name in one field, and all of his qualifications in the next. If you use the LIKE operator on this table, you should get your results.
Select * from tblQualifications
where tblQualifications.EName = myvalue1
and (tblQualifications.EQualifications LIKE myvalue2
and tblQualifications.EQualifications LIKE myvalue3)
It's not pretty, but it will work (with a little putzing with the code above, as I'm writing it off the top of my head and it's untested).
Thanks Johnny, your suggestion inspired me to write a slightly different approach to the solution.
I swapped the 'AND' to an 'OR' as others have suggested to extract any employee into a table if they have any of the qualifications.
The result is a table with every required qualification for every employee who has one of those qualifications.
I then did a count elsewhere of the number of qualifications required for the assignment and compared this to whether or not the employee had the required number of qualifications, as follows:
Dim objSearchTable As DataTable = DSSearch.Tables("Employees")
Dim i As Integer
If QualificationsCount > 0 Then
For i = 0 To objSearchTable.rows.count - 1
strEmployee = objSearchTable.Rows(i).Item("column1")
Dim foundRows As DataRow() = objSearchTable.[Select]("column1 = '" & strEmployee & "'")
Dim numberOfRecords As Integer = foundRows.count
If numberOfRecords < QualificationsCount Then
objSearchTable.Rows(i).Delete
End If
Next
End If
objSearchTable.AcceptChanges
This deletes every instance of an employee from the table if the count of qualifications is less than the number required.
The solution isn't as elegant as extracting only the correct employees from the database to begin with.
There is also an overhead on extracting every employee from the database, but the end result is a table with just one row for each employee with all the qualifications, which does what I need.