Ambiguous left joins in MS Access - sql-server

I want to convert the following query from T-SQL
SELECT
*
FROM
A LEFT JOIN
B ON A.field1 = B.field1 LEFT JOIN
C ON C.field1 = A.field2 AND
C.field2 = B.field2
to Jet SQL. Now MS Access does not accept ambiguous queries. How can I do that? I can't put the second comparison in the WHERE clause. Why? Because my scenario is that I am selecting records that does not exist in C.
How to select all records from one table that do not exist in another table?
Now, how do you that in MS Access? Thanks in advance for your time and expertise.

You need a derived table to make this work in MS Access:
SELECT *
FROM (
SELECT A.Field1, A.Field2 As A2, B.Field2
FROM A
LEFT JOIN B ON A.field1 = B.field1) AS x
LEFT JOIN C ON x.A2 = C.field1 AND x.field2= C.field2

From Help LEFT JOIN, RIGHT JOIN Operations
You can link multiple ON clauses. See the discussion of clause linking
in the INNER JOIN topic to see how this is done.
You can also link several ON clauses in a JOIN statement, using the
following syntax:
SELECT fields
FROM table1
INNER JOIN table2 ON table1.field1 compopr table2.field1
AND ON table1.field2 compopr table2.field2)
OR ON table1.field3 compopr table2.field3)];
But works this (it seems there is an error in help):
SELECT *
FROM A
LEFT JOIN B ON A.field1 = B.field1
LEFT JOIN C ON (C.field1 = A.field2 AND C.field2 = B.field2)

Related

how to use aggregate query result as column in another query having joins using stored procedures in SQL Server

i have a inner join query in stored procedure which is working fine. i need to inject a aggregate query in it so that it show an aggregated result in a new column
https://drive.google.com/file/d/1tAIEACvEnG7sAisSoE2crYRrzCjIcvST/view?usp=sharing
i tried to inject aggregate query as a column TotalQty in my query
SELECT dbo.SO.Id,dbo.Customer.Name, dbo.Product.Name AS ProductName, dbo.SOD.SalePrice
,TotalQty = (select SUM(dbo.SOD.Quantity) from [sod] o where o.SOId='68BD0F69-B957-439F-9AD0-180DF23EF42B' )
FROM dbo.SOD INNER JOIN
dbo.Product ON dbo.SOD.ProductId = dbo.Product.Id RIGHT JOIN
dbo.SO ON dbo.SOD.SOId = dbo.SO.Id INNER JOIN
dbo.Customer ON dbo.SO.CustomerId = dbo.Customer.Id
WHERE (dbo.SO.Id = '68BD0F69-B957-439F-9AD0-180DF23EF42B')
But it says
Column 'dbo.SO.Id' is invalid in the select list because it is not
contained in either an aggregate function or the GROUP BY clause.
or any other good Technique suggested will be appreciated.
so change AS :
SELECT dbo.SO.Id,dbo.Customer.Name, dbo.Product.Name AS ProductName, dbo.SOD.SalePrice
,(select count(dbo.SOD.Quantity) from [sod] o where o.SOId='68BD0F69-B957-439F-9AD0-180DF23EF42B') AS TotalQty
FROM dbo.SOD INNER JOIN
dbo.Product ON dbo.SOD.ProductId = dbo.Product.Id RIGHT JOIN
dbo.SO ON dbo.SOD.SOId = dbo.SO.Id INNER JOIN
dbo.Customer ON dbo.SO.CustomerId = dbo.Customer.Id
WHERE (dbo.SO.Id = '68BD0F69-B957-439F-9AD0-180DF23EF42B')
As in the internal query, your characteristic is you used the o.SOId field on where and in other hand used count aggregate function so you should:
SELECT dbo.SO.Id,dbo.Customer.Name, dbo.Product.Name AS ProductName,
dbo.SOD.SalePrice
,count(dbo.SOD.Quantity) AS TotalQty
FROM dbo.SOD INNER JOIN
dbo.Product ON dbo.SOD.ProductId = dbo.Product.Id RIGHT JOIN
dbo.SO ON dbo.SOD.SOId = dbo.SO.Id INNER JOIN
dbo.Customer ON dbo.SO.CustomerId = dbo.Customer.Id
WHERE (dbo.SO.Id = '68BD0F69-B957-439F-9AD0-180DF23EF42B')
group by
dbo.SO.Id,dbo.Customer.Name, dbo.Product.Name , dbo.SOD.SalePrice
Which will have the same output.
Generally speaking, you encourage others to help if you provide a MVCE. Using cryptic table names (are they tables? or views perhaps?) is not a healthy practice. In addition, it is not clear what you are trying to achieve with your subquery. You attempted to use count but you label the value as "TotalQty" and you replied to a suggestion using "sum". Very confusing.
So since we don't have your tables, I used the common MS sample database AdventureWorks. Below are two examples of counting the quantity values from the detail table.
select Ord.SalesOrderID, Det.SalesOrderDetailID,
Cust.AccountNumber as CustName, -- too lazy to get actual name
Prd.Name as ProductName,
Det.UnitPrice
,Counted.TotalQty
-- TotalQty = (select count(dbo.SOD.Quantity) from [sod] o where o.SOId='68BD0F69-B957-439F-9AD0-180DF23EF42B' )
from Sales.SalesOrderHeader as Ord
inner join Sales.SalesOrderDetail as Det on Ord.SalesOrderID = Det.SalesOrderID
inner join Production.Product as Prd on Det.ProductID = Prd.ProductID
inner join Sales.Customer as Cust on Ord.CustomerID = Cust.CustomerID
cross apply (select count(DetCnt.OrderQty) as TotalQty from Sales.SalesOrderDetail as DetCnt where DetCnt.SalesOrderID = Det.SalesOrderID) as Counted
where Ord.SalesOrderID = 43659
select Ord.SalesOrderID, Det.SalesOrderDetailID,
Cust.AccountNumber as CustName, -- too lazy to get actual name
Prd.Name as ProductName,
Det.UnitPrice
, TotalQty = (select count(DetCnt.OrderQty) from Sales.SalesOrderDetail as DetCnt where DetCnt.SalesOrderID = Det.SalesOrderID)
-- TotalQty = (select count(dbo.SOD.Quantity) from [sod] o where o.SOId='68BD0F69-B957-439F-9AD0-180DF23EF42B' )
from Sales.SalesOrderHeader as Ord
inner join Sales.SalesOrderDetail as Det on Ord.SalesOrderID = Det.SalesOrderID
inner join Production.Product as Prd on Det.ProductID = Prd.ProductID
inner join Sales.Customer as Cust on Ord.CustomerID = Cust.CustomerID
where Ord.SalesOrderID = 43659
I think that interpretation is correct but I don't know your schema. I added the PK of the detail table to help "see" the relationship between Order and Detail.
Examine the code closely. Notice how the query only refers to the specific PK value once (this would be your procedure's parameter). You use correlations and joins to limit the results as needed. And notice how much easier it is to understand the query since it uses names that are actual words - SalesOrder vs. SO. I don't think it makes much sense to right join your Detail table to the Order table - seems like a mistake. Your aggregation attempt is odd so I can't say if the value computed by these queries is correct.
I'll also note that you should not be passing the PK value of your table using a nvarchar parameter. Use the correct datatype to avoid the possibility that someone attempts to pass an actual string (e.g., N'Pick me') instead of a GUID value.

replace nested where condition with join

I have a SQL query that looks like this
Select a.*
From table1 a
where a.ColumnName in
(Select MAX(b.ColumnName)
from table2 b
where b.ColumnName2 in
(
Select MAX(c.columnName)
from table3 c
Group by c.ColumnName2
)
Group by b.ColumnName2
)
I am trying to write this in a join statement. I am positive inner join is what I need to get the right information. If someone could translate this to a join statement, I would be really glad.
Thank you.
EDIT 1:
I tried the typical Join statement that a rookie would.
Select a.*
from table1 a
inner join table2 b
on a.columnname = (Select max(b.columnName) from table2)
inner join table3 c
on b.columnName = (select max(c.columnName) from table3)
Obviously, that didn't work because I get 100,000+ results when I should be getting 800. I tried using an alias for table2 and table3 INSIDE the subselect statements and selecting the columnname using THAT alias like this:
Select max(bPart.columnName from table2 bPart)
Select max(cPart.columnName from table3 cPart)
Still the same result.
PERHAPS....
Though I'm not sure why a join is needed. Performance wise exists would likely be fastest, and since you're not returning values from table2 or 3 it seems like it would be the best approach.
SELECT a.*
FROM table1 a
INNER JOIN (SELECT MAX(ColumnName) MColumnName, columnname2
FROM table2
GROUP BY columnName2) B
ON A.columnName = B.mColumnName
INNER JOIN (SELECT MAX(columnName) mColumnName
FROM table3
GROUP BY ColumnName2) C
ON B.columname2 = C.MColumnName

Cascade of outer joins

As a schematic example, I have 3 tables that I desire to join, A,B,C where A to B is joined via an outer join and B to C is potentially joined via an inner join. In this constellation, I have to write two outer joins to get data if the first join does not have a match A-B in a line:
SELECT [fields] FROM
A
LEFT OUTER JOIN
B ON [a.field]=[b.field]
LEFT OUTER JOIN
C ON [b.field]=[c.field]
It seems to me logically that I have to write the second statement as an outer join. However I'm curious if there is a possiblity to set brackets for the join scope to signal that the second join should only used if the first inner join has found matching data for A-B. Something like:
SELECT [fields] FROM
A
(LEFT OUTER JOIN
B ON [a.field]=[b.field]
INNER JOIN
C ON [b.field]=[c.field]
)
I have played around a little but not found a possiblity to set brackets. The only way I have found to make this working is with a sub-query. Is this the only way to go?
Actually there is a syntax for that case.
SELECT fields
FROM A
LEFT OUTER JOIN (
B INNER JOIN C ON b.field = c.field
) ON a.field = b.field
The parentheses are optional, and the result is the same without them, being equivalent to the result of
SELECT fields
FROM A
LEFT OUTER JOIN B ON a.field = b.field
LEFT OUTER JOIN C ON b.field = c.field
You could perform it as follows
SELECT Fields
FROM TableA a
LEFT OUTER JOIN (SELECT Fields
FROM TableB b
INNER JOIN TableC c ON b.Field = c.Field) x on a.Field = x.Fi
eld
Not too sure if there would be any performance benefit to this without testing it out though.
The 2nd way would be with a subquery as per Jon Bridges' answer
However, they are the same semantically.
A CTE could be used if you have a complex subquery
;WITH BjoinC AS
(
SELECT Fields
FROM TableB b
INNER JOIN
TableC c ON b.Field = c.Field
)
SELECT [fields] FROM
A
LEFT OUTER JOIN
BjoinC ON ...
What About something like:
SELECT [fields]
FROM A
LEFT JOIN ( SELECT DISTINCT [fields]
FROM B
LEFT JOIN C ON b.field = c.field
) on a.field = b.field

using (+) after where statement in sql server

How can i convert following plsql to tsql. B.STATUS(+)=1 doesnt work in tsql.
Select * from A,B where A.ID=B.ID(+)
WHERE B.STATUS(+)=1
this doesnt return rows in mssql because it doesnt understand B.STATUS is optional
Select * from A LEFT JOIN B ON A.ID=B.ID
WHERE B.STATUS=1
An OUTER JOIN changes to an INNER JOIN when a condition is applied to the outer table in the WHERE clause. In the ON clause, it stays as OUTER.
You need to push the predicate "in"
Select * from A LEFT JOIN B ON A.ID = B.ID AND B.STATUS=1
OR
Select * from
A LEFT JOIN
(SELECT * FROM B WHERE B.STATUS=1) B1 ON A.ID = B1.ID
Just make it an OR "(b.status = 1 OR b.status IS NULL)"

Optimize multiple joins with table functions

I would like to join several times with the same table function for different input variables in the same query. But this turns in my case out to be much slower than using table variables and selecting from the table functions separately.
How can I avoid table variables and still have a fast query?
For example, we have a SQL query like
SELECT P.ProjectName, A.Number, B.Number
FROM Project AS P
LEFT JOIN dbo.fn_ProjectNumber(#dateA) AS A
ON P.ProjectID = A.ProjectID
LEFT JOIN dbo.fn_ProjectNumber(#dateB) AS B
ON P.ProjectID = B.ProjectID
but it is much slower than selecting from the functions separately into variables and then joining later, for example:
INSERT INTO #tempA
SELECT P.ProjectID, A.Number
FROM Project AS P
LEFT JOIN dbo.fn_ProjectNumber(#dateA) AS A
ON P.ProjectID = A.ProjectID
INSERT INTO #tempB
SELECT P.ProjectID, B.Number
FROM Project AS P
LEFT JOIN dbo.fn_ProjectNumber(#dateB) AS B
ON P.ProjectID = B.ProjectID
SELECT P.ProjectName, A.Number, B.Number
FROM Project AS P
LEFT JOIN #tempA AS A
ON P.ProjectID = A.ProjectID
LEFT JOIN #tempA AS B
ON P.ProjectID = B.ProjectID
What could be the cause of this? Is there a way I can get a fast query and avoid table variables?
More details:
This is only an example similar to what I'm doing, but the function fn_ProjectNumber(#date datetime) would contain something like joins between four tables...
Try fixing the join, you refer to the wrong alias in the second LEFT JOIN:
ORIGINAL:
SELECT P.ProjectName, A.Number, B.Number
FROM Project AS P
LEFT JOIN dbo.fn_ProjectNumber(#dateA) AS A
ON P.ProjectID = A.ProjectID
LEFT JOIN dbo.fn_ProjectNumber(#dateB) AS B
ON P.ProjectID = A.ProjectID
FIXED:
SELECT P.ProjectName, A.Number, B.Number
FROM Project AS P
LEFT JOIN dbo.fn_ProjectNumber(#dateA) AS A
ON P.ProjectID = A.ProjectID
LEFT JOIN dbo.fn_ProjectNumber(#dateB) AS B
ON P.ProjectID = B.ProjectID --<<<<<
Is there any particular reason you're trying to avoid table variables? They can be a good optimisation technique and don't leave any temp objects to clean up.
Anyway, if you don't want to do it that way you could always try
SELECT
P.ProjectID, A.Number, B.Number
FROM
Project AS P
LEFT JOIN
(SELECT P.ProjectID, A.Number
FROM Project AS P
LEFT JOIN dbo.fn_ProjectNumber(#dateA) AS A
ON P.ProjectID = A.ProjectID
) AS A
ON P.ProjectID = A.ProjectID
LEFT JOIN
(SELECT P.ProjectID, B.Number
FROM Project AS P
LEFT JOIN dbo.fn_ProjectNumber(#dateB) AS B
ON P.ProjectID = B.ProjectID
) AS B
ON P.ProjectID = B.ProjectID
The joins are slow because, in your example query, you are calling each function once for each row in table Project. It's faster with the temp tables because you're only calling the function once.
One way to avoid temp tables would be to use CTEs (common table expressions--they're not just for recursion--available in SQL 2005 and up.). The general syntax would be something like:
WITH cteTempName (<list of columns>)
as (<your table function call>)
SELECT <your query here, with "cteTempName" appearing as just another table to select from>
Maybe the joins are slower because you haven't defined relations between the tables that are joined together?
I don't know much about performance of queries in SQL Server, but defining the relations will improve the performance of joins.

Resources