まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:db

オラクルが、正規ライセンス購入前の勉強目的で試しにソフトウェアを利用することのできるライセンス『OTN(Oracle Technology Network)開発者ライセンス』を提供していることを今更ながら知りました:
自分用Oracle DBを「無償で」作ろう!「OTN開発者ライセンス」

※同じく無料版である IBM Db2 Express-C みたいな位置付け?と思っていたのですが、ちと違いそう。Express-C は機能としては Express Edition に近いのですが、開発者エディションというわけではありません。「サポートなしで本番利用可」なのが IBM Db2 Express-C であり、この辺りが違いと言えそうです。

※むしろ IBM Db2 Developer Community Edition の位置付けが近い感じ。


利用するには Oracle アカウントの登録が必要ですが、登録自体は無料でした。各種オラクル製品がダウンロード可能で、オラクルDBはもちろん、Java や MySQL などの製品版も対象に含まれていました。利用目的は制限されていますが、仕様、検証、開発、テストに使うことはできるとのこと。早速使ってみました。なんと 20年以上ぶりにオラクル DB をインストールしてみた顛末です(笑)。


まずは OTN ページにアクセスして "Sign In" します:
2017072101


自分の OTN ID とパスワードを指定して「サインイン」します:
2017072102


サインイン後、"Software Downloads" をクリックしてダウンロードページに移動します:
20170721025


ダウンロードページをスクロールすると、ダウンロード対象の製品一覧が見つかります。オラクルDBも新しいエディションである 12c の Enterprise/Standard Edition 含めてダウンロードできますが、自分は比較的軽量な方がいいので 11g の Express Edition を選択しました:
2017072103


オラクルDB Express 11g2 のダウンロードページです。License Agreement にチェックを入れます:
2017072104


そして対象プラットフォーム(下図は Linux x64 を選択。他に Windows 32/64 bit 版を選択可)を選ぶとダウンロードが開始されます:
2017072105


ダウンロードできれば後はインストールするだけですが、Linux 版のオラクルDB Express 11g2 の場合、インストールするには スワップメモリが 2GB 必要です(足りないとインストーラーが止まります)。充分なスワップメモリが確保されていない環境の場合は動的にスワップファイルを増やす必要があります。その方法や手順は以下を参照ください:
Linux の SWAP 領域を増やす


改めて Linux 版の場合は zip された rpm ファイルがダウンロードされます。なので 64bit Linux 上でダウンロードしたファイルを unzip し、Disk1 フォルダ以下にある rpm ファイルを指定してインストールします:
# unzip oracle-xe-11.2.0-1.0.x86_64.rpm.zip
# cd Disk1
# rpm -ivh oracle-xe-11.2.0-1.0.x86_64.rpm
準備中...                ########################################### [100%]
   1:oracle-xe              ########################################### [100%]
Executing post-install steps...
You must run '/etc/init.d/oracle-xe configure' as the root user to configure the database.


「root で /etc/init.d/oracle-xe configure を実行しろ」というメッセージが表示されているので、このコマンドを実行して初期セットアップを実行します。セットアップ時の選択肢は全てデフォルトのままで、明示的に指定したのはパスワード(と確認パスワード)だけです:
# /etc/init.d/oracle-xe configure

Oracle Database 11g Express Edition Configuration
-------------------------------------------------
This will configure on-boot properties of Oracle Database 11g Express
Edition.  The following questions will determine whether the database should
be starting upon system boot, the ports it will use, and the passwords that
will be used for database accounts.  Press <enter> to accept the defaults.
Ctrl-C will abort.

Specify the HTTP port that will be used for Oracle Application Express [8080]:

Specify a port that will be used for the database listener [1521]:

Specify a password to be used for database accounts.  Note that the same
password will be used for SYS and SYSTEM.  Oracle recommends the use of
different passwords for each database account.  This can be done after
initial configuration: (パスワード指定)
Confirm the password: (確認パスワード指定)

Do you want Oracle Database 11g Express Edition to be started on boot (y/n) [y]:

Starting Oracle Net Listener...Done
Configuring database...Done
Starting Oracle Database 11g Express Edition instance...Done
Installation completed successfully.


これでセットアップ完了、、のはず、ですが、この後の作業を便利に行うための追加作業をしておきます。オラクル DB をコマンドラインから操作する場合に便利な環境変数が /u01/app/oracle/product/11.2.0/xe/bin/oracle_env.sh(および /u01/app/oracle/product/11.2.0/xe/bin/oracle_env.csh)コマンドによって設定されます。なので、/etc/bashrc などに以下の1行を追加して、対象ユーザーがログインする際にこの環境変数設定が行われるようにしておきます:
. /u01/app/oracle/product/11.2.0/xe/bin/oracle_env.sh

改めてログインし直して(この環境変数設定を有効にして)、SQL*Plus を実行し、system DB に対して SQL を実行してみます:
# sqlplus system

SQL*Plus: Release 11.2.0.2.0 Production on 土 7月 22 01:54:50 2017

Copyright (c) 1982, 2011, Oracle.  All rights reserved.

パスワードを入力してください:


Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production
に接続されました。
SQL> select * from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production
PL/SQL Release 11.2.0.2.0 - Production
CORE    11.2.0.2.0      Production
TNS for Linux: Version 11.2.0.2.0 - Production
NLSRTL Version 11.2.0.2.0 - Production

SQL>

動きましたー! 懐かしい(Solaris の前の)SunOS 以来のオラクル DB インストール体験でした。いやあ、再びオラクルをインストールする日が来るとは・・・

ワードプレスのインポートプラグインを使わず、データベース内のテーブル(wp_posts とか、wp_terms とか、・・)に直接 SQL を実行して、記事をインポートする方法を紹介します。単なる記事だと(簡単すぎて)つまらないので、カテゴリ付きの記事の、そのタイトルと本文をインポートすることに挑戦しました。 なお、ワードプレスのデータベーステーブルプリフィックスは設定によって変更可能ですが、以下はデフォルトである 'wp_' に設定されているものとして紹介します。


【記事のインポート】
単純な記事のインポートだけであれば wp_posts テーブルに post_status = 'publish', post_type = 'post' でデータを挿入するだけです(必要に応じて post_date や post_author なども):
insert into wp_posts( post_content, post_title, post_status, post_type ) values( '(本文)', '(タイトル)', 'publish', 'post' );

単純に記事を公開状態でインポートするだけなら、必要な記事のぶん上記コマンドを繰り返して実行するだけです。以下はカテゴリ情報を有効にする場合の追加作業です。


この SQL を実行した結果、auto_increment なプライマリキーである ID 値が確定します。この ID 値は記事のカテゴリーを指定する際に必要になるので取得しておきます。
select last_insert_id() as ID from wp_posts;


【カテゴリーのインポート】
記事にカテゴリを付与する手順は少し複雑です。

まずカテゴリの名称(と slug)を wp_terms テーブルに挿入します:
insert into wp_terms( name, slug ) values( '(カテゴリー名)', '(カテゴリー名を urlencode したもの)' );

この SQL を実行した結果、auto_increment なプライマリキーである term_id 値が確定するので、この値を取得します:
select last_insert_id() as term_id from wp_terms;

この term_id が所得できたら、次は wp_term_taxonomy テーブルにこの term_id と、term_id が示すカテゴリの記事が何件あるのかという情報を taxonomy 情報(='category')と一緒に wp_term_taxonomy テーブルに挿入します:
insert into wp_term_taxonomy( term_id, taxonomy, count ) values( (term_id の値), 'category', (このカテゴリに属する記事数) );

この SQL を実行した結果、auto_increment なプライマリキーである term_taxonomy_id 値が確定するので、この値を取得します:
select last_insert_id() as term_taxonomy_id from wp_term_taxonomy;

term_taxonomy_id が取得できたら、最初に取得した記事の ID 値と、カテゴリを示す term_taxonomy_id 値との組み合わせを wp_term_relationships テーブルに挿入します:
insert into wp_term_relationships( object_id, term_taxonomy_id ) values( (ID の値), (term_taxonomy_id の値) );

ちとややこしいですが、ここまでの作業でカテゴリに紐付いた記事のインポートが実現できます。ワードプレスDBのテーブルスキーマとの関連だとこんな感じになります:
2017072000


まとまったデータコンテンツをワードプレスに移行する、という場合は上記の作業を必要なだけ繰り返し自動的に行うようなツールを(プログラミングで)作ることになります。本文&タイトル&カテゴリ程度が決まっている単純構成であれば、(それらのデータを取り出す部分さえ作れれば)自動化もそんなに難しくないと思ってます。


Apache Derby(或いは "Cloudscape")という RDB をご存知でしょうか?
derby-logo-web


最近は SQLite や HTML5 のローカルデータストアの台頭であまり名前を聞かなくなりましたが、Pure Java で記述された軽量の RDB です。元々は Cloudscape Inc. によって開発されましたが、Informix Software を経て IBM 製品として扱われていた時代もあります。その影響もあってか "DB2 互換 SQL" に対応し、DB2 の SQL が動く軽量の Java RDB という立ち位置でした。軽量であるが故に組み込み系のアプリケーション内で使われることが多いようです。歴史的には 2004 年に IBM から Apache 財団へソースコードが寄贈され、現在の Apache Derby という名称のプロジェクトになりました。また Oracle JDK 1.6 以降に(オプションとして)組み込まれている JavaDB の実装はこの Apache Derby です。

私自身は "Cloudscape" と呼ばれていた頃に使ったことがありました。今回、久しぶりに Apache Derby を使ってみました。

JDK のオプションに組み込まれているとはいえ、せっかくなので最新版を使ってみることにしました。まずは Apache Derby のダウンロードページから最新バージョン(2016/Oct/07 時点では 10.12.1.1)のリンクをクリックします:
2016100601


最新版のアーカイブファイル:db-derby-(バージョン番号)-bin.zip をクリックしてダウンロードします:
2016100602


ダウンロードした zip ファイルを展開し、lib フォルダ内の必要な JAR ファイルを取り出します。今回は本体である derby.jar と、日本語ロケールが含まれた derbyLocale_ja_JP.jar の2ファイルを取り出します:
2016100603


この2ファイルを Java の開発環境から使えるようにします。J2SE/EE プロジェクトであれば、WebContent/WEB-INF/lib 以下にコピーするなどして、コンパイル/実行時に参照できるようにしておきます:
2016100604


試しに以下のような index.jsp ファイルを用意してみました:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%@ page import="java.sql.*" %>
<%@ page import="me.juge.derby.*" %>
<%
  request.setCharacterEncoding("utf-8");
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3c.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<title>Derby JDBC Sample</title>
</head>
<body>

<table border="1">
<tr><th>ID</th><th>NAME</th><th>PRICE</th></tr>
<%
final String driverName = org.apache.derby.jdbc.EmbeddedDriver.class.getCanonicalName();
final String dbName = "derbydb";
final String connURL = "jdbc:derby:" + dbName + ";create=true";  //. DBが存在していない場合は作成するオプション

try{
  Class.forName( driverName );
  Connection conn = null;

  try{
    conn = DriverManager.getConnection( connURL );
  }catch( Exception e ){
  }

  if( conn != null ){
    ResultSet rs = null;
    //. 初期化
    try{
      //. 試しに items テーブルにアクセス
      Statement s0 = conn.createStatement();
      rs = s0.executeQuery( "select count(*) from items" );
    }catch( SQLException e ){
      //e.printStackTrace();

      String state = e.getSQLState();
      if( state.equals( "42X05" ) ){
        //. テーブルが存在しない
        try{
          Statement s1 = conn.createStatement();
          s1.execute( "create table items("
            + " id int generated always as identity primary key"  //. この辺りが DB2 互換 SQL
            + ", name varchar(100)"
            + ", price int"
            + " )");

          PreparedStatement s2 = conn.prepareStatement( "insert into items( name, price ) values( ?, ? )" );
          s2.setString( 1, "シャンプー" );
          s2.setInt( 2, 1000 );
          s2.execute();

          PreparedStatement s3 = conn.prepareStatement( "insert into items( name, price ) values( ?, ? )" );
          s3.setString( 1, "石鹸" );
          s3.setInt( 2, 500 );
          s3.execute();
        }catch( Exception e1 ){
        }
      }else if( state.equals( "42X14" ) || state.equals( "42821" ) ){
        //. テーブル定義が不正

      }else{
        //. その他の想定外の例外

      }
    }finally{
      if( rs != null ) rs.close();
    }

    //. レコード表示
    try{
      rs = conn.createStatement().executeQuery( "select * from items" );
      while( rs.next() ){
        int id = rs.getInt( 1 );
        String name = rs.getString( 2 );
        int price = rs.getInt( 3 );
%>
<tr><td><%= id %></td><td><%= name %></td><td><%= price %></td></tr>
<%
      }
    }catch( SQLException e ){
    }finally{
      if( rs != null ) rs.close();
    }
  }
}catch( Exception e ){
  e.printStackTrace();
}
 %>
</table>
</body>
</html>

コードそのものは一般的な JDBC プログラミングです。DB を開く際に目的の DB が存在していない場合はその場で作成するようなオプション("create=true")を付与しています。また items というテーブルのレコード数を取得して、エラーが出るようであればテーブルが存在していないと判断し、新規に items テーブルを定義して作成し、2つほどデータを insert するようにしています。 最終的には items テーブル内の全レコードを画面に出力する、という内容にしています。


このプロジェクトを実行して、index.jsp にブラウザでアクセスすると以下のような表形式で2つのレコード内容が確認できます:
2016100701

まあ普通に RDB として使うこともできますが、組み込み系以外であればフットプリントの小ささからローカルレプリカ DB として利用する、というケースも考えられます。特にサーバー側が DB2 だったりすると SQL 互換が便利に使えたりしますね。

久しぶりに Cloudscape を使ってみました。相変わらずですが( Java が動いている前提はありますが)JAR ファイル置くだけで使える RDB は便利ですね。



WordPress のデータベースから SQL でデータを取り出すシリーズ(?)の応用編。今回のテーマは
 ポストした本文とタイトルと、その文書に付与されたカテゴリの一覧だけを取り出す
というものです。

最初に結論を書いておきます。この SQL で目的を達成できるはず(公開データと未公開データの両方を取り出しています):
select wp_posts.post_content as content, wp_posts.post_title, wp_terms.name as category
 from wp_posts, wp_terms, wp_term_relationships
 where wp_posts.post_type = 'post' 
 and ( wp_posts.post_status = 'publish' or wp_posts.post_status = 'draft' )
 and wp_posts.ID = wp_term_relationships.object_id
 and wp_term_relationships.term_taxonomy_id = wp_terms.term_id;

説明する上で、WordPress のテーブルの相関関係を理解しておく必要があります。関連図についてはこちらを参照ください:
データベース構造 - WordPress Codex 日本語版


まず WordPress にポストしたレコードデータは wp_posts テーブル内に格納されています。特に(固定ページなどではなく)ポストデータは
 post_type = 'post'
となっているものが該当します。

そして今回は公開済みレコードと未公開(ドラフト)レコードの両方を取り出すことにしました。公開状況は同テーブルの post_status に格納されており、
 post_status = 'publish' or post_status = 'draft'
のいずれかの条件を満たしているものを取り出し、そこから本文(post_content)とタイトル(post_title)を取りだせばよい、ということになります。もしもドラフトが不要な場合はこの行の or 以降は不要です。

取り出すレコードの選別はこれだけです。次に各レコードに紐付けられたカテゴリ名称を取り出す必要があります。

カテゴリーのデータは wp_terms テーブルに(カテゴリ名は name 列に)含まれています。そしてどの文書がどのカテゴリに属しているのか、というリレーションは wp_term_relationships テーブルに格納されており、同テーブル内の object_id が wp_posts.ID 、term_taxonomy_id が wp_terms.term_id に該当しています。

例えば、wp_term_relationships テーブルに以下のようなレコードが存在していた場合、
object_idterm_taxonomy_id
41
61
82

wp_posts テーブルの ID が 4 の文書と 6 の文書は、wp_terms テーブルの term_id = 1 のカテゴリに属していて、ID が 8 の文書は term_id = 2 のカテゴリに属している、ということになります。

これらの関係を1つの SQL に書き直すと上記の SQL になります。
2016070701


WordPress に使っているデータベース(一般的には MySQL のデータベースだと思います)から、SQL を使って、ブログの名称や説明文を取り出す方法です:

2016070501
↑この図の赤枠部分がブログの名称、青枠部分が説明文です。


前提として、データベース作成時のテーブルのプレフィックスはデフォルトの 'wp_' をそのまま使っているものとします。つまりデータベース内には wp_posts とか wp_terms といったテーブルが存在している状態で利用中のものとします:
2016070502


で、ブログの名称は wp_options テーブル内に option_name の値が 'blogname' であるレコードの option_value 値として格納されています。したがってブログ名称を取り出す際の SQL は以下になります:
select wp_options.option_value as name from wp_options where wp_options.option_name = 'blogname';

2016070503
↑取り出せました


同様に、説明文は option_name の値が 'blogdescription' であるレコードの option_value 値です。したがって SQL だと以下になります:
select wp_options.option_value as name from wp_options where wp_options.option_name = 'blogdescription';
2016070504


この辺りがわかっていると、SQL でブログタイトルや説明を無理やり書き換える、ということも可能になります。


このページのトップヘ