Archive for the ‘MySQL’ Category
MySQL MSI Service Fails
While installing the MySQL 6.0 Alpha release, I encountered a failure running the configuration component. It shows the following dialog, which hangs until you cancel it. By the way, I’ve encountered this on other MySQL 5.0 and 5.1 installs from time to time.
Don’t uninstall and reinstall because nothing will change. The only problem appears to be setting the root password. This show you how to verify it and fix the missing configuration step. While the service says it failed, it actually started. You can check that by launching services.msc
from Start and Run.
You can verify the problem by attempting to connect to the MySQL server. My server is setup on localhost with port 3308 because there are multiple MySQL servers running on my virtual machine. A typically connection would look like this if your password was cangetin (the old Solaris training password):
C:\>mysql -uroot -pcangetin -P3308 |
If you get the following error message, it’s most likely a missing root password.
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES) |
Since my machine is running multiple MySQL servers and it’s my preference to associate their execution to their binaries, the paths to the installations aren’t loaded automatically on installation. A quick caution, my path statements are from the Windows XP 64-bit installation and they’ll differ from a 32-bit installation path. Specifically, the executable programs are in C:\Program Files (x86)
directory not C:\Program Files
. You can set the path like this:
C:\>set PATH=C:\Program Files (x86)\MySQL 6.0\MySQL Server 6.0\bin;%PATH% |
To verify and fix the problem requires you login without a password, connect to the mysql
database, and query the user
table. All those steps follow below, unless you’re on Microsoft Vista. If you’re running Microsoft Vista follow these instructions.
C:\>mysql -uroot -P3308 Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 2 Server version: 6.0.10-alpha-community MySQL Community Server (GPL) Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> USE mysql; Database changed mysql> SELECT host, user, passowrd -> FROM user WHERE user='root' AND host='localhost'\G *************************** 1. row *************************** Host: localhost User: root Password: 1 row in set (0.00 sec) |
You fix this problem by running the following grant of privileges to the root
user:
mysql> GRANT ALL ON *.* TO 'root'@'localhost' -> IDENTIFIED BY 'cangetin' WITH GRANT OPTION; Query OK, 0 rows affected (0.00 sec) |
I learned this technique by attending the MySQL for Database Administrator’s course. I hope it solves a mystery for somebody along the way. I also hope that Oracle Education maintains the excellent folks that Sun Microsystems acquired when they snagged MySQL.
MySQL export to CSV
While working through export techniques in MySQL (5.1.34-community), I discovered some tricks and techniques with MySQL exports to a CSV file.
Here’s a standard export statement to a CSV file format:
mysql> SELECT * -> INTO OUTFILE 'C:/Data/City.csv' -> FIELDS TERMINATED BY ',' -> ENCLOSED BY '"' -> ESCAPED BY '\\' -> LINES TERMINATED BY '\r\n' -> FROM City; Query OK, 4079 ROWS affected (0.02 sec) |
On Microsoft Windows, when you attempt to export it a second time, you’ll get an error unless you’ve previously deleted the physical file. You’ll also need the \r
on the Windows platform but not the Linux platform.
mysql> SELECT * -> INTO OUTFILE 'C:/Data/City.csv' -> FIELDS TERMINATED BY ',' -> ENCLOSED BY '"' -> ESCAPED BY '\\' -> LINES TERMINATED BY '\r\n' -> FROM City; ERROR 1086 (HY000): File 'c:/Data/City.csv' already EXISTS |
While reviewing Alan Beaulieu’s Learning SQL, 2nd Edition, I noticed he’s got a small example in his Appendix B. He’s using the back-quoted backslash approach to directories in Windows. You can use it, but I prefer the one shown in my examples. Here’s the alternative syntax for the outbound file line:
-> INTO OUTFILE 'C:\\Data\\City.csv' |
When you want to use the CASE
statement, you need to use a derived (MySQL terminology). It appears that you can’t include a CASE
statement in the SELECT
clause when exporting the contents to an OUTFILE
. Also, for reference, MySQL doesn’t support the WITH
clause.
SELECT * INTO OUTFILE 'c:/Data/City4.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '"' ESCAPED BY '\\' LINES TERMINATED BY '\r\n' FROM (SELECT ID , CASE WHEN Name IS NULL THEN '' ELSE Name END AS Name , CASE WHEN CountryCode IS NULL THEN '' ELSE CountryCode END AS CountryCode , CASE WHEN District IS NULL THEN '' ELSE District END AS District , CASE WHEN Population IS NULL THEN '' ELSE Population END AS Population FROM City) Subquery; |
Hope this helps somebody.
SQL Concatenation blues
I really like Alan Beaulieu’s Learning SQL because its simple, direct, and clearer than other books on SQL. While his focus is MySQL, he does a fair job of injecting a bit about Oracle’s syntax. Comparative concatenation syntax is one of topics I wished he’d spent more time on. Here’s some clarification on cross platform SQL concatenation.
Oracle
Oracle supports two forms of string concatenation. Concatenation for those new to the idea means gluing two strings into one, or three strings into one, et cetera. One uses the ||
operator, which looks like two pipes. You can use the ||
operator between any number of string elements to glue them together. A quick example of the ||
operator that returns an ABCD
string is:
SELECT 'A' || 'B' || 'C' || 'D' FROM dual; |
The Oracle database also supports the CONCAT
operator that many use in MySQL. Those converting to an Oracle database should beware the difference between how the CONCAT
function is implemented in Oracle versus MySQL. In an Oracle database, the CONCAT
function only takes two arguments. When you call it with three or more arguments like this:
SELECT CONCAT('A','B','C','D') FROM dual; |
It raises the following exception:
SELECT CONCAT('A','B','C','D') FROM dual * ERROR at line 1: ORA-00909: invalid NUMBER OF arguments |
You can use the CONCAT
function to process more than two arguments but you must do so by calling the function recursively. You’d do it like this if you must use it:
SELECT CONCAT('A',CONCAT('B',CONCAT('C','D'))) FROM dual; |
As to an Oracle specific SQL book recommendation, I’d go with Alan’s as a beginner even though it’s focus is MySQL. By the way, if you don’t own Learning SQL hold off on buying it until the second edition is available in May 2009. If you’re using Oracle and have some basic SQL competence, I’d suggest Mastering Oracle SQL, 2nd Edition by Sanjay Mishra and Alan Beaulieu as a reference. Just make sure you get the 2nd Edition of it too.
MySQL
MySQL appears to support the two same forms of string concatenation as an Oracle database. The one that uses the ||
operator (known as pipe concatenation), actually only returns a zero unless you configure the sql_mode
to allow pipe concatenation.
The following concatenation statement uses pipe concatenation:
mysql> SELECT 'A'||'B'||'C'||'D'; +--------------------+ | 'A'||'B'||'C'||'D' | +--------------------+ | 0 | +--------------------+ 1 ROW IN SET, 4 warnings (0.00 sec) |
By default, this fails and returns a zero unless you’ve added the PIPES_AS_CONCAT
mode to your sql_mode
variable. It returns a zero because it attempts to see whether either of the adjoining elements are true. Strings inherently fail to resolve as expressions or Boolean values and the function returns a zero, which means the composite expression was evaluated as false.
You can query the sql_mode
variable as follows. The default values are shown in the results.
mysql> SELECT @@sql_mode; +----------------------------------------------------------------+ | @@sql_mode | +----------------------------------------------------------------+ | STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | +----------------------------------------------------------------+ 1 ROW IN SET (0.00 sec) |
You can modify the sql_mode
as follows from the command line:
SET sql_mode='STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT'; |
If you want to make this a permanent change, you can edit the my.ini
file in Windows or the my.conf
file in Unix or Linux. The following shows the modified line in a configuration file.
# Set the SQL mode to strict sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT" |
With these changes pipe concatenation works in MySQL, as follows:
mysql> SELECT 'A'||'B'||'C'||'D'; +--------------------+ | 'A'||'B'||'C'||'D' | +--------------------+ | ABCD | +--------------------+ 1 ROW IN SET (0.02 sec) |
You can use the CONCAT
function to glue any number of string elements together when you’ve no control of the sql_mode
variable. The CONCAT
function in MySQL takes several arguments. I’ve never needed to use more than the limit and suspect that there isn’t one (based on the documentation). It appears to use a recursive algorithm for parameter processing. Please post a note correcting me if I’m wrong on this.
You call the CONCAT
function like this:
SELECT CONCAT('A','B','C','D'); |
As to a MySQL specific SQL book recommendation, I’d go with Alan Beaulieu’s Learning SQL as a beginner. As noted earlier, don’t buy it until the 2nd Edition ships in May 2009.
Microsoft® Access or SQL Server
Microsoft® SQL Server doesn’t support two forms of string concatenation like Oracle and MySQL. You can only use the +
operator. There is no CONCAT
function in Microsoft® Access or SQL Server. A quick example of the +
operator in Microsoft’s SQL returns an ABCD
string like this:
SELECT 'A' + 'B' + 'C' + 'D'; |
As to a Microsoft® T-SQL book recommendation, I’d go with Itzik Ben-Gan’s Microsoft SQL Server 2008 T-SQL Fundamentals. Just understand, that like most things Microsoft, T-SQL is a dialect and approach that differs substantially from other commercial products.
Basic SQL Query
Somebody suggested that I post a simple quick view of how a basic query works. The following illustrates how the FROM
is read first. Aliases assigned in the FROM
clause, like the i
, replace the full name of the table or view. Column references don’t require prepending with the table or view alias but doing so adds clarity in the query. You do need prepend table aliases or names when two or more columns returned by the query have the same names. This happens when you’re joining two or more tables because the SQL parser finds that they’re ambiguously defined otherwise.
While the column aliases use the optional AS
keyword, table aliases must directly follow the table name. Table name really means a table, view, or inline view name. They can also mean a subquery factoring clause, which is the fancy name for a WITH
statement – blogged on it here.
Selection comes in two phases, identifying the sources or tables in the FROM
clause, and then filtering the sources based on comparisons in the WHERE
clause. Join statements are also filters that match rows from different tables based on value or range comparisons. You find join statements in the FROM
clause as part of a ON
or USING
subclause when queries use key words like JOIN
et cetera. You find join statements in the WHERE
clause when the queries list tables as comma separated elements in a FROM
clause.
Projection is the narrowing of rows into columns qualified by the select list. A select list is the comma separated columns returned by a query in the SELECT
clause.
While database management systems have their own particulars about sequencing and optimization, more or less they find the data sources, read the rows or indexes to rows, and then narrow the columns returned to those qualified in a select list. That’s about it unless a query involves aggregation or sorting operations.
Aggregation typically happens after selection but before projection. While columns in the SELECT
clause often set the aggregation grouping, you may use columns other than those in the select list. You qualify the grouping columns in the optional GROUP BY
clause.
Sorting by a column is done through the ORDER BY
clause. The sorting of data follows the selection process, unless there is an aggregation process. Sorting follows aggregation when it is present in a query. Aggregated data sets are limited to ordering by columns in the GROUP BY
clause.
Oracle ENUM Framework
I went back and edited that post about MySQL nuances not in Oracle from last week about the MySQL ENUM
data type. You can find a framework suggestion that lets you not have to change existing string conditioning application code during a migration from MySQL to Oracle.
You’ll see this in the middle of the page, just click it to unfold the details. Yes, JQuery has arrived on my blog.
As with everything else on this blog, let me know if you see an opportunity for improvement.
Localhost WordPress on MAMP
There’s so much energy that goes into blogging with big blog pages, I figured it was time to setup WordPress on my MacBook Pro MAMP (Mac, Apache, MySQL, and PHP) installation. That way, the world doesn’t need to know when I mess up a <div> tag.
The biggest benefit of creating a local blog becomes obvious when you start building your own plugins, templates, and themes. You also have the benefit of a built in backup if you post your work locally before moving it to your blog. Likewise, you can work on your blog when not connected to the network. By the way, thanks to Joel for pointing out that I forgot to say why this was important.
Here are the setup instructions for those interested in doing it too.
1. Download the software from WordPress.org by clicking their button or this one. If you need the tar.gz
, click on the web site link.
2. When you download it, you’ll be prompted to open it. Choose to save it.
3. You’ll find it in your downloads folder, where you can double click it to launch the unzip process into a file folder. Mine happens on the desktop because it’s convenient.
4. After you’ve unzipped it into a folder. You drag it into your MAMP htdocs
folder. You can check where the htdocs
folder is by launching the MAMP application, and choosing Preferences.
In the preferences dialog, click the Apache tab, you’ll see where the htdocs
folder location is. Mine is set to the default location.
5. After you drag the wordpress folder into the htdocs
folder, you return to the MAMP application and click the Open start page button.
6. Click the phpMyAdmin link and create a database, like the screen shot.
After a moment, you should see that you’ve created a new database named WordPress.
7. Go to your /Applications/MAMP/htdocs/wordpress
folder, and open up wp-config-sample.php
file in a text editor. You should see the same lines 19, 22, and 25. The editor screen capture is from the TextMate editor.
Edit those lines to reflect your database name, and the root password. Naturally, if you’re concerned that your local host isn’t secure, you’ll need to purchase MAMP Pro to change your root password. You can’t change it in the free version.
// ** MySQL settings - You can get this info from your web host ** // /** The name of the database for WordPress */ define('DB_NAME', 'WordPress'); /** MySQL database username */ define('DB_USER', 'root'); /** MySQL database password */ define('DB_PASSWORD', 'root'); |
After these changes, save the wp-config-sample.php
file as wp-config.php
in the same folder.
8. Enter the following in URL in your browser, and press the enter key.
http://localhost:8888/wordpress/wp-admin/install.php |
9. On the Welcome web page, enter your blog name and email (mine is omitted for obvious reasons). Click the Install WordPress button to proceed.
10. You’ll then be prompted with the admin
user and a password. You should print it or copy it to your buffer because you’ll need it when you first login. Then, click the Log In button to proceed. You should probably change your password to something that you’ll remember before you do anything else.
11. You now have a complete localhost
WordPress installation. Now, you can go to your real web accessible WordPress install and export your blog from the Tools menu. Then, on your localhost blog, you can import from the export file of your blog. If you’re using plugins (like anybody doesn’t), then copy the contents from the wp-content/plugins
folder from your online blog to your local one; and then, activate them. You can snag your Akismet key from your online site too, but it is unlikely anybody will see your localhost blog.
Violà, c’est fine.
You can now access WordPress on your local machine by typing in the following URL:
http://localhost:8888/wordpress/ |
MySQL nuances not in Oracle
I use Alan Beaulieu’s book in my entry level SQL class because it is simple and short. The problem is that we focus on Oracle products as an Oracle Academic Partner. Three items that come up frequently are the MySQL ENUM
and SET
data types, and how to perform multiple row inserts.
MySQL’s ENUM
data type
The ENUM
data type lets you enter a list of possible string values. It acts like a check constraint in an Oracle database. As such, it restricts what you enter in the column to a value found in the list, or a NULL
value provided you’ve not added a not null column constraint.
You could define a table that contains video store item types, like the following:
CREATE TABLE item_type ( id INT , text ENUM ('VHS','DVD','Blu-ray') ); |
You should note that the case sensitivity for display is set by how you define them in the ENUM
data type when you create the table. They may be entered in mixed, lowercase, or uppercase in an INSERT
statement because they’re actually stored as a number. The numbers correlate to their order in an internal list of values, and that list start with the number one.
An alternative syntax with a VARCHAR
data type is:
CREATE TABLE item_type ( id INT , TYPE VARCHAR(20) CHECK ( TYPE IN ('DVD','VHS','Blu-ray') )); |
In MySQL, this syntax is exactly equivalent in behavior to an ENUM
data type. The same isn’t true in an Oracle database. While the equivalent check constraint statement is simple, it isn’t alike behavior. The comparable statement for Oracle names the constraint. You can’t name constraints in MySQL.
CREATE TABLE item_type ( id NUMBER , TYPE VARCHAR2(20) CONSTRAINT it_type CHECK ( TYPE IN ('DVD','VHS','Blu-ray') )); |
The difference between an ENUM
type and a check constraint in MySQL is that the data may not display in a uniform way. MySQL check constraints don’t impose case sensitive validation on input strings, and they also store the data however it is input. Whereas, Oracle does impose case sensitive check constraints and rejects non-conforming strings.
Both databases support single and multiple row INSERT
statements. The syntax for single row INSERT
statements is very much alike. Multiple row INSERT
statement syntax differs between the implementations, as shown later in the blog post.
Oracle Framework to mimic ENUM
data type ↓
This framework is predicated on two assumptions. You are migrating from a MySQL to Oracle database, and your external code doesn’t enforce case sensitivity on inputs because it once relied on the MySQL ENUM
data type. Oracle check constraints enforce case sensitive inputs, which may be a solution in itself during migration. At least, it is a solution when you handle case sensitive errors gracefully in your external code. Assuming you want an alternative to changing your external application code and you want to avoid throwing errors, here are the steps to do so.
Create testing environment
Create a testing environment that uses a VARCHAR2
column without a database level constraint in conjunction with a database trigger. You can do this because a before insert or update trigger will enforce the equivalent of a database constraint. The trigger lets you define the behavior of the constraint.
Here’s the setup code, which relies on a small item_type
lookup table:
Setup code ↓
-- Conditionally drop table and sequence before attempting to create them. BEGIN FOR i IN (SELECT TABLE_NAME FROM user_tables WHERE TABLE_NAME IN (UPPER('item_type') ,UPPER('item'))) LOOP EXECUTE IMMEDIATE 'DROP TABLE '||i.table_name||' CASCADE CONSTRAINTS'; END LOOP; FOR i IN (SELECT sequence_name FROM user_sequences WHERE sequence_name IN (UPPER('item_type_s1') ,UPPER('item_s1'))) LOOP EXECUTE IMMEDIATE 'DROP SEQUENCE '||i.sequence_name; END LOOP; END; / -- Create item_type table, index on the natural key, and sequence; then seed data. CREATE TABLE item_type ( item_type_id NUMBER CONSTRAINT pk_item_type PRIMARY KEY , TABLE_NAME VARCHAR2(30) CONSTRAINT nn_item_type_1 NOT NULL , column_name VARCHAR2(30) CONSTRAINT nn_item_type_2 NOT NULL , type_name VARCHAR2(30) CONSTRAINT nn_item_type_3 NOT NULL , code VARCHAR2(5) , meaning VARCHAR2(255) CONSTRAINT nn_item_type_4 NOT NULL); CREATE UNIQUE INDEX item_type_u1 ON item_type(TABLE_NAME,column_name,type_name); CREATE TABLE item ( item_id NUMBER CONSTRAINT pk_item PRIMARY KEY , item_type NUMBER CONSTRAINT nn_item_1 NOT NULL , item_title VARCHAR2(60) CONSTRAINT nn_item_2 NOT NULL , item_rating_id NUMBER CONSTRAINT nn_item_3 NOT NULL); CREATE SEQUENCE item_type_s1 START WITH 1001; CREATE SEQUENCE item_s1 START WITH 1001; INSERT INTO item_type VALUES (item_type_s1.nextval,'ITEM','ITEM_TYPE','DVD',NULL,'DVD'); INSERT INTO item_type VALUES (item_type_s1.nextval,'ITEM','ITEM_TYPE','VHS',NULL,'VHS'); INSERT INTO item_type VALUES (item_type_s1.nextval,'ITEM','ITEM_TYPE','Blu-ray',NULL,'BLRY'); |
As you may have noticed, the seeded data contains case sensitive strings. The absence of a database-level constraint on a column of a table requires a database trigger. The trigger acts as an alternative to a check constraint and lets you enforce a filtered insertion rule.
Oracle lets you write logic directly into a database trigger, but it is better to write the logic into a stored function or procedure and call it from the trigger’s execution block.
Create a stored function
The stored function takes a string, looks it up in the item_type
table, and returns the correct case-sensitive string. The logic is implemented in this function:
CREATE OR REPLACE FUNCTION proper_item_type ( item_type VARCHAR2 ) RETURN VARCHAR2 IS CURSOR c (type_name_in VARCHAR2) IS SELECT type_name FROM item_type WHERE UPPER(type_name) = UPPER(type_name_in); BEGIN FOR i IN c (item_type) LOOP RETURN i.type_name; END LOOP; END; / |
Create a database trigger
After you’ve defined the logic for the trigger, you need to define the database trigger. The following creates a before insert or update database trigger on the item
table. The trigger ensures that INSERT
statements to the item
table store case sensitive copies from the list of possible values found in the item_type
lookup table.
CREATE OR REPLACE TRIGGER proper_item_type_t1 BEFORE INSERT OR UPDATE ON item FOR EACH ROW BEGIN :NEW.item_type := proper_item_type(:new_item_type); END; / |
The trigger takes a non-compliant or compliant string and always return a compliant string before anything is put in the item
table.
Test the framework
You can test the framework by inserting an uppercase string BLU-RAY
and evaluating the actual results. The following INSERT
statement tests the outcome of inserting a non-compliant string.
INSERT INTO item VALUES (item_s1.nextval, 'BLU-RAY', 'Star Wars I', 1001); |
You’ll see that a compliant string was actually inserted because the trigger called the function, and the function changed the value from a non-compliant to compliant string. This query let’s you see the result:
SELECT * FROM item; |
It inserted Blu-ray
in the item_type
column.
MySQL’s SET
data type
The SET
data type lets you enter a list of possible string values but differs from the ENUM
data type only in how the values are indexed. Values in the SET
are stored as bit values. You may store up to 64 members in a set.
You create a table the same way as you did with ENUM
data type, except that you use the SET
key word.
CREATE TABLE item_type ( id INT , TYPE SET ('DVD','VHS','Blu-ray')); |
All insert patterns shown for the ENUM
data type work with the SET
data type.
Multiple row INSERT
statements
The most common multiple row insert statement in MySQL is a comma delimited set of parenthetical values. Each parenthetical set of values maps to a row in the INSERT
statement.
INSERT INTO item_type VALUES ( 1, 'DVD' ), ( 2, 'Blu-ray'), ( 3, 'VHS' ); |
You can’t use the foregoing syntax in Oracle. Oralce only supports a multiple row insert with a subquery.
You can write a subquery in MySQL that fabricates an aggregate table by using numeric and string literals, like the following statement.
INSERT INTO item_type SELECT 1, 'DVD' UNION ALL SELECT 2, 'Blu-ray' UNION ALL SELECT 3, 'VHS' ; |
Another way to perform multiple row inserts in MySQL is to select from an existing table or filtered result set, like this:
INSERT INTO item_type SELECT some_int, some_varchar FROM some_table; |
You raise an error when you use parentheses in a multiple INSERT
statement in MySQL. It’s important to note because the Oracle allows you to write the statement either way.
If you’re coming from MySQL to Oracle, you should note that you may use parentheses to enclose a subquery in Oracle. The Oracle SQL parser works with or without them in an INSERT
statement.
The MySQL subquery example is the closest to the Oracle syntax for a multiple row insert. The difference is that Oracle give you the option to enclose a subquery in parentheses when using them inside INSERT
statements. Oracle requires that you use the FROM dual
clause. I actually wish Oracle would adopt the shorter syntax and maintain backward compatibility to the dual
pseudo table.
INSERT INTO item_type ( SELECT 1, 'DVD' FROM dual UNION ALL SELECT 2, 'Blu-ray' FROM dual UNION ALL SELECT 3, 'VHS' FROM dual); |
Another way to perform a multiple row insert is to select from an existing table, like this:
INSERT INTO item_type (SELECT some_int, some_varchar FROM some_table); |
Other resources:
You should check Oracle’s document that qualifies differences between MySQL and Oracle. It is the Oracle® Database SQL Developer Supplementary Information for MySQL Migrations.
Data normalization notes
I’m trying to eliminate the textbook from my database class, and wrote a data normalization blog page for my students; however, it is only done through 3NF so far. The post tries to remove the discrete math and provide clear examples. The students are checking it out for opportunities to make it clearer, if you’ve thoughts let me know.
Also, I’ve substantially update my initial blog page on SQL joins, and will put one out on set operators probably this Monday. If you’ve time to review it, I would appreciate suggestions for improvement.
The length of these blog pages has compelled me to move to a more friendly editor. I’ve opted for Blogo as my Mac blog editor. It comes from Brainjuice. My son’s experience with their customer service and product convinced me. You can find his blog post discussing customer service here.
Blogo prompts you, but beat the rush. Go to your Site Admin, select Writing under Settings in WordPress, and enable the remote XML-RPC publishing protocol. More or less like this prompt.
Notes on SQL Joins
It’s almost funny reflecting on when I first learned how to write SQL in 1985. It was using SQL/DS 1.1 (Structured Query Language/Data System) on a VMS/CMS operating system at the IBM center in the Los Angeles, California. That was in the Crocker Bank Building when I worked for First Interstate Bank. The building is still there and so are databases but both banks are long gone.
After 24 years of SQL a quick primer on joins seems like a good idea. I have a simple example that should help illustrate various joins and SQL semantics. You’ll find it as a blog page here …
Unnatural outcome of natural joins
You may like the standard syntax of listing tables as comma-delimited lists, rather than using an inner, left, right, full, natural, and cross join syntax. I do! The former method eliminates matching result sets and generally is less tedious.
The natural join is my least favorite of these because it can create problems long after the query is written. The problem is an outcome of its design. A natural join matches rows from one table to another by using all matching column names. This query makes a simplifying assumption: Nobody includes a column of the same name in two tables where they may be joined that isn’t a foreign key.
The following is quick example that creates two tables. The VEHICLE
table uses the VEHICLE_ID
column as a primary key. The WORK_ORDER
table uses the VEHICLE_ID
column as a foreign key.
CREATE TABLE vehicle ( vehicle_id NUMBER , vin VARCHAR2(20)); CREATE TABLE work_order ( work_order_id NUMBER , vehicle_id NUMBER); INSERT INTO vehicle VALUES (1,'VIN_NUMBER1'); INSERT INTO vehicle VALUES (2,'VIN_NUMBER2'); INSERT INTO work_order VALUES (1, 1); INSERT INTO work_order VALUES (2, 2); INSERT INTO work_order VALUES (3, 1); |
The following queries resolve the relationship by using an explicit join:
SELECT * FROM vehicle v, work_order wo WHERE v.vehicle_id = wo.vehicle_id; SELECT * FROM vehicle v JOIN work_order wo ON v.vehicle_id = wo.vehicle_id; SELECT * FROM vehicle v JOIN work_order wo USING(vehicle_id); |
A natural query also resolves the relationship by implicitly finding the columns that share column names, as follows:
SELECT * FROM vehicle v NATURAL JOIN work_order wo; |
All of these return a three row result set. The unnatural part of the natural query arrives during maintenance programming when somebody adds a VIN
column name to the WORK_ORDER
table (let’s say to simplify the ETL to the data warehouse). The code using a natural join isn’t invalidated but its runtime behavior is altered because it now joins both the VEHICLE_ID
and VIN
columns. Continuing the example, you’d change the table by:
ALTER TABLE work_order ADD (vin VARCHAR2(20)); |
The explicit queries still work after the change. The implicit (natural) query now returns no rows because the VIN
column contains all null values, and it is automatically added to the implicit join statement. After updating the VIN
column, the query resolves when the data matches but not when the VIN
column contains different data. What if the VIN
column in WORK_ORDER
only contains the make and model portion of the vehicle identification number while the VIN
column in VEHICLE
contains the complete unique vehicle identifier. You would receive no rows selected from the natural query.
It appears the best thing to do, is avoid natural joins.
I subsequently wrote a post about SQL join semantics. You can find it here.