In Oracle SQL is there a way to join on a value twice? - sql

Lets say I have two tables, with the following columns:
cars
motorcycle_id | fuel_id | secondary_fuel_id ...
fuel_types
fuel_id | fuel_label | ...
In this case fuel_id and secondary fuel_id both refer to the fuel_types table.
Is it possible to include both labels in an inner join? I want to join on the fuel_id but I want to be able to have the fuel label twice as a new column. So the join would be something like:
motorcycle_id | fuel_id | fuel_label | secondary_fuel_id | secondary_fuel_label | ...
In this case I would have created the secondary_fuel_label column.
Is this possible to do in SQL with joins? Is there another way to accomplish this?

You would just join twice:
select c.*, f1.fuel_label, f2.fuel_label as secondary_fuel_label
from cars c left join
fuel_types f1
on c.fuel_id = f1.fuel_id left join
fuel_types f2
on c.fuel_id = f1.secondary_fuel_id ;
The key point here is to use table aliases, so you can distinguish between the two table references to fuel_types.
Note that this uses left join to be sure that rows are returned even if one of the ids are missing.

Related

GROUPBY many to many with itself

I have a User table that has a many to many relation with itself, and I would like to get all the pairs of users with this specific relation. The problem is, in the relation table I store users like that:
+------+---------------+
| User | relation |
+------+---------------+
| id | left_user_id |
| name | right_user_id |
| ... | ... |
+------+---------------+
So when I do a basic
SELECT count(*)
FROM relation LEFT OUTER JOIN user AS user_1 ON user_1.id = relation.left_user_id
LEFT OUTER JOIN user AS user_2 ON user_2.id = relation.right_user_id
GROUP BY left_user_id, right_user_id;
I sometimes get two results for the same pair (for example sometimes (Adam, Eva) and (Eva, Adam) which are the same pair). What I would like to achieve is just one pair: (Adam, Eva).
How can this be achieved?
You can use the functions least() and greatest():
SELECT count(*)
FROM relation r
LEFT OUTER JOIN user AS user_1 ON user_1.id = r.left_user_id
LEFT OUTER JOIN user AS user_2 ON user_2.id = r.right_user_id
GROUP BY LEAST(r.left_user_id, r.right_user_id), GREATEST(r.left_user_id, r.right_user_id);
Or in this case where you don't need the joins:
SELECT count(*)
FROM relation
GROUP BY LEAST(left_user_id, right_user_id), GREATEST(left_user_id, right_user_id);
The left joins should not be necessary. The key is simply using least() and greatest(). That would be:
SELECT LEAST(r.left_user_id, r.right_user_id) as user_id_1,
GREATEST(r.left_user_id, r.right_user_id) as user_id_2,
COUNT(*)
FROM relation
GROUP BY user_id_1, user_id_2;
The one caveat with this approach is that the pair in the result set may not be in the original data -- in that order. So, if you had "Eve"/"Adam" once in the data, then it would return: "Adam"/"Eve"/1. That can be addressed, if necessary.

With SQL, how can I find non-matches in a single table many-to-many relation?

I have a database that, among other things, records the results of reactions between ingredients. It's currently structured with the following three tables:
| Material |
|----------------|
| id : Integer |
| name : Varchar |
| Reaction |
|-----------------|
| id : Integer |
| <other details> |
| Ingredient |
|-----------------------|
| material_id : Integer |
| reaction_id : Integer |
| quantity : Real |
This maps the many-to-many relationship between materials and reactions.
I would like to run a query that returns every pair of materials that do not form a reaction. (i.e. every pair (x, y) such that there is no reaction that uses exactly x and y and no other materials.) In other circumstances, I would do this with a LEFT JOIN onto the intermediate table and then look for NULL reaction_ids. In this case, I'm getting the pairs by doing a CROSS JOIN on the materials table and itself, but I'm not sure how (or whether) doing two LEFT JOINs onto the two materials aliases can work.
How can this be done?
I'm most interested in a generic SQL approach, but I'm currently using SQLite3 with SQLAlchemy. I have the option of moving the database to PostgreSQL, but SQLite is strongly preferred.
Use a cross join to generate the list and then remove the pairs that are in the same reaction.
select m.id, m2.id as id2
from materials m cross join
materials m2
where not exists (select 1
from ingredient i join
ingredient i2
on i.reaction_id = i2.reaction_id and
i.material_id = m.id and
i2.material_id = m2.id
);
Although this query looks complicated, it is essentially a direct translation of your question. The where clause is saying that there are not two ingredients for the same reaction that have each of the materials.
For performance, you want an index on ingredient(reaction_id, material_id).
EDIT:
If you like, you can do this without an exists, using a left join and where:
select m.id, m2.id
from materials m cross join
materials m2 left join
ingredients i
on i.material_id = m.id left join
ingredients i2
on i2.material_id = m2.id and
i2.reaction_id = m2.reaction_id
where i2.reaction_id is null;

'Implicit' JOIN based on schema's foreign keys?

Hello all :) I'm wondering if there is way to tell the database to look at the schema and infer the JOIN predicate:
+--------------+ +---------------+
| prices | | products |
+--------------+ +---------------+
| price_id (PK)| |-1| product_id(PK)|
| prod_id |*-| | weight |
| shop | +---------------+
| unit_price |
| qty |
+--------------+
Is there a way (preferably in Oracle 10g) to go from:
SELECT * FROM prices JOIN product ON prices.prod_id = products.product_id
to:
SELECT * FROM pricesIMPLICIT JOINproduct
The closest you can get to not writing the actual join condition is a natural join.
select * from t1 natural join t2
Oracle will look for columns with identical names and join by them (this is not true in your case). See the documentation on the SELECT statement:
A natural join is based on all columns in the two tables that have the same name. It selects rows from the two tables that have equal values in the relevant columns. If two columns with the same name do not have compatible data types, then an error is raised
This is very poor practice and I strongly recommend not using it on any environment
You shouldnt do that. Some db systems allow you to but what if you modify the fk's (i.e. add foreign keys)? You should always state what to join on to avoid problems. Most db systems won't even allow you to do an implicit join though (good!).

How to build virtual columns?

Sorry if this is a basic question. I'm fairly new to SQL, so I guess I'm just missing the name of the concept to search for.
Quick overview.
First table (items):
ID | name
-------------
1 | abc
2 | def
3 | ghi
4 | jkl
Second table (pairs):
ID | FirstMember | SecondMember Virtual column (pair name)
-------------------------------------
1 | 2 | 3 defghi
2 | 1 | 4 abcjkl
I'm trying to build the virtual column shown in the second table
It could be built at the time any entry is made in the second table, but if done that way, the data in that column would get wrong any time one of the items in the first table is renamed.
I also understand that I can build that column any time I need it (in either plain requests or stored procedures), but that would lead to code duplication, since the second table can be involved in multiple different requests.
So is there a way to define a "virtual" column, that could be accessed as a normal column, but whose content is built dynamically?
Thanks.
Edit: this is on MsSql 2008, but an engine-agnostic solution would be preferred.
Edit: the example above was oversimplified in multiple ways - the major one being that the virtual column content isn't a straight concatenation of both names, but something more complex, depending on the content of columns I didn't described. Still, you've provided multiple paths that seems promising - I'll be back. Thanks.
You need to join the items table twice:
select p.id,
p.firstMember,
p.secondMember,
i1.name||i2.name as pair_name
from pairs as p
join items as i1 on p.FirstMember = i1.id
join items as i2 on p.SecondMember = i2.id;
Then put this into a view and you have your "virtual column". You would simply query the view instead of the actual pairs table wherever you need the pair_name column.
Note that the above uses inner joins, if your "FirstMember" and "SecondMember" columns might be null, you probably want to use an outer join instead.
You can use a view, which creates a table-like object from a query result, such as the one with a_horse_with_no_name provided.
CREATE VIEW pair_names AS
SELECT p.id,
p.firstMember,
p.secondMember,
CONCAT(i1.name, i2.name) AS pair_name
FROM pairs AS p
JOIN items AS i1 ON p.FirstMember = i1.id
JOIN items AS i2 ON p.SecondMember = i2.id;
Then to query the results just do:
SELECT id, pair_name FROM pair_names;
You could create a view for your 'virtual column', if you wanted to, like so:
CREATE VIEW aView AS
SELECT
p.ID,
p.FirstMember,
p.SecondMember,
a.name + b.name as 'PairName'
FROM
pairs p
LEFT JOIN
items a
ON
p.FirstMember = a.ID
LEFT JOIN
items b
ON
p.SecondMember = b.ID
Edit:
Or, of course, you could just use a similar select statement every time.
When selecting from tables you can name the results of a column using AS.
SELECT st.ID, st.FirstMember, st.SecondMember, ft1.Name + ft2.Name AS PairName
FROM Second_Table st
JOIN First_Table ft1 ON st.FirstMember = ft1.ID
JOIN First_Table ft2 ON st.SecondMember = ft2.ID
Should give you something like what you are after.

What kind of SQL join do I need to compress a One to Many relationship into the same view row?

Edit: this isn't to be a dynamic output, the output view structure is fixed.
I am trying to create a SQL Server view that shows a single fixed column row for each user, and flattens out an associated one to many table into that row.
Although the associated table has a one to many relationship, the output table structure is limited to 4 elememts form that table.
My table structure is like so:
User (Id, FirstName, LastName)
Assessment (Id, Date, Location, User_Id)
Topics (Id, Topic, Assessment_Id)
Where the Assessment is joined to the User by the User_Id (One 2 One), and the Topics are joined to the Assessment by the Assessment_Id.
So, if I have three topics for an assessment, I'd want the view to look something like:
User_Id | FirstName | LastName | Date | Location | Topic1 | Topic2 | Topic3 | Topic4 |
1 | dave | toby | 2/2/11 | In situ | apples | pears | lemons | NULL |
My current SQL looks like this:
SELECT User.Id, User.FirstName, User.LastName, Assessment.Date, Assessment.Location, Topic.Topic
FROM User LEFT OUTER JOIN
Assessment INNER JOIN
Topic ON Assessment.Id = Topic.Assessment_Id ON
User.Id = Assessment.User_Id
But this returns a row for each concern - it doesn't compress them to one line. I've played with a few different joins, but haven't been able to get the behaviour I want.
Is it possible to do this in a view?
What do I need to do to make it happen??
Thanks!
There is no such JOIN. SQL has a fixed column output: so you can't add arbritrary numbers of columns. It doesn't matter if it's a view, direct or in a stored procedure.
There are 2 main options
concatenate the many rows into one column which is a popular questions here on SO. One random solution using XML PATH
use dynamic SQL to add a column per row in a stored procedure.
Note: PIVOT is fixed column output too
Edit: for a maximum of 4 child rows
SELECT
P.col1, P.col2,
C1.col1 AS Topic1,
C2.col1 AS Topic2,
C3.col1 AS Topic2,
C4.col1 AS Topic4
FROM
Parent P
LEFT JOIN
Child C1 ON P.Key = C1.FKey AND C1.ID = 1
LEFT JOIN
Child C2 ON P.Key = C2.FKey AND C2.ID = 2
LEFT JOIN
Child C3 ON P.Key = C3.FKey AND C3.ID = 3
LEFT JOIN
Child C4 ON P.Key = C4.FKey AND C4.ID = 4
You can use PIVOT too but I prefer the simpler self joins.
Take a look at PIVOT table functionality - e.g. http://www.help-sql.info/27/9/610208.html and http://blog.sqlauthority.com/2008/05/22/sql-server-pivot-table-example/
Although you will need to know the AssessmentId's before you can write the PIVOT