Tuesday, January 15, 2013

A Visual Explanation of SQL Joins

I thought Ligaya Turmelle's post on SQL joins was a great primer for novice developers. Since SQL joins appear to be set-based, the use of Venn diagrams to explain them seems, at first blush, to be a natural fit. However, like the commenters to her post, I found that the Venn diagrams didn't quite match the SQL join syntax reality in my testing.
I love the concept, though, so let's see if we can make it work. Assume we have the following two tables. Table A is on the left, and Table B is on the right. We'll populate them with four records each.
id name       id  name
-- ----       --  ----
1  Pirate     1   Rutabaga
2  Monkey     2   Pirate
3  Ninja      3   Darth Vader
4  Spaghetti  4   Ninja
Let's join these tables by the name field in a few different ways and see if we can get a conceptual match to those nifty Venn diagrams.
SELECT * FROM TableA
INNER JOIN TableB
ON TableA.name = TableB.name

id  name       id   name
--  ----       --   ----
1   Pirate     2    Pirate
3   Ninja      4    Ninja
Inner join produces only the set of records that match in both Table A and Table B.
Venn diagram of SQL inner join
SELECT * FROM TableA
FULL OUTER JOIN TableB
ON TableA.name = TableB.name

id    name       id    name
--    ----       --    ----
1     Pirate     2     Pirate
2     Monkey     null  null
3     Ninja      4     Ninja
4     Spaghetti  null  null
null  null       1     Rutabaga       
null  null       3     Darth Vader
Full outer join produces the set of all records in Table A and Table B, with matching records from both sides where available. If there is no match, the missing side will contain null.
Venn diagram of SQL cartesian join
SELECT * FROM TableA
LEFT OUTER JOIN TableB
ON TableA.name = TableB.name

id  name       id    name
--  ----       --    ----
1   Pirate     2     Pirate
2   Monkey     null  null
3   Ninja      4     Ninja
4   Spaghetti  null  null
Left outer join produces a complete set of records from Table A, with the matching records (where available) in Table B. If there is no match, the right side will contain null.
Venn diagram of SQL left join
SELECT * FROM TableA
LEFT OUTER JOIN TableB
ON TableA.name = TableB.name
WHERE TableB.id IS null

id  name       id     name
--  ----       --     ----
2   Monkey     null   null
4   Spaghetti  null   null
To produce the set of records only in Table A, but not in Table B, we perform the same left outer join, then exclude the records we don't want from the right side via a where clause.
join-left-outer.png
SELECT * FROM TableA
FULL OUTER JOIN TableB
ON TableA.name = TableB.name
WHERE TableA.id IS null 
OR TableB.id IS null

id    name       id    name
--    ----       --    ----
2     Monkey     null  null
4     Spaghetti  null  null
null  null       1     Rutabaga
null  null       3     Darth Vader
To produce the set of records unique to Table A and Table B, we perform the same full outer join, then exclude the records we don't want from both sides via a where clause.
join-outer.png
There's also a cartesian product or cross join, which as far as I can tell, can't be expressed as a Venn diagram:
SELECT * FROM TableA
CROSS JOIN TableB
This joins "everything to everything", resulting in 4 x 4 = 16 rows, far more than we had in the original sets. If you do the math, you can see why this is a very dangerous join to run against large tables.

More learning about link:http://wyden.com/linux/mailserver

mysql queries


 Neuer Eintrag einfügen
INSERT INTO tabelle (var1, var2) VALUES ('value1', 'value2');
Eintrag bearbeiten
UPDATE tabelle SET var1 = 'value1', var2 = 'value2' WHERE id = '2'
Eintrag löschen
DELETE FROM tabelle WHERE id = '22'
  Left join
SELECT * 
FROM tbl1
LEFT JOIN tbl2 ON tbl1.id = tbl2.foreign_id
WHERE tbl1.id > 1000
 
 
 
Left Joins to link three or more tables

MANY-TABLE JOINS IN MYSQL - BACKGROUND

Data held in SQL tables should be normalised - in other words, held in neat multiple tables with complete rows, only one piece of logical data per cell, and with information not being repeated in multiple places. (The "why" is off topic for this article, but it basically helps data maintenance and integrity no end).

Multiple normalised tables can be linked together within select commands and this linking is known as joining; when you specifiy a join, you also specify a criteria to tell MySQL how to make the connection, and that's typically done using a key. Let's see a simple example.

Two tables - bdg containing buildings ....

-------+-----+
| name | bid |
-------+-----+
| 404 | 1 |
| 405 | 2 |
-------+-----+

... and res containing residents living there.

---------+------+-----+
| person | bid | rid |
---------+------+-----+
| Graham | 1 | 101 |
| Lisa | 1 | 102 |
---------+------+-----+

When I connect (join) those tables together, I wish to do so by linking the "bid"s - and the syntax I use is:

select * from bdg, res where bdg.bid = res.bid ;

You'll notice that I DON'T use the word join (I could ... but that's another story). Here's my output:

-------+-----+--------+------+-----+
| name | bid | person | bid | rid |
-------+-----+--------+------+-----+
| 404 | 1 | Graham | 1 | 101 |
| 404 | 1 | Lisa | 1 | 102 |
-------+-----+--------+------+-----+

Which is good - in other words, it's what I expected. BUT ... it might be that I want to see at least one row on my report for each of the incoming rows in (say) my building table - to alert me to buildings that don't match any resident records at all. Than can be done using a LEFT JOIN in my select:

select * from bdg left join res on bdg.bid = res.bid ;
   
which gives:

-------+-----+--------+------+------+
| name | bid | person | bid | rid |
-------+-----+--------+------+------+
| 404 | 1 | Graham | 1 | 101 |
| 404 | 1 | Lisa | 1 | 102 |
| 405 | 2 | NULL | NULL | NULL |
-------+-----+--------+------+------+

THREE WAY JOINS

Regular joins and left joins can be extended to three and more tables - the principle is easy but the syntax less so; let's say that we had a third table called dom containing the names of any internet domains registered to each individual:

------------------------+------+-----+
| domain | rid | did |
------------------------+------+-----+
| www.grahamellis.co.uk | 101 | 201 |
| www.sheepbingo.co.uk | 101 | 202 |
------------------------+------+-----+

A regular join on the (now) three tables is straightforward:

select * from bdg, res, dom where bdg.bid = res.bid and res.rid = dom.rid;

and gives the following result:

-------+-----+--------+------+-----+-----------------------+------+-----+
| name | bid | person | bid | rid | domain | rid | did |
-------+-----+--------+------+-----+-----------------------+------+-----+
| 404 | 1 | Graham | 1 | 101 | www.grahamellis.co.uk | 101 | 201 |
| 404 | 1 | Graham | 1 | 101 | www.sheepbingo.co.uk | 101 | 202 |
-------+-----+--------+------+-----+-----------------------+------+-----+

The syntax for a three way LEFT JOIN is more complex (and thus the inspiration for this article):

select * from (bdg left join res on bdg.bid = res.bid) left join dom on res.rid = dom.rid;

and gives the following result:

-------+-----+--------+------+------+-----------------------+------+------+
| name | bid | person | bid | rid | domain | rid | did |
-------+-----+--------+------+------+-----------------------+------+------+
| 404 | 1 | Graham | 1 | 101 | www.grahamellis.co.uk | 101 | 201 |
| 404 | 1 | Graham | 1 | 101 | www.sheepbingo.co.uk | 101 | 202 |
| 404 | 1 | Lisa | 1 | 102 | NULL | NULL | NULL |
| 405 | 2 | NULL | NULL | NULL | NULL | NULL | NULL |
-------+-----+--------+------+------+-----------------------+------+------+

Notice that our report now includes orphan records at both join levels - entries in the bdg table that have no corresponding entry in the res table, and entries in the res table that have no corresponding entry in the dom table.

THREE WAY JOINS - LOOKING FOR INCOMPLETE RECORDS

Should we wish to report on orphan records only, we can do so by testing for NULL fields in fields that may not otherwise have a null value.

Example - looking for all incomplete records:

select * from (bdg left join res on bdg.bid = res.bid) left join dom on res.rid = dom.rid where dom.rid is NULL;

-------+-----+--------+------+------+--------+------+------+
| name | bid | person | bid | rid | domain | rid | did |
-------+-----+--------+------+------+--------+------+------+
| 404 | 1 | Lisa | 1 | 102 | NULL | NULL | NULL |
| 405 | 2 | NULL | NULL | NULL | NULL | NULL | NULL |
-------+-----+--------+------+------+--------+------+------+

Example - looking for all buildings with no residents:

select * from (bdg left join res on bdg.bid = res.bid) left join dom on res.rid = dom.rid where res.rid is NULL;

-------+-----+--------+------+------+--------+------+------+
| name | bid | person | bid | rid | domain | rid | did |
-------+-----+--------+------+------+--------+------+------+
| 405 | 2 | NULL | NULL | NULL | NULL | NULL | NULL |
-------+-----+--------+------+------+--------+------+------+

(Hey - you really don't need to join in the domain table for this)

Example - looking for all residents with no domains:

select * from (bdg left join res on bdg.bid = res.bid) left join dom on res.rid = dom.rid where dom.rid is NULL and res.rid is not NULL;

-------+-----+--------+------+------+--------+------+------+
| name | bid | person | bid | rid | domain | rid | did |
-------+-----+--------+------+------+--------+------+------+
| 404 | 1 | Lisa | 1 | 102 | NULL | NULL | NULL |
-------+-----+--------+------+------+--------+------+------+

SUMMARY OF MYSQL COMMANDS USED

Here's a complete set of the commands I used to set up this example - you're welcome to cut and paste it for your own testing and experimentation:

use test;
drop table if exists bdg;
drop table if exists res;
drop table if exists dom;
create table bdg (name text, bid int primary key);
create table res (person text, bid int, rid int primary key);
create table dom (domain text, rid int, did int primary key);

insert into bdg values ("404",1);
insert into res values ("Graham",1,101);
insert into dom values ("www.grahamellis.co.uk",101,201);
insert into dom values ("www.sheepbingo.co.uk",101,202);
insert into res values ("Lisa",1,102);
insert into bdg values ("405",2);

select * from bdg;
select * from res;
select * from dom;

select * from bdg, res where bdg.bid = res.bid ;
select * from bdg, res, dom where bdg.bid = res.bid and
    res.rid = dom.rid;

select * from bdg left join res on bdg.bid = res.bid ;
select * from (bdg left join res on bdg.bid = res.bid)
    left join dom on res.rid = dom.rid;

select * from (bdg left join res on bdg.bid = res.bid)
    left join dom on res.rid = dom.rid where dom.rid is NULL;
select * from (bdg left join res on bdg.bid = res.bid)
    left join dom on res.rid = dom.rid where res.rid is NULL;
select * from (bdg left join res on bdg.bid = res.bid)
    left join dom on res.rid = dom.rid
    where dom.rid is NULL and res.rid is not NULL;


 

Datenbank-Replikation mit MySQL

Konfiguration des Master-Servers (/etc/mysql/my.cnf)

#bind-address            = 127.0.0.1

# replication stuff
# we're using bin-logs
log-bin
binlog-do-db=replication1
# this is our master
server-id       = 1
replicate-do-db=exampledb
/etc/init.d/mysql restart
GRANT REPLICATION SLAVE ON *.* TO 'slave_user'@'%' IDENTIFIED BY '<some_password>'; (Replace <some_password> with a real password!)
FLUSH PRIVILEGES;
SHOW MASTER STATUS;

The last command will show something like this:
+---------------+----------+--------------+------------------+
| File          | Position | Binlog_do_db | Binlog_ignore_db |
+---------------+----------+--------------+------------------+
| mysql-bin.006 | 183      | exampledb    |                  |
+---------------+----------+--------------+------------------+
1 row in set (0.00 sec)


Konfiguration des Slave-Server (/etc/mysql/my.cnf)

# replication stuff
# we're using bin-logs
log-bin
# this is our slave
server-id       = 2
master-host=<IP des Master-Servers>
master-user=slave_user
master-password=<Passwort von slave_user>
master-connect-retry=60
replicate-do-db=replication1
/etc/init.d/mysql restart
SLAVE STOP;
CHANGE MASTER TO MASTER_HOST='192.168.0.100', MASTER_USER='slave_user', MASTER_PASSWORD='<some_password>', MASTER_LOG_FILE='mysql-bin.006', MASTER_LOG_POS=183;