SQL: Proper Join Syntax - sql

Suppose I have two tables, one with blog posts and another with readers and their comments:
Table 1:
table name: BlogPosts:
structure:
id (int)
title (string)
Table 2:
table name: Readers:
id (int)
blog_post_id (int)
name (string)
comment (string)
in the Readers table there is a unique composite key on blog_post_id/name (i.e. one comment per reader per post), though this may not matter for my question.
I want to be able to do a single query tells me what a particular reader's comment was on each BlogPost, but it should include BlogPosts for which there is no comment entered for that reader (so the query should return one row for each blog post in the database).
I tried several variations that look like this:
SELECT
BlogPosts.id,
BlogPosts.title,
Readers.name,
Readers.comment
FROM
BlogPosts
RIGHT JOIN Readers ON
(BlogPosts.id = Readers.blog_post_id)
WHERE Readers.name = "joe"
..this just returns the rows where there is actually a comment from joe. Other variations where I was able to get all of the blog posts gave me an invalid identifier whenever I included the where clause.
I am using Oracle Express 10g in case that makes any difference.
Thanks for any help.

Everything looks correct except that you want to move the WHERE clause into the join, like this:
SELECT BlogPosts.id,
BlogPosts.title,
Readers.name,
Readers.comment
FROM BlogPosts
RIGHT JOIN Readers ON BlogPosts.id = Readers.blog_post_id
AND Readers.name = 'joe'
You effectively make the OUTER JOIN into an INNER one when you put the Readers.name = 'joe' in the WHERE clause.
Edit:
Thanks to the OP for clarification in the comments.
What you describe in your most recent comment can be achieved simply by switching from a RIGHT JOIN to a LEFT JOIN (as suggested earlier on by another commenter).
Note that you can get more than one row per BlogPost when there are multiple comments per post.

Related

SQL join with references to references

I am still new to SQL. I have been making good progress on my project until I ran into this problem. I have tried to search the net for this kind of problem but I cannot find anything specific to this or I am not using the correct keywords in my search.
I have three tables relevant to this problem. Apparently I do not have any kind of "describe table" command to be able to copy the output. My other queries with joins are working as expected.
Table 1 - "Sites"
ID int auto-increment key, site_code short text, site_name short text, more but not relevant.
Table 2 - "Hubs"
ID int auto-increment key, HUB int (lookup from Sites.ID), more but not relevant.
Table 3 - "DialPlan"
ID int auto-increment key, site int (lookup from Sites.ID), HUB int (lookup from Hubs.HUB), more but not relevant.
When viewing the query for "DialPlan" I need to see "DialPlan.site" being replaced by "Sites.site_code" for that specific int. I need to see "DialPlan.HUB" being replaced by "Sites.site_code" for that specific int. Example of table output without joins:
DialPlan: 28, 29, 2, 203 That last number is not relevant.
Sites.ID = 29, Sites.site_name = BENN. Hubs.ID = 2, Hubs.HUB = 27, Sites.ID = 27, Sites.site_name = BRAG. So, the output I need to see when using the join is: 28, BENN, BRAG, 203. I am not getting that, I am getting: 28, BENN, BENN, 203.
My search query is:
select
Sites.site_code, Sites.site_name, Sites.site_code as Hubs.HUB,
DialPlan.OC
from
DialPlan
left join
Sites on DialPlan.site = Sites.ID
left join
Hubs on DialPlan.HUB = Hubs.ID
left join
Hubs on Hubs.HUB = Sites.ID;
I have tried to change field 3 using "AS" and even tried "=" and several other things. If I try to put field 3 as "Sites.site_code" then the output is the same as the first "Sites.site_code" lookup. I am not sure how to proceed. I have tried so many things now that I am not even sure exactly what I have tried. I saw one thread where there were multiple dots per column and I have no idea what that is used for. Does anyone have any ideas?
OK, I have found the answer and it is called an inner query. Actually there are two inner queries. I am posting this so that others may benefit from this. The problem with referencing the first table from two fields that are on the same table is that SQL cannot determine that the second call means a new search. So, you must perform an inner query to resolve this. Here is the code. As an Access admin I have always been able to get this kind result easy but I am behind the power curve for SQL. Here is the working code sample.
SELECT Sites.site_code, Sites.site_name,
( SELECT site_code FROM Sites WHERE ID =
( SELECT HUB FROM Hubs WHERE DialPlan.HUB = Hubs.ID )
),
DialPlan.OC
FROM DialPlan
LEFT JOIN Sites
ON DialPlan.site = Sites.ID;
The output is what I was looking for, "BENN Benning BRAG 203". I hope this helps someone.
When you use the AS clause you are just giving that column a title for your table. It will not fill in the data from your alias in place of the data you are aliasing.
If you want to list the data for Hubs.hub you should SELECT Hubs.hub and then give it the title you want using AS mytitle.
Also you dint need that last join as your tables are already joined.
Also, the describe command is DESC and it should work... DESC sites
select
Sites.site_code, Sites.site_name, Hubs.HUB as Site, DialPlan.OC
from
DialPlan
left join
Sites on DialPlan.site = Sites.ID
left join
Hubs on DialPlan.HUB = Hubs.ID

SQL Join Tables - 2nd newest entry to related main entry in other table

following abstract scenario. I have 2 tables. One is containing "posts" and the other "comments"
If you create a new "post", there is also a new "comment" added of type "initial". Whenever you now add a real comment, it gets the postID and a type like "picture","text", "movie" ...
I now need to get one table showing the initial posts date and the first "real" comment. I want to know how long it took until the first comment was written.
SELECT post.ID,post.Title,post.Author,post.CreateDate,
comment.Type,comment.Text,comment.CreateDate
FROM Posts as post
INNER JOIN Comments as comments on post.ID = comment.PostID
That gives me the Post Info for the amount of comments.
Now I want to just have the very first "real" comment date standing next to the createDate of the post ( where comment.Type != 'initial')
Don't know how to do that. Could someone help me in this case?
It should be possible to run it also on MS SQL 2000...
Thanks in advance...
try this:
SELECT post.ID,max(post.Title),max(post.Author),min(post.CreateDate),
min(comment.CreateDate),
datediff(mi,min(post.CreateDate),min(comment.CreateDate)) as 'first comment in minutes'
FROM Posts as post
INNER JOIN Comments as comments on post.ID = comment.PostID
where comment.Type != 'initial'
group by post.id

Linking Three Tables together

I'm creating an archive for Academic Papers. Each paper may have one author, or multiple authors. I've created the tables in the following manner:
Table 1: PaperInfo - Each row contains information on the paper
Table 2: PaperAuthor - Only Two Columns: contains PaperID, and AuthorID
Table 3: AuthorList - Contains Author Information.
There is also a Table 4 which is linked to Table 4, which contains a list of Universities which the author belongs to, but I'm going to leave it out for now in case it gets too complicated.
I wish to have a Query which will link all three tables together, and display Paper Information of the recordset in a table, with columns such as these:
Paper Title
Paper Authors
The column "Paper Authors" is going to contain more than one authors in some cases.
I've wrote the following query:
SELECT a.*,b.*,c.*
FROM PaperInfo a, PaperAuthor b, AuthorList c
WHERE a.PaperID = b.PaperID AND b.AuthorID = c.AuthorID
So far, the results I've been getting for each row is one author per row. I wish to contain more authors in one column. Can this be done in anyway?
Note: I'm using Access 2010 as my database.
In straight SQL the answer unfortunately is that it isn't possible. You would need to use a processing language in order to get the result you are after.
Since you mention you are using Access 2010 please refer to this question: is there a group_concat function in ms-access?
Particularly, read the post which points to http://www.rogersaccesslibrary.com/forum/generic-function-to-concatenate-child-records_topic16&SID=453fabc6-b3z9-34z6zb14-a78f832z-19z89a2c.html
You probably need to implement a custom function but the 2nd url does what you are looking for.
This functionality is not part of the SQL standard, but different vendors have solutions for it, see for instance Pivot Table with many to many table, MySQL pivot table.
If you know the maximum number of authors per paper (for example 3 or 4), you could get away with a triple or quadruple left join.
What you are after is an inner join.
An SQL JOIN clause is used to combine rows from two or more tables, based on a common field between them.
The most common type of join is: SQL INNER JOIN (simple join). An SQL INNER JOIN return all rows from multiple tables where the join
condition is met.
http://www.w3schools.com/sql/sql_join.asp
You may want to combine the inner join with a group to give you 1 paper to many authors in your results.
The GROUP BY statement is used in conjunction with the aggregate
functions to group the result-set by one or more columns.
http://www.w3schools.com/sql/sql_groupby.asp

how do i display all the tags related to all the feedbacks in one query

I am trying to write a sql query which fetches all the tags related to every topic being displayed on the page.
like this
TITLE: feedback1
POSTED BY: User1
CATEGORY: category1
TAGS: tag1, tag2, tag3
TITLE: feedback2
POSTED BY: User2
CATEGORY: category2
TAGS: tag2, tag5, tag7,tag8
TITLE: feedback3
POSTED BY: User3
CATEGORY: category3
TAGS: tag1, tag5, tag6, tag3
The relationship of tags to topics is many to many.
Right now I am first fetching all the topics from the "topics" table and to fetch the related tags of every topic I loop over the returned topics array for fetching tags.
But this method is very expensive in terms of speed and not efficient too.
Please help me write this sql query.
Query for fetching all the topics and its information is as follows:
SELECT
tbl_feedbacks.pk_feedbackid as feedbackId,
tbl_feedbacks.type as feedbackType,
DATE_FORMAT(tbl_feedbacks.createdon,'%M %D, %Y') as postedOn,
tbl_feedbacks.description as description,
tbl_feedbacks.upvotecount as upvotecount,
tbl_feedbacks.downvotecount as downvotecount,
(tbl_feedbacks.upvotecount)-(tbl_feedbacks.downvotecount) as totalvotecount,
tbl_feedbacks.viewcount as viewcount,
tbl_feedbacks.title as feedbackTitle,
tbl_users.email as userEmail,
tbl_users.name as postedBy,
tbl_categories.pk_categoryid as categoryId,
tbl_clients.pk_clientid as clientId
FROM
tbl_feedbacks
LEFT JOIN tbl_users
ON ( tbl_users.pk_userid = tbl_feedbacks.fk_tbl_users_userid )
LEFT JOIN tbl_categories
ON ( tbl_categories.pk_categoryid = tbl_feedbacks.fk_tbl_categories_categoryid )
LEFT JOIN tbl_clients
ON ( tbl_clients.pk_clientid = tbl_feedbacks.fk_tbl_clients_clientid )
WHERE
tbl_clients.pk_clientid = '1'
What is the best practice that should be followed in such cases when you need to display all the tags related to every topic being displayed on a single page.
How do I alter the above sql query, so that all the tags plus related information of topics is fetched using a single query.
For a demo of what I am trying to achieve is similar to the'questions' page of stackoverflow.
All the information (tags + information of every topic being displayed) is properly displayed.
Thanks
To do this, I would have three tables:
Topics
topic_id
[whatever else you need to know for a topic]
Tags
tag_id
[etc]
Map
topic_id
tag_id
select t.[whatever], tag.[whatever]
from topics t
join map m on t.topic_id = m.topic_id
join tags tag on tag.tag_id = m.tag_id
where [conditionals]
Set up partitions and/or indexes on the map table to maximize the speed of your query. For example, if you have many more topics than tags, partition the table on topics. Then, each time you grab all the tags for a topic, it will be 1 read from 1 area, no seeking needed. Make sure to have both topics and tags indexed on their _id.
Use your 'explain plan' tool. (I am not familiar with mysql, but I assume there is some tool that can tell you how a query will be run, so you can optimize it)
EDIT:
So you have the following tables:
tbl_feedbacks
tbl_users
tbl_categories
tbl_clients
tbl_tags
tbl_topics
tbl_topics_tags
The query you provide as a starting point shows how feedback, users, categories and clients relate to each other.
I assume that tbl_topics_tags contains FKs to tags and topics, showing which topic has which tag. Is this correct?
What of (feedbacks, users, categories, and clients) has a FK to topics or tags? Or, do either topics or tags have a FK to any of the initial 4?
Once I know this, I'll be able to show how to modify the query.
EDIT #2
There are two different ways to go about this:
The easy way is the just join on your FK. This will give you one row for each tag. It is much easier and more flexible to put together the SQL to do it this way. If you are using some other language to take the results of the query and translate them to present them to the user, this method is better. If nothing else, it will be far more obvious what is going on, and will be easier to debug and maintain.
However, you may want each row of the query results to contain one feedback (and the tags that go with it).
SQL joining question <- this is a question I posted on how to do this. The answer I accepted is an oracle-only answer AFAIK, but there are other non-oracle answers.
Adapting Kevin's answer (which is supposed to work in SQL92 compliant systems):
select
[other stuff: same as in your post],
(select tag
from tbl_tag tt
join tbl_feedbacks_tags tft on tft.tag_id = tt.tag_id
where tft.fk_feedbackid = tbl_feedbacks.pk_feedbackid
order by tag_id
limit 1
offset 0 ) as tag1,
(select tag
from tbl_tag tt
join tbl_feedbacks_tags tft on tft.tag_id = tt.tag_id
where tft.fk_feedbackid = tbl_feedbacks.pk_feedbackid
order by tag_id
limit 1
offset 1 ) as tag2,
(select tag
from tbl_tag tt
join tbl_feedbacks_tags tft on tft.tag_id = tt.tag_id
where tft.fk_feedbackid = tbl_feedbacks.pk_feedbackid
order by tag_id
limit 1
offset 2 ) as tag3
from [same as in the OP]
This should do the trick.
Notes:
This will pull the first three tags. AFAIK, there isn't a way to have an arbitrary number of tags. You can expand the number of tags shown by copying and pasting more of those parts of the query. Make sure to increase the offset setting.
If this does not work, you'll probably have to write up another question, focusing on how to do the pivot in mysql. I've never used mysql, so I'm only guessing that this will work based on what others have told me.
One tip: you'll usually get more attention to your question if you strip away all the extra details. In the question I linked to above, I was really joining between 4 or 5 different tables, with many different fields. But I stripped it down to just the part I didn't know (how to get oracle to aggregate my results into one row). I know some stuff, but you can usually do far better than just one person if you trim your question down to the essentials.

What is the best way to reduce sql queries in my situation

Here is the situation,each page will show 30 topics,so I had execute 1 sql statements at least,besides,I also want to show how many relpies with each topic and who the author is,thus
I have to use 30 statements to count the number of replpies and use other 30 statements to find the author.Finally,I got 61 statements,I really worry about the efficiency.
My tables looks like this:
Topic Reply User
------- ---------- ------------
id id id
title topic_id username
... ...
author_id
You should look into joining tables during a query.
Joins in SQLServer http://msdn.microsoft.com/en-us/library/ms191517.aspx
Joins in MySQL http://dev.mysql.com/doc/refman/5.0/en/join.html
As an example, I could do the following:
SELECT reply.id, reply.authorid, reply.text, reply.topicid,
topic.title,
user.username
FROM reply
LEFT JOIN topic ON (topic.id = reply.topicid)
LEFT JOIN user ON (user.id = reply.authorid)
WHERE (reply.isactive = 1)
ORDER BY reply.postdate DESC
LIMIT 10
If I read your requirements correctly, you want the result of the following query:
SELECT Topic.title, User.username, COUNT(Reply.topic_id) Replies
FROM Topic, User, Reply
WHERE Topic.id = Reply.topic_id
AND Topic.author_id = User.id
GROUP BY Topic.title, User.username
When I was first starting out with database driven web applications I had similar problems. I then spent several years working in a database rich environment where I actually learned SQL. If you intend to continue developing web applications (which I find are very fun to create) it would be worth your time to pick up a book or checking out some how-to's on basic and advance SQL.
One thing to add, on top of JOINS
It may be that your groups of data do not match or relate, so JOINs won't work. Another way: you may have 2 main chunks of data that is awkward to join.
Stored procedures can return multiple result sets.
For example, for a summary page you could return one aggregate result set and another "last 20" result set in one SQL call. To JOIN the 2 is awkward because it doesn't "fit" together.
You certainly can use some "left joins" on this one, however since the output only changes if someone updates/adds to your tables you could try to cache it in a xml/text file. Another way could be to build in some redundancy by adding another row to the topic table that keeps the reply count, username etc... and update them only if changes occur...