Multiple 'AND' conditions on same column - sql

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.

Related

Selectively deleting duplicates from table based on field value in Access

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

SQL query to display rows of 2 different tables

I'm trying to figure out how to pull all rows from two different tables with the OutageID = X. The purpose of this is to view the current outage and all the revisions in one statement to see all of the changes. Below is an example I was trying. However, it puts it all in one row. I want it to display all rows separately like you would if you were to query SELECT * From Table WHERE X = Y.
The Current Outages are in one table and the history is in another so they are not written over and not to change the design of the current DB.
Outages Table
`strSQL = "SELECT Outages.OutageID, Outages.Outage, Outages.Building,
Outages.OutageType, Outages.OutageStart, Outages.OutageStartTime,
Outages.OutageEnd, Outages.OutageEndTime, Outages.Duration,
Outages.Reason, Outages.Areas, Outages.Comment, Outages.ORN,
Outages.Contact, Outages.Phone, Outages.Job, Outages.Timestamp
FROM Outages
WHERE (((Outages.OutageID)=3305));"`
Outage History Table
`strSQL = "SELECT OutageHistory.RevisonID, OutageHistory.OutageID,
OutageHistory.Outage, OutageHistory.Building,
OutageHistory.OutageType,
OutageHistory.OutageStart, OutageHistory.OutageStartTime,
OutageHistory.OutageEnd, OutageHistory.OutageEndTime,
OutageHistory.Duration, OutageHistory.Reason, OutageHistory.Areas,
OutageHistory.Comment, OutageHistory.ORN, OutageHistory.Contact,
OutageHistory.Phone, OutageHistory.Job, OutageHistory.Timestamp
FROM OutageHistory
WHERE (((OutageHistory.OutageID)=3305));"`
`Private Sub All_Revision_Histoy_Click()
Dim strSQL As String
strSQL = "SELECT * From OutageHistory WHERE OutageHistory.OutageID = " &
Me.OutageID & ";"
Debug.Print strSQL
ShowDataSheet strSQL`
I think that I might need to create a temp table and insert both rows for the results and then Delete the table when its closed. However, I am not sure how to do that. I already feel I may of bitten off more than I can chew with this one. Thank you in advance.
select * from (
select 1 as revisionID, Outages.* FROM Outages
WHERE (((Outages.OutageID)=3305))
union
select OutageHistory.* FROM OutageHistory
WHERE (((OutageHistory.OutageID)=3305))
) order by revisionID desc

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.

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')

when 2 output values are returned it should display the hardcorded one and if 1 output value is returned it should display the 1output itself

When I execute a query for input parameter ABC it returns two values (Partner, Smith); whenever two values are returned of those two values Smith will be a compulsory value which will be returned.
But whenever the same query is executed with input parameter as 'xyz' it returns only one value.
Now my requirement is whenever I execute a query if it returns two values of those two values only SMITH must be returned in output and if the same query returns one output value then it should display the loutput value itself.
The below query satisfies 1st part of my requirement but it doesn’t satisfy my 2nd part of the requirement. Instead of displaying the 1output value it’s returning ‘Null’ value whenever the output value quantity is 1.
SELECT R.REGION_GID
FROM GTM_TRANSACTION T,
GTM_TRANSACTION_INVOLVED_PARTY P,
CONTACT C,
LOCATION L,
REGION_DETAIL R
WHERE T.GTM_TRANSACTION_GID=P.GTM_TRANSACTION_GID
AND R.COUNTRY_CODE3_GID = L.COUNTRY_CODE3_GID
AND R.REGION_GID LIKE 'SSN/BP.GTM_COMPL%'
AND L.LOCATION_GID = C.LOCATION_GID
AND P.INVOLVED_PARTY_CONTACT_GID=C.CONTACT_GID
AND P.INVOLVED_PARTY_QUAL_GID='SHIP_FROM'
AND T.GTM_TRANSACTION_GID=$SHIP_FORM
INTERSECT
SELECT R.REGION_GID
FROM GTM_TRANSACTION T,
GTM_TRANSACTION_INVOLVED_PARTY P,
CONTACT C,
LOCATION L,
REGION_DETAIL R
WHERE T.GTM_TRANSACTION_GID=P.GTM_TRANSACTION_GID
AND R.COUNTRY_CODE3_GID = L.COUNTRY_CODE3_GID
AND R.REGION_GID ='SSN/BP.GTM_COMPL_NO_CODING'
AND L.LOCATION_GID = C.LOCATION_GID
AND P.INVOLVED_PARTY_CONTACT_GID=C.CONTACT_GID
AND P.INVOLVED_PARTY_QUAL_GID='SHIP_FROM'
AND T.GTM_TRANSACTION_GID=$SHIP_FROM
As far as I can tell, the only difference between the two halves of your INTERSECT are in the filters for P.REGION_GID. The first half has:
R.REGION_GID LIKE 'SSN/BP.GTM_COMPL%'
while the second has
R.REGION_GID = 'SSN/BP.GTM_COMPL_NO_CODING'
Given how INTERSECT works, I think this means the first half is redundant. The only question then is whether the second half is returning one row or two rows. You want it to always return one row, with 'SMITH' taking precedence. The following logic may be what you want (as a bonus, I've tidied up your JOINs too):
SELECT TOP 1
R.REGION_GID
FROM
GTM_TRANSACTION T
JOIN GTM_TRANSACTION_INVOLVED_PARTY P ON
T.GTM_TRANSACTION_GID=P.GTM_TRANSACTION_GID
JOIN CONTACT C ON
P.INVOLVED_PARTY_CONTACT_GID=C.CONTACT_GID
JOIN LOCATION L ON
L.LOCATION_GID = C.LOCATION_GID
JOIN REGION_DETAIL R ON
R.COUNTRY_CODE3_GID = L.COUNTRY_CODE3_GID
WHERE
R.REGION_GID ='SSN/BP.GTM_COMPL_NO_CODING'
AND P.INVOLVED_PARTY_QUAL_GID='SHIP_FROM'
AND T.GTM_TRANSACTION_GID=$SHIP_FROM
ORDER BY
CASE WHEN R.REGION_GID = 'SMITH' then 1 else 2 end
That last line will want to be something like: CASE WHEN R.REGION_GID = 'SMITH' then 1 else 2 end but I you haven't told us much about your data, so I really don't know.