Left Join works with table but fails with query - sql

The following left join query in MS Access 2007
SELECT
Table1.Field_A,
Table1.Field_B,
qry_Table2_Combined.Field_A,
qry_Table2_Combined.Field_B,
qry_Table2_Combined.Combined_Field
FROM Table1
LEFT JOIN qry_Table2_Combined
ON (Table1.Field_A = qry_Table2_Combined.Field_A)
AND (Table1.Field_B = qry_Table2_Combined.Field_B);
is expected by me to return this result:
+--------+---------+---------+---------+----------------+
|Field_A | Field_B | Field_A | Field_B | Combined_Field |
+--------+---------+---------+---------+----------------+
|1 | | | | |
+--------+---------+---------+---------+----------------+
|1 | | | | |
+--------+---------+---------+---------+----------------+
|2 |1 |2 |1 |John, Doe |
+--------+---------+---------+---------+----------------+
|2 |2 | | | |
+--------+---------+---------+---------+----------------+
[Table1] has 4 records, [qry_Table2_Combined] has 1 record.
But it gives me this:
+--------+---------+---------+---------+----------------+
|Field_A | Field_B | Field_A | Field_B | Combined_Field |
+--------+---------+---------+---------+----------------+
|2 |1 |2 |1 |John, Doe |
+--------+---------+---------+---------+----------------+
|2 |2 |2 | |, |
+--------+---------+---------+---------+----------------+
Really weird is that the [Combined_Field] has a comma in the second row. I use a comma to concatenate two fields in [qry_Table2_Combined].
If the left join query uses a table created from the query [qry_Table2_Combined] it works as expected.
Why does this left join query not give the same result for a query and a table? And how can i get the right results using a query in the left join?

Looking at you logic, it seems that you only want combined fields where field_A = "2" (SELECT '2' AS Field_A). I suspect that this is causing the problem. Would it be possible to go about a solution in a different way, for example:
SELECT
t1.Field_A,
t1.Field_B,
t2.Field_B As t2B,
[t2].[Col_1] & ", " & [t2].[Col_2] AS Combined
FROM t1 LEFT JOIN t2
ON t1.Field_B = t2.Field_B
WHERE t1.Field_A="2"
UNION ALL
SELECT
t1.Field_A,
t1.Field_B,
"None" As t2B,
"None" AS Combined
FROM t1
WHERE t1.Field_A<>"2"

Concatenation: change the & operators to + operators and the result should be as expected.
Missing rows: I can reproduce this issue but cannot explain it, other than to say a) it's probably a bug and b) it will probably never get fixed :(
For sanity I tested the same code in SQL Server and it works as expected.
As a general point an outer join can be simulated using union and padding the missing values e.g. pseudo code:
( A JOIN B )
UNION
( A NOT MATCH B { A.*, <pad values for B> } )
In your case and in Access SQL:
SELECT Table1.Field_A, Table1.Field_B,
qry_Table2_Combined.Field_A,
qry_Table2_Combined.Field_B,
qry_Table2_Combined.Combined_Field
FROM Table1
INNER JOIN qry_Table2_Combined
ON (Table1.Field_A = qry_Table2_Combined.Field_A)
AND (Table1.Field_B = qry_Table2_Combined.Field_B)
UNION ALL
SELECT Table1.Field_A, Table1.Field_B,
NULL AS Field_A,
NULL AS Field_B,
NULL AS Combined_Field
FROM Table1
WHERE NOT EXISTS ( SELECT *
FROM qry_Table2_Combined
WHERE (Table1.Field_A = qry_Table2_Combined.Field_A)
AND (Table1.Field_B = qry_Table2_Combined.Field_B) );
The above seems to produce the results you were expecting.
Access repro code, with concatenation fix, uncomment code for suggested workaround:
Sub EXfewfTempler()
On Error Resume Next
Kill Environ$("temp") & "\DropMe.mdb"
On Error GoTo 0
Dim cat
Set cat = CreateObject("ADOX.Catalog")
With cat
.Create _
"Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=" & _
Environ$("temp") & "\DropMe.mdb"
With .ActiveConnection
Dim Sql As String
Sql = "CREATE TABLE Table1 ( Field_A VARCHAR(10), Field_B VARCHAR(10) );"
.Execute Sql
Sql = "CREATE TABLE Table2 ( Field_B VARCHAR(10), Col_1 VARCHAR(10), Col_2 VARCHAR(10));"
.Execute Sql
Sql = "CREATE VIEW qry_Table2_Combined AS SELECT '2' AS Field_A, Table2.Field_B, Table2.Col_1 + ', ' + Table2.Col_2 AS Combined_Field FROM Table2; "
.Execute Sql
Sql = "INSERT INTO Table1 VALUES (1, NULL);"
.Execute Sql
Sql = "INSERT INTO Table1 VALUES (1, NULL);"
.Execute Sql
Sql = "INSERT INTO Table1 VALUES (2, 1);"
.Execute Sql
Sql = "INSERT INTO Table1 VALUES (2, 2);"
.Execute Sql
Sql = "INSERT INTO Table2 VALUES (1, 'John', 'Doe');"
.Execute Sql
Sql = _
"SELECT " & _
"Table1.Field_A, " & _
"Table1.Field_B, " & _
"qry_Table2_Combined.Field_A, " & _
"qry_Table2_Combined.Field_B, " & _
"qry_Table2_Combined.Combined_Field " & _
"FROM Table1 " & _
"LEFT JOIN qry_Table2_Combined " & _
" ON (Table1.Field_A = qry_Table2_Combined.Field_A) " & _
"AND (Table1.Field_B = qry_Table2_Combined.Field_B);"
' Sql = _
' "SELECT Table1.Field_A, Table1.Field_B, " & _
' " qry_Table2_Combined.Field_A, " & _
' " qry_Table2_Combined.Field_B, " & _
' " qry_Table2_Combined.Combined_Field " & _
' " FROM Table1 " & _
' " INNER JOIN qry_Table2_Combined " & _
' " ON (Table1.Field_A = qry_Table2_Combined.Field_A) " & _
' " AND (Table1.Field_B = qry_Table2_Combined.Field_B) " & _
' "UNION ALL " & _
' "SELECT Table1.Field_A, Table1.Field_B, " & _
' " NULL AS Field_A, " & _
' " NULL AS Field_B, " & _
' " NULL AS Combined_Field " & _
' " FROM Table1 " & _
' " WHERE NOT EXISTS ( SELECT * " & _
' " FROM qry_Table2_Combined " & _
' " WHERE (Table1.Field_A = qry_Table2_Combined.Field_A) " & _
' " AND (Table1.Field_B = qry_Table2_Combined.Field_B) );"
Dim rs
Set rs = .Execute(Sql)
MsgBox rs.GetString(2, , vbTab & vbTab, , "<NULL>")
End With
Set .ActiveConnection = Nothing
End With
End Sub

isn't this a problem with MSAccess parsing. for a test change the field names in the query to Field_C and Field_D and see if you still have the same problem

Related

SQL in VBA right join with no data from first table

I try to join data from multiple workbooks and use it in current workbook instead of VLOOKUP function. So I do not want return key column, just those that match criteria in key column in current workbook.
I got "Syntax error in FROM clause."
Everything works fine without "RIGHT JOIN" part. I use ADO.
"SELECT t1.number " & _
"FROM" & _
"(SELECT * FROM [Sheet1$] " & _
"IN '" & ThisWorkbook.Path & "\Src1.xlsm' " & _
"[Excel 12.0;Provider=Microsoft.ACE.OLEDB.12.0;Mode=Read;Extended Properties='HDR=YES;'] " & _
"UNION ALL " & _
"SELECT * FROM [Sheet1$] " & _
"IN '" & ThisWorkbook.Path & "\Src2.xlsb' " & _
"[Excel 12.0;Provider=Microsoft.ACE.OLEDB.12.0;Mode=Read;Extended Properties='HDR=YES;']" & _
"UNION ALL " & _
"SELECT * FROM [Sheet1$] " & _
"IN '" & ThisWorkbook.Path & "\Src2.xlsb' " & _
"[Excel 12.0;Provider=Microsoft.ACE.OLEDB.12.0;Mode=Read;Extended Properties='HDR=YES;']) t1" & _
"RIGHT JOIN [Sheet1$] " & _
"IN '" & ThisWorkbook.FullName & "' " & _
"[Excel 12.0;Provider=Microsoft.ACE.OLEDB.12.0;Mode=Read;Extended Properties='HDR=YES;'] t2 ON t2.key = t1.key;"
Data looks like
ThisWorkbook.Fullname:
key | someColumns | number
k1 | somedata |
k3 | somedata |
k5 | somedata |
\Src1.xlsm (also Src2):
key | number
k1 | 15
k2 | 11
k3 | 8
k4 | 16
k5 | 7
Likely result in Thisworkbook.fullname
key | someColumns | number
k1 | somedata | 15
k3 | somedata | 8
k5 | somedata | 7
Try
Dim Ws As Worksheet
Dim Rs As Object
Sub getRs(strSQL As String)
Dim strConn As String
Dim i As Integer
strConn = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=" & ThisWorkbook.FullName & ";" & _
"Extended Properties=Excel 12.0;"
Set Rs = CreateObject("ADODB.Recordset")
Rs.Open strSQL, strConn
End Sub
Sub test()
Dim strQuery As String
strQuery = "SELECT t1.number " & _
"FROM [Sheet1$] as t2 LEFT JOIN " & _
"(SELECT * FROM [Sheet1$] " & _
"IN '" & ThisWorkbook.Path & "\Src1.xlsm' " & _
"[Excel 12.0;Provider=Microsoft.ACE.OLEDB.12.0;Mode=Read;Extended Properties='HDR=YES;'] " & _
"UNION ALL " & _
"SELECT * FROM [Sheet1$] " & _
"IN '" & ThisWorkbook.Path & "\Src2.xlsb' " & _
"[Excel 12.0;Provider=Microsoft.ACE.OLEDB.12.0;Mode=Read;Extended Properties='HDR=YES;']) as t1 " & _
"ON t1.key = t2.key Where not isnull(t2.key) "
getRs strQuery
Range("c2").CopyFromRecordset Rs
Rs.Close
Set Rs = Nothing
End Sub

Selecting Distinct from two tables

I have a table tblConsentQuestion with questions
intID nvcText bitActive
17 Question1 True
18 Question2 True
19 Question3 False
and a table tblConsentData with the questions for every customer
intID intCustomerID bitConsent intIDQuestion
14 1 False 19
15 1 True 18
WHERE tblConsentQuestion.intID = tblConsentData.intIDQuestion
I would like to retrieve in a VB.net dataset:
all tblConsentData for a specific customer i.e, two records with intID = 14 and 15
all active (bitActive = true) records in tblConsentQuestion WHERE
tblConsentData.intIDQuestion <> tblConsentQuestion.intID, in this case only intID = 17 record (in addition to the two records)
The output should be:
Question3 False
Question2 True
Question1 Null
I tried something like:
str = "Select tblConsentQuestion.intID, bitConsent, nvcText" & fungGetLangId() & " AS nvcqText " _
& " From tblConsentData " _
& " Left OUTER JOIN tblConsentQuestion " _
& " On tblConsentData.intIDQuestion = tblConsentQuestion.intID " _
& " where tblConsentData.intCustomerID = " & intCustomerID & " " _
& " UNION ALL " _
& " Select tblConsentQuestion.intID, -1, nvcText" & fungGetLangId() & " " _
& " From tblConsentQuestion " _
& " Left OUTER JOIN tblConsentData " _
& " On tblConsentData.intIDQuestion = tblConsentQuestion.intID " _
& " WHERE(tblConsentQuestion.bitActive = 'True') "
and I received all active records in tblConsentQuestion, whereas I should not receive records with intID = 18, which exist in tblConsentData.
What is the data type of bitActive? Is it a text field with two possible strings: 'True' and 'False'? Or is it an Access Yes/No field?
If the latter, then your SQL shouldn't check for the string values, but rather for the True / False constants (let's say that `fungGetLangId:
WHERE tblConsentQuestion.bitActive = True
Or, better yet, you can check the yes/no field directly:
WHERE tblConsentQuestion.bitActive

How to get last 5 games of a team out of my access database

Hi all below you see a screenshot of my database:
But now I want to be able to make a table that calculates every players last 5 games. As I'm totaly new to access db I really have no clue how to do this.
Can you guys help me a hand with this one?
When I use the 2nd snippet in the answer below I get these:
Below are SQL routes according to your data. To use in MS Access simply create a new query under Create Tab on Ribbon and place the below SQL in the SQL view of a new created query. You may need to adjust query according to your actual table names and/or fields.
SAME GAMES FOR ALL PLAYERS
Assuming every player shares the same last five games, you could run an aggregate query across all players, using a subquery in INNER JOIN clause to calculate last five game dates. Do note: subquery, LastFiveDates can be saved as its own query and used directly in INNER JOIN.
SELECT [LookUp to Players],
Sum(GamesWon) As SumOfGamesWon, Sum(GamesLost) As SumOfGamesLost,
Sum(OwnOdds) As SumOfOwnOdds, Sum(OppOdds) As SumOfOppOdds,
Sum(GamesPlayed) As SumOfGamesPlayed
FROM GamesTable
INNER JOIN
(
SELECT DISTINCT TOP 5 [Date]
FROM GamesTable
ORDER BY [Date] DESC
) As LastFiveDates
ON GamesTable.[Date] = LastFiveDates.[Date]
GROUP BY [LookUp to Players];
DIFFERING GAMES FOR EACH PLAYER
SIMPLE SELECT APPROACH
Now, if players differ in their last five games, you have to join on different queries or union queries. Again, the below uses a subquery in an inner join but you can save that LastFiveGames as its own stored query and join in INNER JOIN line.
SELECT GamesTable.[LookUp to Players],
Sum(GamesWon) As SumOfGamesWon, Sum(GamesLost) As SumOfGamesLost,
Sum(OwnOdds) As SumOfOwnOdds, Sum(OppOdds) As SumOfOppOdds,
Sum(GamesPlayed) As SumOfGamesPlayed
FROM GamesTable
INNER JOIN
(
SELECT [Lookup to Players], [Date],
(SELECT Count(*)
FROM GamesTable t2
WHERE GamesTable.[Date] <= t2.[Date]
AND GamesTable.[Lookup to Players] = t2.[Lookup to Players]) AS GameOrder
FROM GamesTable
) As LastFiveDates
ON GamesTable.[Date] = LastFiveDates.[Date]
AND GamesTable.[Lookup to Players] = LastFiveDates.[Lookup to Players]
WHERE LastFiveDates.GameOrder <= 5
GROUP BY GamesTable.[LookUp to Players];
DIFFERING GAMES FOR EACH PLAYER
VBA CREATE TABLE APPROACH
Due to performance issues of Access running the query as a stored query, VBA can re-create the GamesStats iteratively looping through all distinct players using the very first query condition for player.
Public Function GameTableStats()
Dim db As Database
Dim tbldef As TableDef, rst As Recordset
Dim strSQL As String, i As Integer
Set db = CurrentDb
Set rst = db.OpenRecordset("SELECT DISTINCT PlayerName FROM GamesTable", dbOpenDynaset)
For Each tbldef In db.TableDefs
If tbldef.Name = "GamesStats" Then
db.Execute "DROP TABLE [GamesStats]", dbFailOnError
End If
Next tbldef
rst.MoveLast
rst.MoveFirst
i = 1
Do While Not rst.EOF
If i = 1 Then
' FIRST PLAYER (MAKE-TABLE QUERY) '
strSQL = "SELECT GamesTable.[PlayerName]," _
& " Sum(GamesWon) As SumOfGamesWon, Sum(GamesLost) As SumOfGamesLost," _
& " Sum(OwnOdds) As SumOfOwnOdds, Sum(OppOdds) As SumOfOppOdds," _
& " Sum(GamePlayed) As SumOfGamePlayed" _
& " INTO GamesStats" _
& " FROM GamesTable" _
& " INNER JOIN" _
& " (" _
& " SELECT DISTINCT TOP 5 [Date], [PlayerName]" _
& " FROM GamesTable" _
& " WHERE [PlayerName]=""" & rst!PlayerName & """" _
& " ORDER BY [Date] DESC" _
& " ) As LastFiveDates" _
& " ON GamesTable.[Date] = LastFiveDates.[Date]" _
& " WHERE GamesTable.[PlayerName]= """ & rst!PlayerName & """" _
& " GROUP BY GamesTable.[PlayerName];"
Else
' ALL OTHER PLAYERS (INSERT APPEND QUERIES) '
strSQL = "INSERT INTO GamesStats ([PlayerName], [SumOfGamesWon], [SumOfGamesLost]," _
& " [SumOfOwnOdds], [SumOfOppOdds], [SumOfGamePlayed])" _
& " SELECT GamesTable.[PlayerName], " _
& " Sum(GamesWon) As SumOfGamesWon, Sum(GamesLost) As SumOfGamesLost, " _
& " Sum(OwnOdds) As SumOfOwnOdds, Sum(OppOdds) As SumOfOppOdds, " _
& " Sum(GamePlayed) As SumOfGamePlayed " _
& " FROM GamesTable " _
& " INNER JOIN " _
& " ( " _
& " SELECT DISTINCT TOP 5 [Date], [PlayerName] " _
& " FROM GamesTable " _
& " WHERE [PlayerName]=""" & rst!PlayerName & """" _
& " ORDER BY [Date] DESC" _
& " ) As LastFiveDates " _
& " ON GamesTable.[Date] = LastFiveDates.[Date]" _
& " WHERE GamesTable.[PlayerName]= """ & rst!PlayerName & """" _
& " GROUP BY GamesTable.[PlayerName];"
End If
db.Execute strSQL, dbFailOnError
i = i + 1
rst.MoveNext
Loop
rst.Close
Set rst = Nothing
Set db = Nothing
MsgBox "Successfully created GamesStats table!", vbInformation
End Function

Using insert into where not exists in VBA

I have a lovely form and a lovely table in MS access (I promise). I would like to insert into this table at the press of a button using where not exists but I am getting a not-so-friendly run-time error 3067: "Query input must contain at least one table or query."
My query already does...
strSQL = "insert into tbl_MAP_systemTask (TaskID, SystemID) " & _
" Values (" & taskID & ", " & sysID & _
") where not exists " & _
" (select M.TaskID, M.SystemID from tbl_MAP_systemTask as M where M.TaskID = " & taskID & _
" and M.SystemID = " & sysID & ");"
Debug.Print strSQL
DoCmd.RunSQL (strSQL)
strSQL is now
insert into tbl_MAP_systemTask (TaskID, SystemID)
Values (1, 1)
where not exists
(select M.TaskID, M.SystemID
from tbl_MAP_systemTask as M where M.TaskID = 1 and M.SystemID = 1);
Can anyone shed any light on
a) what I broke?
b) how to fix it?
Well instead of using a SubQuery, you could use a Domain function to get this going,
If Dcount("*", "tbl_MAP_systemTask", "TaskID = " & taskID & " AND SystemID = " &sysID) = 0 Then
strSQL = "INSERT INTO tbl_MAP_systemTask (TaskID, SystemID) " & _
" VALUES (" & taskID & ", " & sysID & ")
CurrentDb.Execute strSQL
Else
MsgBox "The Data already exists in the table, so nothing was added."
End If
Try this:
strSQL = "insert tbl_MAP_systemTask (TaskID, SystemID) " & _
" select " & taskID & ", " & sysID & _
" where not exists " & _
" (select M.TaskID, M.SystemID from tbl_MAP_systemTask as M where M.TaskID = " & taskID & _
" and M.SystemID = " & sysID & ");"
=>
insert tbl_MAP_systemTask (TaskID, SystemID)
select 1, 1
where not exists
(select M.TaskID, M.SystemID
from tbl_MAP_systemTask as M where M.TaskID = 1 and M.SystemID = 1);
and seems to work in my case. Seems like the where not exists needs a select statement, so you have to model your insert like this.
Maybe you can use a recordset to insert these values.
Dim rs as Recordset
Set rs = Currentdb.openRecordset("SELECT * FROM tbl_MAP_systemTask WHERE TaskID=" & Me.TaskID & " AND SystemID=" & Me.SystemID)
if not (rs.eof or rs.bof) then
rs.addnew
rs.TaskID = Me.TaskID
rs.SystemID = Me.SystemID
rs.update
end if
rs.close
set rs = nothing
TOP 1 clause is must in the main query.
INSERT INTO tbl_MAP_systemTask (TaskID, SystemID)
SELECT TOP 1 1 AS TaskID 1 AS SystemID
FROM tbl_MAP_systemTask
WHERE NOT EXISTS (SELECT TOP 1 TaskID,SystemID FROM
tbl_MAP_systemTask WHERE TaskID = 1 and SystemID=1);
If tbl_MAP_systemTask table is empty or if there is only one record in the table then TOP 1 clause must be omitted in sub-query.
I have included Top 1 clause is sub-query for performance purpose.

How to use distinct in mssql with many tables?

With the below SQL statement I get all information about a student as needed except for one problem which is duplicate records when in fact last entered record for each student needed only.
Student ID Year level Semester
20012-000001 20012-20013 1
20012-000001 20012-20013 2
20012-000001 20012-20013 3
20012-000001 20012-20013 4
20012-000001 20012-20013 5
sqlSTR = "SELECT tblStudent.StudentID AS '??? ??????' , (tblStudent.FirstName + ' ' + tblStudent.MiddleName + ' ' + tblStudent.GrandFatherName + ' ' + tblStudent.LastName) as '?????' ," & _
"tblStudent.Gender as '?????', tblStudent.Citizenship as '???????', tblStudent.Type as '??? ?????'," & _
"tblDepartment.DepartmentTitle as '?????', tblEnrolment.SchoolYear as '????? ????????',tblYearLevel.YearLevelTitle as '????? ???????', " & _
"'????'= CASE WHEN Len(tblGraduate.StudentID)<>12 THEN '???' ELSE ''End , " & _
"'???? ???'= CASE WHEN Len(tblDropped.StudentID)<>12 THEN '???' ELSE ''End , " & _
"'??????'= CASE WHEN tblStudent.Transferee ='True' THEN '???' ELSE ''End" & _
" FROM tblDropped RIGHT JOIN " & _
"(tblGraduate RIGHT JOIN " & _
"(tblDepartment RIGHT JOIN " & _
"(tblYearLevel RIGHT JOIN " & _
"(tblSection RIGHT JOIN " & _
"(tblStudent LEFT JOIN tblEnrolment " & _
"ON tblStudent.StudentID = tblEnrolment.StudentID) " & _
"ON tblSection.SectionID = tblEnrolment.SectionOfferingID) " & _
"ON tblYearLevel.YearLevelID = tblEnrolment.YearLevelID) " & _
"ON tblDepartment.DepartmentID = tblSection.DepartmentID) " & _
"ON tblGraduate.StudentID = tblStudent.StudentID) " & _
"ON tblDropped.StudentID = tblStudent.StudentID" & _
" " & sFilter & _
" ORDER BY tblStudent.StudentID DESC , tblYearLevel.YearLevelTitle ASC"
Add a Date Field to your table containing the records and filter by most recent. IF the table already has one include a WHERE clause by date?
If I understand your table correctly the last record is always in the last semester, if you know the semester just do a UNIQUE keyword for the Student_ID