SQL Notes

Russell Bateman
last update:

Links

Naming objects

(These practices weren't necessarily followed in the notes elsewhere on this page which preceded this much later section on naming.)

  1. Avoid the name of a table/column in the plural. It is better to use employee instead of employees.
  2. If the name of the table or column must consist of more than one word, use an underscore to connect them, for example employee_city. Some prefer to use what is called camel-back style instead, for example EmployeeCity. The preferred style is different for different relational database systems.
  3. Check that the name is not already used as a keyword in SQL.
  4. If the name is the same as an SQL keyword, enclose the name within quotation marks.
  5. The name of an object in a database for a table or a column should be unique and not too long. Avoid special characters in the name like $, &, * , etc. (use only letters, numbers, and underscores).
  6. Use an underscore in a name only if necessary.
  7. Never start the name with an underscore.
  8. Use comments only if necessary.
  9. Avoid abbreviations, but, if you do use them, be sure that they will be understood.
  10. Avoid giving the same name to both a table and a column.
  11. Use the same naming rules for aliases of columns and tables.
  12. Include the AS keyword for creating aliases, because this makes the code more readable.
  13. For the primary key column avoid the name id. A good idea is to combine id with the name of a table, for example: id_employee.

Aligning/indenting SQL code

SELECT p.PersonId,
     p.FirstName,
     p.LastName,
     c.Name
 FROM Person AS p
 JOIN City AS c
  ON p.CityId = c.CityId;

Commenting SQL code

SELECT p.PersonId,
     p.FirstName,
     p.LastName,
     /* Name column is the name of the city: */
     p.Name,
  FROM Person AS p
 WHERE p.Name = 'New York';

...or:

SELECT -- we have to delete this column p.PersonId,
      p.FirstName,
      p.LastName,
      p.Name
  FROM Person AS p;

Nested/subqueries indentation

SELECT p.PersonId,
      p.FirstName,
      p.LastName,
      CASE
        WHEN p.Age < 18 THEN 'below 18'
        WHEN p.Age >= 18 THEN '18 or more'
      END AS Age
  FROM Person AS p;

Inserting values

INSERT INTO Car( id_car, name, year )
    VALUES ( 1, 'Audi', 2010 ) ,
         ( 2, 'Skoda', 2015 ) ;

Command syntax

Legend for below

table   —name of a table.
column   —name of a column.
index   —name of an index.
etc.
CREATE TABLE Create new table CREATE TABLE table ( column1 datatype, ... columnN datatype )
ALTER TABLE Modify table ALTER TABLE table ADD column datatype
ALTER TABLE table DROP column
DROP TABLE Erase table DROP TABLE table
SELECT Query data in table SELECT column1 [ , ...columnN ] FROM table WHERE condition
Conditional operators =   >   <   =>   =<   <>   LIKE pattern
Pattern matching 'A%' strings that start with A
'%a' strings that end with a
'%b%' strings that contain b
INSERT INTO Insert new row INSERT INTO table ( column1 [ , ...columnN ] ) VALUES ( value1 [ , ...valueN ] )
UPDATE Modify an existing row UPDATE table SET column1 = value1 [ , columnN = valueN ] WHERE condition
DELETE Remove an existing row DELETE FROM table WHERE condition
JOIN Create temporary table of pertinent data from others* SELECT column1 [ , ...columnN ] FROM table1
[INNER|LEFT|RIGHT|FULL ] JOIN
ON table1 . column CONDITIONAL-OPERATOR table2 . column
INNER (Default) Returns all rows from both tables where there is at least one match in both.
OUTER
LEFT Returns all rows from first ("left") table plus those rows that match in the second ("right") table.
RIGHT Returns all rows from second ("right") table plus those rows that match in the first ("left") table.
FULL Return all rows where there is any match in either table.
UNION Select data from two tables with columns of identical datatype. SELECT column1 [ , ...columnN ] FROM table1
UNION
SELECT column1 [ , ...columnN ] FROM table2
CREATE INDEX Create index on which to search a table CREATE INDEX index ON table ( column1 [ , columnN ] )
DROP INDEX Delete an index—vendor-specific DROP INDEX index
ALTER TABLE table DROP INDEX index (MySQL)

Data types

Vary from vendor to vendor.

CHAR[ACTER] Fixed-length n string.
VARCHAR(n) Variable-length string, maximum length n.
BINARY Fixed-length n, binary string.
BOOLEAN TRUE or FALSE.
VARBINARY(n) Variable-length, binary string, maximum length n.
INTEGER(p) Integer numeric, precision p.
SMALLINT Integer numeric, precision 5.
INTEGER Integer numeric, precision 10.
BIGINT Integer numeric, precision 19.
DECIMAL(p,s) Exact numerica, precision p.
NUMERIC(p,s) Exact numerica, precision p, identical to DECIMAL.
FLOAT(p) Approximate numeric, mantissa precision p, in base 10.
REAL Approximate numeric, mantissa precision 7.
FLOAT Approximate numeric, mantissa precision 16.
DOUBLE PRECISION Approximate numeric, mantissa precision 16.
DATE Year, month and day.
TIME Hours, minutes and seconds.
TIMESTAMP Year, month, day, hour, minute and seconds.
INTERVAL Integer fields representing period of time.
ARRAY Set-length, ordered collection.
MULTISET Variable-length, unordered collection.
XML XML data.

Table JOINs

JOIN is a way of creating a new table from two others such that the result contains only (mostly) the data inside that you specifically want, as if it had existed all along. Below are some very useful, if a tiny bit misleading, Venn diagrams that cleverly illustrate LEFT, RIGHT, INNER and OUTER JOIN statements.

Batched INSERTs/UPDATEs example

package com.dzone.batchsql;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * Demonstrate batch update.
 *
 * id  name  code  department
 *
 * 1   Allen abc   Sales
 * 2   Max   102   Marketing
 * 3   Ward  55    Sales
 * 4   Sam   xyz   Marketing
 *
 * TODO: Integrate use of PreparedStatement into this example.
 */
public class BatchSql1
{
  private static final String CONNECTION_URL = "jdbc:mysql://localhost/personnel?user=root&;password=test123";

  Connection connection = null;

  public BatchSql1()
  {
    try
    {
      connection = DriverManager.getConnection( CONNECTION_URL );
    }
    catch( SQLException e )
    {
      System.out.println( "SQLException: " + e.getMessage() );
      System.out.println( "SQLState:     " + e.getSQLState() );
      System.out.println( "VendorError:  " + e.getErrorCode() );
    }
  }

  private final String[] newEmployeesString =
  {
    "INSERT INTO employee { id, name, code, department } values ( '1', 'Allen', 'abc', 'Sales' )",
    "INSERT INTO employee { id, name, code, department } values ( '2', 'Max',   '102', 'Marketing' )",
    "INSERT INTO employee { id, name, code, department } values ( '3', 'Ward',  'xyz', 'Sales' )",
    "INSERT INTO employee { id, name, code, department } values ( '4', 'Sam',   '55',  'Marketing' )",
  };

  /**
   * Hits the database for each insert statement.
   */
  public void doItTheSlowWay() throws SQLException
  {
    Statement statement  = connection.createStatement();

    for( String insert : newEmployeesString )
      statement.execute( insert );

    statement.close();
    connection.close();
  }

  private final Employee[] newEmployees =
  {
    new Employee( 1, "Allen", "abc", "Sales" ),
    new Employee( 2, "Max",   "102", "Marketing" ),
    new Employee( 3, "Ward",  "xyz", "Sales" ),
    new Employee( 4, "Sam",   "55",  "Marketing" )
  };

  /**
   * Create a batch of inserts, then insert all together at one time. Note
   * that batch updates can include or consist of updates or deletes too.
   */
  public void doItTheFasterWay() throws SQLException
  {
    PreparedStatement statement = connection.prepareStatement( INSERT );

    for( Employee employee : newEmployees )
    {
      StringBuilder query = new StringBuilder();

      query.append( "INSERT INTO employee( id, name, code, department ) " );
      query.append( "VALUES (' " );
      query.append( employee.getId()         ).append( "', '" );
      query.append( employee.getName()       ).append( "', '" );
      query.append( employee.getCode()       ).append( "', '" );
      query.append( employee.getDepartment() );
      query.append( "')" );
      statement.addBatch( query.toString() );
    }

    statement.executeBatch();

    statement.close();
    connection.close();
  }

  private final String INSERT = "INSERT INTO employee( id, name, code, department ) VALUES( ?, ?, ?, ? )";

  public void doItFastAndThePreparedWay() throws SQLException
  {
    PreparedStatement statement = connection.prepareStatement( INSERT );

    for( Employee employee : newEmployees )
    {
      statement.setInt   ( 1, employee.getId() );
      statement.setString( 2, employee.getName() );
      statement.setString( 3, employee.getCode() );
      statement.setString( 4, employee.getDepartment() );
      statement.addBatch();
    }

    statement.executeBatch();

    statement.close();
    connection.close();
  }
}

SQLException: A string constant ... is too long

This demonstrates the > 32K length problem. MySql/MariaDB suffer from this problem too. This is the error:


java.sql.SQLException: A string constant starting with '<?xml version="1.0" encoding="UTF-8"?>
<ClinicalDocument xm∓' is too long.
at org.apache.derby.impl.jdbc.SQLExceptionFactory.getSQLException(Unknown Source)
at org.apache.derby.impl.jdbc.Util.generateCsSQLException(Unknown Source)
at org.apache.derby.impl.jdbc.TransactionResourceImpl.wrapInSQLException(Unknown Source)
at org.apache.derby.impl.jdbc.TransactionResourceImpl.handleException(Unknown Source)
at org.apache.derby.impl.jdbc.EmbedConnection.handleException(Unknown Source)
at org.apache.derby.impl.jdbc.ConnectionChild.handleException(Unknown Source)
at org.apache.derby.impl.jdbc.EmbedPreparedStatement.(Unknown Source)
at org.apache.derby.impl.jdbc.EmbedPreparedStatement42.(Unknown Source)
at org.apache.derby.jdbc.Driver42.newEmbedPreparedStatement(Unknown Source)
at org.apache.derby.impl.jdbc.EmbedConnection.prepareStatement(Unknown Source)
at org.apache.derby.impl.jdbc.EmbedConnection.prepareStatement(Unknown Source)
at org.apache.commons.dbcp.DelegatingConnection.prepareStatement(DelegatingConnection.java:281)
at org.apache.commons.dbcp.PoolingDataSource$PoolGuardConnectionWrapper.prepareStatement(PoolingDataSource.java:313)

Here is the code:

import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

String MsgContent, MsgReceived, ... ;
String INSERT = "INSERT INTO ssdi_small_tbl( MsgContent, MsgReceived, ... )"
           + "  VALUES("
           + "          'this is the really long content',"
           + "          '2023-02-25 06:54:12',"
           + "          ... )";

public boolean insertRowInto_broken( Connection connection )
{
  logger.trace( "Inserting a row into ssdi_small_tbl in Apache Derby as if Mirth" );

  try
  {
    Statement statement = connection.createStatement();
    return( statement.executeUpdate( INSERT ) == 1 );
  }
  catch( SQLException e )
  {
    e.printStackTrace();
    return false;
  }
}

(Depending on where you get MsgContent above, string concatenation may leave you with security issues by injecting it this way anyway, but this isn't the problem we're trying to illustrate here.)

The problem occurs with string literals. The successful way to do this is to replace, in the INSERT string the (over 32K) body with ? and then replace that by virtue of the PreparedStatement.

String INSERT = "INSERT INTO ssdi_small_tbl( MsgContent, MsgReceived, ... )"
           + "  VALUES("
           + "          '?',"
           + "          '2023-02-25 06:54:12',"
           + "          ... )";

public boolean insertRowInto( Connection connection )
{
  try
  {
    Clob clob = connection.createClob();
    clob.setString( 1, MsgContent );
    PreparedStatement preparedStatement = connection.prepareStatement( INSERT_CLOB );
    preparedStatement.setClob( 1, clob );
    return ( preparedStatement.executeUpdate() == 1 );
  }
  catch( SQLException e )
  {
    e.printStackTrace();
    return false;
  }
}

A last note... If the MariaDB driver is used in place of the MySQL driver (which still works for MariaDB and long was the driver used for it), it doesn't support data type Clob. Use Blob instead in the code just above which was written originally for Derby.