How to turn JOINS into Subqueries without breaking the SQL logic - sql

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

Related

Joining tables and finding values that do not exist

I am having some issues with joining tables to get null values, and I can't find what I am doing wrong.
The case: I am trying to make a cinema system, where I have made entities that match the cinema.
I have a Hall, Row and Seat table, and a Show table that holds the value for movies and what hall it will be played in. To bond everything together, I have made a Reservation table that is keeping track of what seats to that specific show is taken.
My entities look like this:
My problem: I am trying to fetch all free seats for the show, I can get all seats for the show, but when I try to add the Reservation to get the free ones I get no records.
My query that is able to fetch all seats:
SELECT show.id AS ShowID,
seat.id AS SeatID,
seat.rowid AS RowID,
show.hallid AS HallId,
reservation.seatid AS Expr1
FROM show
INNER JOIN hall
ON show.hallid = hall.id
FULL OUTER JOIN seat
ON hall.id = seat.hallid
LEFT OUTER JOIN reservation
ON reservation.showid = show.id
WHERE ( show.id = 1 )
AND ( reservation.seatid IS NULL )
ORDER BY reservation.showid,
rowid
You need INNER joins between Show, Hall, Row and Seat and a LEFT join to Reservation, so you can filter out the matched rows:
SELECT s.Id AS ShowID, t.Id AS SeatID, t.RowId AS RowID, s.HallId
FROM Show s
INNER JOIN Hall h ON h.Id = s.HallId
INNER JOIN Seat t ON t.HallId = h.Id
INNER JOIN Row w ON w.HallId = h.Id AND w.Id = t.RowId
LEFT JOIN Reservation r ON r.ShowId = s.Id AND r.HallId = h.Id AND r.SeatId = t.Id AND r.RowId = w.Id
WHERE (s.Id = 1) AND (r.SeatId IS NULL)
Replace:
INNER JOIN Hall ON Show.Id = Hall.Id FULL OUTER JOIN
With:
INNER JOIN Hall ON Show.HallId = Hall.Id FULL OUTER JOIN
While it might not be the full answer to your question, i think this might cause issues for you too.

Fetching columns, 4 tables

I’ve ran in to some problems with the following SQL assignment. I’m to retrieve the ID, firstname and lastname of any member who is registered in section that contains the word ‘xyz’.
So far I’ve managed the following:
SELECT m.id, p.firstname, p.lastname FROM member m
INNER JOIN person p ON m.id = p.id
WHERE m.id IN (SELECT id FROM membersection);
How do I go forward from here? I have no idea how to retrieve the sectionid from the membersection table then fetch the section name from the section id using that ID so I can check if the section name contains the previously stated word.
member:
id
member_number
registration_date
membersection:
memberid
sectionid
person:
id
firstname
lastname
section:
id
name
Just keep joining. And in the end use LIKE to check if section.name contains 'xyz'.
SELECT m.id,
p.firstname,
p.lastname
FROM member m
INNER JOIN person p
ON p.id = m.id
INNER JOIN membersection ms
ON ms.memberid = m.id
INNER JOIN section s
ON s.id = ms.sectionid
WHERE s.name LIKE '%xyz%';
There is some ambiguity in your question with regards to how your data are structured; what are the primary and foreign keys?
But, making some assumptions, you're almost there, you can chain multiple join statements together:
select
m.id,
p.firstname,
p.lastname
from
member m
inner join person p on
m.id = p.id
inner join membersection ms on
m.id = ms.memberid
inner join section s on
ms.sectionid = s.id
where
s.name like '%xyz%'
It's not super obvious what's going on with your Data Relationships, but this would be the basic route you might want to take (LEFT JOIN as I do not know the relationship):
SELECT m.id, p.firstname, p.lastname, ms.sectionid
FROM member m
INNER JOIN person p ON m.id = p.id
LEFT JOIN section s ON m.id = s.id
LEFT JOIN membersection ms ON m.id = ms.memberid
WHERE s.name = 'xyz'

WHERE clause in an SQL query

I THINK what is happening with this query is if there are no records in the GenericAttribute table associated with the Product, then that product is not displayed. See line below in WHERE clause: "AND GenericAttribute.KeyGroup = 'Product'"
Is there a way to reword so that that part of the WHERE is ignored if no associated record in the GenericAttribute table?
Also, looking at my ORDER BY clause, will a record from the product table still show up if it has no associated record in the Pvl_AdDates table?
Thanks!
SELECT DISTINCT Product_Category_Mapping.CategoryId, Product.Id, Product.Name, Product.ShortDescription, Pvl_AdDates.Caption, Pvl_AdDates.EventDateTime, convert(varchar(25), Pvl_AdDates.EventDateTime, 120) AS TheDate, Pvl_AdDates.DisplayOrder, Pvl_Urls.URL, [Address].FirstName, [Address].LastName, [Address].Email, [Address].Company, [Address].City, [Address].Address1, [Address].Address2, [Address].ZipPostalCode, [Address].PhoneNumber
FROM [Address]
RIGHT JOIN (GenericAttribute
RIGHT JOIN (Pvl_Urls RIGHT JOIN (Pvl_AdDates
RIGHT JOIN (Product_Category_Mapping
LEFT JOIN Product
ON Product_Category_Mapping.ProductId = Product.Id)
ON Pvl_AdDates.ProductId = Product.Id)
ON Pvl_Urls.ProductId = Product.Id)
ON GenericAttribute.EntityId = Product.Id)
ON Address.Id = convert(int, GenericAttribute.Value)
WHERE
Product_Category_Mapping.CategoryId=12
AND GenericAttribute.KeyGroup = 'Product'
AND Product.Published=1
AND Product.Deleted=0
AND Product.AvailableStartDateTimeUtc <= getdate()
AND Product.AvailableEndDateTimeUtc >= getdate()
ORDER BY
Pvl_AdDates.EventDateTime DESC,
Product.Id,
Pvl_AdDates.DisplayOrder
I strongly encourage you to not mix left join and right join. I have written many SQL queries and cannot think of an occasion when that was necessary.
In fact, just stick to left join.
If you want all products (or at least all products not filtered out by the where clause), then start with the products table and go from there:
FROM Products p LEFT JOIN
Product_Category_Mapping pcm
ON pcm.ProductId = p.Id LEFT JOIN
Pvl_AdDates ad
ON ad.ProductId = p.id LEFT JOIN
Pvl_Urls u
ON u.ProductId = p.id LEFT JOIN
GenericAttribute ga
ON ga.EntityId = p.id LEFT JOIN
Address a
ON a.Id = convert(int, ga.Value)
Note that I added table aliases. These make queries easier to write and to read.
I would add a caution. It looks like you are combining data along different dimensions. You are likely to get a Cartesian product of the dimension attributes for each dimension. Perhaps that is what you want or the WHERE clause takes care of the additional rows.
Yes put constraints (restrictions) on tables on the outer side of outer joins in the on conditions of the outer join, not in the where clause. Conditions in where clauses are not evaluated and applied until after the outer joins are evaluated, so where there is not record in the outer table, the predicate will be false and entire row will be eliminated, undoing the outer-ness. Conditions in the join are evaluated during the join, before the rows from the inner side are added back in, so the result set will still include them.
Second, formatting formatting, formatting! Stick to one direction of join (left is easier) and use Aliases for tables names!
SELECT DISTINCT m.CategoryId, p.Id,
p.Name, p.ShortDescription, d.Caption, d.EventDateTime,
convert(varchar(25), d.EventDateTime, 120) TheDate,
d.DisplayOrder, u.URL, a.FirstName, a.LastName,
a.Email, a.Company, a.City, a.Address1, a.Address2,
a.ZipPostalCode, a.PhoneNumber
FROM Product_Category_Mapping m
left join Product p on p.Id = m.ProductId
and p.Published=1
and p.Deleted=0
and p.AvailableStartDateTimeUtc <= getdate()
and p.AvailableEndDateTimeUtc >= getdate()
left join Pvl_AdDates d ON d.ProductId = p.Id
left join Pvl_Urls u ON u.ProductId = p.Id
left join GenericAttribute g ON g.EntityId = p.Id
and g.KeyGroup = 'Product'
left join [Address] a ON a.Id = convert(int, g.Value)
WHERE m.CategoryId=12
ORDER BY d.EventDateTime DESC, p.Id, d.DisplayOrder

SQL Left Joining four different tables

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

SQL Select statement - multiple tables allow null values

I do realise that problem I am facing is not a rocket science but still, I did not find any information about fixing this.
I have multiple tables in my database (PSQL) I want to create a select query to make a reporting function for my app.
Here is my query:
select
s.id, s.name, st.name, p.firstname || ' ' || p.lastname,
f.name, f.store_date, bdt.name, bd.comment
from
system s, systemstatus st, role w, person p, file f,
documenttype bdt, document bd
where
w.system_id = s.id and
p.id = w.person_id and
st.id = s.status_id and
bd.system_id = s.id and
bd.file_id = f.id and
bd.type_id = bdt.id and
bd.role_id = w.id;
Query works I get 300 rows fully filled with values I am searching for. Problem is that I have about 1000 rows in System Table. It is possible that there is no Person or Document which could be linked with particular System.
I would like to see all rows that are in my System table (I mean about 1000), and when I can't link Person or Document with System I want the field to be null ( now it is not shown at all)
Main part of the answer is - you need left outer join.
Additional parts of the answer - use ANSI join syntax and format your queries:
select
s.id, s.name, st.name, p.firstname || ' ' || p.lastname, f.name,
f.store_date, bdt.name, bd.comment
from system as s
left outer join systemstatus as st on st.id= s.status_id
left outer join role as w on w.system_id = s.id
left outer join person as p on p.id = w.person_id
left outer join document as bd on bd.system_id = s.id and bd.role_id = w.id
left outer join documenttype as bdt on bdt.id = bd.type_id
left outer join file as f on f.id = bd.file_id
Always remember that somebody will read your code someday (may be it will be future you :) ) - so readability counts!
You are connecting your tables in query via "where" clause, which is equal to inner join. Instead you should left join Person and Document tables to System table, e.g.:
select *
from system s
left join role w on w.system_id = s.id
left join person p on p.id = w.person_id