SQL JOIN: Just not able to understand them - mysql

Now, I know know this question related to JOIN have been asked many times. I went through many of them. But it still isn't clear to me. I read these aricles too: http://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins#_comments and http://www.codinghorror.com/blog/2007/10/a-visual-explanation-of-sql-joins.html but no, it still didn't help.
I do understand the Vein diagrams mathematically but not able to grab basic concept behind JOIN.
Suppose I have two tables.
tbl_bookdescription:
| BOOKID | BOOKNAME | BOOKREVIEW | AUTHORID |
------------------------------------------------
| 1 | SQL Basics | Cool | 1 |
| 2 | PHP Basics | Good | 2 |
| 3 | AJAX Basics | Superb | 2 |
| 4 | HTML Basics | Very Good | 3 |
tbl_authordescription
| AUTHORID | AUTHORNAME |
-------------------------
| 1 | Tom |
| 2 | Jerry |
| 3 | Phil |
I want to script a search engine for my website
So, when the user enters Tom as $searchTerm, I want the program to return the name of the book which is written by Tom. And at the same time, the user can also enter Good. This time the query should again return the name of the book. So, I thought to do something like this
SELECT bookname FROM tbl_bookdescription MATCH(bookReview) AGAINST('$searchTerm')`
and then UNION this table with SOMETHING (something which matches authorName against $searchterm).
Now, two questions:
Is this query right? Will it give me the desired results?
WHat should I write in the code in place of SOMETHING. I think I will have to JOIN both the tables(not sure). And don't know how should I join.
Help appreciated.

If you search using only one search term then your query might look like
SELECT b.*, a.*
FROM tbl_bookdescription b JOIN tbl_authordescription a
ON b.authorID = a.authorID
WHERE b.bookName LIKE '%searchterm%'
OR b.bookReview LIKE '%searchterm%'
OR a.authorName LIKE '%searchterm%'
If you replace searchterm with 'Tom' you'll get
| BOOKID | BOOKNAME | BOOKREVIEW | AUTHORID | AUTHORNAME |
------------------------------------------------------------
| 1 | SQL Basics | Cool | 1 | Tom |
Now, if it's 'Good' then
| BOOKID | BOOKNAME | BOOKREVIEW | AUTHORID | AUTHORNAME |
-------------------------------------------------------------
| 2 | PHP Basics | Good | 2 | Jerry |
| 4 | HTML Basics | Very Good | 3 | Phil |
Here is SQLFiddle demo

Try this query
SELECT
a.*
FROM
tbl_bookdescription a
INNER JOIN
tbl_authordescription b
ON
a.authorid = b.authorid
WHERE
b.authorname=''

Related

optimizing a query to use a join between two tables and translate rows to columns

I know it's an already done question, but all the answer I found do not suits my needs and, more of this, I am unable to tail a proper solution by myself.
I explain the situation:
2 tables (user, user_preferences)
in the first one there's, as you probably guessed, the name, last name, id and login (there's more data but theese are the ones I need) and in the second one we have user_id, preferences_key and preferences_value.
If I run my query:
select a.id, a.login, a.first_name, a.last_name, b.preferences_key from users a, user_preferences b where a.id=b.user_id and b.preferences_key like 'msg%';
I receive back an answer like this:
+----+---------+---------------+---------------+----------------------+
| id | login | first_name | last_name | preferences_key |
+----+---------+---------------+---------------+----------------------+
| 4 | usrn1 | User1 | NumberOne | msg002 |
| 7 | usrn5 | User5 | NumberFive | msg001 |
| 7 | usrn5 | User5 | NumberFive | msg002 |
| 10 | usrn9 | User0 | NumberNine | msg002 |
+----+---------+---------------+---------------+----------------------+
I'm trying to figure out how to switch from this view to this one:
+----+---------+---------------+---------------+--------+--------+
| id | login | first_name | last_name | msg001 | msg002 |
+----+---------+---------------+---------------+--------+--------+
| 4 | usrn1 | User1 | NumberOne | No | Yes |
| 7 | usrn5 | User5 | NumberFive | Yes | Yes |
| 10 | usrn9 | User0 | NumberNine | No | Yes |
+----+---------+---------------+---------------+--------+--------+
If you have any suggestion will be very appreciated, and, by the way, if you can add some more explanation I'll appreciate it even more.
Thank you
There isn't really an easy way to pivot a table like you want easily that I know of.
There is the following manual approach by JOINing to the same table multiple times. Something like the following should work:
SELECT
a.id, a.login, a.first_name, a.last_name,
IF(b1.preferences_key IS NULL, 'No', 'Yes') msg001,
IF(b2.preferences_key IS NULL, 'No', 'Yes') msg002
FROM
users a
LEFT JOIN user_preferences b1
ON b1.user_id = a.id
AND b1.preferences_key = 'msg001'
LEFT JOIN user_preferences b2
ON b2.user_id = a.id
AND b2.preferences_key = 'msg002';
If this doesn't help. check out MySQL pivot table

How to properly join two tables to use alternative ORDER BY

Two tables...
people (personid, name, mainordering)
entries (userid, personid, altordering)
"personid" is the common field. My app displays a draggable list users can move around. When done, they click to "lock" in their order.
Table : people
+----------+---------+--------------+
| personid | name | mainordering |
+----------+---------+--------------+
| 1 | Bob | 2 |
| 2 | Charlie | 4 |
| 3 | Jim | 1 |
| 4 | Doug | 3 |
+----------+---------+--------------+
So using mainordering, it would display:
Jim
Bob
Doug
Charlie
entries table might have (for user 16):
+--------+----------+-------------+
| userid | personid | altordering |
+--------+----------+-------------+
| 16 | 1 | 3 |
| 16 | 2 | 1 |
| 16 | 3 | 2 |
| 16 | 4 | 4 |
+--------+----------+-------------+
So if user 16 has already submitted his entry BUT NOT LOCKED IT IN, I want to display his list using altordering. i.e.
Charlie
Jim
Bob
Doug
I'm struggling with the proper join to use. Here is what I tried and isn't working (it's simply ordering by mainordering still)...
$sql = "SELECT * from entries
WHERE userid=".$_SESSION['userid']."
LEFT JOIN people ON entries.personid = people.personid
ORDER BY altordering";
Any thoughts would be much appreciated. Thank you...
Are you sure you don't get an error when using WHERE before JOIN?
It should work like this:
SELECT people.*
FROM people
JOIN entries ON entries.personid = people.personid
WHERE entries.userid={$_SESSION['userid']}
ORDER BY entries.altordering
I assume entries.personid will always have a matching person in people, so you should use an INNER JOIN. You would use FROM entries LEFT JOIN people if you wanted to retrieve altordering even for non-existing people.

SQL: how can I use GROUP BY to take an aggregate of an aggregate?

I have a query that groups by (column_a, column_b) and selects an aggregated value. I would like to then group by column_a and take an aggregate sum of the previously aggregated values.
Probably clearer with an example:
We have 3 tables: projects, devs, and contributors. Each project has many contributors, and each dev is a contributor to many projects:
+======== projects =========+ +====== devs =======+
+--------------+------------+ +--------+----------+
| project_name | project_id | | dev_id | dev_name |
+--------------+------------+ +--------+----------+
| parsalot | 1 | | 1 | Ally |
| vimplug | 2 | | 2 | Ben |
| gamify | 3 | | 3 | Chris |
+--------------+------------+ +--------+----------+
+==== contributors ===+
+------------+--------+
| project_id | dev_id |
+------------+--------+
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 2 |
| 3 | 3 |
+------------+--------+
I'm interested in how much work goes into each project. I could just count how many contributors each has, but I'd like to give more weight to contributions made by devs who aren't splitting their time over lots of other projects.
So vimplug is more actively developed than parsalot: each project has two contributors, but one of vimplug's (Ally) does nothing else, whereas parsalot's contributors are both splitting their time across other projects.
I've constructed a query that groups by (project, contributor) and calculates each contributors "dedication" to the project:
SELECT
projects.project_name,
devs.dev_name,
1 / COUNT(contributions.project_id) as dedication
FROM
projects
JOIN
contributors USING (project_id)
JOIN
devs USING (dev_id)
JOIN
contributors contributions USING (dev_id)
GROUP BY projects.project_id , contributors.dev_id;
Which yields,
+--------------+----------+------------+
| project_name | dev_name | dedication |
+--------------+----------+------------+
| parsalot | Ben | 0.5000 |
| parsalot | Chris | 0.5000 |
| vimplug | Ally | 1.0000 |
| vimplug | Ben | 0.5000 |
| gamify | Chris | 0.5000 |
+--------------+----------+------------+
What I really want, though, is the total dedication for each project, i.e.
+--------------+------------------+
| project_name | total_dedication |
+--------------+------------------+
| gamify | 0.5000 |
| parsalot | 1.0000 |
| vimplug | 1.5000 |
+--------------+------------------+
I (naively) tried changing my select statement to
SELECT
projects.project_name,
SUM(1 / COUNT(contributions.project_id)) as total_dedication
but that doesn't work ("Invalid use of group function"). Is there a way I can do this without having to do a sub-select?
Just use a subquery:
select project_name, sum(dedication)
from (<your query here>) q
group by project_name;
You are close to the solution please use the following :
SELECT project_name,sum(dedication) as total_dedication FROM (SELECT
projects.project_name,
devs.dev_name,
1 / COUNT(contributions.project_id) as dedication
FROM
projects
JOIN
contributors USING (project_id)
JOIN
devs USING (dev_id)
JOIN
contributors contributions USING (dev_id)
GROUP BY projects.project_id , contributors.dev_id) as A GROUP BY project_name
Ivan,
You asked "Is there a way I can do this without having to do a sub-select" ... is there a reason you cannot sub-select?
Unfortunately, you'll need to use a sub-select, because you cannot combine aggregate functions (which would be the only way you'd be able to accomplish this). See: How to combine aggregate functions in MySQL?
So as the other answers have shown, you'll have to use a sub-query.

Join multiple tables with same column name

I have these tables in my MySQL database:
General table:
+----generalTable-----+
+---------------------+
| id | scenario | ... |
+----+----------+-----+
| 1 | facebook | ... |
| 2 | chief | ... |
| 3 | facebook | ... |
| 4 | chief | ... |
Facebook Table:
+----facebookTable-----+
+----------------------+
| id | expiresAt | ... |
+----+-----------+-----+
| 1 | 12345678 | ... |
| 3 | 45832458 | ... |
Chief Table:
+------chiefTable------+
+----------------------+
| id | expiresAt | ... |
+----+-----------+-----+
| 2 | 43547343 | ... |
| 4 | 23443355 | ... |
Basically, the general table holds some (obviously) general data. Based on the generalTable.scenario you can look up more details in the other two tables, which are in some columns familiar (expiresAt for example) but in others not.
My question is, how to get the joined data of generalTable and the right detailed table in just one query.
So, I would like a query like this:
SELECT id, scenario, expiresAt
FROM generalTable
JOIN facebookTable
ON generalTable.id = facebookTable.id
JOIN chiefTable
ON generalTable.id = chiefTable.id
And an output like this:
| id | scenario | expiresAt |
+----+----------+-----------+
| 1 | facebook | 12345678 |
| 2 | chief | 43547343 |
| 3 | facebook | 45832458 |
| 4 | chief | 23443355 |
However, this doesn't work, because both facebookTable and chiefTable have ambiguous column name "expiresAt". For the ease of use I want to keep it that way. The result table should also only have one column "expiresAt" that is automatically filled with the right values from either facebookTable or chiefTable.
You might want to consider adding expiredAt to your general table, and removing it from the others, to remove duplication in the schema, and to make this particular query simpler.
If you need to stick with your current schema, you can use table aliases to resolve the name ambiguity, and use two joins and a union to create the result you are looking for:
SELECT g.id, g.scenario, f.expiresAt
FROM generalTable g
JOIN facebookTable f
ON g.id = f.id
UNION ALL
SELECT g.id, g.scenario, c.expiresAt
FROM generalTable g
JOIN chiefTable c
ON g.id = c.id;
The outer join approach mentioned in another answer would also solve the problem.
One way you could accomplish it is with LEFT JOIN. In the result fields you can do something like this for common fields IF(fTbl.id IS NULL, cTbl.expiresAt, fTbl.expiresAt) AS expiresAt.

How to condense a column like this?

I've tried finding something like this, but to no avail...
This is about a system of tables for a customer management system. In particular, I need to create a note history for each customer.
So, I have a table 'customers' with the columns customers.customer_ID, customers.lastname, customers.firstname, customers.postal_code, customers.city and customers.street;
and another table 'notes' with the columns notes.note_ID, notes.customer_ID, notes.subject, notes.description and notes.entered_on
Now I need to create a third table search which condenses much of the information above. It has the tables search.contact_ID, search.name, search.address and search.history. This is supposed to look like this:
contacts:
contact_ID | lastname | firstname | ...
------------+-----------+-----------+-----
1 | Doe | John | ...
2 | Dane | Jane | ...
note:
note_ID | contact_ID | subject | description | entered_on
--------+---------------+-----------------------+-----------------------+----------------
1 | 1 | call received | John Doe called us to | 2014-05-03
| | | ask for an offer |
2 | 1 | offer made | We called John Doe to | 2014-06-03
| | | submit our offer |
3 | 2 | advertisement call | We called Jane Dane to| 2014-06-03
| | | inform her of our |
| | | latest offer |
4 | 1 | offer accepted | John Doe called to | 2014-08-03
| | | accept our offer |
search:
contact_ID | name | address | history
------------+---------------+---------------------------------+-------------------
1 | Doe, John | 55 Main Street, 12345 Oldtown | 'On 2014-08-03 offer accepted: John Doe accepted our offer.
| | | On 2014-06-03 offer made: We called John Doe to submit our offer.
| | | On 2014-05-03 call received: John Doe called us to ask for an offer.'
2 | Dane, Jane | 111 Wall Street, 67890 Newtown | 'On 2014-06-03 advertisement call: We called Jane Dane to submit our offer.'
While I can deal with much of the rest, I have no idea how to generate the history information. My idea was as follows
WHILE
customers.customer_ID = note.customer_ID
AND
note.entered_on = GREATEST(note.entered_on)
DO
SET customers.note_history = CONCAT_WS(' | ', CONCAT_WS(': ',note.subject,note.description), customers.note_history);
But that one isn't necessarily chronological. Also how do I transform that into a statement compatible with the SELECT INTO used for the creation of the rest of the table?
Sounds like a case for a Group-By, along with GROUP_CONCAT
CREATE TABLE search (PRIMARY KEY(contact_ID))
SELECT contact_ID, CONCAT(lastname,', ',firstname) AS name, address,
GROUP_CONCAT(CONCAT('On ',entered_on,' ',subject,': ',description)
ORDER BY note_ID SEPARATOR "\n") AS history
FROM contacts LEFT JOIN note USING (contact_ID)
GROUP BY contact_ID
If dont want to use CREATE TABLE .. SELECT ... , can first just create (or truncate!) the table, and then use INSERT INTO ... SELECT ... instead.