前回の投稿でSQLiteにjdbcで接続したときにユーザー定義関数を使う方法について書きました。 今回はユーザー定義関数の簡単な例を投稿します。 ネタはorder byで使うことを念頭に置いた、乱数を返す関数です。
// UserFunctionRandomOrder.java
package sqliteuserfunctiontest;
import java.sql.SQLException;
import java.util.Random;
import org.sqlite.Function;
public class UserFunctionRandomOrder extends Function
{
public final int DEFAULT_TABLE_SIZE = 256;
public final int MAX_SIZE = 16384;
private Random _rand = null;
private int[] _randTable = null;
public UserFunctionRandomOrder()
{
_randTable = new int[DEFAULT_TABLE_SIZE];
_rand = new Random();
}
public String getName()
{
return "ufRandomOrder";
}
public void setSize(int size)
{
if(size < 2 || MAX_SIZE < size)
throw new IllegalArgumentException("サイズが不正です。2~" + MAX_SIZE + "にしてください。");
if(_randTable.length != size)
_randTable = new int[size];
}
public void setSeed(int seed)
{
_rand.setSeed(seed);
for(int i = 0; i < _randTable.length; i++)
_randTable[i] = _rand.nextInt();
}
@Override
protected void xFunc() throws SQLException
{
int id = value_int(0);
result(_randTable[Math.abs(id) % _randTable.length] ^ id);
}
}
元から用意されているrandom関数を並び替え用に使うとlimit offsetを使うことができません。 例えば、「1ページにつき10項目の情報をランダムにピックアップして載せるウェブページ」などは作れないのです。
このサンプルのランダム関数なら、シード値さえ取っておけば
(1ページ目) select * from テーブル order by ufRandomOrder(id) limit 10 (2ページ目) select * from テーブル order by ufRandomOrder(id) limit 10 offset 10 ...
というようにselectすることができます。
シード値には、最初のselectを実行するときの時間などを使えばいいでしょう。 乱数と言うには適当すぎる作りかもしれませんが、用途次第では十分かと。
試しに使ってみると、
package sqliteuserfunctiontest;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Random;
import org.sqlite.Function;
public class SqliteUserFunctionTest
{
private static final String DB_NAME = "d:/test.db";
public static void main(String[] args)
{
PrintStream out = System.out;
Connection c = null;
Random r = new Random(System.currentTimeMillis() );
try
{
out.println("◆データベースの用意");
Class.forName("org.sqlite.JDBC");
c = DriverManager.getConnection("jdbc:sqlite:" + DB_NAME);
c.setAutoCommit(false);
String sqlDropTable = "drop table if exists tmain";
String sqlCreateTable = "create table tmain (col1 integer, col2 text)";
String[] col2s = {"家", "道", "店", "駅", "田"};
Statement s = c.createStatement();
s.executeUpdate(sqlDropTable);
s.executeUpdate(sqlCreateTable);
PreparedStatement ps = c.prepareStatement(
"insert into tmain values (?, ?)"
);
for(int i = 0; i < 10000; i++)
{
ps.setInt(1, r.nextInt() );
ps.setString(2, "" + Math.abs(r.nextInt() ) + "番目の" + col2s[i % col2s.length] );
ps.executeUpdate();
}
c.commit();
out.println("◆UserFunctionRandomOrderの登録");
UserFunctionRandomOrder ufRandomOrder = new UserFunctionRandomOrder();
ufRandomOrder.setSeed( (int)System.currentTimeMillis() );
Function.create(c, ufRandomOrder.getName(), ufRandomOrder);
out.println("◆order byでufRandomOrderを使用(limit 10)");
ResultSet res = s.executeQuery
(
"select col1, col2 from tmain " +
"order by ufRandomOrder(col1) " +
"limit 10"
);
int line = 1;
while(res.next() )
{
out.println(String.format(
"res8 %03d : %12d, %20s",
line,
res.getInt("col1"),
res.getString("col2")
));
line++;
}
out.println("◆同じseedなら同じ順番に(limit 5 offset 0)");
res = s.executeQuery
(
"select col1, col2 from tmain " +
"order by ufRandomOrder(col1) " +
"limit 5"
);
line = 1;
while(res.next() )
{
out.println(String.format(
"res9 %03d : %12d, %20s",
line,
res.getInt("col1"),
res.getString("col2")
));
line++;
}
out.println("◆同じseedなら同じ順番に(limit 5 offset 5)");
res = s.executeQuery
(
"select col1, col2 from tmain " +
"order by ufRandomOrder(col1) " +
"limit 5 offset 5"
);
while(res.next() )
{
out.println(String.format(
"res10 %03d : %12d, %20s",
line,
res.getInt("col1"),
res.getString("col2")
));
line++;
}
}
catch(ClassNotFoundException exc)
{
System.err.println("jdbcドライバの読み込みエラー。\n" + exceptionToString(exc) );
}
catch(SQLException exc)
{
System.err.println(
"SQL例外 : code = " + exc.getErrorCode() + '\n' +
exc.getMessage() + '\n' +
exceptionToString(exc)
);
}
finally
{
if(c != null)
{
try
{
c.close();
}
catch (SQLException exc)
{
System.err.println(
"SQLコネクションクローズ失敗 : code = " + exc.getErrorCode() + '\n' +
exc.getMessage() + '\n' +
exceptionToString(exc)
);
}
}
}
}
private static String exceptionToString(Exception exc)
{
StringBuilder res = new StringBuilder();
for(StackTraceElement elem : exc.getStackTrace() )
{
res.append('\t');
res.append(elem.toString() );
res.append('\n');
}
return res.toString();
}
}
結果は、
◆データベースの用意 ◆UserFunctionRandomOrderの登録 ◆order byでufRandomOrderを使用(limit 10) res8 001 : -2071494873, 457399573番目の道 res8 002 : 558256421, 1151252657番目の道 res8 003 : -946425784, 1241792479番目の店 res8 004 : 1953106339, 1538172827番目の田 res8 005 : 2097671560, 977070869番目の駅 res8 006 : -124948858, 855964781番目の家 res8 007 : 1950821907, 923013975番目の駅 res8 008 : 523221652, 21569394番目の駅 res8 009 : -1997337479, 680613490番目の店 res8 010 : 1560767072, 1890788397番目の店 ◆同じseedなら同じ順番に(limit 5 offset 0) res9 001 : -2071494873, 457399573番目の道 res9 002 : 558256421, 1151252657番目の道 res9 003 : -946425784, 1241792479番目の店 res9 004 : 1953106339, 1538172827番目の田 res9 005 : 2097671560, 977070869番目の駅 ◆同じseedなら同じ順番に(limit 5 offset 5) res10 006 : -124948858, 855964781番目の家 res10 007 : 1950821907, 923013975番目の駅 res10 008 : 523221652, 21569394番目の駅 res10 009 : -1997337479, 680613490番目の店 res10 010 : 1560767072, 1890788397番目の店