Showing posts with label SQL. Show all posts
Showing posts with label SQL. Show all posts

Sunday, April 14, 2013

Howto get auto generated values from rows created by SQL inserts in DB2

Normally you know exactly which values are in the columns of a newly inserted row in a database table. But when your table has an automatic generated identity column which is the primary key of the table you will want the automatic generated key value from your insert statement. But the normal insert command does not give back values. Fortunatly newer DB2 Version have the "Select from final Table" clause to solve the problem.

For example we have an address table created by the following commands:

CREATE TABLE PETTER/ADRESSEN (
    "KEY" INTEGER GENERATED ALWAYS AS IDENTITY (
    START WITH 1 INCREMENT BY 1
    NO MINVALUE NO MAXVALUE
    NO CYCLE NO ORDER
    CACHE 20 ),
    NAME1 CHAR(20) CCSID 273 NOT NULL ,
    NAME2 CHAR(20) CCSID 273 NOT NULL ) ;
 
LABEL ON COLUMN PETTER/ADRESSEN
( "KEY" TEXT IS 'Primary Key' ,
    NAME1 TEXT IS 'Name1' ,
    NAME2 TEXT IS 'Name2' ) ;


And here is the code to add a row and get the automatic created identity number back in one step.

   
   PreparedStatement ps = con
     .prepareStatement("Select * from final table (Insert into petter/adressen( name1,name2) values('Ralf','Petter'))");
   ResultSet rs = ps.executeQuery();
   rs.next();
   System.out.println(rs.getBigDecimal(1));


With the "Select from final table" clause you can retrieve values which are set by trigger prgograms or other advanced Db2 features too. 

Sunday, February 17, 2013

Notes ist eine relationale Datenbank!

Natürlich ist der Standardspeicher NSF von Notes nach wie vor keine relationale Datenbank und auch NSFDB2 ist schon lange kein Thema mehr. Jedoch gibt es seit der Version 8 gut versteckt im Expeditor Framework eine leichtgewichtige aber nicht desto trotz sehr leistungsfähige relationale Datenbank (Apache Derby) die über eine umfangreiche SQL Unterstützung verfügt. Diese Datenbank ist sehr praktisch, wenn man Daten lokal in einer relationalen Form ablegen will. Ein Beispiel wäre man will ein Plugin, dass normalerweise nur mit einem SQL Server funktioniert offline fähig machen. Man kann aber auch Daten aus einer NSF in die Derby Datenbank kopieren und dort dann mit SQL Auswertugen über die Daten machen die mit der Notes API nur schwer oder beinahe unmöglich sind.

Wie kann man nun Apache Derby aus einem Plugin ansprechen? Als erste muss man das Derby Plugin zu den Abhängigkeiten seines Plugins hinzufügen.


Dann kann man auch schon von seinem Code auf Apache Derby zugreifen. Der folgende Code erstellt eine Datenbank mit dem Namen testDB mit einer Mitarbeitertabelle die mit ein paar Namen befüllt wird. Nachher kommt ein Query auf die neu erstellten Daten das Ergebnis wird in eine Messagebox ausgegeben. Natürlich nicht sehr sinnvoll, aber es zeigt alle notwendigen Befehle die man für die Verwendung von Derby braucht. Natürlich wird man die Initialisierung und Erstellung der Datenbank in eine eigenes Plugin auslagern, aus denen sich dann alle Plugins die Derby verwenden wollen die Connection holen. Ebenso sollte man die stmt.execute in einer Echtanwendung durch PreparedStatements ersetzen.


try {
    // Derby Datenbankverzeichnis in das Datenverzeichnis des Plugins
    // legen.
 // Dies sollte natürlich immer das gleiche sein und am besten in ein
 // Initialisierungsplugin verlegt werden.
 System.setProperty("derby.system.home", Activator.getDefault()
   .getStateLocation().toFile().getAbsolutePath());
 // Treiber für Apache Derby laden.
 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
 // Eine Konnection zu Apache Derby erstellen und bei Bedarf die
 // Datenbank testDB erstellen.
 Connection con = DriverManager
   .getConnection("jdbc:derby:testDB;create=true");
 // Ein SQLStatement Objekt erstellen.
 Statement stmt = con.createStatement();
 // Eine Tabelle in der Datenbank erstellen.
 stmt.execute("create table mitarbeiter (name1 varchar(30), name2 varchar(30), department varchar(30))");
 // Ein paar Sätze hinzufügen.
 stmt.execute("insert into mitarbeiter values('Ralf','Petter','IT')");
 stmt.execute("insert into mitarbeiter values('Max', 'Mustermann','IT')");
 stmt.execute("insert into mitarbeiter values('Lieschen', 'Müller','Sales')");
 // Ein Query auf die Datenbank absetzen.
 ResultSet rs = stmt
   .executeQuery("Select * from mitarbeiter where department='IT'");
 // Ergebnis verarbeiten und als Messagebox anzeigen.
 StringBuilder result = new StringBuilder();
 while (rs.next()) {
  result.append((rs.getString("name1").trim() + " "
    + rs.getString("name2").trim() + "\n"));
 }
 MessageBox box = new MessageBox(PlatformUI.getWorkbench()
   .getActiveWorkbenchWindow().getShell());
 box.setMessage(result.toString());
 box.open();
 // Mitarbeiter Tabelle wieder löschen.
 stmt.execute("Drop table mitarbeiter");
 // Derby wieder herunterfahren.
 DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (Exception e) {
 e.printStackTrace();
}

Eine SQL Datenbank kann in vielen Projekten sehr nützlich sein und mit dem Notesclient hat man eine sehr leistungsfähige immer bei der Hand.

Thursday, December 27, 2012

Hochperformante und sichere SQL Zugriffe in Java

Beim Zugriff auf SQL Datenbanken wird immer gerne der Fehler gemacht, dass wenn ein SQL Statement mehrmals mit verschiedenen Parametern ausgeführt werden soll nicht die dafür vorgesehenen PreparedStatements sondern die SQL Befehle mit String Konkatinierung  zusammengebaut und mit execute() ausgeführt werden. Sogar in Java Lehrbüchern findet man immer wieder Beispiele wie das folgende, dass man aber in der Praxis auf keinen Fall so verwenden sollte.

public class StatementJDBC {
 private static Statement stmt;
 /**
  * @param args
  */
 public static void main(String[] args) {
  //Verbinde mit Datenbank und initalisiere Anweisung
  AS400JDBCDataSource dataSource = new AS400JDBCDataSource("localhost", "user", "password");
  try {
   Connection con=dataSource.getConnection();
   stmt=con.createStatement();
  //Gib verschiedene Namen aus der Adressdatei mit verschiedenen Adressnummern aus.
   Date start=new Date();
   System.out.println(getName("40"));
   System.out.println(getName("41"));
   System.out.println(getName("42"));
   System.out.println(getName("43"));
   System.out.println(new Date().getTime()-start.getTime());
  } catch (SQLException e) {
   e.printStackTrace();
  }
 }
 /**
  * Liest mittels SQL den Namen eines Adressatzes.
  * 
  * Hier  wird jedes mal ein SQLStatement mit String Konkatinierung zusammengebaut 
     * und ausgeführt. 
     *
     * Dies soll man in der Praxis nicht machen.
  * @param adressNummer
  * @return Name
  */
 private static String getName(String adressNummer) throws SQLException {
  ResultSet rs=stmt.executeQuery("select * from adressen where adnr="+adressNummer);
  if(rs.next()) return rs.getString("adnam1");
  return "";
 }

}

Es gibt zwei Gründe warum man seinen SQL Code nicht jedes mal als String zusammenbauen und dann mit execute() ausführen soll.

Bei wiederholter Ausführung des selben SQL Code ist die Performance viel besser, wenn der SQL Code nicht jedesmal geparst, geprüft und in einen Zugriffsplan übersetzt werden muß. Der Vorgang ein SQL Kommando in einen Zugriffsplan zu übersetzen ist je nach verwendeter Datenbank und Komplexität der Anweisung extrem aufwendig. Der oben angeführte Codeblock in dem die Zeit gemessen wird, läuft wenn man Preparedstatements verwendet, ca. 3 mal schneller als mit dem oben angeführten Code.

Was aber fast noch wichtiger ist, wenn ich SQL Befehle mit String Konkatinierung zusammenbaue, dann wird der Code anfällig für SQL Injections. Das heißt, es kann in der Variable mit ein paar Tricks beliebiger SQL Code eingeschleust werden. Dies ist eine der häufigsten Einfallstore für Hacks auf Webseiten. Also selbst wenn ein Statement nur einmal verwendet wird, sollte man auf jeden Fall um SQL Injections zu vermeiden Preparedstatements verwenden.

Noch dazu ist die Verwendung von Preparedstatements eigentlich extrem einfach. Man ändert den Typ von stmt auf PreparedStatement, und erstellt das Objekt mit prepareStatement(). Für jeden Wert der in der Anweisung variabel sein soll fügt man ein Fragezeichen ein. Der sqlCode aus obigen Beispiel heißt dann "select * from adressen where adnr=?". Bei jeder Verwendung der SQL Anweisung muss dann mit der passenden set Methode der Platzhalter im Statement mit dem richtigen Wert befüllt werden. Am leichtesten zu verstehen ist es wenn man sich folgendes Beispiel anschaut.
public class PreparedStatementJDBC {
 private static PreparedStatement stmt;
 /**
  * @param args
  */
 public static void main(String[] args) {
  //Verbinde mit Datenbank und initalisiere Anweisung
  AS400JDBCDataSource dataSource = new AS400JDBCDataSource("localhost", "user", "password");
  try {
   Connection con=dataSource.getConnection();
   stmt=con.prepareStatement("select * from adressen where adnr=?");
  //Gib verschiedene Namen aus der Adressdatei mit verschiedenen Adressnummern aus.
   Date start=new Date();
   System.out.println(getName("40"));
   System.out.println(getName("41"));
   System.out.println(getName("42"));
   System.out.println(getName("43"));
   System.out.println(new Date().getTime()-start.getTime());
  } catch (SQLException e) {
   e.printStackTrace();
  }
 }
 /**
  * Liest mittels SQL den Namen eines Adressatzes.
  * @param adressNummer
  * @return Name
  */
 private static String getName(String adressNummer) throws SQLException {
  //Parameter im vorbereiteten Statement setzen.
  stmt.setInt(1, new Integer(adressNummer).intValue());
  ResultSet rs=stmt.executeQuery();
  if(rs.next()) return rs.getString("adnam1");
  return "";
 }

}

Die Preparedstatements können natürlich nicht nur für "Selects", sondern genauso gut auch für "Inserts" und "Updates" verwendet werden. 

Wer JDBC Zugriffe in seinen xPages, Plugins oder Agenten verwendet, sollte prüfen ob er auch wirklich überall schon PreparedStatements verwendet. Der Performancegewinn und vor allem die zusätzliche Sicherheit vor SQL Injections sollte den geringen Änderungsaufwand auf jeden Fall lohnen.

Monday, May 21, 2012

Performanceverbesserungen durch Indexonly Access beim Zugriff auf die DB/2

Sehr oft braucht man bei relationalen Zugriffen nicht den ganzen Datensatz sondern nur ein Feld. Ein typisches Beispiel ist der Kundenstamm. Meistens braucht man in Selects für die Anzeige nur den Kundennamen nicht aber den Rest der Informationen. In einer Auftragsanzeige könnte z.B. die Auftragsnummer, Kundennummer ,Kundenname, Artikelnummer angezeigt werden.

Das SQL Statement dafür wäre:

select auftraege.auftragsnummer, auftraege.kundennummer, kunden.kundenname, auftraege.artikelnummer from auftraege, kunden where auftraege.kundennummer=kunden.kundennummer

Um die Verarbeitung zu beschleunigen wurde ein Index über die Kundentabelle mit der kundennummer als Unique Key erstellt. Damit wird bei der Verarbeitung folgender Zugriffsplan verwendet:

Die Auftragsdatei wird mittels Tablescan komplett durchgelesen. Für jeden Auftrag wird ein Index Zugriff durchgeführt und mit den Auftragen zusammengejoint. Dann muss aber noch für jeden Kunden ein extra Zugriff auf den Satz in der Kunden Tabelle erfolgen, um den Namen zu lesen. In unserer Beispieldatenbank sind weil wir 14.941 Aufträge haben dafür genau so viele Zugriffe notwendig.

Wenn diese Abfrage in der Anwendung sehr oft verwendet wird, entsteht natürlich ein relativ großer Overhead. Dies kann man durch einen besseren Index, der die Verwendung von Index only Access erlaubt verbessert werden.

Wir ergänzen daher unseren Index und fügen hinter der kundennummer noch das Feld kundenname an.

Wenn wir nun die selbe SQL Anweisung wie oben ausführen, bekommen wir einen schnelleren Zugriffsplan:


  
Die aufwendige Verarbeitung des Tableprobes ist komplett weggefallen. In den Erläuterungen zu dem Zugriffsplan steht auch, dass die Indexonly Methode für den Zugriff verwendet wurde.

Bei meinen Tests konnte ich eine 25% Performanceverbesserung des Query mit dem verbesserten Index gegenüber der ursprünglichen Variante feststellen.

Der Indexonly Zugriff beschleunigt nicht nur Joins sondern auch normale Zugriffe.

Das SQL:

select kundenname from kunden where kundennummer=4711

wird wenn der um kundenname erweiterte Index vorhanden ist mit einem optimierten Zugriffsplan ausgeführt.

statt dem schlechteren Zugriffsplan wenn nur die kundenNummer im Index vorhanden ist.
Die Tests wurden auf einen i/5 V6R1 durchgeführt. Sollten sich aber auf anderen Systemen ebenfalls nachvollziehen lassen. Einfach einmal ausprobieren.

Friday, April 13, 2012

Problem mit nicht Casesensitiver Suche in SQL

Bei vielen alphanumerischen Suchen in meinen Javaprogrammen habe ich bisher folgendes Pattern verwendet:

Ein PreparedStatement mit folgenden SQL erstellt "select *from table where ucase(name)=?"

Sting caseInsentiveSuchbegriff=suchbegriff.toUpperCase();
ps.setString(caseInsentiveSuchbegriff)
ResultSet rs=ps.executeQuery();

Das hat bisher auch sehr gut funktioniert, bis mir gestern ein Benutzer gesagt hat, dass er keine Suchbegriff mit einem "ß" findet. Nach kurzer Debugsitzung war auch klar warum. Java verwendet für Strings Unicode in dem kein großes "ß" definiert ist und verwandelt daher ein "ß" in ein "SS". Unserer Datenbank db/2 auf OS/400 gibt bei ucase('ß') aber ein "ß" zurück. Daher findet das Query keinen Satz.

Die einfachste Lösung für das Problem ist die upperCase auf lowerCase umzubauen und dann funktionieren auch Suchen nach "ß" einwandfrei.

P.S.

Interessant ist in diesem Zusammenhang, dass folgender Code false ergibt.

"groß".equals("groß".toUpperCase().toLowerCase());

Was ganz logisch ist, da "groß" nach dem toUpperCase() zu "GROSS" wird und bei dem toLowerCase() dann zu "gross" konvertiert wird.
 


ad