Access Query mysteriously omitting records - sql

I have an MS Access Contact & Correspondence database consisting of a "Contacts" table (Name, phone, email, etc.) and a "Calls" table (Contact, Incoming/Outgoing, Time, Medium, Notes, etc.). The tables are linked on Contacts.ID = Calls.Contact
For example:
NAME NUMBER EMAIL
Michaelangelo 123-4567 M#TMNT.com
Donatelo 123-4567 d#TMNT.com
Leonardo 123-4567 L#TMNT.com
Raphael 123-4567 R#TMNT.com
CONTACT TIME IN/OUT
Michaelangelo 1/1/2019 Outgoing
Michaelangelo 1/15/2019 Incoming
Michaelangelo 2/1/2019 Outgoing
Michaelangelo 3/1/2019 Outgoing
Leonardo 1/1/2019 Outgoing
Leonardo 2/1/2019 Outgoing
Michaelangelo 3/15/2019 Incoming
I'm trying to build a query to report all the contacts information (just as when the table is opened up directly), but with a field showing the most recent Incoming and Outgoing correspondence.
So, for the above data:
NAME LAST OUT LAST IN NUMBER EMAIL
Michaelangelo 3/1/2019 3/15/2019 123-4567 M#TMNT.com
Donatelo 123-4567 d#TMNT.com
Leonardo 2/1/2019 123-4567 L#TMNT.com
Raphael 123-4567 R#TMNT.com
So the results would independently identify the latest date of a correspondence record of both the incoming and outgoing types, and would return [nulls] wherever such a correspondence type did not yet exist.
I have a query that is working, mostly. However it seems to be mysteriously omitting certain records for which there are no records in 'Calls'. Not all such records, mind you.
My existing, troubled code is shown below. I have also tried moving the WHERE statements inside the JOINS (before ON); I have tried opening the WHERE statement with Calls.Time IS NULL OR Calls_1.Time IS NULL OR …
, and several other versions of the WHERE statement.
Here is the existing query:
SELECT Contacts.Name_First, Contacts.Name_Last, Max(Calls.Time) AS [Last Incoming], Max(Calls_1.Time) AS [Last Outgoing]
FROM (Contacts
LEFT OUTER JOIN Calls AS Calls_1 ON Contacts.ID = Calls_1.Contact )
LEFT OUTER JOIN Calls ON Contacts.ID = Calls.Contact
WHERE (
(((Calls.Outgoing_Incoming)="Incoming") OR Calls.Outgoing_Incoming IS NULL)
AND
(((Calls_1.Outgoing_Incoming)="Outgoing") OR Calls_1.Outgoing_Incoming IS NULL)
)
GROUP BY Contacts.Name_First, Contacts.Name_Last;
The full 'Contacts' table has 361 records. The intended result is that all 361 records will be returned, whether they have corresponding records in 'Calls' or not.
In fact only 208 records are returned. Many of these do not have corresponding 'Calls' records, telling me that the OR NULL statements are working, at least partly. I cannot find any consistent distinction between the records that are omitted verses the records that are returned.

Do the aggregation before doing the JOIN. Then you only need to aggregate once:
SELECT c.Name_First, c.Name_Last, ca.Last_Incoming, ca.Last_Outgoing
FROM Contacts as c LEFT OUTER JOIN
(SELECT Contact,
MAX(IIF(Outgoing_Incoming IS NULL OR Outgoing_Incoming = "Incoming", Time, NULL)) as Last_Incoming,
MAX(IIF(Outgoing_Incoming IS NULL OR Outgoing_Incoming = "Outgoing", Time, NULL)) as Last_Outgoing,
FROM Calls
GROUP BY Contact
) as ca
ON c.ID = c.Contact ;

Related

Using TOP 1 (or CROSS APPLY) within multiple joins

I've reviewed multiple Q&A involving TOP 1 and CROSS APPLY (including the very informative 2043259), but I still can't figure out how to solve my issue. If I had a single join I'd be fine, but fitting TOP 1 into the middle of a chain of joins has stumped me.
I have four tables and one of the tables contains multiple matches when joining due to a previous bug (since fixed) that created new records in the table instead of updating existing records. In all cases, where there are multiple records, it is the top-most record that I want to use in one of my joins. I don't have access to the table to clean up the extraneous data, so I just have to deal with it.
The purpose of my query is to return a list of all "Buildings" managed by a particular person (user choses a person's name and they get back a list of all buildings managed by that person). My tables are:
Building (a list of all buildings):
BuildingId BuildingName
1 Oak Tree Lane
2 Lighthoue Court
3 Fairview Lane
4 Starview Heights
WebBuildingMapping (mapping of BuidingId from Building table, that is part of an old system, and corresponding WebBuildingId in another piece of software):
BuildingId WebBuildingId
1 201
2 202
3 203
4 204
WebBuildingContacts (list of ContactID for the building manager of each building). This is the table with duplicate values - where I want to choose the TOP 1. In sample data below, there are two references to WebBuidingId = 203 (row 3 & row 5) - I only want to use row 3 data in my join.
Id WebBuildingId ContactId
1 201 1301
2 202 1301
3 203 1303
4 204 1302
5 203 1302
Contacts (list of ContactIds and corresponding property manager Names)
ContactId FullName
1301 John
1302 Mike
1303 Judy
As noted, in the example above, the table WebBuildingContact has two entries for the building with a WebBuidingId = 203 (row 3 and row 5). In my query, I want to select the top one (row 3).
My original query for a list of buildings managed by 'Mike' is:
SELECT BuildingName
FROM Building bu
JOIN WebBuildingMapping wbm ON wbm.BuildingId = bu.BuildingId
JOIN WebBuildingContact wbc ON wbc.WebBuildingId = wbm.WebBuildingId
JOIN Contacts co ON co.ContactId = wbc.ContactId
WHERE co.FullName = 'Mike'
This returns 'Fairview Lane' and 'Starview Heights'; however, Judy manages 'Fairview Lane' (she's the top entry in the WebBuildingContacts table). To modify the query and eliminate row 5 in WebBuildingContacts from the join, I did the following:
SELECT BuildingName
FROM Building bu
JOIN WebBuildingMapping wbm ON wbm.BuildingId = bu.BuildingId
JOIN WebBuildingContact wbc ON wbc.WebBuildingId =
(
SELECT TOP 1 WebBuildingId
FROM WebBuildingContact
WHERE WebBuildingContact.WebBuildingId = wbm.WebBuildingId
)
JOIN Contacts co ON co.ContactId = wbc.ContactId
WHERE co.FullName = 'Mike'
When I try this; however, I get the same result set (ie it returns 'Mike' as manager for 2 buildings). I've also made various attempts to use CROSS APPLY but I just end up with 'The multi-part identifier could not be bound', which is a whole other rabbit hole to go down.
You could try this:
SELECT bu2.BuildingName
FROM building bu2
WHERE bu2.BuildingId IN
(SELECT MAX(bu.BuildingId)
FROM Building bu
JOIN WebBuildingMapping wbm ON wbm.BuildingId = bu.BuildingId
JOIN WebBuildingContact wbc ON wbc.WebBuildingId = wbm.WebBuildingId
JOIN Contacts co ON co.ContactId = wbc.ContactId
WHERE co.FullName = 'Mike'
);

Select items where count in another field matches (not updatable)

Here I am trying to get the record for my products where the # swab location in Main table matches the count of swab locations in swab Table and Users can checked off the Y/N to verify that the description of the locations are correct.
Here is the example of my 2 tables.
tblMainEquipment
Asset_ID EquipmentName Num_SwapLocations Verified
234 Saijimon 2 N
235 Pasquale 3 N
tblMainSwapLocations
Asset_ID Swap_location
234 Particle Cannon
234 RailGun
235 Particle Cannon
I use the following query to count the number of records, i avoided using a having query to combine both tables since it is not updatable.
qryMainSwapLocationCount
SELECT MSL.Asset_ID, Count(Asset_ID) AS [Count]
FROM tblMainSwapLocation AS MSL
GROUP BY MSL.Asset_ID;
This will give me the result of
qryMainSwapLocationCount
Asset_ID count
234 2
234 1
I used the following as a record source for my form to allow users to verify the inputs.
SELECT MEQ.Asset_ID, MEQ.Equipment_Name,MEQ.Num_swapLocations MEQ.Verified
FROM tblMainEquipment AS MEQ, qryMainSwapLocationCount AS MSLC
WHERE (((MEQ.Asset_ID)=[MSLC].[Asset_ID]) AND ((MEQ.Num_SwapLocations)=[MSLC].[Count]);
This result would be
tblMainEquipment
Asset_ID EquipmentName Num_SwapLocations Verified
234 Saijimon 2 N
However this record set is not editable. Is there any reasons for this?
I think you should put your table tblMainEquipment as your recordsource and bring all the fields from that on to your form:
Then insert an unbound textbox (perhaps close to your Num_SwapLocations field for easy comparison):
Then in this new textbox, put the following in the ControlSource:
=DCount("ASSET_ID","tblMainSwapLocations","ASSET_ID=" & [Asset_ID])
Then open your form and it should count the number of records in table tblMainSwapLocations that have the same Asset_ID as the record currently showing:
You'll then be able to update the Verified field in your tblMainEquipment table.

Is it possible to match the "next" unmatched record in a SQL query where there is no strictly unique common field between tables?

Using Access 2010 and its version of SQL, I am trying to find a way to relate two tables in a query where I do not have strict, unique values in each table, using concatenated fields that are mostly unique, then matching each unmatched next record (measured by a date field or the record id) in each table.
My business receives checks that we do not cash ourselves, but rather forward to a client for processing. I am trying to build a query that will match the checks that we forward to the client with a static report that we receive from the client indicating when checks were cashed. I have no control over what the client reports back to us.
When we receive a check, we record the name of the payor, the date that we received the check, the client's account number, the amount of the check, and some other details in a table called "Checks". We add a matching field which comes as close as we can get to a unique identifier to match against the client reports (more on that in a minute).
Checks:
ID Name Acct Amt Our_Date Match
__ ____ ____ ____ _____ ______
1 Dave 1001 10.51 2/14/14 1001*10.51
2 Joe 1002 12.14 2/28/14 1002*12.14
3 Sam 1003 50.00 3/01/14 1003*50.00
4 Sam 1003 50.00 4/01/14 1003*50.00
5 Sam 1003 50.00 5/01/14 1003*50.00
The client does not report back to us the date that WE received the check, the check number, or anything else useful for making unique matches. They report the name, account number, amount, and the date of deposit. The client's report comes weekly. We take that weekly report and append the records to make a second table out of it.
Return:
ID Name Acct Amt Their_Date Unique1
__ ____ ____ ____ _____ ______
355 Dave 1001 10.51 3/25/14 1001*10.51
378 Joe 1002 12.14 4/04/14 1002*12.14
433 Sam 1003 50.00 3/08/14 1003*50.00
599 Sam 1003 50.00 5/11/14 1003*50.00
Instead of giving us back the date we received the check, we get back the date that they processed it. There is no way to make a rule to compare the two dates, because the deposit dates vary wildly. So the closest thing I can get for a unique identifier is a concatenated field of the account number and the amount.
I am trying to match the records on these two tables so that I know when the checks we forward get deposited. If I do a simple join using the two concatenated fields, it works most of the time, but we run into a problem with payors like Sam, above, who is making regular monthly payments of the same amount. In a simple join, if one of Sam's payments appears in the Return table, it matches to all of the records in the Checks table.
To limit that behavior and match the first Sam entry on the Return table to the first Sam entry on the Checks table, I wrote the following query:
SELECT return.*, checks.*
FROM return, checks
WHERE (( ( checks.id ) = (SELECT TOP 1 id
FROM checks
WHERE match = return.unique1
ORDER BY [our_date]) ));
This works when there is only one of Sam's records in the Return table. The problem comes when the second entry for Sam hits the Return table (Return.ID 599) as the client's weekly reports are added to the table. When that happens, the query appropriately (for my purposes) only lists that two of Sam's checks have been processed, but uses the "Top 1 ID" record to supply the row's details from the Return table:
Checks_Return_query:
Checks.ID Name Acct Amt Our_Date Their_Date Return.ID
__ ____ ____ ____ _____ ______ ________
1 Dave 1001 10.51 2/14/14 3/25/14 355
2 Joe 1002 12.14 2/28/14 4/04/14 378
3 Sam 1003 50.00 3/01/14 3/08/14 433
4 Sam 1003 50.00 4/01/14 3/08/14 433
In other words, the query repeats the Return table info for record Return.ID 433 instead of matching Return.ID 599, which is I guess what I should expect from the TOP 1 operator.
So I am trying to figure out how I can get the query to take the two concatenated fields in Checks and Return, compare them to find matching sets, then select the next unmatched record in Checks (with "next" being measured either by the ID or Our_Date) with the next unmatched record in Return (again, with "next" being measured either by the ID or Their_Date).
I spent many hours in a dark room turning the query into various joins, and back again, looking at functions like WHERE NOT IN, WHERE NOT EXISTS, FIRST() NEXT() MIN() MAX(). I am afraid I am way over my head.
I am beginning to think that I may have a structural problem, and may need to write the "matched" records in this query to another table of completed transactions, so that I can differentiate between "matched" and "unmatched" records better. But that still wouldn't help me if two of Sam's transactions are on the same weekly report I get from my client.
Are there any suggestions as to query functions I should look into for further research, or confirmation that I am barking up the wrong tree?
Thanks in advance.
I'd say that you really need another table of completed transactions, it could be temporary table.
Regarding your fears "... if two of Sam's transactions are on the same weekly report ", you can use cursor in order to write records "one-by-one" instead of set based transaction.

Join two tables and return data from either one or the other based on data

SQL 2012
Cannot change tables
Have two tables in question, both tables are identical
(I didn't design this)
Table A has application data. We accept applications and at the time of application we require a minimum of information - many values here can be null (or some placeholder like NA etc). It is assigned an application number (application id). If the application is accepted, we require more information - and we verify it.
Leading us to Table B - a copy of A except it is only populated with accepted applications - and verified data. So an application id will only exist in Table B if the manager accepts it. Sometimes, at the application process, the data is correct and no changes were made - leading to an application id having the exact values in both tables. Sometimes, the data changes or is added (we require all fields upon acceptance).
I would like to an easy way to capture all applications and the most current/verified data.
For instance:
Table A (Active Applications)
ApplicationID Phone State
1234 123-456-7890 AK
5678 246-802-4680 NULL
Table B (Approved/Accepted Applications)
ApplicationID Phone State
5678 246-802-4680 NY
Application 5678 was approved and, for demonstration only, the state was verified to be NY.
Application 1234 was not approved to date (but maybe in the future).
I would like to write a query which gives the following result:
ApplicationID Phone State
1234 123-456-7890 AK
5678 246-802-4680 NY
Desired behavior is essentially...return Table A unless the ApplicationID exists in Table then give me Table B instead.
Table A does contain every application expired or not, but there is an expiry date (applications good for 10 days) and it will be easy to cull those out based on that date.
Just flummoxed by needing approved and active applications.
Any help greatly appreciated.
--EDIT--
Thank you...but how do I handle the placeholders (like NA or XX) or when the verified data is deifferent from the application data? Say there is a third active application as so:
Table A (Active Applications)
ApplicationID Phone State
9876 234-432-1234 NY
Table B (Approved Applications)
ApplicationID Phone State
9876 234-432-1234 TX
The application was accepted by virtue of its existance in Table B but the state was verified to be TX and not NY.
I would like to see an output as such...Table A data if not in Table B. IF In Table B then Table B data.
ApplicationID Phone State
1234 123-456-7890 AK
5678 246-802-4680 NY
9876 234-432-1234 TX
Select a.applicationid, case when b.phone is null then a.phone else b.phone end as phone, case when b.state is null then a.state else b.state end as state
From a
Left outer join b on a.applicationid = b.applicationid
select
a.applicationid as a1, a.phone as a2, a.state as a3,
b.applicationid as b1, b.phone as b2, b.state as b3,
isnull(b.applicationid, a.applicationid) as applicationid,
isnull(b.phone, a.phone) as phone,
isnull(b.state, a.state) as state
from a
left outer join b on a.applicationid=b.applicationid
(omit line 2 and 3, these are just to show what the column entries are before isnull())

SQL count query not returning correct results

Struggling getting a query to work……..
I have two tables:-
tbl.candidates:
candidate_id
agency_business_unit_id
tbl.candidate_employment_tracker
candidate_id
The candidate employment can have duplicate records of a candidate_id as it contains records on their working history for different clients.
The candidates tables is unique for each candidate.
I'm trying to obtain results which will group by agency_business_unit_id and count the amount of candidates each has which exist in the candidate_employment_tracker.
E.g.
Agency Business Unit Id | Candidates
------------------------------------------------------------
100 | 2
987 | 1
12 | 90
The query I'm working on doesn't appear to be working as I'm getting the count of the candidates in candidate_employment_tracker.
SELECT
abu.agency_business_unit_id,
abu.agency_business_unit_name,
count(c.candidate_id) AS candidateCount
FROM candidate_employment_tracker cet
INNER JOIN candidate c ON c.candidate_id = cet.candidate_id
INNER JOIN agency_business_unit abu ON abu.agency_business_unit_id = c.agency_business_unit_id
WHERE c.candidate_ni_number NOT REGEXP '^[A-CEGHJ-PR-TW-Z][A-CEGHJ-NPR-TW-Z] ?[0-9]{2} ?[0-9]{2} ?[0-9]{2} ?[ABCD]$'
GROUP BY abu.agency_business_unit_id
ORDER BY abu.agency_business_unit_name ASC
I've tried several approaches and the results are inconsistent. For instance I know one of the agency business units only has 1 candidate but the result is 2. This is as a result of this particular candidate having 2 records in the candidate employment tracker table. I'll keep bashing away but any help would be much appreciated.
Do you need
count(DISTINCT c.candidate_id)
That would avoid the double counting where candidates have 2 records in the candidate employment tracker table.
Hmmm this doesn't appear to work now that I look further into the results. When I compare the candidates for a agency business unit I get inconsistent count numbers.