Using TOP 1 (or CROSS APPLY) within multiple joins - sql

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'
);

Related

Run a set number of joined select statements under a single sql query

I have a counselling appointment website. Currently I list clients in one table, but also have couples listed by id in a second table for when they book a couples session. ie:
client
id_no first last
564 John Smith
983 Mary Jones
999 Mark Fields
882 Joan Hancock
couple
id_no client1 client2
623 564 983
555 999 882
I would like to write a single select statement, using aliases, which will list out couples on a single line. Up until now, I have been doing a simple join then cleaning up the result using php after running the query, but would like to clean this up in sql so that I get a result like the following
id_no first_1 last_1 first_2 last_2
623 John Smith Mary Jones
555 Mark Fields Joan Hancock
I suspect that sub queries might be involved, but can't for the life of me wrangle them to get this result.
Update
I just tried the following:
SELECT id_no,first_1,last_1,first_2,last_2
FROM ( SELECT a.id_no AS id_no, b.first AS first_1,b.last AS last_1
FROM couple AS a, client AS b WHERE a.client1=b.id_no ) c1
JOIN ( SELECT a.id_no AS id_no, b.first AS first_2,b.last AS last_2
FROM couple AS a, client AS b WHERE a.client2=b.id_no ) c2 ON
(c1.id_no=c2.id_no)
And am getting a message that "Column 'id_no' in field list is ambiguous". Not sure if I am on the right track
You are getting the exception because you did not specified the table alias to id_no as this column belongs to both tables, so SQL is not sure which column to return, so it will throw ambiguous column error.
Also you don't have to use sub queries, you just need to join client table twice with couple table, one for client1 and other one for client2 like below
Select cp.id_no,
cl1.first as first_1,
cl1.last as last_1,
cl2.first as first_2,
cl2.last as last_2
From couple cp
inner join client cl1 on cl1.id_no = cp.client1
inner join client cl2 on cl2.id_no = cp.client2

Table Join issue

Right now I've got a Main table in which I am uploading data. Because the Main table has many different duplicates, I Append various data out of the Main table into other tables such as, username, phone number, and locations in order to keep things optimized. Once I have everything stripped down from the Main table, I then append what's left into a final optimized Main table. Before this happens though, I run a select query joining all the stripped tables with the original Main table in order to connect the IDs from each table, with the correct data. For example:
Original Main Table
--Name---------Number------Due Date-------Location-------Charges Monthly-----Charges Total--
John Smith 111-1111 4/3 Chicago 234.56 500.23
Todd Jones 222-2222 4/3 New York 174.34 323.56
John Smith 111-1111 4/3 Chicago 274.56 670.23
Bill James 333-3333 4/3 Orlando 100.00 100.00
This gets split into 3 tables (name, number, location) and then there is a date table with all the dates for the year:
Name Table Number Table Location Table Due Date Table
--ID---Name------ -ID--Number--------- ---ID---Location---- --Date---
1 John Smith 1 111-1111 1 Chicago 4/1
2 Todd Jones 2 222-2222 2 New York 4/2
3 Bill James 3 333-3333 3 Orlando 4/3
Before The Original table gets stripped, I run a select query that grabs the ID from the 3 new tables, and joins them based on the connection they have with the original Main table.
Select Output
--Name ID----Number ID---Location ID---Due Date--
1 1 1 4/3
2 2 2 4/3
1 1 1 4/3
3 3 3 4/3
My issue comes when I need to introduce a new table that isn't able to be tied into the Original Main Table. I have an inventory table that, much like the original Main table, has duplicates and needs to be optimized. I do this by creating a secondary table that takes all the duplicated devices out and put them in their own table, and then strips the username and number out and puts them into their tables. I would like to add the IDs from this new device table into the select output that I have above. Resulting in:
Select Output
--Name ID----Number ID---Location ID---Due Date--Device ID---
1 1 1 4/3 1
2 2 2 4/3 1
1 1 1 4/3 2
3 3 3 4/3 1
Unlike the previous tables, the device table has no relationship to the originalMain Table, which is what is causing me so much headache. I can't seem to find a way to make this happen...is there anyway to accomplish this?
Any two tables can be joined. A table represents an application relationship. In some versions (not the original) of Entity-Relationship Modelling (notice that the "R" in E-R stands for "(application) relationship"!) a foreign key is sometimes called a "relationship". You do not need other tables or FKs to join any two tables.
Explain, in terms of its column names and the values for those names, exactly when a row should turn up in the result. Maybe you want:
SELECT *
FROM the stripped-and-ID'd version of the Original AS o
JOIN the stripped-and-ID'd version of the Device AS d
USING NameID, NumberID, LocationID and DueDate
Ie
SELECT *
FROM the stripped-and-ID'd version of the Original AS o
JOIN the stripped-and-ID'd version of the Device AS d
ON o.NameID=d.NameId AND o.NumberID=d.NumberID
AND o.LocationID=d.LocationID AND o.DueDateID=d.DueDate.
Suppose p(a,...) is some statement parameterized by a,... .
If o holds the rows where o(NameID,NumberID,LocationID,DueDate) and d holds the rows where d(NameID,NumberID,LocationID,DueDate,DeviceID) then the above holds the rows where o(NameID, NumberID, LocationID, DueDate) AND d(NameID,NumberID,LocationID,DueDate,DeviceID). But you really have not explained what rows you want.
The only way to "join" tables that have no relation is by unioning them together:
select attribute1, attribute2, ... , attributeN
from table1
where <predicate>
union // or union all
select attribute1, attribute2, ... , attributeN
from table2
where <predicate>
the where clauses are obviously optional
EDIT
optionally you could join the tables together by stating ON true which will act like a cross product

Hibernate criteria left join with query

I have two classes Apartment and AdditionalSpace representing tables as below.
Apartment table
ID AREA SOLD
---- ------ ----
1 100 1
2 200 0
AdditionalSpace table
ID AREA APARTMENTID
---- ------ -----------
10 10 1
11 10 1
12 10 1
20 20 2
21 20 2
As you can see Apartment's table has a one-to-many relation with AdditionalSpace table, i.e. Apartment.ID=AdditionalSpace.APARTMENTID.
Question:- How to retrieve total area of a sold apartment including its additional space area.
The SQL which I have used so far to retrieve similar result is :-
select sum(apt.area + ads.adsarea) from apartment apt left outer join (select sum(area) as adsarea, apartmentid from additionalspace group by apartmentid) ads on ads.apartmentid=apt.id where apt.sold=1
I am struggling to find a way in order to implement the above scenario via criteria instead of SQL/HQL. Please suggest. Thanks.
I don't think this is possible in criteria. The closest I can see is to simply get the size of the apartment and the sum of the additional areas as two columns in your result, like this:
Criteria criteria = session.createCriteria(Apartment.class,"a");
criteria.createAlias("additionalSpaces", "ads");
criteria.setProjection(Projections.projectionList()
.add(Projections.property("area"))
.add(Projections.groupProperty("a.id"))
.add(Projections.sum("ads.area")));
Alternatively, if you still want to use Hibernate but are happy to write it in HQL, you can do the following:
select ads.apartment.id,max(a.area)+sum(ads.area)
from Apartment a
join a.additionalSpaces ads
group by ads.apartment.id
This works because HQL allows you to write the + to add together the two projections, but I don't know that an analogous method exists on the projections api.

Query to JOIN / *overwrite* field

I'm not sure if I'm using the correct terminology.
SELECT movies.*, actors.`First Name`, actors.`Last Name`
From movies
Inner Join actors on movies.`actor1` Where movies.`actor1` = actors.`indexActors`;
#Inner Join actors on movies.`actor2` Where movies.`actor2` = actors.`indexActors`;
I have the 2nd line commented out, each one works individually, and I'm wondering how to combine them.
2ndly, when I execute the query, I get the results:
ID Title Runtime Rating Actor1 Actor2 First Name Last Name
1 Se7en 127 R 1 2 Morgan Freeman
2 Bruce Almighty 101 PG-13 1 3 Morgan Freeman
3 Mr. Popper's Penguins 94 PG 3 4 Jim Carrey
4 Superbad 113 R 4 5 Emma Stone
5 Crazy, Stupid, Love. 118 PG-13 4 Null Emma Stone
Is there a way to add the results from the 2nd join to the rightmost columns?
Also, is it possible to combine the strings/VARCHARs from First Name and Last Name, and then have that value show up under the corresponding Actor Field?
(aka the field under Actor 1 for row 1 would be "Morgan Freeman" instead of "1")
Thanks.
Your sql is not valid, but you can achieve your goal by joining to the same table twice, with different aliases. This sort of thing
select blah blah blah
from table1 t1 join table2 t2 on t1.field1 = t2.field1
join table2 t2_again on t1.field1 = t2_again.field2
etc
As far as joining first and last names in a single field, most databases have a way to concatenate strings, but they are not all the same. You'll have to specify your db engine.

Compare 2 values of different types inside of subquery

I am using a MS SQL db and I have 3 tables: 'base_info', 'messages', 'config'
bases:
ID Name NameNum
====================================
1 Home 101
2 Castle 102
3 Car 103
messages:
ID Signal RecBy HQ
============================
111 120 Home 1
111 110 Castle 1
111 125 Car 1
222 120 Home 2
222 125 Castle 2
222 130 Car 2
333 100 Home 1
333 110 Car 2
config:
ID SignalRec SignalOut RecBy HQ
====================================
111 60 45 101 1
111 40 60 102 1
222 50 60 102 2
222 30 90 101 2
333 80 10 103 1
Ok so now I have a subquery in which I select the 'SignalRec' and 'SignalOut' from the config table and match it on the messages table by ID and Date(not included above), the problem is that I need it to match where messages.RecBy = config.RecBy but config.RecBy is a string but it's equivalent Name is in the bases table. So I almost need to do a subquery inside a subquery or some type of join and compare the returned value.
Here is what I have so far:
(SELECT TOP 1 config.SignalRec from config WHERE config.ID = messages.ID AND ||I need th other comparison here||...Order By...) As cfgSignalRec,
(SELECT TOP 1 config.SignalOut from config WHERE config.ID = messages.ID AND ||I need th other comparison here||...Order By...) As cfgSignalOut
I tried to make this as clear as possible but if you need more info let me know.
I would normalize out RecBy in your messages table to reference the bases table. Why would you insert the string content there if it's also referenced in bases?
This is exactly why normalization exists: reduce redundancy, reduce ambiguity, and enforce referential integrity.
To make this more clear, RecBy in the messages table should be a foreign key to Bases.
I think this could do the trick (although I have not tried it...)
SELECT
c.SignalRec
FROM config c
INNER JOIN bases b
ON c.RecBy = b.NameNum
INNER JOIN messages m
ON b.Name = m.RecBy
WHERE c.ID = m.ID
However, as Anthony pointed out, you probably want to normalize out the strings in the RecBy column in the messages table, as you have the same data in the bases table.
From your description, it just sounds like you need two JOINS
SELECT TOP 1
c.SignalRec
FROM
config c
INNER JOIN
bases b
ON c.RecBy = b.NameNum
INNER JOIN
messages m
ON b.Name = m.RecBy
I think I might have not been clear enough what I wanted to do, sorry about that.
The data is actually different in the 2 tables, although the correlations are the same. It's kind of confusing to explain without going into detail about how the system works.
I actually found a very fast way of doing this.
Inside my sub-query I do this:
(SELECT TOP 1 config.Signal FROM config,bases
WHERE config.ID = messages.ID AND bases.Name = messages.RecBy AND bases.NameNum =
config.RecBy Order By...)
So this essentially compares the 2 RecBy's of different tables even though one is an integer and the other is a string. It reminds me of a match and look up in Excel.