SQL Left Joining four different tables - sql

From AdventureWorks2012, I want to write a query using the Sales.SalesOrderHeader, Sales.Customer, Sales.Store, and Person.Person tables, showing the SalesOrderID, StoreName, the customer’s first and last name as CustomerName and the salesperson’s first and last names as SalesPersonName. I want to do a left join with Sales.Customer to the Sales.Store and Person.Person tables.
Here is my work so far. However, the CustomerName and SalesPersonName both have the same information when they should be different.
SELECT soh.SalesOrderID, ST.Name AS StoreName, pp.[PersonType], pp.[FirstName] + [LastName] AS CustomerName,
pp.[FirstName] + [LastName] AS SalesPersonName
FROM Sales.SalesOrderHeader soh
JOIN Sales.Customer SC ON soh.SalesOrderID = sc.CustomerID
JOIN Sales.Store ST ON sc.CustomerID = ST.BusinessEntityID
JOIN Person.Person PP ON ST.BusinessEntityID = PP.BusinessEntityID
WHERE Persontype LIKE 'SP%'

You're getting bad results, because your joins are wrong. You should join on fields, that represent a relation between the 2 table. For example:
JOIN Sales.Customer sc ON soh.CustomerID = sc.CustomerID

Related

Query with Inner Join, without diagrams only using object explorer and sp_help

I know this is very easy but I've tried this in different ways. I'm using the
AdventureWorks2008R2
database and I need to obtain Salesorderid, Orderdate, orderqty, name, firstname, lastname from all the orders (SalesOrderHeader) with all the sold products (Product) and customers (Customer)
First one I saw my mistake but decided to start over again
Select OrderDate,OrderQty,FirstName,LastName,Name,soh.SalesOrderID
from Sales.SalesOrderHeader soh
Inner Join Sales.SalesOrderDetail sod on soh.SalesOrderID = soh.SalesOrderID
Inner Join Sales.Customer sc on soh.CustomerID = sc.CustomerID
Inner Join Person.Person pp on soh.PersonID = pp.PersonID <-- My mistake
Inner Join Purchasing.ProductVendor ppv on pp.BusinessEntityID = ppv.BusinessEntityID
inner join Production.Product ppr on ppv.ProductID = ppr.ProductID
I made this one but it displays nothing
Select OrderDate,OrderQty,FirstName,LastName,Name,soh.SalesOrderID
From Person.BusinessEntity pbe
inner join person.person pp on pbe.BusinessEntityID = pp.BusinessEntityID
inner join sales.store ss on pbe.BusinessEntityID = ss.BusinessEntityID
inner join sales.SalesOrderHeader soh on ss.SalesPersonID = soh.SalesPersonID
inner join sales.Customer sc on soh.CustomerID = sc.CustomerID
inner join sales.SalesOrderDetail ppo on soh.SalesOrderID = ppo.SalesOrderID;
I was using the sphelp method, but I don´t know if you can give some tips on how to find the relation between each table without a diagram, I´m struggling with that part, also I try to use the object explorer as well.
For example:
In the above picture from my understanding is that the Sales.SalesOrderDetail has a primary key named SalesOrderId then SalesOrderHeader has a foreign key of this same one, same as SpecialOfferProduct,so that said I should be able to make a join between SalesOrderDetail and SalesOrderHeader, using SalesOrderID
I think that´s my main issue, sorry for all the bother I´m having trouble understanding this

Retrieving records with inner joins

My assignment is to get the the First name, Middle name and Last name for all Customers that have had an order before '2012-09-30' and after '2013-09-30'. I'm using the AdventureWorks2017 as a sample DB
Table: Sales.SalesOrderHeader
[SalesOrderID]
,[RevisionNumber]
,[OrderDate]
,[DueDate]
,[ShipDate]
,[Status]
,[OnlineOrderFlag]
,[SalesOrderNumber]
,[PurchaseOrderNumber]
,[AccountNumber]
,[CustomerID]
,[SalesPersonID]
,[TerritoryID]
,[BillToAddressID]
,[ShipToAddressID]
,[ShipMethodID]
,[CreditCardID]
,[CreditCardApprovalCode]
,[CurrencyRateID]
,[SubTotal]
,[TaxAmt]
,[Freight]
,[TotalDue]
,[Comment]
,[rowguid]
,[ModifiedDate]
Table: Person.Person
[BusinessEntityID]
,[PersonType]
,[NameStyle]
,[Title]
,[FirstName]
,[MiddleName]
,[LastName]
,[Suffix]
,[EmailPromotion]
,[AdditionalContactInfo]
,[Demographics]
,[rowguid]
,[ModifiedDate]
Table: Sales.Customers
[CustomerID]
,[PersonID]
,[StoreID]
,[TerritoryID]
,[AccountNumber]
,[rowguid]
,[ModifiedDate]
My Query
SELECT DISTINCT person_table.FirstName,
person_table.MiddleName,
person_table.LastName
FROM Sales.SalesOrderHeader as sales_order_table
inner join Sales.Customer as sales_customer_table
on (sales_customer_table.CustomerID = sales_order_table.CustomerID
and sales_order_table.OrderDate <= '2012-09-30' )
inner join Sales.Customer as sales_customer_table2
on (sales_customer_table2.CustomerID = sales_order_table.CustomerID
and sales_order_table.OrderDate >= '2013-06-30' )
inner join Sales.Customer as match_result
on (match_result.CustomerID = sales_customer_table2.CustomerID)
inner join Person.Person as person_table
on (person_table.BusinessEntityID = match_result.PersonID)
In this current state returns no rows and im unsure where the problem is
[UPDATE]
Found a relatevly good solution to the problem by editing Bilal Fakih answer
SELECT DISTINCT person_table.FirstName,
person_table.MiddleName,
person_table.LastName,
count(*) as Total_Instanses
FROM Sales.SalesOrderHeader as sales_order_table
inner join Sales.Customer as sales_customer_table
on (sales_customer_table.CustomerID = sales_order_table.CustomerID)
inner join Person.Person as person_table
on (person_table.BusinessEntityID = sales_customer_table.PersonID)
WHERE sales_order_table.OrderDate NOT BETWEEN '2012-09-30' AND '2013-06-30'
GROUP BY person_table.FirstName,
person_table.MiddleName,
person_table.LastName
HAVING count(*) >= 2
The suggestion was good but it woud return records that only had one instance. Im running into a few corner cases now. For example If a person has made 2 Orders that are bewfore 2012 or after 2013 will still be shown. The result im looking for is for a person to show up only when he has made orders before AND after the given dates
Try this, I'm not sure if it works I don't have the dataset to test, but it should
SELECT DISTINCT person_table.FirstName,
person_table.MiddleName,
person_table.LastName
FROM Sales.SalesOrderHeader as sales_order_table
inner join Sales.Customer as sales_customer_table
on (sales_customer_table.CustomerID = sales_order_table.CustomerID
inner join Person.Person as person_table
on (person_table.BusinessEntityID = match_result.PersonID)
WHERE sales_order_table.OrderDate NOT BETWEEN '2012-09-30' AND '2013-06-30'
You could simply this using below. Also your dates filter was not correct.
SELECT DISTINCT p.FirstName,
p.MiddleName,
p.LastName
FROM Sales.SalesOrderHeader as s
INNER JOIN Sales.Customer as c
ON c.CustomerID = s.CustomerID
INNER JOIN Person.Person as p
ON p.BusinessEntityID = c.PersonID)
WHERE s.OrderDate >= '2012-09-30' <----- add this
AND s.OrderDate <= '2013-06-30' ) ---- and this
My assignment is to get the the First name, Middle name and Last name for all Customers that have had an order before '2012-09-30' and after '2013-09-30'.
One method uses aggregation:
SELECT p.FirstName, p.MiddleName, p.LastName
FROM person_table p JOIN
Sales.Customer c
ON p.BusinessEntityID = c.PersonID JOIN
Sales.SalesOrderHeader so
ON c.CustomerID = so.Cus tomerID
GROUP BY p.FirstName, p.MiddleName, p.LastName
HAVING MIN(so.OrderDate) < '2020-09-30' AND
MAX(so.OrderDate) >'2013-06-30';
I will say that this condition looks suspicious:
ON p.BusinessEntityID = c.PersonID
However, that is what you use in your query. I would expect the person table to have an id called something like PersonId.

How to turn JOINS into Subqueries without breaking the SQL logic

I have performance issues while using multiple joins in T-SQL and if anyone can help me to turn these joins into sub-queries would be nice.
Whenever i try to change the join into subquery, i'm loosing the name declaration for the specific table. For instance if i try to turn the Album join (which is the first join on code below) into subquery i'm loosing the alias "AS a" and the "a.Title AS Album" stopped working so i have no idea how this would be done. If anyone gives me an example how should it work for one of the cases i suppose i will be able to reconstruct all of them.
SQL
SELECT
t.TrackId,
t.[Name] AS Track,
a.Title AS Album,
aa.[Name] AS Artist,
p.[Name] AS Playlist,
m.[Name] AS MediaType,
il.UnitPrice AS InvoicePrice,
CONCAT(c.FirstName, ' ', c.LastName) AS CustomerName,
CONCAT(e.FirstName, ' ', e.LastName) AS ResponsibleEmployeeName
FROM dbo.Track AS t
INNER JOIN dbo.Album AS a
ON t.AlbumId = a.AlbumId
INNER JOIN dbo.Artist AS aa
ON a.ArtistId = aa.ArtistId
INNER JOIN dbo.PlaylistTrack AS plt
ON t.TrackId = plt.TrackId
INNER JOIN dbo.Playlist AS p
ON p.PlaylistId = plt.PlaylistId
INNER JOIN dbo.MediaType AS m
ON t.MediaTypeId = m.MediaTypeId
INNER JOIN dbo.InvoiceLine AS il
ON t.TrackId = il.TrackId
INNER JOIN dbo.Invoice AS i
ON il.InvoiceId = i.InvoiceId
INNER JOIN dbo.Customer AS c
ON i.CustomerId = c.CustomerId
INNER JOIN dbo.Employee AS e
ON c.SupportRepId = e.EmployeeId
WHERE m.[Name] LIKE '%audio%'
ORDER BY t.[Name] ASC
Turn a join in subquery could be not the best solution
assuming you have already the index for foreign key for each retaled table
table Artist index on column (ArtistId)
table PlaylistTrack index on column (TrackId)
table Playlist index on column (PlaylistId)
table MediaType index on column ( MediaTypeId )
.....
for performance be sure you have index on
table track a composite index on column (AlbumId, TrackId, MediaTypeId )
table Album a cmposite index on column ( AlbumId, ArtistId )
What kind of a subqyery you mean? Something like:
SELECT t.TrackId,
t.[Name] AS Track,
(SELECT title FROM dbo.Album WHERE AlbumId = t.AlbumId) AS AlbumTitle
That's not going if you remove the join to Album, because you need the album reference to get to artist. If you want to join a subquery, you can do that, and keep the alias:
SELECT
t.TrackId,
t.[Name] AS Track,
a.Title AS Album,
aa.[Name] AS Artist,
p.[Name] AS Playlist,
m.[Name] AS MediaType,
il.UnitPrice AS InvoicePrice,
CONCAT(c.FirstName, ' ', c.LastName) AS CustomerName,
CONCAT(e.FirstName, ' ', e.LastName) AS ResponsibleEmployeeName
FROM dbo.Track AS t
INNER JOIN (SELECT * FROM dbo.Album) AS a
ON t.AlbumId = a.AlbumId
-- rest of joins
But logically, this is exactly the same as you have right now, and there will be no difference in the plan generated by the query optimizer. Even this:
SELECT
t.TrackId,
t.[Name] AS Track,
aa.Title AS Album, -- note change here
aa.[Name] AS Artist,
p.[Name] AS Playlist,
m.[Name] AS MediaType,
il.UnitPrice AS InvoicePrice,
CONCAT(c.FirstName, ' ', c.LastName) AS CustomerName,
CONCAT(e.FirstName, ' ', e.LastName) AS ResponsibleEmployeeName
FROM dbo.Track AS t
INNER JOIN (SELECT alb.Title
, art.Name
, alb.AlbumId
FROM dbo.Album alb
INNER JOIN dbo.Artist art
ON art.ArtistId = alb.ArtistID) AS aa
ON t.AlbumId = aa.AlbumId
INNER JOIN dbo.PlaylistTrack AS plt
ON t.TrackId = plt.TrackId
-- rest of them
Will produce the exact same plan. We might have moved Album <-> Artist join to a joined subquery, but essentially, it's still the same thing - inner joins.
Nothing like that will help with your performance. What might help you is to create indexes on these tables. If this is the kind of query you execute a lot, you can also create an indexed view, something like:
CREATE VIEW BoughtTracks
WITH SCHEMABINDING
AS
SELECT
il.InvoiceLineId, -- I'm guessing here, we need a unique ID
t.TrackId,
t.[Name] AS Track,
a.Title AS Album,
aa.[Name] AS Artist,
p.[Name] AS Playlist,
m.[Name] AS MediaType,
il.UnitPrice AS InvoicePrice,
CONCAT(c.FirstName, ' ', c.LastName) AS CustomerName,
CONCAT(e.FirstName, ' ', e.LastName) AS ResponsibleEmployeeName
FROM dbo.Track AS t
INNER JOIN dbo.Album AS a
ON t.AlbumId = a.AlbumId
INNER JOIN dbo.Artist AS aa
ON a.ArtistId = aa.ArtistId
INNER JOIN dbo.PlaylistTrack AS plt
ON t.TrackId = plt.TrackId
INNER JOIN dbo.Playlist AS p
ON p.PlaylistId = plt.PlaylistId
INNER JOIN dbo.MediaType AS m
ON t.MediaTypeId = m.MediaTypeId
INNER JOIN dbo.InvoiceLine AS il
ON t.TrackId = il.TrackId
INNER JOIN dbo.Invoice AS i
ON il.InvoiceId = i.InvoiceId
INNER JOIN dbo.Customer AS c
ON i.CustomerId = c.CustomerId
INNER JOIN dbo.Employee AS e
ON c.SupportRepId = e.EmployeeId
WHERE m.[Name] LIKE '%audio%'
CREATE UNIQUE CLUSTERED INDEX ux ON BoughtTracks (InvoiceLineId);
This will slow down inserts to these tables, but selecting on BoughtTracks will be fast (you can also create additional indexes on that view), something like:
SELECT *
FROM BoughtTracks WITH (NOEXPAND) -- NOEXPAND is important
WHERE CustomerName = 'Joe Smith'
Could execute orders of magnitude faster than your current query, depending of course on the size of your data. Especially if you create an index on it
CREATE INDEX ix_CustomerName ON BoughtTracks (CustomerName)
INCLUDE (...) -- maybe include some columns you know you will need when querying for CustomerName
WHERE (...) -- maybe there are alsways accompanying predicates when querying for CustomerName

Find Average Sales Amount Per Customer using AdventureWorks2012

I am following a tutorial using MS AdventureWorks2012 and I wanted to write a query to find average sales amount per customer (or in other words, Average Sales for each customer) using AdventureWorks2012. Below is my attempt and it doesn't run. What Am I doing wrong here ?
SELECT soh.CustomerID AS 'Customer ID'
,p.FirstName + ' ' + p.LastName AS 'Customer Name'
,AVG(soh.TotalDue) AS 'Average Sales Amount Per Customer'
FROM Sales.SalesOrderHeader AS soh
INNER JOIN Sales.Customer AS c ON c.CustomerID = soh.CustomerID
INNER JOIN Person.BusinessEntityContact AS bec ON bec.PersonID = c.PersonID
INNER JOIN Person.Person AS p ON p.BusinessEntityID = bec.BusinessEntityID
GROUP BY p.FirstName , p.LastName, soh.CustomerID;
Your query runs, it just returns an empty result set.
If you look at BusinessEntityContact, it relates a BusinessEntityID, which is a customer business, to a PersonID, who is a person that is the contact for the business. So if you change your query to this:
SELECT soh.CustomerID AS 'Customer ID', p.FirstName + ' ' + p.LastName AS 'Customer Name',
AVG(soh.TotalDue) AS 'Average Sales Amount Per Customer'
FROM Sales.SalesOrderHeader AS soh
INNER JOIN Sales.Customer AS c ON c.CustomerID = soh.CustomerID
INNER JOIN Person.BusinessEntityContact AS bec ON bec.PersonID = c.PersonID
INNER JOIN Person.Person AS p ON p.BusinessEntityID = bec.PersonID
GROUP BY p.FirstName , p.LastName, soh.CustomerID;
(note the third inner join)
You will get 635 rows.
In SQL single quote ' identifies a string. If you must to have spaces in your result column names then you can identify column names with double quotes " or preferably by wrapping the column name in square brackes.
SELECT
soh.CustomerID AS [Customer ID]
,p.FirstName + ' ' + p.LastName AS [Customer Name]
,AVG(soh.TotalDue) AS [Average Sales Amount Per Customer]
FROM Sales.SalesOrderHeader AS soh
INNER JOIN Sales.Customer AS c ON c.CustomerID = soh.CustomerID
INNER JOIN Person.BusinessEntityContact AS bec ON bec.PersonID = c.PersonID
INNER JOIN Person.Person AS p ON p.BusinessEntityID = bec.BusinessEntityID
GROUP BY p.FirstName , p.LastName, soh.CustomerID;
Otherwise your syntax looks okay to me

SQL Query Adding All Customers even if they don't have any sales

Below is my relatively simple sales query but I want it to pick up all of my dealers, not just the dealers that have sales. Is this possible?
SELECT dbo.tblDealer.DealerID, Firstname, LastName,
SUM(dbo.tblOrderDetail.PSVAmount) AS "PSV",
SUM(dbo.tblOrderDetail.UplineVolume) AS "Upline",
SUM(dbo.tblOrderDetail.LineTotal) AS "Product Total"
FROM tblOrderDetail
inner join tblOrder
on dbo.tblOrderDetail.OrderID = dbo.tblOrder.OrderID
inner join tblDealership on dbo.tblOrder.DealershipID = dbo.tblDealership.DealershipID
inner join tblDealer on dbo.tblDealership.DealerID = dbo.tblDealer.DealerID
where orderDate between '3/01/12' and '3/21/12 23:59:59'
and dbo.tbldealer.active = -1
GROUP BY dbo.tblDealer.DealerID, Firstname, LastName
You should change the order of your joins, and do an outer join instead of an inner join:
SELECT dbo.tblDealer.DealerID, Firstname, LastName,
SUM(dbo.tblOrderDetail.PSVAmount) AS "PSV",
SUM(dbo.tblOrderDetail.UplineVolume) AS "Upline",
SUM(dbo.tblOrderDetail.LineTotal) AS "Product Total"
FROM tblDealer
inner join tblDealership
on dbo.tblDealership.DealerID = dbo.tblDealer.DealerID
left join tblOrder
on dbo.tblOrderDetail.OrderID = dbo.tblOrder.OrderID AND orderDate between '3/01/12' and '3/21/12 23:59:59'
left join tblOrder
on dbo.tblOrderDetail.OrderID = dbo.tblOrder.OrderID
where dbo.tbldealer.active = -1
GROUP BY dbo.tblDealer.DealerID, Firstname, LastName