tag:blogger.com,1999:blog-6772074129483843792024-02-19T19:16:02.977+09:0090H Techkkino90hの技術メモです。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.comBlogger34125tag:blogger.com,1999:blog-677207412948384379.post-30853526186405224632015-04-20T15:26:00.002+09:002015-04-20T15:30:04.665+09:00SQLでWHERE区に複数列を指定PostgreSQLで、次のようなテーブルを作成し、データを入れます。<br />
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
CREATE TABLE sample1 (last_name TEXT, first_name TEXT);
BEGIN;
INSERT INTO sample1 (last_name, first_name) VALUES ('佐藤', '一郎');
INSERT INTO sample1 (last_name, first_name) VALUES ('佐藤', '二郎');
INSERT INTO sample1 (last_name, first_name) VALUES ('田中', '一郎');
INSERT INTO sample1 (last_name, first_name) VALUES ('田中', '二郎');
COMMIT;
]]></script>
ここで、WHERE区にlast_nameとfirst_nameに条件を設定してデータを取得します。<br />
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
SELECT * FROM sample1 WHERE (last_name, first_name)=('佐藤', '一郎');
]]></script>
結果は次のようになります。<br />
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
sample1=> SELECT * FROM sample1 WHERE (last_name, first_name)=('佐藤', '一郎');
last_name | first_name
-----------+------------
佐藤 | 一郎
(1 row)
]]></script>
<br />
これと同様のことをOracleで試してみました。<br />
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
CREATE TABLE sample1 (last_name VARCHAR2(20), first_name VARCHAR2(20));
INSERT INTO sample1 (last_name, first_name) VALUES ('佐藤', '一郎');
INSERT INTO sample1 (last_name, first_name) VALUES ('佐藤', '二郎');
INSERT INTO sample1 (last_name, first_name) VALUES ('田中', '一郎');
INSERT INTO sample1 (last_name, first_name) VALUES ('田中', '二郎');
COMMIT;
SELECT * FROM sample1 WHERE (last_name, first_name)=('佐藤', '一郎');
]]></script>
結果は次のようになります。<br />
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
SQL> SELECT * FROM sample1 WHERE (last_name, first_name)=('佐藤', '一郎');
SELECT * FROM sample1 WHERE (last_name, first_name)=('佐藤', '一郎')
*
行1でエラーが発生しました。:
ORA-00920: 関係演算子が無効です。
]]></script>
エラーとなりました。<br />
そこで、WHERE区を<br />
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
SELECT * FROM sample1 WHERE (last_name, first_name)=(('佐藤', '一郎'));
]]></script>
と修正して実行してみると<br />
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
SQL> SELECT * FROM sample1 WHERE (last_name, first_name)=(('佐藤', '一郎'));
LAST_NAME FIRST_NAME
-------------------- --------------------
佐藤 一郎
]]></script>
正しく動作しました。<br />
Oracleでは、値を入れる辺を括弧で括ってやるとうまくいくようです。<br />
元々、次のように<br />
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
SQL> SELECT * FROM sample1 WHERE (last_name, first_name) IN (('佐藤','一郎'),('田中','二郎'));
LAST_NAME FIRST_NAME
-------------------- --------------------
佐藤 一郎
田中 二郎
]]></script>
IN区で複数の条件を指定するように作られているのかな?っという気がしますが、どうなんでしょう。<br />kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-36450038480700806392014-10-24T11:27:00.001+09:002014-10-24T11:29:44.744+09:00UNIQUE制約でのNULLの扱いPostgreSQLで、テーブルの複数列でUNIQUE制約をかけたとき、その中にNULLを許可する列があった場合の挙動について。<br />
<br />
次のテーブルを作成します。<br />
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
CREATE TABLE sample1
(
id INTEGER NOT NULL
, name TEXT NOT NULL
, std TEXT
, CONSTRAINT pkey_sample1 PRIMARY KEY (id)
, CONSTRAINT ukey_sample1 UNIQUE (name, std)
);
]]></script>
このテーブルに次のINSERT文でデータを入れてみます。<br />
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
INSERT INTO sample1 (id, name, std) VALUES (1, 'beef', 'a5');
INSERT INTO sample1 (id, name, std) VALUES (2, 'beef', 'a4');
INSERT INTO sample1 (id, name, std) VALUES (3, 'beef', 'a5');
INSERT INTO sample1 (id, name, std) VALUES (4, 'pork', NULL);
INSERT INTO sample1 (id, name, std) VALUES (5, 'pork', NULL);
]]></script>
結果は次のようになります。<br />
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
db1=> INSERT INTO sample1 (id, name, std) VALUES (1, 'beef', 'a5');
INSERT 0 1
db1=> INSERT INTO sample1 (id, name, std) VALUES (2, 'beef', 'a4');
INSERT 0 1
db1=> INSERT INTO sample1 (id, name, std) VALUES (3, 'beef', 'a5');
ERROR: duplicate key value violates unique constraint "ukey_sample1"
db1=> INSERT INTO sample1 (id, name, std) VALUES (4, 'pork', NULL);
INSERT 0 1
db1=> INSERT INTO sample1 (id, name, std) VALUES (5, 'pork', NULL);
INSERT 0 1
]]></script>
id=3は、UNIQUE制約違反でINSERTに失敗しています。<br />
ここで引っかかったのはid=5がUNIQUE制約違反にならず、INSERTに成功しているところです。<br />
<br />
私のイメージだと、NULLは何もないという感覚だったので、id=4とid=5のデータはUNIQUE制約に違反してid=5はINSERTに失敗すると思っていました。<br />
調べてみると、PostgreSQLの一意制約の解説では、<br />
<blockquote>一般に、制約の対象となる列について同じ値を持つ行が、テーブル内に1行を上回る場合は、一意性制約違反になります。 しかし、この比較では2つのNULL値は等価とはみなされません。 つまり、一意性制約があったとしても、制約対象の列の少なくとも1つにNULL値を持つ行を複数格納することができるということです。 この振舞いは標準SQLに準拠していますが、この規則に従わないSQLデータベースがあることを聞いたことがあります。 ですから、移植する予定のアプリケーションを開発する際には注意してください。
</blockquote>
とあり、NULLは等価ではないとあり、上の例ではid=4とid=5はUNIQUE制約に違反せず、INSERTできるということになります。<br />
文中の<blockquote>この規則に従わないSQLデータベースがあることを聞いたことがあります。</blockquote>は、MicrosoftのSQL Serverが該当するみたいです。<br />
OracleやMySQLなどはPostgreSQLと同じ挙動をするんだとか。<br />
移植の際などには気をつける必要があります。<br />
<br />
対策としては、std列をNOT NULLとして、id=4,id=5には''を入れるようにすれば、意図した動きになります。<br />
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
CREATE TABLE sample1
(
id INTEGER NOT NULL
, name TEXT NOT NULL
, std TEXT NOT NULL
, CONSTRAINT pkey_sample1 PRIMARY KEY (id)
, CONSTRAINT ukey_sample1 UNIQUE (name, std)
);
db1=> INSERT INTO sample1 (id, name, std) VALUES (1, 'beef', 'a5');
INSERT 0 1
db1=> INSERT INTO sample1 (id, name, std) VALUES (2, 'beef', 'a4');
INSERT 0 1
db1=> INSERT INTO sample1 (id, name, std) VALUES (3, 'beef', 'a5');
ERROR: duplicate key value violates unique constraint "ukey_sample1"
db1=> INSERT INTO sample1 (id, name, std) VALUES (4, 'pork', '');
INSERT 0 1
db1=> INSERT INTO sample1 (id, name, std) VALUES (5, 'pork', '');
ERROR: duplicate key value violates unique constraint "ukey_sample1"
]]></script>
<br />
では、ADO.NETのDataTableはどうなんでしょう?<br />
こんなサンプルを作って試してみました。
<script type='syntaxhighlighter' class='brush: csharp'><![CDATA[
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace DataTableUniqueTest
{
class Program
{
static void Main(string[] args)
{
// テーブル作成
DataTable sample1 = new DataTable();
sample1.Columns.Add(new DataColumn("id" , typeof(int) ));
sample1.Columns.Add(new DataColumn("name", typeof(string)));
sample1.Columns.Add(new DataColumn("std" , typeof(string)));
sample1.PrimaryKey = new DataColumn[] { sample1.Columns["id"] };
sample1.Constraints.Add(new UniqueConstraint(new DataColumn[] { sample1.Columns["name"]
, sample1.Columns["std"] }));
// DataRowの作成
DataRow row1 = sample1.NewRow(); row1["id"] = 1; row1["name"] = "beef"; row1["std"] = "a5;
DataRow row2 = sample1.NewRow(); row2["id"] = 2; row2["name"] = "beef"; row2["std"] = "a4";
DataRow row3 = sample1.NewRow(); row3["id"] = 3; row3["name"] = "beef"; row3["std"] = "a5";
DataRow row4 = sample1.NewRow(); row4["id"] = 4; row4["name"] = "pork"; row4["std"] = DBNull.Value;
DataRow row5 = sample1.NewRow(); row5["id"] = 5; row5["name"] = "pork"; row5["std"] = DBNull.Value;
// 行の追加
try { sample1.Rows.Add(row1); } catch (Exception ex) { Console.WriteLine("id=1 : " + ex.Message); }
try { sample1.Rows.Add(row2); } catch (Exception ex) { Console.WriteLine("id=2 : " + ex.Message); }
try { sample1.Rows.Add(row3); } catch (Exception ex) { Console.WriteLine("id=3 : " + ex.Message); }
try { sample1.Rows.Add(row4); } catch (Exception ex) { Console.WriteLine("id=4 : " + ex.Message); }
try { sample1.Rows.Add(row5); } catch (Exception ex) { Console.WriteLine("id=5 : " + ex.Message); }
Console.ReadKey();
}
}
}
]]></script>
結果はこうなりました。
<script type='syntaxhighlighter' class="brush: sql;"><![CDATA[
id=3 : 列 'name, std' は一意であるように制約されています。値 'beef, a5' は既に存在します。
id=5 : 列 'name, std' は一意であるように制約されています。値 'pork, ' は既に存在します。
]]></script>
PostgreSQLとは異なり、NULLを同じ値として扱っているようです。<br />
PostgreSQLにNULLを許可した列を含むUNIQUE制約を持つテーブルからADO.NETのDataTableにデータを取り込んだ場合、エラーになる可能性が考えられます。<br />
UNIQUE制約をかける列は、NOT NULLにするべきでしょうね。<br />kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-20285254904785345552014-05-15T10:00:00.000+09:002014-08-05T13:45:52.877+09:00ubuntu14.04にoracle-xe 11gをインストールubuntu14.04 LTS ServerにOracle Database Express Edition 11g Release2をインストールしたときの手順のまとめ。<br />
<br />
<a href="http://blog.livedoor.jp/jun_dime/archives/51761343.html">こちらのサイトを参考にさせていただきました。</a>というか、パクリです。<br />
<br />
まず、<a href="http://www.oracle.com/technetwork/jp/products/express-edition/downloads/index.html">Oracle-XE-11.2.0のLinux x64版</a>をダウンロードして、ubuntuにコピーしておく。<br />
<br />
必要なパッケージをインストール。<br />
<pre><code><xmp>sudo apt-get install alien libaio1 unixodbc unzip
</xmp></code></pre>
<br />
oracle-xeのパッケージを解凍。<br />
<pre><code><xmp>unzip oracle-xe-11.2.0-1.0.x86_64.rpm.zip
</xmp></code></pre>
<br />
rpmパッケージをdebパッケージに変換。<br />
<pre><code><xmp>cd Disk1/
sudo alien --to-deb --scripts oracle-xe-11.2.0-1.0.x86_64.rpm
</xmp></code></pre>
<br />
パッケージをインストール。<br />
/sbin/chkconfig をviで編集し<br />
<pre><code><xmp>sudo vi /sbin/chkconfig
</xmp></code></pre>
以下の内容を記述。<br />
<pre><code><xmp>#!/bin/bash
# Oracle 11gR2 XE installer chkconfig hack for Debian by Dude
file=/etc/init.d/oracle-xe
if [[ ! `tail -n1 $file | grep INIT` ]]; then
echo >> $file
echo '### BEGIN INIT INFO' >> $file
echo '# Provides: OracleXE' >> $file
echo '# Required-Start: $remote_fs $syslog' >> $file
echo '# Required-Stop: $remote_fs $syslog' >> $file
echo '# Default-Start: 2 3 4 5' >> $file
echo '# Default-Stop: 0 1 6' >> $file
echo '# Short-Description: Oracle 11g Express Edition' >> $file
echo '### END INIT INFO' >> $file
fi
update-rc.d oracle-xe defaults 80 01
</xmp></code></pre>
configureのawkのパスが違うのでシンボリックリンクを作成。<br />
<pre><code><xmp>sudo ln -s /usr/bin/awk /bin/awk
</xmp></code></pre>
ファイルのパーミッションを変更。<br />
<pre><code><xmp>sudo chmod 755 /sbin/chkconfig
</xmp></code></pre>
<br />
パッケージのインストール。<br />
<pre><code><xmp>sudo dpkg --install ./oracle-xe_11.2.0-2_amd64.deb
</xmp></code></pre>
<br />
/etc/init.d/oracle-xeの /var/lock/subsys/ を /var/lock/ に変更。<br />
<pre><code><xmp>sudo vi /etc/init.d/oracle-xe
</xmp></code></pre>
<br />
<br />
※私の試した環境(仮想マシンに1Gのメモリを設定)では、このままconfigureを実行してうまくいきましたが、configureでエラーが出るような場合は、以下のパラメータを変更してみてください。<br />
/u01/app/oracle/product/11.2.0/xe/config/scripts/init.ora<br />
/u01/app/oracle/product/11.2.0/xe/config/scripts/initXETemp.ora<br />
の2つのファイルの、memory_targetをコメントにして、pga_aggregate_targetとsga_targetを設定します。<br />
<pre><code><xmp>sudo vi /u01/app/oracle/product/11.2.0/xe/config/scripts/init.ora
sudo vi /u01/app/oracle/product/11.2.0/xe/config/scripts/initXETemp.ora
</xmp></code></pre>
以下の内容を記述。<br />
<pre><code><xmp>#memory_target=418381824
pga_aggregate_target=200540160
sga_target=601620480
</xmp></code></pre>
<br />
<br />
configureを実行。<br />
<pre><code><xmp>sudo /etc/init.d/oracle-xe configure
</xmp></code></pre>
<br />
.profileを編集して、oracleの環境変数が設定されるようにする。<br />
<pre><code><xmp>vi ~/.profile
</xmp></code></pre>
以下の内容を追加する。<br />
<pre><code><xmp>source /u01/app/oracle/product/11.2.0/xe/bin/oracle_env.sh
</xmp></code></pre>
<br />
編集した.profileの内容を反映する。<br />
<pre><code><xmp>source ~/.profile
</xmp></code></pre>
<br />
oracleに接続できるか確認する。<br />
<pre><code><xmp>sqlplus system/[設定したパスワード]@XE
</xmp></code></pre>
<br />
これでoracleに接続できたらOK!kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com1tag:blogger.com,1999:blog-677207412948384379.post-41553747267475122112013-03-29T13:55:00.001+09:002013-03-29T13:55:03.354+09:00ADO.NETのDataTableクラスのCaseSensitiveプロパティADO.NETのDataTableで、プライマリキーに文字列型の列を指定したとき、その列に例えば、"A"という値と"a"という値が入るとエラーとなる。<br />
大文字/小文字の区別するためには、プロパティのCaseSensitiveをtrueにするとよい。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-31442451576126724462013-01-11T09:41:00.000+09:002013-01-11T09:41:18.983+09:00ubuntuにredmineをapt-getでインストールubuntu 12.04 LTS Serverにapt-getでredmineを入れてみました。<br />
<br />
データベースはPostgreSQLを使用します。<br />
<pre><code><xmp>sudo apt-get install postgresql libapache2-mod-passenger redmine-pgsql redmine
</xmp></code></pre>
<br />
apache2の設定は自分でやらないといけないみたいなので、<a href="http://ohhappy.jp/sakuravps/597.html">Oh!Happy.JPさん</a>を参考に(というかそのまま真似して)設定しました。<br />
<br />
まず、/usr/share/redmine/publicを/var/www/redmineにシンボリックリンク。<br />
<pre><code><xmp>sudo ln -s /usr/share/redmine/public /var/www/redmine
</xmp></code></pre>
/etc/apache2/mod-available/passenger.confに、PassengerDefaultUserの設定を追加。<br />
<pre><code><xmp>PassengerDefaultUser www-data
</xmp></code></pre>
/etc/apache2/sites-enabled/000-defaultに、/var/www/redmineのディレクトリ情報を追加。<br />
<pre><code><xmp><Directory /var/www/redmine>
RailsBaseURI /redmine
PassengerResolveSymlinksInDocumentRoot on
</Directory>
</xmp></code></pre>
passegerの有効化。<br />
<pre><code><xmp>cd /etc/apache2/mods-available
sudo a2enmod passenger
</xmp></code></pre>
apache2の再起動。<br />
<pre><code><xmp>sudo /etc/init.d/apache2 restart
</xmp></code></pre>
これでブラウザから、http://サーバー名/redmine/にアクセスすると、redmineのトップページが開かれました。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-32121292854284959472012-12-07T15:35:00.001+09:002012-12-07T15:35:12.013+09:00PostgreSQL9.2で同期レプリケーション
次のような環境を構築してみようと思います。<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG-4lb1jjmeDnrpwKeRqQczbzGFjFxptHsactZ-dOh2yP0M9UrduMggxQTvcbg9FIdMvvyhVh1p4DuLrXRaepY3Wdnc7GJvMPeInN9Z1k36tWhn8tzCk6kmC9YaSo0qez9OjXgHob2SuV7/s1600/postgresql_replication.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG-4lb1jjmeDnrpwKeRqQczbzGFjFxptHsactZ-dOh2yP0M9UrduMggxQTvcbg9FIdMvvyhVh1p4DuLrXRaepY3Wdnc7GJvMPeInN9Z1k36tWhn8tzCk6kmC9YaSo0qez9OjXgHob2SuV7/s1600/postgresql_replication.jpg" /></a>
<table border="1">
<caption>各サーバーの構成</caption>
<tr><th>名前</th><th>役割</th></tr>
<tr><td>pgpool</td><td>pgpool2で負荷分散と障害時のフェイルオーバー</td></tr>
<tr><td>pgsql1</td><td>PostgreSQL9.2で同期レプリケーションのプライマリ</td></tr>
<tr><td>pgsql2</td><td>PostgreSQL9.2で同期レプリケーションのセカンダリ</td></tr>
</table>
OSはすべてubuntu 12.04 LTS Serverを使います。<br />
<br />
<h4>[PostgreSQLのセットアップ]</h4>
まず、各サーバーにPostgreSQLをソースからビルドしてセットアップします。<br />
ビルドに必要なパッケージをインストールします。<br />
<pre><code><xmp>sudo apt-get install build_essential libreadline6-dev zlib1g-dev</xmp></code></pre>
PostgreSQLユーザーとディレクトリを作成します。<br />
<pre><code><xmp>sudo adduser postgres
sudo mkdir /usr/local/pgsql
sudo chown postgres:postgres /usr/local/pgsql
</xmp></code></pre>
PostgreSQLのソースをダウンロードします。<br />
<pre><code><xmp>cd /usr/local/src
sudo wget http://ftp.postgresql.org/pub/source/v9.2.1/postgresql-9.2.1.tar.gz
sudo tar zxvf postgresql-9.2.1.tar.gz
sudo chown -R postgres:postgres postgresql-9.2.1
</xmp></code></pre>
PostgreSQLのソースをビルドします。<br />
<pre><code><xmp>su - postgres
cd /usr/local/source/postgresql-9.2.1
./configure
make
make install
</xmp></code></pre>
postgresユーザーの.profileに<br />
<pre><code><xmp># User specific environment and startup programs
PGHOME=/usr/local/pgsql
PGDATA=$PGHOME/data
PGLIB=$PGHOME/lib
PATH=$PATH:$HOME/bin:$PGHOME/bin
export PGHOME PGDATA PGLIB PATH
</xmp></code></pre>
を追加して<br />
<pre><code><xmp>source ~/.profile</xmp></code></pre>
で、環境変数に反映します。<br />
PostgreSQLの初期化を行います。<br />
<pre><code><xmp>initdb</xmp></code></pre>
sudoの権限のあるユーザーに戻って、PostgreSQLの自動起動の設定を行います。<br />
<pre><code><xmp>sudo vi /etc/ld.so.conf</xmp></code></pre>
で、ファイルの末尾に /usr/local/pgsql/lib を追加して、<br />
<pre><code><xmp>sudo ldconfig -v</xmp></code></pre>
を実行します。<br />
PostgreSQL起動スクリプトをコピーします。<br />
<pre><code><xmp>sudo cp /usr/local/src/postgresql-9.2.1/contrib/start-scripts/linux /etc/init.d/postgresql
sudo chmod 755 /etc/init.d/postgresql
sudo update-rc.d postgresql start 90 2 3 4 5 . stop 10 0 1 6 .
</xmp></code></pre>
PostgreSQLを起動してみます。<br />
<pre><code><xmp>sudo /etc/init.d/postgresql start</xmp></code></pre>
問題なく起動できたら、データベースのユーザーのpostgresにパスワードを設定します。<br />
<pre><code><xmp>su - postgres
psql -c "alter user postgres with password 'postgresのパスワード';"
</xmp></code></pre>
<br />
<h4>[下準備]</h4>
pgpoolから、pgsql1,pgsql2のコマンドを、パスワードなしのsshで実行できるようにします。<br />
これはpgsql1かpgsql2で障害が発生した時、その対応処理をpgpoolから実行させるためです。<br />
まず、pgpoolで<br />
<pre><code><xmp>su - postgres
mkdir .ssh
chmod 700 .ssh
ssh-keygen -t dsa
</xmp></code></pre>
で、パスワードなしの鍵を作成し、作成された公開鍵をpgsql1とpgsql2にコピーします。<br />
<pre><code><xmp>scp .ssh/id_dsa.pub postgres@pgsql1:~/
scp .ssh/id_dsa.pub postgres@pgsql2:~/
</xmp></code></pre>
次に、pgsql1とpgsql2の両方で、<br />
<pre><code><xmp>su - postgres
mkdir .ssh
chmod 700 .ssh
cd .ssh
cat ~/id_dsa.pub >> authorized_keys
chmod 600 authorized_keys
rm ~/id_dsa.pub
</xmp></code></pre>
として、公開鍵を追加します。<br />
試しに、pgpoolからpgsql1とpgsql2のコマンドを実行してみて<br />
<pre><code><xmp>ssh postgres@pgsql1 "/usr/local/pgsql/bin/psql -l"
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
postgres | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 |
template0 | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(3 rows)
ssh postgres@pgsql2 "/usr/local/pgsql/bin/psql -l"
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
postgres | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 |
template0 | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(3 rows)
</xmp></code></pre>
と表示されれば成功です。<br />
<br />
<h4>[PostgreSQLの同期レプリケーションの設定]</h4>
pgsql1をプライマリ、pgsql2をセカンダリという構成で同期レプリケーションするように設定します。<br />
<br />
まず、プライマリのpgsql1の設定を変更します。<br />
<pre><code><xmp>su - postgres
cd /usr/local/pgsql/data
vi postgresql.conf
</xmp></code></pre>
postgresql.confに以下の設定を追加します。<br />
<pre><code><xmp>listen_addresses = '*'
wal_level = hot_standby
archive_mode = on
archive_command= '/bin/cp %p /usr/local/pgsql/data/pg_archive/%f'
max_wal_senders = 3
wal_keep_segments = 5
synchronous_standby_names = 'pgsql2'
</xmp></code></pre>
postgresql.confをコピーして、同期レプリケーション用(postgresql.conf.sync)と非同期レプリケーション用(postgresql.conf.async)を作成します(これはフェイルオーバーした時に使用します)。<br />
<pre><code><xmp>cp postgresql.conf postgresql.conf.sync
cp postgresql.conf postgresql.conf.async
</xmp></code></pre>
非同期レプリケーション用のpostgresql.conf.asyncのsynchronous_standby_namesをコメントアウトして無効にします(行を削除してもいいです)。<br />
<pre><code><xmp>#synchronous_standby_names = 'pgsql2'</xmp></code></pre>
次に、pgsql1にpgsql2からアクセスできるようにpg_hba.confを編集します。<br />
<pre><code><xmp>vi pg_hba.conf</xmp></code></pre>
pg_hba.confに以下の内容を追加します。<br />
<pre><code><xmp>host replication postgres 192.168.0.0/24 trust
</xmp></code></pre>
※IPアドレスはサーバーのセグメントに合わせます。<br />
続けて、アーカイブログのディレクトリを作成します。<br />
<pre><code><xmp>mkdir -p pg_archive
chmod 700 pg_archive
</xmp></code></pre>
sudoの権限のあるユーザーで、PostgreSQLを再起動します。<br />
<pre><code><xmp>sudo /etc/init.d/posgresql restart</xmp></code></pre>
<br />
次にセカンダリのpgsql2の設定を変更します。<br />
もし、PostgreSQLが起動しているなら停止します。<br />
<pre><code><xmp>sudo /etc/init.d/postgresql stop</xmp></code></pre>
<pre><code><xmp>su - postgres
cd /usr/local/pgsql
rm -rf data
pg_basebackup -h pgsql1 -p 5432 -D /usr/local/pgsql/data --xlog --progress --verbose
cd data
vi postgresql.conf
</xmp></code></pre>
postgresql.confに以下の設定を追加します。<br />
<pre><code><xmp>#synchronous_standby_names = '' #をつけてコメントにするか行を削除する
hot_standby = on
</xmp></code></pre>
recovery.confを作成します。<br />
<pre><code><xmp>cp ../share/recovery.conf.sample recovery.conf
vi recovery.conf
</xmp></code></pre>
recovery.confに以下の設定を追加します。<br />
<pre><code><xmp>standby_mode = on
primary_conninfo = 'host=pgsql1 port=5432 application_name=pgsql2'
</xmp></code></pre>
sudoの権限のあるユーザーで、PostgreSQLを起動します。<br />
<pre><code><xmp>sudo /etc/init.d/posgresql start</xmp></code></pre>
<br />
同期レプリケーションできているか確認してみます。<br />
pgsql1で次のコマンドを実行して確認します。<br />
<pre><code><xmp>su - postgres
psql -c "select application_name, state, sync_priority, sync_state from pg_stat_replication;"
application_name | state | sync_priority | sync_state
------------------+-----------+---------------+------------
pgsql2 | streaming | 1 | sync
(1 rows)
</xmp></code></pre>
pgsql2のsync_stateがsyncになっていれば、同期レプリケーションで動作しています。<br />
サンプルのデータベースを作成してみます。<br />
<pre><code><xmp>createuser -P test1
createdb test1 -O test1 -E UTF8 -T template0
psql test1 -U test1 -c "create table table1 (id integer not null primary key, value text);"
</xmp></code></pre>
pgsql2でデータベースとテーブルが作成されているか確認します。<br />
<pre><code><xmp>su - postgres
psql -l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
postgres | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 |
template0 | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
test1 | test1 | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 |
(4 rows)
psql test1 -U test1 -c "\dt;"
List of relations
Schema | Name | Type | Owner
--------+--------+-------+----------
public | table1 | table | postgres
(1 row)
psql -c "select pg_is_in_recovery();"
pg_is_in_recovery
-------------------
t
(1 row)
</xmp></code></pre>
データベースtest1とテーブルtable1がpgsql2にも作成されていればOKです。<br />
また、pg_is_in_recovery()の結果が"t"(true)になっていれば、セカンダリとして動作していることが確認できます。<br />
<br />
<h4>[pgpool-IIの設定]</h4>
まず、pgpoolサーバーにpgpool-IIをソースからビルドしてセットアップします。<br />
pgpool-IIのディレクトリを作成します。<br />
<pre><code><xmp>sudo mkdir /usr/local/pgpool2
sudo chown postgres:postgres /usr/local/pgpool2
cd /usr/local/src
sudo wget http://www.pgpool.net/download.php?f=pgpool-II-3.2.1.tar.gz
sudo tar zxvf pgpool-II-3.2.1.tar.gz
sudo chown -R postgres:postgres pgpool-II-3.2.1
</xmp></code></pre>
PostgreSQLのソースをビルドします。<br />
<pre><code><xmp>su - postgres
cd /usr/local/source/pgpool-II-3.2.1
./configure --prefix=/usr/local/pgpool2 -with-pgsql=/usr/local/pgsql
make
make install
</xmp></code></pre>
pgpool-IIの設定を変更します。<br />
<pre><code><xmp>cd /usr/local/pgpool2/etc
cp pcp.conf.sample pcp.conf
vi pcp.conf
</xmp></code></pre>
pcp.confの末尾に、postgresユーザーのパスワードをMD5で暗号化したものを記述します。パスワードのMD5変換したものは、<br />
<pre><code><xmp>/usr/local/pgpool2/bin/pg_md5 postgresのパスワード</xmp></code></pre>
で取得できます。これを
<pre><code><xmp># USERID:MD5PASSWD
postgres:MD5変換されたパスワード
</xmp></code></pre>
というように追加します。<br />
次に、pgpool.confを設定します。<br />
<pre><code></xmp>cp pgpool.conf.sample-stream pgpool.conf
vi pgpool.conf
</xmp></code></pre>
pgpool.confに以下の設定を追加します。<br />
<pre><code><xmp>listen_addresses = '*'
port = 9999
pid_file_name = '/usr/local/pgpool2/pgpool.pid'
failover_command = '/usr/local/pgpool2/etc/failover.sh %d "%h" %p %D %m %M "%H" %P %r %R'
# pgsql1
backend_hostname0 = 'pgsql1'
backend_port0 = 5432
backend_weight0 = 1
backend_data_directory0 = '/usr/local/pgsql/data'
backend_flag0 = 'ALLOW_TO_FAILOVER'
# pgsql2
backend_hostname1 = 'pgsql2'
backend_port1 = 5432
backend_weight1 = 1
backend_data_directory1 = '/usr/local/pgsql/data'
backend_flag1 = 'ALLOW_TO_FAILOVER‘
sr_check_user = 'postgres'
sr_check_password = 'postgresのパスワード'
health_check_user = 'postgres'
health_check_password = 'postgresのパスワード'
recovery_user = 'postgres'
recovery_password = 'postgresのパスワード'
</xmp></code></pre>
フェイルオーバー時のスクリプトを作成します。<br />
<pre><code><xmp>vi failover.sh</xmp></code></pre>
failover.shに以下のようにスクリプトを記述します。<br />
<pre><code><xmp>#!/bin/sh
failed_node_id=$1
failed_host_name=$2
failed_port=$3
failed_db_cluster=$4
new_master_id=$5
old_master_id=$6
new_master_host_name=$7
old_primary_node_id=$8
new_master_port=$9
new_master_db_cluster=$10
logfile=/usr/local/pgpool2/log/failover.log
echo "------------------------------------------------------------------" >> $logfile
date >> $logfile
echo "failed_node_id=$failed_node_id" >> $logfile
echo "failed_host_name=$failed_host_name" >> $logfile
echo "failed_port=$failed_port" >> $logfile
echo "failed_db_cluster=$failed_db_cluster" >> $logfile
echo "new_master_id=$new_master_id" >> $logfile
echo "old_master_id=$old_master_id" >> $logfile
echo "new_master_host_name=$new_master_host_name" >> $logfile
echo "old_primary_node_id=$old_primary_node_id" >> $logfile
echo "new_master_port=$new_master_port" >> $logfile
echo "new_master_db_cluster=$new_master_db_cluster" >> $logfile
if [ $new_master_id -eq -1 ]
then
echo "unknown new_master_id=$new_master_id" >> $logfile
exit 0
fi
if [ -z $new_master_host_name ]
then
echo "unknown new_master_host_name=$new_master_host_name" >> $logfile
exit 0
fi
if [ $failed_node_id = $old_primary_node_id ] # primary failed
then
echo "secondary promote" >> $logfile
/usr/bin/ssh postgres@$new_master_host_name "/usr/local/pgsql/bin/pg_ctl -D $new_master_db_cluster promote" >> $logfile 2>&1
else # secondary failed
echo "primary async mode" >> $logfile
/usr/bin/ssh postgres@$new_master_host_name "/bin/cp /usr/local/pgsql/data/postgresql.conf.async /usr/local/pgsql/data/postgresql.conf" >> $logfile 2>&1
/usr/bin/ssh postgres@$new_master_host_name "/usr/local/pgsql/bin/pg_ctl reload -D $new_master_db_cluster" >> $logfile 2>&1
fi
</xmp></code></pre>
ログを入れるディレクトリを作成します。<br />
<pre><code><xmp>cd ..
mkdir log
</xmp></code></pre>
<br />
pgsql1とpgsql2のpg_hba.confを編集して、pgpoolからアクセスできるようにします。<br />
psql1とpsql2で、
<pre><code><xmp>su - postgres
cd /usr/local/pgsql/data
vi pg_hba.conf
</xmp></code></pre>
pg_hba.confに以下の設定を追加します。<br />
<pre><code><xmp>host all all pgpoolのIPアドレス/32 password
</xmp></code></pre>
pgsql1とpgsqlのPostgreSQLのパラメータを再読み込みします。<br />
sudoの権限のあるユーザーで<br />
<pre><code><xmp>sudo /etc/init.d/postgresql reload</xmp></code></pre>
<br />
pgpoolを起動します。<br />
起動のためのスクリプトをpostgresqlの起動スクリプトを真似して次のように作ってみました。<br />
<pre><code><xmp>#! /bin/sh
# Installation prefix
prefix=/usr/local/pgpool2
# Who to run the postmaster as, usually "postgres". (NOT "root")
PGUSER=postgres
# Where to keep a log file
PGLOG="$prefix/log/serverlog"
# It's often a good idea to protect the postmaster from being killed by the
# OOM killer (which will tend to preferentially kill the postmaster because
# of the way it accounts for shared memory). Setting the OOM_SCORE_ADJ value
# to -1000 will disable OOM kill altogether. If you enable this, you probably
# want to compile PostgreSQL with "-DLINUX_OOM_SCORE_ADJ=0", so that
# individual backends can still be killed by the OOM killer.
#OOM_SCORE_ADJ=-1000
# Older Linux kernels may not have /proc/self/oom_score_adj, but instead
# /proc/self/oom_adj, which works similarly except the disable value is -17.
# For such a system, enable this and compile with "-DLINUX_OOM_ADJ=0".
#OOM_ADJ=-17
## STOP EDITING HERE
# The path that is to be used for the script
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# What to use to start up the pgpool.
DAEMON="$prefix/bin/pgpool"
# What to use to shut down the pgpool
PGPOOL="$prefix/bin/pgpool"
set –e
# Only start if we can find the pgpool.
test -x $DAEMON ||
{
echo "$DAEMON not found"
if [ "$1" = "stop" ]
then exit 0
else exit 5
fi
}
# Parse command line parameters.
case $1 in
start)
echo -n "Starting pgpool-II: "
test x"$OOM_SCORE_ADJ" != x && echo "$OOM_SCORE_ADJ" > /proc/self/oom_score_adj
test x"$OOM_ADJ" != x && echo "$OOM_ADJ" > /proc/self/oom_adj
su - $PGUSER -c "$DAEMON &" >>$PGLOG 2>&1
echo "ok"
;;
stop)
echo -n "Stopping pgpool-II: "
su - $PGUSER -c "$PGPOOL -m fast stop"
echo "ok"
;;
restart)
echo -n "Restarting pgpool-II: "
su - $PGUSER -c "$PGPOOL -m fast stop"
test x"$OOM_SCORE_ADJ" != x && echo "$OOM_SCORE_ADJ" > /proc/self/oom_score_adj
test x"$OOM_ADJ" != x && echo "$OOM_ADJ" > /proc/self/oom_adj
su - $PGUSER -c "$DAEMON &" >>$PGLOG 2>&1
echo "ok"
;;
reload)
echo -n "Reload pgpool-II: "
su - $PGUSER -c "$PGPOOL reload"
echo "ok"
;;
*)
# Print help
echo "Usage: $0 {start|stop|reload|restart}" 1>&2
exit 1
;;
esac
exit 0
</xmp></code></pre>
これを /etc/init.d/pgpool に保存します。<br />
※完全に理解できていない箇所もあるので、もし真似してやった方がいて何か問題が起きても自己責任でお願いします(^^;<br />
ちなみにubuntuのpgpool2のパッケージをインストールした時に生成される /etc/init.d/pgpool2 は以下のようになっていました。<br />
<pre><code><xmp>#! /bin/sh
### BEGIN INIT INFO
# Provides: pgpool2
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Should-Start: postgresql
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: start pgpool-II
# Description: pgpool-II is a connection pool server and replication
# proxy for PostgreSQL.
### END INIT INFO
PATH=/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/pgpool
PIDFILE=/var/run/postgresql/pgpool.pid
test -x $DAEMON || exit 5
# Include pgpool defaults if available
if [ -f /etc/default/pgpool2 ] ; then
. /etc/default/pgpool2
fi
OPTS=""
if [ x"$PGPOOL_LOG_DEBUG" = x"yes" ]; then
OPTS="$OPTS -d"
fi
. /lib/lsb/init-functions
is_running() {
pidofproc -p $PIDFILE $DAEMON >/dev/null
}
d_start() {
if is_running; then
:
else
su -c "$DAEMON -n $OPTS 2>&1 </dev/null | logger -t pgpool -p ${PGPOOL_SYSLOG_FACILITY:-local0}.info >/dev/null 2>&1 &" - postgres
fi
}
d_stop() {
killproc -p $PIDFILE $DAEMON -INT
status=$?
[ $status -eq 0 ] || [ $status -eq 3 ]
return $?
}
case "$1" in
start)
log_daemon_msg "Starting pgpool-II" pgpool
d_start
log_end_msg $?
;;
stop)
log_daemon_msg "Stopping pgpool-II" pgpool
d_stop
log_end_msg $?
;;
status)
is_running
status=$?
if [ $status -eq 0 ]; then
log_success_msg "pgpool-II is running."
else
log_failure_msg "pgpool-II is not running."
fi
exit $status
;;
restart|force-reload)
log_daemon_msg "Restarting pgpool-II" pgpool
d_stop && sleep 1 && d_start
log_end_msg $?
;;
try-restart)
if $0 status >/dev/null; then
$0 restart
else
exit 0
fi
;;
reload)
exit 3
;;
*)
log_failure_msg "Usage: $0 {start|stop|status|restart|try-restart|reload|force-reload}"
exit 2
;;
esac
</xmp></code></pre>
pgpoolに -n をつけて、デーモンとして走らせないようにしてるっぽいです。<br />
こっちに合わせて書きなおしたほうがいいかもしれません。<br />
<br />
pgpoolを自動起動するように設定します。<br />
<pre><code><xmp>sudo chmod 755 /etc/init.d/pgpool
sudo update-rc.d pgpool start 91 2 3 4 5 . stop 11 0 1 6 .
</xmp></code></pre>
pgpoolを実行します。<br />
<pre><code><xmp>sudo /etc/init.d/pgpool start</xmp></code></pre>
<br />
<br />
ここまでで、設定は完了です。<br />
<br />
<br />
<h4>[pgsql1を落とすテスト]</h4>
プライマリのpgsql1を停止させてみます。<br />
<pre><code><xmp>su - postgres
pg_ctl -D /usr/local/pgsql/data -m immediate stop
</xmp></code></pre>
適当なクライアントからアクセスしてみます。<br />
<pre><code><xmp>psql -h pgpool -p 9999 -U test1 test1
psql: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
</xmp></code></pre>
一旦エラーになるようです。<br />
どうも、pgsql1を落としたあと、次にアクセスがきた時にフェイルオーバー処理が走るみたいです(ちょっと自信はありませんが)。<br />
もう一度アクセスしてみると、<br />
<pre><code><xmp>psql -h pgpool -p 9999 -U test1 test1
Password for user test1: test1のパスワード
test1=> select * from table1;
id | value
----+-------
(0 row)
test1=> insert into table1 (id, value) values (1, 'aaaa');
INSERT 0 1
</xmp></code></pre>
無事接続でき、更新クエリも処理されています。<br />
もし、pgsql2がプライマリに昇格できていなければ、更新クエリはエラーになるはずなので、問題なくフェイルオーバー処理が実行されているようです。<br />
念のため、pgsql2の状態を確認してみると<br />
<pre><code><xmp>psql -c "select pg_is_in_recovery();"
pg_is_in_recovery
-------------------
f
(1 row)
</xmp></code></pre>
pg_is_in_recovery()の結果が"f"(false)になっており、プライマリに昇格しています。<br />
<br />
<h4>[pgsql2を落とすテスト]</h4>
※一旦pgsql1とpsql2を元に戻します。<br />
<br />
セカンダリのpgsql2を停止させてみます。<br />
<pre><code><xmp>su - postgresql
pg_ctl -D /usr/local/pgsql/data -m immediate stop
</xmp></code></pre>
適当なクライアントからアクセスしてみます。<br />
<pre><code><xmp>psql -h pgpool -p 9999 -U test1 test1
psql: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
</xmp></code></pre>
※一旦エラーになるようです。<br />
もう一度アクセスしてみると、<br />
<pre><code><xmp>psql -h pgpool -p 9999 -U test1 test1
Password for user test1: test1のパスワード
test1=> select * from table1;
id | value
----+-------
(0 row)
test1=> insert into table1 (id, value) values (1, 'aaaa');
INSERT 0 1
</xmp></code></pre>
無事接続でき、更新クエリも処理されています。<br />
もし、pgsql1が非同期レプリケーションに切り替わっていなければ、更新クエリはエラーになる(タイムアウトする?)はずなので、ちゃんとフェイルオーバー処理が実行されています。<br />
念のため、pgsqlの/usr/local/pgsql/data/recovery.confを確認してみると、<br />
<pre><code><xmp>#synchronous_standby_names = 'pgsql2'</xmp></code></pre>
のように、synchronous_standby_namesがコメントアウトされて無効になっているはずです。<br />
<br />
長ーくなりましたが、これで同期レプリケーションのまとめはここまで。<br />
<br />
<br />
もし、サーバーの構成で、セカンダリが複数台あったとき、pgpoolに設定したフェイルオーバースクリプトのsecondary failedの部分の処理は不要かと思います。<br />
同期しているセカンダリが落ちたときは sync_state が potential になっているサーバーが、sync になるので問題ないはず。<br/>kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-61298363686295599232012-09-28T15:22:00.002+09:002014-07-01T10:15:23.456+09:00PostgreSQLの列の型のtimestamp with time zoneとNpgsqlでのアクセス<a href="http://90h-tech.blogspot.jp/2012/09/postgresqltimestamptime-zone.html">昨日のエントリ</a>で、PostgreSQLのtimestamp型のwith time zoneについて色々試してみましたが、もう少し調べて<a href="http://d.hatena.ne.jp/ayakobaba/">ayakobabaの日記</a>というブログの<a href="http://d.hatena.ne.jp/ayakobaba/20110531/1306825237">[Postgresql]Postgresql のTimezone</a>というエントリを見つけました。<br />
<br />
<a href="http://www.postgresql.jp/document/8.3/html/functions-datetime.html">PostgreSQLのマニュアル</a>にも記述があり、timestamp with time zone型の列にアクセスするとき、at time zone構文を使うことでタイムスタンプを異なる時間帯に変換できるようです。
例えば、こんな感じ。
<script type='syntaxhighlighter' class='brush: sql'><![CDATA[
db1=> select current_timestamp, current_timestamp at time zone 'UTC';
now | timezone
-------------------------------+----------------------------
2012-09-28 11:52:24.066437+09 | 2012-09-28 02:52:24.066437
(1 row)
]]></script>
<br />
そこで、昨日のコードを修正して、次のようにしてみました。<br />
<script type='syntaxhighlighter' class='brush: csharp'><![CDATA[
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
namespace pgTimestamp
{
class Program
{
static void Main(string[] args)
{
// データベース接続
Npgsql.NpgsqlConnection conn = new Npgsql.NpgsqlConnection("Server=xxxx;"
+ "Port=5432;"
+ "User Id=yyyy;"
+ "Password=zzzz;"
+ "Database=db1;"
+ "Pooling=false;"
+ "Encoding=UNICODE;");
// データベース接続
try
{
conn.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// クライアントの時間とUTCとの差を取得
DateTime tmpTime = new DateTime(2000, 1, 1); // 適当な日時をセットして
TimeSpan diffUTC = tmpTime - tmpTime.ToUniversalTime(); // UTCとの差を取得
string strDiffUTC = (diffUTC.TotalHours >= 0 ? "+" : "") + diffUTC.Hours.ToString("00") + ":" + diffUTC.Minutes.ToString("00");
// テーブルの生成
DataTable table1 = new DataTable("table1");
table1.Columns.Add(new DataColumn("id" , typeof(int) ));
table1.Columns.Add(new DataColumn("datetime1", typeof(DateTime)));
table1.Columns.Add(new DataColumn("datetime2", typeof(DateTime)));
table1.PrimaryKey = new DataColumn[] { table1.Columns["id"] };
// データアダプタの生成
Npgsql.NpgsqlDataAdapter da = new Npgsql.NpgsqlDataAdapter();
da.SelectCommand = new Npgsql.NpgsqlCommand
(
"select"
+ " id"
+ ", datetime1"
+ ", datetime2 at time zone interval '" + strDiffUTC + "' as datetime2"
+ " from"
+ " table1"
, conn
);
da.InsertCommand = new Npgsql.NpgsqlCommand
(
"insert into table1 ("
+ "id"
+ ", datetime1"
+ ", datetime2"
+ ") values ("
+ " :id"
+ ", :datetime1"
+ ", :datetime2 at time zone interval '" + strDiffUTC + "'"
+ ")"
, conn
);
da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("id" , NpgsqlTypes.NpgsqlDbType.Integer , 0, "id" , ParameterDirection.Input, false, 0, 0, DataRowVersion.Current, DBNull.Value));
da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime1", NpgsqlTypes.NpgsqlDbType.Timestamp , 0, "datetime1", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current, DBNull.Value));
da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime2", NpgsqlTypes.NpgsqlDbType.TimestampTZ, 0, "datetime2", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current, DBNull.Value));
da.UpdateCommand = new Npgsql.NpgsqlCommand
(
"update table1 set"
+ " id=:id"
+ ", datetime1=:datetime1"
+ ", datetime2=:datetime2 at time zone interval '" + strDiffUTC + "'"
+ " where"
+ " id=:org_id"
, conn
);
da.UpdateCommand.Parameters.Add(new Npgsql.NpgsqlParameter("id" , NpgsqlTypes.NpgsqlDbType.Integer , 0, "id" , ParameterDirection.Input, false, 0, 0, DataRowVersion.Current , DBNull.Value));
da.UpdateCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime1", NpgsqlTypes.NpgsqlDbType.Timestamp , 0, "datetime1", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current , DBNull.Value));
da.UpdateCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime2", NpgsqlTypes.NpgsqlDbType.TimestampTZ, 0, "datetime2", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current , DBNull.Value));
da.UpdateCommand.Parameters.Add(new Npgsql.NpgsqlParameter("org_id" , NpgsqlTypes.NpgsqlDbType.Integer , 0, "id" , ParameterDirection.Input, false, 0, 0, DataRowVersion.Original, DBNull.Value));
da.DeleteCommand = new Npgsql.NpgsqlCommand
(
"delete from table1"
+ " where"
+ " id=:org_id"
, conn
);
da.DeleteCommand.Parameters.Add(new Npgsql.NpgsqlParameter("org_id" , NpgsqlTypes.NpgsqlDbType.Integer , 0, "id" , ParameterDirection.Input, false, 0, 0, DataRowVersion.Original, DBNull.Value));
// データの取得
da.Fill(table1);
// データの一旦削除
try
{
foreach (DataRow row in table1.Rows)
{
row.Delete();
}
Update(conn, da, table1);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// データの追加
DateTime value = DateTime.Parse("2012-10-01 09:00:00");
DataRow newRow = table1.NewRow();
newRow["id" ] = 1;
newRow["datetime1"] = value;
newRow["datetime2"] = value;
table1.Rows.Add(newRow);
// 保存
try
{
Update(conn, da, table1);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// テーブルを一旦クリアして取得し直す
table1.Rows.Clear();
da.Fill(table1);
// テーブル内容の表示
Show(table1);
// データの更新
value = DateTime.Parse("2012-12-01 09:00:00");
table1.Rows[0]["datetime1"] = value;
table1.Rows[0]["datetime2"] = value;
// 保存
try
{
Update(conn, da, table1);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// テーブルを一旦クリアして取得し直す
table1.Rows.Clear();
da.Fill(table1);
// テーブル内容の表示
Show(table1);
// データベース切断
try
{
conn.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
/// <summary>
/// データの保存
/// </summary>
/// <param name="conn">データベース接続子</param>
/// <param name="da">データアダプタ</param>
/// <param name="table1">データテーブル</param>
static private void Update(Npgsql.NpgsqlConnection conn, Npgsql.NpgsqlDataAdapter da, DataTable table1)
{
// トランザクション開始
Npgsql.NpgsqlTransaction tran = null;
try
{
tran = conn.BeginTransaction();
da.InsertCommand.Transaction = tran;
da.UpdateCommand.Transaction = tran;
da.DeleteCommand.Transaction = tran;
}
catch
{
throw;
}
// 保存
try
{
da.Update(table1);
}
catch
{
tran.Rollback();
throw;
}
// コミット
try
{
tran.Commit();
}
catch
{
throw;
}
}
/// <summary>
/// テーブルの内容を表示
/// </summary>
/// <param name="table1">データテーブル</param>
static private void Show(DataTable table1)
{
foreach (DataRow row in table1.Rows)
{
Console.WriteLine("id=" + row["id"].ToString()
+ ", datetime1=" + ((DateTime)row["datetime1"]).ToString()
+ ", datetime2=" + ((DateTime)row["datetime2"]).ToString());
}
}
}
}
]]></script>
ポイントは、DataAdapterのSelectCommand,InsertCommand,UpdateCommandのそれぞれのSQLで、datetime2の列にアクセスするところで、at time zone構文を使っているところです。<br />
SelectCommandでは、datetime2の値を取得するところで、<br />
<pre><code><xmp>datetime2 at time zone interval '+09:00' as datetime2</xmp></code></pre>
となるようにしています。ここで'+09:00'はクライアントのタイムゾーンが東京の場合です。<br />
この'+09:00'は、<br />
<pre><code><xmp>DateTime tmpTime = new DateTime(2000, 1, 1);
TimeSpan diffUTC = tmpTime - tmpTime.ToUniversalTime();
string strDiffUTC = (diffUTC.TotalHours >= 0 ? "+" : "") + diffUTC.Hours.ToString("00") + ":" + diffUTC.Minutes.ToString("00");
</xmp></code></pre>
の部分で、ある適当な時間(ここでは2000年1月1日0時0分0秒)から、その時間のUTCを引いた差から生成しています。<br />
InsertCommandでは<br />
<pre><code><xmp>:datetime2 at time zone interval '+09:00'
</xmp></code></pre>
UpdateCommandでは<br />
<pre><code><xmp>datetime2=:datetime2 at time zone interval '+09:00'
</xmp></code></pre>
としています。<br />
<br />
まず、クライアントPCのタイムゾーンを東京、サーバーのタイムゾーンも東京の状態で実行してみると<br />
データの追加が終わったところ(にブレークポイントをいれて確認)で、コンソールに<br />
<pre><code><xmp>id=1, datetime1=2012/10/01 9:00:00, datetime2=2012/10/01 9:00:00
</xmp></code></pre>
と表示され、データベース側でtable1の内容を確認すると、<br />
<pre><code><xmp>db1=> select * from table1;
id | datetime1 | datetime2
----+---------------------+------------------------
1 | 2012-10-01 09:00:00 | 2012-10-01 09:00:00+09
(1 row)
</xmp></code></pre>
となっています。<br />
そのまま処理を続けて、次の更新処理が終わったところで、コンソールに<br />
<pre><code><xmp>id=1, datetime1=2012/12/01 9:00:00, datetime2=2012/12/01 9:00:00
</xmp></code></pre>
と表示され、データベース側でtable1の内容を確認すると、<br />
<pre><code><xmp>db1=> select * from table1;
id | datetime1 | datetime2
----+---------------------+------------------------
1 | 2012-12-01 09:00:00 | 2012-12-01 09:00:00+09
(1 row)
</xmp></code></pre>
となります。<br />
<br />
次に、サーバー側のタイムゾーンを台北に変更して、同じ事をやってみます。<br />
データの追加処理が終わったところでは、コンソールに<br />
<pre><code><xmp>id=1, datetime1=2012/10/01 9:00:00, datetime2=2012/10/01 11:00:00
</xmp></code></pre>
と表示され、データベース側のtable1の内容は<br />
<pre><code><xmp>db1=> select * from table1;
id | datetime1 | datetime2
----+---------------------+------------------------
1 | 2012-10-01 09:00:00 | 2012-10-01 10:00:00+08
(1 row)
</xmp></code></pre>
となり、続けて更新処理を行うと、コンソールに<br />
<pre><code><xmp>id=1, datetime1=2012/12/01 9:00:00, datetime2=2012/12/01 11:00:00
</xmp></code></pre>
と表示され、データベース側でtable1の内容を確認すると、<br />
<pre><code><xmp>db1=> select * from table1;
id | datetime1 | datetime2
----+---------------------+------------------------
1 | 2012-12-01 09:00:00 | 2012-12-01 10:00:00+08
</xmp></code></pre>
となります。<br />
<br />
あれれれ、予定では追加処理のあと、datetime2の値は、コンソールでは2012/10/01 09:00:00と表示され、データベース側のtable1のdatetime2の値は、2012-10-01 08:00:00+08になっているはずなのに、2時間ズレているようです。<br />
<br />
原因を調べるために、追加処理で実際に実行されたSQLを調べてみると、<br />
<pre><code><xmp>insert into table1 (
id
, datetime1
, datetime2
) values (
((1)::int4)
, ((E'2012-10-01 09:00:00.000000')::timestamp)
, ((E'2012-10-01 09:00:00.000000')::timestamptz) at time zone interval '+09:00'
)
</xmp></code></pre>
となっていました。<br />
datetime2の値となる<br />
<pre><code><xmp>((E'2012-10-01 09:00:00.000000')::timestamptz) at time zone interval '+09:00'</xmp></code></pre>
の部分をよく考えてみると、((E'2012-10-01 09:00:00.000000')::timestamptz)の、'2012-10-01 09:00:00'はtimestamptz型(with time zone)なので、9:00はUTCでは1:00となります。さらにat time zone interval '+09:00'となるので、1:00+9:00で、10:00という時間がdatetime2に書き込まれます。<br />
これで2時間のズレが発生しているようです。<br />
ようするに、<br />
<pre><code><xmp>db1=> select timestamp with time zone '2012-10-01 9:00:00' at time zone interval '+09:00';
timezone
---------------------
2012-10-01 10:00:00
(1 row)
db1=> select timestamp without time zone '2012-10-01 9:00:00' at time zone interval '+09:00';
timezone
------------------------
2012-10-01 08:00:00+08
(1 row)
</xmp></code></pre>
この2つのselect文の違いと同じことです。<br />
<br />
そうなると、今回のコードで問題になるのは、InsertCommandとUpdateCommandのdatetime2に対するパラメータの型ということになります。<br />
<pre><code><xmp>da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime2", NpgsqlTypes.NpgsqlDbType.TimestampTZ, 0, "datetime2", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current, DBNull.Value));</xmp></code></pre>
この部分は<br />
<pre><code><xmp>da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime2", NpgsqlTypes.NpgsqlDbType.Timestamp, 0, "datetime2", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current, DBNull.Value));</xmp></code></pre>
このように、NpgsqlTypes.NpgsqlDbType.TimestampTZではなく、NpgsqlTypes.NpgsqlDbType.Timestampでなければいけないということのようです。<br />
<br />
コードを修正して、もう一度、クライアントは東京、サーバーは台北という状態で実行してみます。<br />
追加処理が終わったところで、コンソールには<br />
<pre><code><xmp>id=1, datetime1=2012/10/01 9:00:00, datetime2=2012/10/01 9:00:00
</xmp></code></pre>
と表示され、この時データベースのtable1は<br />
<pre><code><xmp>db1=> select * from table1;
id | datetime1 | datetime2
----+---------------------+------------------------
1 | 2012-10-01 09:00:00 | 2012-10-01 08:00:00+08
(1 row)
</xmp></code></pre>
となっていて、続けて更新処理が終わったところで、コンソールには<br />
<pre><code><xmp>id=1, datetime1=2012/12/01 9:00:00, datetime2=2012/12/01 9:00:00
</xmp></code></pre>
と表示され、データベースのtable1は<br />
<pre><code><xmp>db1=> select * from table1;
id | datetime1 | datetime2
----+---------------------+------------------------
1 | 2012-12-01 09:00:00 | 2012-12-01 08:00:00+08
(1 row)
</xmp></code></pre>
となっています。<br />
これで予定通りの処理となりました。<br />
<br />
<br />
念のため、クライアントのタイムゾーンを台北、サーバーのタイムゾーンを東京にして同じ処理を実行してみます。<br />
追加処理が終わると、コンソールに<br />
<pre><code><xmp>id=1, datetime1=2012/10/01 9:00:00, datetime2=2012/10/01 9:00:00
</xmp></code></pre>
と表示され、この時データベースのtable1は<br />
<pre><code><xmp>db1=> select * from table1;
id | datetime1 | datetime2
----+---------------------+------------------------
1 | 2012-10-01 09:00:00 | 2012-10-01 10:00:00+09
(1 row)
</xmp></code></pre>
となり、続けて更新処理が終わると、コンソールには<br />
<pre><code><xmp>id=1, datetime1=2012/12/01 9:00:00, datetime2=2012/12/01 9:00:00
</xmp></code></pre>
と表示され、データベースのtable1は<br />
<pre><code><xmp>db1=> select * from table1;
id | datetime1 | datetime2
----+---------------------+------------------------
1 | 2012-12-01 09:00:00 | 2012-12-01 10:00:00+09
(1 row)
</xmp></code></pre>
となっています。<br />
<br />
これで、クライアント側のプログラムでの日時は、クライアントのタイムゾーンに従った日時が入り、データベース上の日時は、UTC(with time zone)で入るようにできそうです。<br />
<br />
<br />
一応、修正したソースを載せておきます。<br />
<pre><code><xmp>using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
namespace pgTimestamp
{
class Program
{
static void Main(string[] args)
{
// データベース接続
Npgsql.NpgsqlConnection conn = new Npgsql.NpgsqlConnection("Server=xxxx;"
+ "Port=5432;"
+ "User Id=yyyy;"
+ "Password=zzzz;"
+ "Database=db1;"
+ "Pooling=false;"
+ "Encoding=UNICODE;");
// データベース接続
try
{
conn.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// クライアントの時間とUTCとの差を取得
DateTime tmpTime = new DateTime(2000, 1, 1); // 適当な日時をセットして
TimeSpan diffUTC = tmpTime - tmpTime.ToUniversalTime(); // UTCとの差を取得
string strDiffUTC = (diffUTC.TotalHours >= 0 ? "+" : "") + diffUTC.Hours.ToString("00") + ":" + diffUTC.Minutes.ToString("00");
// テーブルの生成
DataTable table1 = new DataTable("table1");
table1.Columns.Add(new DataColumn("id" , typeof(int) ));
table1.Columns.Add(new DataColumn("datetime1", typeof(DateTime)));
table1.Columns.Add(new DataColumn("datetime2", typeof(DateTime)));
table1.PrimaryKey = new DataColumn[] { table1.Columns["id"] };
// データアダプタの生成
Npgsql.NpgsqlDataAdapter da = new Npgsql.NpgsqlDataAdapter();
da.SelectCommand = new Npgsql.NpgsqlCommand
(
"select"
+ " id"
+ ", datetime1"
+ ", datetime2 at time zone interval '" + strDiffUTC + "' as datetime2"
+ " from"
+ " table1"
, conn
);
da.InsertCommand = new Npgsql.NpgsqlCommand
(
"insert into table1 ("
+ "id"
+ ", datetime1"
+ ", datetime2"
+ ") values ("
+ ":id"
+ ", :datetime1"
+ ", :datetime2 at time zone interval '" + strDiffUTC + "'"
+ ")"
, conn
);
da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("id" , NpgsqlTypes.NpgsqlDbType.Integer , 0, "id" , ParameterDirection.Input, false, 0, 0, DataRowVersion.Current, DBNull.Value));
da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime1", NpgsqlTypes.NpgsqlDbType.Timestamp, 0, "datetime1", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current, DBNull.Value));
da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime2", NpgsqlTypes.NpgsqlDbType.Timestamp, 0, "datetime2", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current, DBNull.Value));
da.UpdateCommand = new Npgsql.NpgsqlCommand
(
"update table1 set"
+ " id=:id"
+ ", datetime1=:datetime1"
+ ", datetime2=:datetime2 at time zone interval '" + strDiffUTC + "'"
+ " where"
+ " id=:org_id"
, conn
);
da.UpdateCommand.Parameters.Add(new Npgsql.NpgsqlParameter("id" , NpgsqlTypes.NpgsqlDbType.Integer , 0, "id" , ParameterDirection.Input, false, 0, 0, DataRowVersion.Current , DBNull.Value));
da.UpdateCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime1", NpgsqlTypes.NpgsqlDbType.Timestamp, 0, "datetime1", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current , DBNull.Value));
da.UpdateCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime2", NpgsqlTypes.NpgsqlDbType.Timestamp, 0, "datetime2", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current , DBNull.Value));
da.UpdateCommand.Parameters.Add(new Npgsql.NpgsqlParameter("org_id" , NpgsqlTypes.NpgsqlDbType.Integer , 0, "id" , ParameterDirection.Input, false, 0, 0, DataRowVersion.Original, DBNull.Value));
da.DeleteCommand = new Npgsql.NpgsqlCommand
(
"delete from table1"
+ " where"
+ " id=:org_id"
, conn
);
da.DeleteCommand.Parameters.Add(new Npgsql.NpgsqlParameter("org_id" , NpgsqlTypes.NpgsqlDbType.Integer , 0, "id" , ParameterDirection.Input, false, 0, 0, DataRowVersion.Original, DBNull.Value));
// データの取得
da.Fill(table1);
// データの一旦削除
try
{
foreach (DataRow row in table1.Rows)
{
row.Delete();
}
Update(conn, da, table1);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// データの追加
DateTime value = DateTime.Parse("2012-10-01 09:00:00");
DataRow newRow = table1.NewRow();
newRow["id" ] = 1;
newRow["datetime1"] = value;
newRow["datetime2"] = value;
table1.Rows.Add(newRow);
// 保存
try
{
Update(conn, da, table1);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// テーブルを一旦クリアして取得し直す
table1.Rows.Clear();
da.Fill(table1);
// テーブル内容の表示
Show(table1);
// データの更新
value = DateTime.Parse("2012-12-01 09:00:00");
table1.Rows[0]["datetime1"] = value;
table1.Rows[0]["datetime2"] = value;
// 保存
try
{
Update(conn, da, table1);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// テーブルを一旦クリアして取得し直す
table1.Rows.Clear();
da.Fill(table1);
// テーブル内容の表示
Show(table1);
// データベース切断
try
{
conn.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
/// <summary>
/// データの保存
/// </summary>
/// <param name="conn">データベース接続子</param>
/// <param name="da">データアダプタ</param>
/// <param name="table1">データテーブル</param>
static private void Update(Npgsql.NpgsqlConnection conn, Npgsql.NpgsqlDataAdapter da, DataTable table1)
{
// トランザクション開始
Npgsql.NpgsqlTransaction tran = null;
try
{
tran = conn.BeginTransaction();
da.InsertCommand.Transaction = tran;
da.UpdateCommand.Transaction = tran;
da.DeleteCommand.Transaction = tran;
}
catch
{
throw;
}
// 保存
try
{
da.Update(table1);
}
catch
{
tran.Rollback();
throw;
}
// コミット
try
{
tran.Commit();
}
catch
{
throw;
}
}
/// <summary>
/// テーブルの内容を表示
/// </summary>
/// <param name="table1">データテーブル</param>
static private void Show(DataTable table1)
{
foreach (DataRow row in table1.Rows)
{
Console.WriteLine("id=" + row["id"].ToString()
+ ", datetime1=" + ((DateTime)row["datetime1"]).ToString()
+ ", datetime2=" + ((DateTime)row["datetime2"]).ToString());
}
}
}
}
</xmp></code></pre>kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-65948001331203814382012-09-27T15:58:00.001+09:002012-09-28T15:35:16.162+09:00PostgreSQLの列の型のtimestampのtime zoneについてPostgreSQLの列をtimestamp with time zoneで定義して、Npgsqlでアクセスした時、どんなふうになるのか気になったのでテスト。<br />
<br />
PostgreSQLの動いている環境はこんな感じ。<br />
<pre><code><xmp>$ psql --version
psql (PostgreSQL) 8.3.10
contains support for command-line editing
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=8.04
DISTRIB_CODENAME=hardy
DISTRIB_DESCRIPTION="Ubuntu 8.04.4 LTS"
</xmp></code></pre>
<br />
ここにdb1というデータベースを作成して、確認用のテーブルtable1を作成します。<br />
<pre><code><xmp>$ createdb db1
$ psql db1
db1=> create table table1
db1-> (
db1(> id integer not null primary key
db1(> , datetime1 timestamp without time zone
db1(> , datetime2 timestamp with time zone
db1(> );
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "table1_pkey" for table "table1"
CREATE TABLE
db1=> insert into table1(id, datetime1, datetime2) values (1, current_timestamp, current_timestamp);
INSERT 0 1
db1=> select * from table1;
id | datetime1 | datetime2
----+----------------------------+-------------------------------
1 | 2012-09-27 11:23:28.487888 | 2012-09-27 11:23:28.487888+09
(1 row)
</xmp></code></pre>
列のdatetime1をtimestamp without time zoneで作成し、列のdatetime2をtimestamp with time zoneで作成しています。<br />
<br />
C#で次のコードを実行して、table1の内容を取得してみます。<br />
<pre><code><xmp>using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
namespace pgTimestamp
{
class Program
{
static void Main(string[] args)
{
// データベース接続
Npgsql.NpgsqlConnection conn = new Npgsql.NpgsqlConnection("Server=xxxx;"
+ "Port=5432;"
+ "User Id=yyyy;"
+ "Password=zzzz;"
+ "Database=db1;"
+ "Pooling=false;"
+ "Encoding=UNICODE;");
try
{
conn.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// テーブルの生成
DataTable table1 = new DataTable("table1");
table1.Columns.Add(new DataColumn("id" , typeof(int) ));
table1.Columns.Add(new DataColumn("datetime1", typeof(DateTime)));
table1.Columns.Add(new DataColumn("datetime2", typeof(DateTime)));
table1.PrimaryKey = new DataColumn[] { table1.Columns["id"] };
// データの取得
Npgsql.NpgsqlDataAdapter da = new Npgsql.NpgsqlDataAdapter();
da.SelectCommand = new Npgsql.NpgsqlCommand("select id, datetime1, datetime2 from table1", conn);
da.Fill(table1);
// 取得したデータの表示
foreach (DataRow row in table1.Rows)
{
Console.WriteLine("id=" + row["id"].ToString()
+ ", datetime1=" + ((DateTime)row["datetime1"]).ToString()
+ ", datetime2=" + ((DateTime)row["datetime2"]).ToString());
Console.WriteLine("dateTime2(UTC)=" + ((DateTime)row["datetime2"]).ToUniversalTime().ToString());
}
// データベース切断
try
{
conn.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
}
</xmp></code></pre>
結果はこうなります。<br />
<pre><code><xmp>id=1, datetime1=2012/09/27 11:23:28, datetime2=2012/09/27 11:23:28
dateTime2(UTC)=2012/09/27 2:23:28
</xmp></code></pre>
<br />
ここで、サーバー側のタイムゾーンを台北に変更してみます。<br />
<pre><code><xmp>$ sudo dpkg-reconfigure tzdata
Current default timezone: 'Asia/Taipei'
Local time is now: Thu Sep 27 10:34:32 CST 2012.
Universal Time is now: Thu Sep 27 02:34:32 UTC 2012.
</xmp></code></pre>
PostgreSQLを再起動して、table1の内容を確認します。<br />
<pre><code><xmp>sudo /etc/init.d/postgresql-8.3 restart
$ psql db1
db1=> select * from table1;
id | datetime1 | datetime2
----+----------------------------+-------------------------------
1 | 2012-09-27 11:23:28.487888 | 2012-09-27 10:23:28.487888+08
(1 row)
</xmp></code></pre>
datetime1はタイムゾーンに関係なく入れた時のまま(タイムゾーンが東京のcurrent_timestampの値)で、datetime2は入れた時の台北の時間が表示されます。<br />
<br />
ここで、先ほどのC#のコードを実行してみると<br />
<pre><code><xmp>id=1, datetime1=2012/09/27 11:23:28, datetime2=2012/09/27 11:23:28
dateTime2(UTC)=2012/09/27 2:23:28
</xmp></code></pre>
となります。<br />
datetime2の値は、タイムゾーンが東京での時間になっています。<br />
<br />
ここで、C#のコードを実行しているPCのタイムゾーンの設定を台北に変更してみます。<br />
<div style="text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvxg27FlsBo9sS987SV1yxxpJje6-CkVunb6v1ZXBGdKbacgBxxhhgUvmPYklK6bl0Qs0ZBgJdSxO_NSLtjMxq6HeEfXXTa5dkd2KyfI3YD9K8vmXBt16MW9x7rm9AIivzViZ5ByqnQT7h/s1600/timezone_taipei.png" imageanchor="1" style="clear: left; margin-bottom: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvxg27FlsBo9sS987SV1yxxpJje6-CkVunb6v1ZXBGdKbacgBxxhhgUvmPYklK6bl0Qs0ZBgJdSxO_NSLtjMxq6HeEfXXTa5dkd2KyfI3YD9K8vmXBt16MW9x7rm9AIivzViZ5ByqnQT7h/s1600/timezone_taipei.png" /></a></div>
この状態で、コードを実行してみると<br />
<pre><code><xmp>id=1, datetime1=2012/09/27 11:23:28, datetime2=2012/09/27 10:23:28
dateTime2(UTC)=2012/09/27 2:23:28
</xmp></code></pre>
となり、datetime2は台北での時間となります。<br />
ただ、このときToUniversalTime()で取得した時間は、どのパターンでも同じ時間となっています。<br />
<br />
今度は、サーバー側のタイムゾーンを東京に戻してPostgreSQLを再起動します。<br />
<pre><code><xmp>$ sudo dpkg-reconfigure tzdata
Current default timezone: 'Asia/Tokyo'
Local time is now: Thu Sep 27 11:52:27 JST 2012.
Universal Time is now: Thu Sep 27 02:52:27 UTC 2012.
$ sudo /etc/init.d/postgresql-8.3 restart
</xmp></code></pre>
PCのタイムゾーンは台北のまま、コードを実行します。<br />
<pre><code><xmp>id=1, datetime1=2012/09/27 11:23:28, datetime2=2012/09/27 10:23:28
dateTime2(UTC)=2012/09/27 2:23:28
</xmp></code></pre>
となり、datetime2の値はサーバー側のタイムゾーンには関係なく、クライアント側のタイムゾーンで取得されています。<br />
ここでPCのタイムゾーンも東京に戻します。<br />
<br />
<br />
今度は、C#のコードでtable1に行を追加してみます。<br />
次のようなコードを用意します。<br />
<pre><code><xmp>using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
namespace pgTimestamp
{
class Program
{
static void Main(string[] args)
{
// データベース接続
Npgsql.NpgsqlConnection conn = new Npgsql.NpgsqlConnection("Server=xxxx;"
+ "Port=5432;"
+ "User Id=yyyy;"
+ "Password=zzzz;"
+ "Database=db1;"
+ "Pooling=false;"
+ "Encoding=UNICODE;");
try
{
conn.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// テーブルの生成
DataTable table1 = new DataTable("table1");
table1.Columns.Add(new DataColumn("id" , typeof(int) ));
table1.Columns.Add(new DataColumn("datetime1", typeof(DateTime)));
table1.Columns.Add(new DataColumn("datetime2", typeof(DateTime)));
table1.PrimaryKey = new DataColumn[] { table1.Columns["id"] };
// データの取得
Npgsql.NpgsqlDataAdapter da = new Npgsql.NpgsqlDataAdapter();
da.SelectCommand = new Npgsql.NpgsqlCommand("select id, datetime1, datetime2 from table1", conn);
da.Fill(table1);
// データの追加
da.InsertCommand = new Npgsql.NpgsqlCommand
(
"insert into table1 ("
+ "id"
+ ", datetime1"
+ ", datetime2"
+ ") values ("
+ " :id"
+ ", :datetime1"
+ ", :datetime2"
+ ")"
, conn
);
da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("id" , NpgsqlTypes.NpgsqlDbType.Integer , 0, "id" , ParameterDirection.Input, false, 0, 0, DataRowVersion.Current, DBNull.Value));
da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime1", NpgsqlTypes.NpgsqlDbType.Timestamp , 0, "datetime1", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current, DBNull.Value));
da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime2", NpgsqlTypes.NpgsqlDbType.TimestampTZ, 0, "datetime2", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current, DBNull.Value));
DateTime value = DateTime.Parse("2012-10-01 09:00:00");
DataRow newRow = table1.NewRow();
newRow["id" ] = 2;
newRow["datetime1"] = value;
newRow["datetime2"] = value;
table1.Rows.Add(newRow);
// トランザクション開始
Npgsql.NpgsqlTransaction tran = null;
try
{
tran = conn.BeginTransaction();
da.InsertCommand.Transaction = tran;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// 保存
try
{
da.Update(table1);
}
catch (Exception ex)
{
tran.Rollback();
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// コミット
try
{
tran.Commit();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
return;
}
// データベースからtable1の内容を取得しなおす
table1.Rows.Clear();
da.Fill(table1);
// 取得したデータの表示
foreach (DataRow row in table1.Rows)
{
Console.WriteLine("id=" + row["id"].ToString()
+ ", datetime1=" + ((DateTime)row["datetime1"]).ToString()
+ ", datetime2=" + ((DateTime)row["datetime2"]).ToString());
Console.WriteLine("dateTime2(UTC)=" + ((DateTime)row["datetime2"]).ToUniversalTime().ToString());
}
// データベース切断
try
{
conn.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
}
</xmp></code></pre>
これを実行すると次のようになります。
<br />
<pre><code><xmp>id=1, datetime1=2012/09/27 11:23:28, datetime2=2012/09/27 11:23:28
dateTime2(UTC)=2012/09/27 2:23:28
id=2, datetime1=2012/10/01 9:00:00, datetime2=2012/10/01 9:00:00
dateTime2(UTC)=2012/10/01 0:00:00
</xmp></code></pre>
サーバーでtable1を確認すると、
<br />
<pre><code><xmp>db1=> select * from table1;
id | datetime1 | datetime2
----+----------------------------+-------------------------------
1 | 2012-09-27 11:23:28.487888 | 2012-09-27 11:23:28.487888+09
2 | 2012-10-01 09:00:00 | 2012-10-01 09:00:00+09
(2 rows)
</xmp></code></pre>
となっており、datetime2もタイムゾーンは東京で、C#側のDateTime型で指定された時間になっています。<br />
ここで、一旦id=2のレコードを削除します。
<br />
<pre><code><xmp>db1=> begin;
BEGIN
db1=> delete from table1 where id=2;
DELETE 1
db1=> commit;
COMMIT
</xmp></code></pre>
今度は、サーバー側のタイムゾーンを台北にして実行してみます。<br />
<pre><code><xmp>id=1, datetime1=2012/09/27 11:23:28, datetime2=2012/09/27 11:23:28
dateTime2(UTC)=2012/09/27 2:23:28
id=2, datetime1=2012/10/01 9:00:00, datetime2=2012/10/01 10:00:00
dateTime2(UTC)=2012/10/01 1:00:00
</xmp></code></pre>
サーバーでtable1を確認すると、
<br />
<pre><code><xmp>db1=> select * from table1;
id | datetime1 | datetime2
----+----------------------------+-------------------------------
1 | 2012-09-27 11:23:28.487888 | 2012-09-27 10:23:28.487888+08
2 | 2012-10-01 09:00:00 | 2012-10-01 09:00:00+08
(2 rows)
</xmp></code></pre>
となり、C#側でDateTimeの時間を9:00にしてinsertすると、サーバー側には台北での9:00がinsertされ、クライアント側でその時刻を取得すると、10:00(台北時間の9:00を東京時間で表示)となります。<br />
<br />
さらに、今度はクライアント側のタイムゾーンを台北にしてやってみると、
<br />
<pre><code><xmp>id=1, datetime1=2012/09/27 11:23:28, datetime2=2012/09/27 10:23:28
dateTime2(UTC)=2012/09/27 2:23:28
id=2, datetime1=2012/10/01 9:00:00, datetime2=2012/10/01 9:00:00
dateTime2(UTC)=2012/10/01 1:00:00
</xmp></code></pre>
サーバーでtable1を確認すると、
<br />
<pre><code><xmp>db1=> select * from table1;
id | datetime1 | datetime2
----+----------------------------+-------------------------------
1 | 2012-09-27 11:23:28.487888 | 2012-09-27 10:23:28.487888+08
2 | 2012-10-01 09:00:00 | 2012-10-01 09:00:00+08
(2 rows)
</xmp></code></pre>
となり、クライアント側の結果は、クライアントとサーバーでタイムゾーンが一致しているので、insertした9:00となり、データベース側はクライアントのタイムゾーンが東京の時と同じ結果となっています。<br />
サーバー側から見ると、クライアントのタイムゾーンがなんであっても、9:00としてinsertされたら、それはサーバー側のタイムゾーンでの時刻としてinsertされるようです。<br />
念のため、今度はサーバー側のタイムゾーンを東京に戻して(クライアントのタイムゾーンは台北のまま)やってみると、<br />
<pre><code><xmp>id=1, datetime1=2012/09/27 11:23:28, datetime2=2012/09/27 10:23:28
dateTime2(UTC)=2012/09/27 2:23:28
id=2, datetime1=2012/10/01 9:00:00, datetime2=2012/10/01 8:00:00
dateTime2(UTC)=2012/10/01 0:00:00
</xmp></code></pre>
サーバーでtable1を確認すると、
<br />
<pre><code><xmp>db1=> select * from table1;
id | datetime1 | datetime2
----+----------------------------+-------------------------------
1 | 2012-09-27 11:23:28.487888 | 2012-09-27 11:23:28.487888+09
2 | 2012-10-01 09:00:00 | 2012-10-01 09:00:00+09
(2 rows)
</xmp></code></pre>
となります。<br />
<br />
datetiime2の列の値を表にしてみると<br />
<table border="1" style="border-style:sold">
<tr><td colspan="4" style="text-align:center">タイムゾーンと結果</td><td rowspan="2" style="text-align:center">UTC</td></tr>
<tr><td colspan="2" style="text-align:center">サーバー</td><td colspan="2" style="text-align:center">クライアント</td></tr>
<tr><td>東京</td><td style="text-align:right">9:00</td><td>東京</td><td style="text-align:right">9:00</td><td style="text-align:right">0:00</td></tr>
<tr><td>台北</td><td style="text-align:right">9:00</td><td>東京</td><td style="text-align:right">10:00</td><td style="text-align:right">1:00</td></tr>
<tr><td>台北</td><td style="text-align:right">9:00</td><td>台北</td><td style="text-align:right">9:00</td><td style="text-align:right">1:00</td></tr>
<tr><td>東京</td><td style="text-align:right">9:00</td><td>台北</td><td style="text-align:right">8:00</td><td style="text-align:right">0:00</td></tr>
</table>
となり、クライアントのタイムゾーンがなんであっても、サーバー側には9:00でinsertされ、それをクライアント側が取得するときは、サーバー側のタイムゾーンでの9:00をクライアント側のタイムゾーンでの時間にして取得されています。<br />
<br />
<br />
ここで、少し気になるのが、C#のコードの中のDataAdapterのInsertCommandのNpgsqlParameterの設定で、<br />
<pre><code><xmp>da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime2", NpgsqlTypes.NpgsqlDbType.TimestampTZ, 0, "datetime2", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current, DBNull.Value));
</xmp></code></pre>
としているところを、<br />
<pre><code><xmp>da.InsertCommand.Parameters.Add(new Npgsql.NpgsqlParameter("datetime2", NpgsqlTypes.NpgsqlDbType.Timestamp, 0, "datetime2", ParameterDirection.Input, true , 0, 0, DataRowVersion.Current, DBNull.Value));
</xmp></code></pre>
というように、NpgsqlDbTypeをTimestampTZからTimestampに変更した場合、どういう動作になるのか。<br />
試してみると、<br />
<table border="1" style="border-style:sold">
<tr><td colspan="4" style="text-align:center">タイムゾーンと結果</td><td rowspan="2" style="text-align:center">UTC</td></tr>
<tr><td colspan="2" style="text-align:center">サーバー</td><td colspan="2" style="text-align:center">クライアント</td></tr>
<tr><td>東京</td><td style="text-align:right">9:00</td><td>東京</td><td style="text-align:right">9:00</td><td style="text-align:right">0:00</td></tr>
<tr><td>台北</td><td style="text-align:right">9:00</td><td>東京</td><td style="text-align:right">10:00</td><td style="text-align:right">1:00</td></tr>
<tr><td>台北</td><td style="text-align:right">9:00</td><td>台北</td><td style="text-align:right">9:00</td><td style="text-align:right">1:00</td></tr>
<tr><td>東京</td><td style="text-align:right">9:00</td><td>台北</td><td style="text-align:right">8:00</td><td style="text-align:right">0:00</td></tr>
</table>
となり、全く同じ結果となりました。<br />
NpgsqlDbType.TimestampTZとNpgsqlDbType.Timestampの使い分けがよくわからない感じですが、PostgreSQL側のテーブルの列がtimestamp with time zoneなら、NpgsqlDbType.TimestampTZを使って、timestamp without time zoneならNpgsqlDbType.Timestampを使っておけばいいのかな。<br />
<br />
ただ、私のイメージしていた動きだと、C#のコードでDateTimeの時刻に9:00と入っていて、それをデータベースに書き込み、さらに読みなおしたときは9:00になっていて欲しい感じです。<br />
<table border="1" style="border-style:sold">
<tr><td colspan="4" style="text-align:center">タイムゾーンと結果</td><td rowspan="2" style="text-align:center">UTC</td></tr>
<tr><td colspan="2" style="text-align:center">サーバー</td><td colspan="2" style="text-align:center">クライアント</td></tr>
<tr><td>東京</td><td style="text-align:right">9:00</td><td>東京</td><td style="text-align:right">9:00</td><td style="text-align:right">0:00</td></tr>
<tr><td>台北</td><td style="text-align:right">8:00</td><td>東京</td><td style="text-align:right">9:00</td><td style="text-align:right">0:00</td></tr>
<tr><td>台北</td><td style="text-align:right">9:00</td><td>台北</td><td style="text-align:right">9:00</td><td style="text-align:right">1:00</td></tr>
<tr><td>東京</td><td style="text-align:right">10:00</td><td>台北</td><td style="text-align:right">9:00</td><td style="text-align:right">1:00</td></tr>
</table>
テーブルの列の型がTimestamp with time zoneで、且つNpgsqlCommandのパラメータの型をNpgsqlDbType.TimestampTZにしているなら、サーバー側に書き込まれる時刻のUTCはクライアントの書き込もうとしている時刻のUTCと一致するようになるようなイメージ。そうだとクライアント側はサーバーのタイムゾーンを意識しなくてもいい気がするんだけど。。。もしかして、私が気がついていなくて考え方が間違っているのかな(^^;<br />
<br />
追記:<a href="http://90h-tech.blogspot.jp/2012/09/postgresqltimestamp-with-time-zonenpgsql.html">別のエントリ</a>で、もう少しツッコんで解決方法を考えてみました。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-75555154284782563822012-09-19T14:47:00.000+09:002012-09-19T14:47:18.296+09:00Windows8のODBCデータソースWindows8 RTMで気がついたこと。<br />
<br />
Windows7 64ビット版では、コントロールパネルにあるODBCデータソースを開くと、64ビット専用のものが開かれていました。32ビット版ODBCデータソースを開くときは、C:\WINDOWS\SysWOW64\Odbcad32.exeを実行する必要がありました。<br />
<br />
Windows8では、コントロールパネルに<br />
<br />
<ul>
<li>ODBC データ ソース (32 ビット)</li>
<li>ODBC データ ソース (64 ビット)</li>
</ul>
<br />
が用意され、別々に開くことができるようになっています。<br />
<div style="text-align: left;">
<a href="http://2.bp.blogspot.com/-9iHumCQzieU/UFlWxxxlRWI/AAAAAAAAFaM/vAWJTA5A-WY/s1600/Windows8_ControlPanel.png" imageanchor="1" style="clear: left; margin-bottom: 1em;"><img border="0" height="462" src="http://2.bp.blogspot.com/-9iHumCQzieU/UFlWxxxlRWI/AAAAAAAAFaM/vAWJTA5A-WY/s640/Windows8_ControlPanel.png" width="640" /></a></div>
<br />
また、ユーザーDSNとシステムDSNには、32ビット版で登録したものと64ビット版で登録したものの両方がリストに表示されます。<br />
※Windows7では、32ビット版ODBCデータソースには32ビット版のDSNのみが表示され、64ビット版DSNデータソースには64ビット版のDSNのみが表示されていました。<br />
<br />
こちらが32ビット版の画面。<br />
<div style="text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuYjlJ3jkTZZ4fT6nh51zKM_zfN3Atz1vtLxOGFfh4-u_X3XTJ8ntfWH4wtW0FYzt0GWbdHYKTl70Zu4GXmsnL7NibinIWnOke5zKdUZy3ex93FQ1O7wHrG0V-YKf6Wz6D0BnqC322BbJ7/s1600/odbc%2528x86%2529.png" imageanchor="1" style="clear: left; margin-bottom: 1em;"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuYjlJ3jkTZZ4fT6nh51zKM_zfN3Atz1vtLxOGFfh4-u_X3XTJ8ntfWH4wtW0FYzt0GWbdHYKTl70Zu4GXmsnL7NibinIWnOke5zKdUZy3ex93FQ1O7wHrG0V-YKf6Wz6D0BnqC322BbJ7/s640/odbc%2528x86%2529.png" width="640" /></a></div>
<br />
こちらが64ビット版の画面。<br />
<div style="text-align: left;">
<a href="http://4.bp.blogspot.com/-ayR9y0YKq2o/UFlVQZ68mwI/AAAAAAAAFaA/BOT7oJX2P_I/s1600/odbc%2528x64%2529.png" imageanchor="1" style="clear: left; margin-bottom: 1em;"><img border="0" height="440" src="http://4.bp.blogspot.com/-ayR9y0YKq2o/UFlVQZ68mwI/AAAAAAAAFaA/BOT7oJX2P_I/s640/odbc%2528x64%2529.png" width="640" /></a></div>
<br />
このように、どちらの一覧にも32ビット版と64ビット版のDSNの一覧が表示されています。<br />
※名前にモザイク入ってますけど、同じ物が表示されています(^_^;<br />
<br />
ただし、32ビット版で追加したDSNは32ビット版のODBCデータソースでしか編集できず、同じく64ビット版で追加したDSNは64ビット版のODBCデータソースでしか編集できません。<br />
<br />
また、DSNの名前は、32ビット版と64ビット版で同じものを使うことができます。<br />
例えば、32ビット版でhogehogeという名前のDSNを登録しても、64ビット版でhogehogeという名前のDSNを登録することができます。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-46876478728167731472012-09-07T13:23:00.001+09:002012-09-07T13:23:44.440+09:00Windows8のスタートアップはデスクトップを開いた時に実行されるWindows8 RTMで気がついたこと。<br />
<br />
ログインした時、自動的に実行したいプログラムをスタートアップに入れてみました。<br />
スタートアップフォルダは、<br />
C:\Users\ユーザー名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup<br />
にあり、ここにプログラムのショートカットを入れておきます。<br />
<br />
ただ、Windows8の場合、Metro UI(Modern UI?)のスタート画面が開いた時にはまだ実行されず、デスクトップを開いた時に実行されるようです。<br />
<br />kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com2tag:blogger.com,1999:blog-677207412948384379.post-28491045700854859212012-09-06T11:02:00.001+09:002012-09-06T11:02:50.713+09:00Windows8のシャットダウンは高速起動が初期設定になっているWindows8 RTMで気がついたこと。<br />
<br />
Windows8 RTMを試していますが、まだ元のWindows7の環境と行ったり来たりしないといけないので、こういうのを使ってSSDを差し替えて使っています。<br />
<br />
<img alt="2.5インチSATA内蔵リムーバブルケース(SATA接続トレイ付き) SA25-RC1-BK" src="http://www.ratocsystems.com/products/subpage/sa25/images/sa25rc1top.jpg" /><br />
<br />
Windows8からWindows7に環境を変えるとき、Windows8をシャットダウンして、SSDを入れ替えて電源を入れるんですが、そのとき画面に「Hibanationなんとかかんとか」が一瞬表示され、内蔵している別のHDDのチェックディスクが始まります。<br />
<br />
どうやら、Windows8のシャットダウンはハイバネーションと組み合わせて起動時間を短縮するようになっているみたいです。<br />
<br />
ということで、コントロールパネルの設定を見ると、<br />
<div style="text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlHwu7P053wiwB0YMfw44MNvsUEMJMCW9EUQLPhwVrdQJ1JCdB3p4SvozLSjC8Uoj4mQMuEwBvYXjW7FrAPZD80Re2cPiE8apnSsw8kj3sKpctTwCzyRvZ5v_QtdaPn7waefy_o0gKk9s8/s1600/Windows8_Power1.png" imageanchor="1" style="clear: left; margin-bottom: 1em;"><img border="0" height="523" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlHwu7P053wiwB0YMfw44MNvsUEMJMCW9EUQLPhwVrdQJ1JCdB3p4SvozLSjC8Uoj4mQMuEwBvYXjW7FrAPZD80Re2cPiE8apnSsw8kj3sKpctTwCzyRvZ5v_QtdaPn7waefy_o0gKk9s8/s640/Windows8_Power1.png" width="640" /></a></div>
となっていて、下段のシャットダウン設定の中に<br />
<br />
<ul>
<li>高速スタートアップを有効にする(推奨)</li>
</ul>
<br />
という項目があり、有効になっています。<br />
このチェックを外そうとしましたが、操作できない状態になっています。<br />
<br />
これを変更したい時は、画面上段の<br />
「現在利用可能ではない設定を変更します」<br />
をクリックします。<br />
<br />
そうすると、シャットダウン設定も変更可能になるので、<br />
<div style="text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjj6OptaD_J7j3e6RbH3BoiHiOglsheMCMn7gbL6RobFFw3-y9BY2R2GlBl_pZ09Y6K2rg0_2tTxCay7dM3KoK5T1vS8lxJrmoSExk35be8AC42s19x8ZMmb8vbxnKnnWNcILgEXf_8M4sI/s1600/Windows8_Power2.png" imageanchor="1" style="clear: left; margin-bottom: 1em;"><img border="0" height="524" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjj6OptaD_J7j3e6RbH3BoiHiOglsheMCMn7gbL6RobFFw3-y9BY2R2GlBl_pZ09Y6K2rg0_2tTxCay7dM3KoK5T1vS8lxJrmoSExk35be8AC42s19x8ZMmb8vbxnKnnWNcILgEXf_8M4sI/s640/Windows8_Power2.png" width="640" /></a></div>
高速スタートアップを有効にする(推奨)のチェックを外して、[変更の保存]をクリックします。<br />
<br />
これで、シャットダウン時にハイバネーションを利用しないようになります。<br />
<br />
この設定を変更しても、SSDを使っている場合はそれほど遅くなった感じはしませんでした。<br />
ただ、通常の使い方なら、このチェックは(推奨)とあるように、有効にしておいたほうが良いと思います。<br />
<br />kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-47310459891561392202012-09-04T16:36:00.000+09:002012-09-04T16:36:06.255+09:00Windows8でユーザーフォルダ名が日本語にWindows8 RTMで気がついたこと。<br />
<br />
Windows8をインストールするとき、MicrosoftアカウントでPCにサインインすると、C:\Usersの下に作られるフォルダ名が、Microsoftアカウントに登録している名前(苗字ではなく)になります。<br />
<br />
このとき、Microsoftアカウントに名前を日本語で登録していると、日本語のフォルダ名になります。<br />
<br />
問題はない(KOBOでは問題になっていたけど)とは思うけど、フォルダ名が日本語なのは少し気持ち悪いです。<br />
<br />
インストール時に一旦「Microsoftアカウントでサインインしない」を選択して、英字のローカルアカウントを作成し、そのあとでMicrosoftアカウントに切り替えると、C:\Usersの下は一旦作ったローカルアカウント名のフォルダが使用されるみたいなので、正規版が出て入れなおすときはそうしようかと思っています。
kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-30804013562077721732012-04-05T18:46:00.001+09:002012-04-25T14:59:59.779+09:00DataTableのデータをDBに保存する際、エラーが発生してRollbackしたときの問題ADO.NETのDataTableとDataAdapterを使って、DataTableのデータをデータベースに書き込むとき、途中でエラーが発生してロールバックすると、DataTableのDataRowの一部(更新処理がうまくいったDataRow)のRowStateがUnchangedになってしまう場合がある。<br />
<br />
サンプルを、SQLiteを使って試してみた。<br />
<br />
テスト用に、sample_tableという名前のテーブルを<br />
<pre><code><xmp>CREATE TABLE sample_table
(
id INTEGER NOT NULL PRIMARY KEY
, value TEXT NOT NULL
, update_date TIMESTAMP DEFAULT (DATETIME('now','localtime'))
);
</xmp></code></pre>
上記のように作成。<br />
ここに、<br />
<table border="1" summary="初期のテーブル内容"><tbody>
<tr><th>id</th><th>value</th><th>update_date</th></tr>
<tr><td>1</td><td>ABCD</td><td>2012-04-05 18:05:56</td></tr>
<tr><td>2</td><td>EFGH</td><td>2012-04-05 18:05:56</td></tr>
<tr><td>3</td><td>IJKL</td><td>2012-04-05 18:05:56</td></tr>
</tbody></table>
というデータを予め入れておく。<br />
このテーブルをDataTableに取り込み、<br />
<table border="1" summary="初期のテーブル内容"><tbody>
<tr><th>id</th><th>value</th><th>update_date</th></tr>
<tr><td>1</td><td>ABCD</td><td>DATE('now','localtime')</td></tr>
<tr><td>2</td><td>EFGH</td><td>DATE('now','localtime')</td></tr>
<tr style=background-color:#FFFF77><td>3</td><td>MNOP</td><td>DATE('now','localtime')</td></tr>
<tr style=background-color:#FFFF77><td>4</td><td>NULL</td><td>DATE('now','localtime')</td></tr>
</tbody></table>
id=3の行のvalueを'MNOP'に書き換え、id=4の行をvalue=NULLで追加する。<br />
※value列はNOT NULLとしているので、id=4の行の追加はエラーとなる。<br />
DataTableのデータをデータベースに書きこむとき、id=4の行の追加でエラーが発生するのでロールバックする。<br />
このときのDataTableのRowStateをチェックするというサンプルを用意した。<br />
<pre><code><xmp>using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SQLite;
namespace SQLiteSample1
{
class Program
{
static private SQLiteConnection m_conn;
static void Main(string[] args)
{
//////////////////////////////////////////////// 準備
// データベース接続
string dbfile = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "dbfile.db");
m_conn = new SQLiteConnection("Data Source=" + dbfile);
m_conn.Open();
// テーブルの有無を確認
bool exists = false;
SQLiteCommand existsCommand = new SQLiteCommand("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='sample_table';", m_conn);
object existsResult = existsCommand.ExecuteScalar();
try
{
if (int.Parse(existsResult.ToString()) > 0)
{
exists = true;
}
}
catch
{
}
// テーブル生成
if (exists == false)
{
SQLiteCommand createCommand = new SQLiteCommand
(
"CREATE TABLE sample_table ("
+ " id INTEGER NOT NULL PRIMARY KEY"
+ ", value TEXT NOT NULL"
+ ", update_date TIMESTAMP DEFAULT (DATETIME('now','localtime'))"
+ ");"
, m_conn
);
createCommand.ExecuteNonQuery();
// データ生成
SQLiteCommand insertCommand;
insertCommand = new SQLiteCommand("INSERT INTO sample_table (id, value) VALUES (1, 'ABCD');", m_conn);
insertCommand.ExecuteNonQuery();
insertCommand = new SQLiteCommand("INSERT INTO sample_table (id, value) VALUES (2, 'EFGH');", m_conn);
insertCommand.ExecuteNonQuery();
insertCommand = new SQLiteCommand("INSERT INTO sample_table (id, value) VALUES (3, 'IJKL');", m_conn);
insertCommand.ExecuteNonQuery();
}
// データベース切断
m_conn.Close();
//////////////////////////////////////////////// テスト
// データベース接続
m_conn.Open();
// データテーブル生成
DataTable table = new DataTable();
table.Columns.Add(new DataColumn("id" , typeof(int) ));
table.Columns.Add(new DataColumn("value", typeof(string)));
table.PrimaryKey = new DataColumn[] { table.Columns["id"] };
// DataAdapter生成
SQLiteDataAdapter da = new SQLiteDataAdapter();
// SelectCommand
da.SelectCommand = new SQLiteCommand("SELECT id, value, update_date FROM sample_table", m_conn);
// InsertCommand
da.InsertCommand = new SQLiteCommand("INSERT INTO sample_table(id, value) VALUES (@id, @value);", m_conn);
da.InsertCommand.Parameters.Add(new SQLiteParameter("id" , DbType.Int32 , 0, ParameterDirection.Input, false, 0, 0, "id" , DataRowVersion.Current, DBNull.Value));
da.InsertCommand.Parameters.Add(new SQLiteParameter("value", DbType.String, 0, ParameterDirection.Input, false, 0, 0, "value", DataRowVersion.Current, DBNull.Value));
// UpdateCommand
da.UpdateCommand = new SQLiteCommand("UPDATE sample_table SET value=@value, update_date=(DATETIME('now','localtime')) WHERE id=@id;", m_conn);
da.UpdateCommand.Parameters.Add(new SQLiteParameter("value", DbType.String, 0, ParameterDirection.Input, false, 0, 0, "value", DataRowVersion.Current , DBNull.Value));
da.UpdateCommand.Parameters.Add(new SQLiteParameter("id" , DbType.Int32 , 0, ParameterDirection.Input, false, 0, 0, "id" , DataRowVersion.Original, DBNull.Value));
// DeleteCommand
da.DeleteCommand = new SQLiteCommand("DELETE FROM sample_table WHERE id=@id;", m_conn);
da.DeleteCommand.Parameters.Add(new SQLiteParameter("id" , DbType.Int32 , 0, ParameterDirection.Input, false, 0, 0, "id" , DataRowVersion.Original, DBNull.Value));
// RowUpdated
da.RowUpdated += new EventHandler<System.Data.Common.RowUpdatedEventArgs>(da_RowUpdated);
// Fill
da.Fill(table);
// データ変更
DataRow updateRow = table.Rows.Find(3);
updateRow["value"] = "MNOP";
// value列がNULLの行を追加...Updateでエラーになる
DataRow newRow = table.NewRow();
newRow["id" ] = 4;
newRow["value"] = DBNull.Value;
table.Rows.Add(newRow);
Console.WriteLine("保存前");
foreach (DataRow row in table.Rows)
{
Console.WriteLine(row["id"].ToString() + " : " + row.RowState.ToString());
}
// 保存
SQLiteTransaction tran = m_conn.BeginTransaction();
try
{
da.Update(table);
tran.Commit();
Console.WriteLine("保存成功");
}
catch (Exception ex)
{
tran.Rollback();
Console.WriteLine("保存失敗");
Console.WriteLine(ex.Message);
}
Console.WriteLine("保存後");
foreach (DataRow row in table.Rows)
{
Console.WriteLine(row["id"].ToString() + " : " + row.RowState.ToString());
}
// id=4のvalueをセットして再度保存
DataRow row4 = table.Rows.Find(4);
row4["value"] = "QRST";
tran = m_conn.BeginTransaction();
try
{
da.Update(table);
tran.Commit();
Console.WriteLine("保存成功");
}
catch (Exception ex)
{
tran.Rollback();
Console.WriteLine("保存失敗");
Console.WriteLine(ex.Message);
}
Console.WriteLine("保存後");
foreach (DataRow row in table.Rows)
{
Console.WriteLine(row["id"].ToString() + " : " + row.RowState.ToString());
}
Console.ReadKey();
// データベース切断
m_conn.Close();
}
static void da_RowUpdated(object sender, System.Data.Common.RowUpdatedEventArgs e)
{
if (e.Status == UpdateStatus.Continue)
{
if ((e.StatementType == StatementType.Insert) || (e.StatementType == StatementType.Update))
{
// update_dateの取得
SQLiteCommand cmd = new SQLiteCommand("SELECT update_date FROM sample_table WHERE id=@id", m_conn);
SQLiteParameter param = new SQLiteParameter("id", DbType.Int32, 0, "id", DataRowVersion.Original);
param.Value = e.Row["id"];
cmd.Parameters.Add(param);
try
{
e.Row["update_date"] = cmd.ExecuteScalar();
e.Row.AcceptChanges();
}
catch
{
throw;
}
}
}
}
}
}
</xmp></code></pre>
これを実行すると<br />
<pre><code><xmp>保存前
1 : Unchanged
2 : Unchanged
3 : Modified
4 : Added
保存失敗
Abort due to constraint violation
sample_table.value may not be NULL
保存後
1 : Unchanged
2 : Unchanged
3 : Unchanged
4 : Added
保存成功
保存後
1 : Unchanged
2 : Unchanged
3 : Unchanged
4 : Unchanged
</xmp></code></pre>
となり、この時のデータベースのテーブルの内容は、<br />
最初の<code>da.Update(table)</code>(失敗する)のあとでは<br />
<table border="1" summary="最初のda.Update(table)のテーブル内容"><tbody>
<tr><th>id</th><th>value</th><th>update_date</th></tr>
<tr><td>1</td><td>ABCD</td><td>2012-04-05 18:05:56</td></tr>
<tr><td>2</td><td>EFGH</td><td>2012-04-05 18:05:56</td></tr>
<tr><td>3</td><td>IJKL</td><td>2012-04-05 18:05:56</td></tr>
</tbody></table>
と、ロールバックしているので当然初期の状態と何も変わらない。<br />
DataTableの内容は<br />
<table border="1" summary="最初のda.Update(table)のDataTable内容"><tbody>
<tr><th>id</th><th>value</th><th>update_date</th><th>RowState</th></tr>
<tr><td>1</td><td>ABCD</td><td>2012-04-05 18:05:56</td><td>Unchanged</td></tr>
<tr><td>2</td><td>EFGH</td><td>2012-04-05 18:05:56</td><td>Unchanged</td></tr>
<tr><td>3</td><td>MNOP</td><td>2012-04-05 18:05:56</td><td>Unchanged</td></tr>
<tr><td>4</td><td>QRST</td><td>NULL</td><td>Added</td></tr>
</tbody></table>
と、DataTableのid=3のDataRowのRowStateはUnchangedになり、エラーの原因となるid=4のDataRowのRowStateはAddedのままとなっている。<br />
次に、id=4のvalueに文字列をセットした後の2回目の<code>da.Update(table)</code>のあとのデータベースのテーブルは<br />
<table border="1" summary="2回目のda.Update(table)のテーブル内容"><tbody>
<tr><th>id</th><th>value</th><th>update_date</th></tr>
<tr><td>1</td><td>ABCD</td><td>2012-04-05 18:05:56</td></tr>
<tr><td>2</td><td>EFGH</td><td>2012-04-05 18:05:56</td></tr>
<tr><td>3</td><td>IJKL</td><td>2012-04-05 18:05:56</td></tr>
<tr><td>4</td><td>QRST</td><td>2012-04-05 18:07:27</td></tr>
</tbody></table>
となっている。<br />
このとき、id=4のDataRowが保存されるので、このDataRowのRowStateがUnchangedに変わり、すべての行がUnchangedとなる。<br />
このとき、DataTableの中身は
<table border="1" summary="2回目のda.Update(table)のDataTableの内容"><tbody>
<tr><th>id</th><th>value</th><th>update_date</th><th>RowState</th></tr>
<tr><td>1</td><td>ABCD</td><td>2012-04-05 18:05:56</td><td>Unchanged</td></tr>
<tr><td>2</td><td>EFGH</td><td>2012-04-05 18:05:56</td><td>Unchanged</td></tr>
<tr style=background-color:#FF7777><td>3</td><td>MNOP</td><td>2012-04-05 18:05:56</td><td>Unchanged</td></tr>
<tr><td>4</td><td>QRST</td><td>2012-04-05 18:07:27</td><td>Unchanged</td></tr>
</tbody></table>
となっていて、実際のデータベース上のテーブルと食い違いが発生している。<br />
<br />
このサンプルプログラムでは、2回続けて保存したが、例えばこのDataTableがDataGridViewで編集されるようなFormアプリケーションだった場合、エラー発生後に利用者がid=4のvalueに値をセットして、再度保存処理をやっても、id=3の行のvalueはデータベースには反映されない。<br />
※しかも、Form上でid=3のvalueは修正した内容になっているため、利用者側にはid=3のvalueもデータベースに正しく書きこまれたかのように見えてしまう。<br />
<br />
データベースへの保存の際、エラーが発生してロールバックするとき、DataTableの各行のRowStateも元の状態に戻せれば問題を解決できる。<br />
方法として、DataTableの変更した行のみを取り出して、別のDataTableを生成し、そのDataTableを使って保存処理を行う。<br />
保存が失敗した場合、元のDataTableの各行のRowStateは何も影響を受けていないため、保存処理前の状態になっている。<br />
保存が成功した場合、コピーしたDataTableの内容と各行のRowStateを元のテーブルに反映させる。<br />
<br />
具体的には、元のソースの
<pre><code><xmp> // 保存
SQLiteTransaction tran = m_conn.BeginTransaction();
try
{
da.Update(table);
tran.Commit();
Console.WriteLine("保存成功");
}
</xmp></code></pre>
上記部分(2ヶ所)を、
<pre><code><xmp> // 保存
SQLiteTransaction tran = m_conn.BeginTransaction();
try
{
DataTable tempTable = table.GetChanges();
da.Update(tempTable);
tran.Commit();
Console.WriteLine("保存成功");
table.Merge(tempTable);
foreach (DataRow row in tempTable.Rows)
{
if (row.RowState == DataRowState.Unchanged)
{
DataRow orgRow = table.Rows.Find(row["id"]);
if (orgRow != null)
{
orgRow.AcceptChanges();
}
}
}
}
</xmp></code></pre>
のように修正する。<br />
<br />
<code>DataTable tempTable = table.GetChanges();</code>で、元DataTableの変更された行のみをコピーしたDataTableを生成する。<br />
データベースへの保存もこのコピーしたDataTableを使って<code>da.Update(tempTable);</code>とする。<br />
保存が成功した場合、<code>table.Merge(tempTable);</code>でコピーしたDataTableの内容を元のDataTableに取り込む。<br />
ただし、まだ元のDataTableの各行のRowStateはModifiedやAddedのままなので、コピーしたDataTableの行に対応する元のDataTableのDataRowを見つけ、<code>AcceptChanges()</code>で、Unchangedにする。<br />
※このサンプルなら<code>table.AcceptChanged();</code>でも良い。<br />
<br />
変更したプログラムを実行すると<br />
<pre><code><xmp>保存前
1 : Unchanged
2 : Unchanged
3 : Modified
4 : Added
保存失敗
Abort due to constraint violation
sample_table.value may not be NULL
保存後
1 : Unchanged
2 : Unchanged
3 : Modified
4 : Added
保存成功
保存後
1 : Unchanged
2 : Unchanged
3 : Unchanged
4 : Unchanged
</xmp></code></pre>
となり、最初の保存で失敗したあとのロールバック後もDataTableの各行のRowStateは元のままになっている。<br />
この時のデータベースのテーブルの内容は、<br />
最初の<code>da.Update(table)</code>(失敗する)のあとでは<br />
<table border="1" summary="最初のda.Update(table)のテーブル内容"><tbody>
<tr><th>id</th><th>value</th><th>update_date</th></tr>
<tr><td>1</td><td>ABCD</td><td>2012-04-05 18:28:23</td></tr>
<tr><td>2</td><td>EFGH</td><td>2012-04-05 18:28:23</td></tr>
<tr><td>3</td><td>IJKL</td><td>2012-04-05 18:28:23</td></tr>
</tbody></table>
と、ロールバックしているので当然初期の状態と何も変わらない。<br />
DataTableの内容は<br />
<table border="1" summary="最初のda.Update(table)のDataTable内容"><tbody>
<tr><th>id</th><th>value</th><th>update_date</th><th>RowState</th></tr>
<tr><td>1</td><td>ABCD</td><td>2012-04-05 18:05:56</td><td>Unchanged</td></tr>
<tr><td>2</td><td>EFGH</td><td>2012-04-05 18:05:56</td><td>Unchanged</td></tr>
<tr><td>3</td><td>MNOP</td><td>2012-04-05 18:05:56</td><td>Modified</td></tr>
<tr><td>4</td><td>QRST</td><td>NULL</td><td>Added</td></tr>
</tbody></table>
となっている。<br />
次に、id=4のvalueに文字列をセットした後の2回目の<code>da.Update(table)</code>のあとのデータベースのテーブルは<br />
<table border="1" summary="2回目のda.Update(table)のテーブル内容"><tbody>
<tr><th>id</th><th>value</th><th>update_date</th></tr>
<tr><td>1</td><td>ABCD</td><td>2012-04-05 18:28:23</td></tr>
<tr><td>2</td><td>EFGH</td><td>2012-04-05 18:28:23</td></tr>
<tr><td>3</td><td>MNOP</td><td>2012-04-05 18:28:27</td></tr>
<tr><td>4</td><td>QRST</td><td>2012-04-05 18:28:27</td></tr>
</tbody></table>
となっている。<br />
このとき、DataTableの中身は
<table border="1" summary="2回目のda.Update(table)のDataTableの内容"><tbody>
<tr><th>id</th><th>value</th><th>update_date</th><th>RowState</th></tr>
<tr><td>1</td><td>ABCD</td><td>2012-04-05 18:28:23</td><td>Unchanged</td></tr>
<tr><td>2</td><td>EFGH</td><td>2012-04-05 18:28:23</td><td>Unchanged</td></tr>
<tr><td>3</td><td>MNOP</td><td>2012-04-05 18:28:27</td><td>Unchanged</td></tr>
<tr><td>4</td><td>QRST</td><td>2012-04-05 18:28:27</td><td>Unchanged</td></tr>
</tbody></table>
となっていて、実際のデータベース上のテーブルと一致している。<br />
<br />
<br />
修正版のソースを載せておきます。<br />
<pre><code><xmp>using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SQLite;
namespace SQLiteSample1
{
class Program
{
static private SQLiteConnection m_conn;
static void Main(string[] args)
{
//////////////////////////////////////////////// 準備
// データベース接続
string dbfile = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "dbfile.db");
m_conn = new SQLiteConnection("Data Source=" + dbfile);
m_conn.Open();
// テーブルの有無を確認
bool exists = false;
SQLiteCommand existsCommand = new SQLiteCommand("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='sample_table';", m_conn);
object existsResult = existsCommand.ExecuteScalar();
try
{
if (int.Parse(existsResult.ToString()) > 0)
{
exists = true;
}
}
catch
{
}
// テーブル生成
if (exists == false)
{
SQLiteCommand createCommand = new SQLiteCommand
(
"CREATE TABLE sample_table ("
+ " id INTEGER NOT NULL PRIMARY KEY"
+ ", value TEXT NOT NULL"
+ ", update_date TIMESTAMP DEFAULT (DATETIME('now','localtime'))"
+ ");"
, m_conn
);
createCommand.ExecuteNonQuery();
// データ生成
SQLiteCommand insertCommand;
insertCommand = new SQLiteCommand("INSERT INTO sample_table (id, value) VALUES (1, 'ABCD');", m_conn);
insertCommand.ExecuteNonQuery();
insertCommand = new SQLiteCommand("INSERT INTO sample_table (id, value) VALUES (2, 'EFGH');", m_conn);
insertCommand.ExecuteNonQuery();
insertCommand = new SQLiteCommand("INSERT INTO sample_table (id, value) VALUES (3, 'IJKL');", m_conn);
insertCommand.ExecuteNonQuery();
}
// データベース切断
m_conn.Close();
//////////////////////////////////////////////// テスト
// データベース接続
m_conn.Open();
// データテーブル生成
DataTable table = new DataTable();
table.Columns.Add(new DataColumn("id" , typeof(int) ));
table.Columns.Add(new DataColumn("value", typeof(string)));
table.PrimaryKey = new DataColumn[] { table.Columns["id"] };
// DataAdapter生成
SQLiteDataAdapter da = new SQLiteDataAdapter();
// SelectCommand
da.SelectCommand = new SQLiteCommand("SELECT id, value, update_date FROM sample_table", m_conn);
// InsertCommand
da.InsertCommand = new SQLiteCommand("INSERT INTO sample_table(id, value) VALUES (@id, @value);", m_conn);
da.InsertCommand.Parameters.Add(new SQLiteParameter("id" , DbType.Int32 , 0, ParameterDirection.Input, false, 0, 0, "id" , DataRowVersion.Current, DBNull.Value));
da.InsertCommand.Parameters.Add(new SQLiteParameter("value", DbType.String, 0, ParameterDirection.Input, false, 0, 0, "value", DataRowVersion.Current, DBNull.Value));
// UpdateCommand
da.UpdateCommand = new SQLiteCommand("UPDATE sample_table SET value=@value, update_date=(DATETIME('now','localtime')) WHERE id=@id;", m_conn);
da.UpdateCommand.Parameters.Add(new SQLiteParameter("value", DbType.String, 0, ParameterDirection.Input, false, 0, 0, "value", DataRowVersion.Current , DBNull.Value));
da.UpdateCommand.Parameters.Add(new SQLiteParameter("id" , DbType.Int32 , 0, ParameterDirection.Input, false, 0, 0, "id" , DataRowVersion.Original, DBNull.Value));
// DeleteCommand
da.DeleteCommand = new SQLiteCommand("DELETE FROM sample_table WHERE id=@id;", m_conn);
da.DeleteCommand.Parameters.Add(new SQLiteParameter("id" , DbType.Int32 , 0, ParameterDirection.Input, false, 0, 0, "id" , DataRowVersion.Original, DBNull.Value));
// RowUpdated
da.RowUpdated += new EventHandler<System.Data.Common.RowUpdatedEventArgs>(da_RowUpdated);
// Fill
da.Fill(table);
// データ変更
DataRow updateRow = table.Rows.Find(3);
updateRow["value"] = "MNOP";
// value列がNULLの行を追加...Updateでエラーになる
DataRow newRow = table.NewRow();
newRow["id" ] = 4;
newRow["value"] = DBNull.Value;
table.Rows.Add(newRow);
Console.WriteLine("保存前");
foreach (DataRow row in table.Rows)
{
Console.WriteLine(row["id"].ToString() + " : " + row.RowState.ToString());
}
// 保存
SQLiteTransaction tran = m_conn.BeginTransaction();
try
{
DataTable tempTable = table.GetChanges();
da.Update(tempTable);
tran.Commit();
Console.WriteLine("保存成功");
table.Merge(tempTable);
foreach (DataRow row in tempTable.Rows)
{
if (row.RowState == DataRowState.Unchanged)
{
DataRow orgRow = table.Rows.Find(row["id"]);
if (orgRow != null)
{
orgRow.AcceptChanges();
}
}
}
}
catch (Exception ex)
{
tran.Rollback();
Console.WriteLine("保存失敗");
Console.WriteLine(ex.Message);
}
Console.WriteLine("保存後");
foreach (DataRow row in table.Rows)
{
Console.WriteLine(row["id"].ToString() + " : " + row.RowState.ToString());
}
// id=4のvalueをセットして再度保存
DataRow row4 = table.Rows.Find(4);
row4["value"] = "QRST";
tran = m_conn.BeginTransaction();
try
{
DataTable tempTable = table.GetChanges();
da.Update(tempTable);
tran.Commit();
Console.WriteLine("保存成功");
table.Merge(tempTable);
foreach (DataRow row in tempTable.Rows)
{
if (row.RowState == DataRowState.Unchanged)
{
DataRow orgRow = table.Rows.Find(row["id"]);
if (orgRow != null)
{
orgRow.AcceptChanges();
}
}
}
}
catch (Exception ex)
{
tran.Rollback();
Console.WriteLine("保存失敗");
Console.WriteLine(ex.Message);
}
Console.WriteLine("保存後");
foreach (DataRow row in table.Rows)
{
Console.WriteLine(row["id"].ToString() + " : " + row.RowState.ToString());
}
Console.ReadKey();
// データベース切断
m_conn.Close();
}
static void da_RowUpdated(object sender, System.Data.Common.RowUpdatedEventArgs e)
{
if (e.Status == UpdateStatus.Continue)
{
if ((e.StatementType == StatementType.Insert) || (e.StatementType == StatementType.Update))
{
// update_dateの取得
SQLiteCommand cmd = new SQLiteCommand("SELECT update_date FROM sample_table WHERE id=@id", m_conn);
SQLiteParameter param = new SQLiteParameter("id", DbType.Int32, 0, "id", DataRowVersion.Original);
param.Value = e.Row["id"];
cmd.Parameters.Add(param);
try
{
e.Row["update_date"] = cmd.ExecuteScalar();
e.Row.AcceptChanges();
}
catch
{
throw;
}
}
}
}
}
}
</xmp></code></pre>kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-38550372286959364732012-04-05T11:06:00.001+09:002012-04-05T11:06:21.459+09:00SQLiteでTIMESTAMP列のデフォルト値のタイムゾーンをJSTにするSQLite3で次のようなテーブルを作った。<br />
<pre><code><xmp>CREATE TABLE sample_table
(
id INTEGER NOT NULL PRIMARY KEY
, value TEXT,
, update_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
</xmp></code></pre>
すると、update_dateにはタイムゾーンがUTCで日時がセットされてしまう。<br />
<br />
調べてみると、<code>DATETIME('now','localtime')</code>とすると、タイムゾーンがJSTで日時が取れるということがわかった。<br />
そこで、早速上記SQLを<br />
<pre><code><xmp>CREATE TABLE sample_table
(
id INTEGER NOT NULL PRIMARY KEY
, value TEXT,
, update_date TIMESTAMP DEFAULT DATETIME('now','localtime')
);
</xmp></code></pre>
としてみた。
しかし、<br />
<pre><code><xmp>SQLite error
near "(": syntax error
</xmp></code></pre>
というエラーが発生。<br />
<br />
色々試してみて、<code>DATETIME('now','localtime')</code>を括弧で囲めばいいことがわかった。<br />
最終的には、次のようなSQLとなった。<br />
<pre><code><xmp>CREATE TABLE sample_table
(
id INTEGER NOT NULL PRIMARY KEY
, value TEXT,
, update_date TIMESTAMP DEFAULT (DATETIME('now','localtime'))
);
</xmp></code></pre>kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-2258710273118381192012-03-22T11:39:00.002+09:002012-03-22T11:39:54.979+09:00Google Chromeのプロキシ接続でのアップデートGoogle Chromeのアップデートが、プロキシ接続の場合、うまくいかないことがある。<br />
コントロールパネルのインターネットオプションで、プロキシを設定してある場合でも、WinHTTPのプロキシが設定されていないといけないらしい。<br />
<br />
そこで、インターネットオプションに設定されているプロキシの設定内容を、WinHTTPのプロキシに反映させるため、次のコマンドを実行する。<br />
<pre><code><xmp>proxycfg -u
</xmp></code></pre>
ちゃんと設定されたか確認するには、<br />
<pre><code><xmp>proxycfg
</xmp></code></pre>
と、引数なしで実行する。<br />
WinHTTPのプロキシ設定を元に戻すには、次のコマンドを実行する。<br />
<pre><code><xmp>proxycfg -d
</xmp></code></pre>kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-20989117174590775192012-03-09T16:09:00.000+09:002012-03-09T16:09:30.773+09:00DataGridViewのDataSourceをバックグラウンドで生成するとフリーズする.NETのDataGridViewコントロールでハマってしまったことがあったので、まとめておきます。<br />
<br />
DataGridViewのDataSourceにDataTableをバインドして、そのDataTableの内容をBackgroundWorkerで生成すると、アプリケーションがフリーズしてしまう現象が出ました。<br />
<br />
具体的には、次のようなフォームを作ります。<br />
<img src="https://lh5.googleusercontent.com/-LTb9SWCbxx8/T1mjlLljdAI/AAAAAAAADlc/e1SQ1leJZgg/s800/dataGridView1.png" /><br />
フォームには、DataGridViewをdataGridView1という名前で貼り付けてあります。<br />
このフォームに次のようなコードを記述します。<br />
<pre><code><xmp>using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace DataGridViewTest
{
public partial class Form1 : Form
{
private DataTable m_table;
public Form1()
{
InitializeComponent();
// データテーブル生成
m_table = new DataTable();
m_table.Columns.Add(new DataColumn("id" , typeof(int)));
m_table.Columns.Add(new DataColumn("value", typeof(int)));
m_table.PrimaryKey = new DataColumn[] { m_table.Columns["id"] };
m_table.Columns["id"].AutoIncrement = true;
m_table.Columns["id"].AutoIncrementSeed = 1;
m_table.Columns["id"].AutoIncrementStep = 1;
}
private void Form1_Load(object sender, EventArgs e)
{
// データテーブルをDataGridViewにバインド
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = m_table;
// バックグラウンド処理開始
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
System.Random rnd = new Random();
// データテーブルに1000行のデータを追加する
for (int index = 0; index &lt; 1000; index++)
{
DataRow newRow = m_table.NewRow();
newRow["value"] = rnd.Next(100);
m_table.Rows.Add(newRow);
}
}
}
}
</xmp></code></pre>
コンストラクタで、DataTableを生成しています。<br />
Loadイベントで、生成したDataTableをDataGridViewのDataSourceにバインドします(AutoGenerateColumnsをtrueにしてグリッドの列を自動生成しています)。そして、BackgroundWorkerで、DataTableに仮のデータを1000件作ります。<br />
<br />
このコードをVisualStudioのデバッガで実行すると、<br />
<img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjS9vWoYePNCRLmtJDoAAbfSg7KIItqXJpxzuSN6oWEF0SELcan8CQPo19BHG_bGv4WMVN6eiKMlC-TBcnAgYUEFtAHzua-ZOJQcXsYZj8Pr0MoXBArbYOAwjkY5wwijivGEkZWrc9Fvmza/s800/DataGridViewTestResult.png" /><br />
と表示されますが、グリッドにスクロールバーが表示されていません(ウインドウサイズを変更したりすると表示されます)。<br />
<br />
<span style="font-size: large;">このプログラムをReleaseでビルドして、実行ファイルを直接起動すると、プログラムがフリーズしてしまいます。</span><br />
この現象は、<br />
<ul>
<li>.NET Framework2.0</li>
<li>.NET Framework3.5</li>
<li>.NET Framework4</li>
</ul>
でも発生しました。またVisualStudio2008でもVisualStudio2010でも同様でした。<br />
<br />
<br />
デバッガでもスクロールバーが表示されないところがアヤシイので、いろいろ試してみたら、DataTableに追加するレコード(DataRow)が少ない時はフリーズしないことがわかり、よく見てみると、データ量が少ない=スクロールバーが表示される必要がない件数、であるようでした。<br />
そこで、BackgroundWorkerでRunWorkerAsyncする前にDataGridViewのScrollBarsを一旦なしにして、BackgroundWorkerの処理が終了した時に、DataGridViewのScrollBarsを元に戻すようにしてみました。
<pre><code><xmp>using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace DataGridViewTest
{
public partial class Form1 : Form
{
private DataTable m_table;
public Form1()
{
InitializeComponent();
// データテーブル生成
m_table = new DataTable();
m_table.Columns.Add(new DataColumn("id" , typeof(int)));
m_table.Columns.Add(new DataColumn("value", typeof(int)));
m_table.PrimaryKey = new DataColumn[] { m_table.Columns["id"] };
m_table.Columns["id"].AutoIncrement = true;
m_table.Columns["id"].AutoIncrementSeed = 1;
m_table.Columns["id"].AutoIncrementStep = 1;
}
private void Form1_Load(object sender, EventArgs e)
{
// データテーブルをDataGridViewにバインド
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = m_table;
// グリッドのスクロールバーを一旦なしにする
dataGridView1.ScrollBars = ScrollBars.None;
// バックグラウンド処理開始
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
System.Random rnd = new Random();
// データテーブルに1000行のデータを追加する
for (int index = 0; index < 1000; index++)
{
DataRow newRow = m_table.NewRow();
newRow["value"] = rnd.Next(100);
m_table.Rows.Add(newRow);
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// グリッドのスクロールバーを元に戻す
dataGridView1.ScrollBars = ScrollBars.Both;
}
}
}
</xmp></code></pre>
こうすると、フリーズせずに動きました。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-64142868707755326892012-03-08T19:08:00.000+09:002012-03-08T19:08:56.116+09:00OracleでDBMS_OUTPUTを使う時にOracleのSQL/Plusで、デバッグメッセージなどを表示したい時、DBMS_OUTPUT.PUT_LINE('メッセージ');とするが、このとき、SQL/Plusでコマンド<br />
<pre><code><xmp>set serverout on;
</xmp></code></pre>とする必要がある。<br />
バッファサイズが足りないときは
<pre><code><xmp>set serverout on size 1000000;
</xmp></code></pre>のようにバッファサイズを指定する。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-9735281691969061262012-03-06T09:23:00.001+09:002012-03-06T09:23:37.360+09:00.NETのNumericUpDownのReadOnlyについて.NETのSystem.Windows.Forms.NumericUpDownコントロールのReadOnlyについて、メモ。<br />
<br />
NumericUpDownコントロールのReadOnlyプロパティをTrueにして、編集できないようにしたが、上下ボタンを押すと、値が変更できてしまう。<br />
えーーーって感じだけど、対策としてこちらのサイトを参照して対応した。<br />
<a href="http://kbdpage.blog82.fc2.com/blog-entry-118.html">忘れる前にメモ</a><br />
<br />
対応方法としては、Incrementプロパティを0にして、値のインクリメントをできないようにする。<br />
これで一応大丈夫かな。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-84126741682357356942012-02-20T18:22:00.000+09:002012-02-20T18:22:24.527+09:00Windows7のChromeのフォントをメイリオに変更する方法<a href="http://giocoapp.com/blog/%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6/google-chrome-%E3%81%A7%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88%E3%82%92%E3%83%A1%E3%82%A4%E3%83%AA%E3%82%AA%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B3%E3%81%A4%E3%81%AE%E6%96%B9%E6%B3%95">google chrome でフォントをメイリオに変更する3つの方法</a><br />
ここを参考に、ユーザースタイルシートを編集する方法でやってみました。<br />
<br />
%LOCALAPPDATA%\Google\Chrome\User Data\Default\User StyleSheets<br />
というディレクトリにある、Custom.cssというファイル(最初空っぽでした)に、<br />
<pre><code><xmp>* {
}
@font-face {
font-family: "MS PGothic";
src: local("メイリオ");
}
@font-face {
font-family: "MS Pゴシック";
src: local("メイリオ");
}
@font-face {
font-family: "arial";
src: local("メイリオ");
}
</xmp></code></pre>という記述を追加して、保存(文字コードはUTF-8 BOM無)します。<br />
そうすると、Chromeで表示される文字がメイリオに変わって読みやくすくなります。<br />
<br />
※Windows XPの場合、事前にメイリオフォントをダウンロードして、画面のプロパティ→デザイン→効果でClearTypeフォントを使うようにしておく必要があります。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-17871237754111846942011-07-01T16:49:00.000+09:002011-07-01T16:49:37.170+09:00AutoSync for Windows ver2.0.10をリリースしました。<a href="http://www4.ocn.ne.jp/~kkino/autosync.html">AutoSync for Windows ver2.0.10</a>をリリースしました。<br />
今回のバージョンアップで、以下の対応を行いました。<br />
<ul><li>ファイルの転送後、転送元のファイルを削除するオプションを追加(ただし、転送元がLocalかFTPの場合のみ)</li>
</ul>kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-61552557792060981712011-01-10T14:48:00.000+09:002011-01-10T14:48:41.609+09:00WPFのDataGridにDataTableを表示WPFのDataGridコントロールにDataTableの内容を表示してみました。<br />
<br />
テーブルの構成は下図のような感じ。<br />
<img border="0" height="140" width="500" src="http://lh6.ggpht.com/_uvE5pQDYMns/TSqNi4XwP6I/AAAAAAAAB1k/5iyMRT7x-Qs/s800/wpf_er.PNG" /><br />
この中の商品マスタをDataGridで表示します。<br />
<br />
まず、VisualStudio 2010で、[新規作成]→[プロジェクト]→[WPFアプリケーション]を選択し、WPFDataGridSampleというプロジェクトを作成します。<br />
<br />
MainWindowsというフォームが作成されるので、ここにDataGridコントロールを貼り付けて、XAMLのソースを次のように編集して、カラムを追加します。<br />
<pre><code><xmp><Window x:Class="WPFDataGridSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="NumericStyle" TargetType="TextBlock">
<Setter Property="TextAlignment" Value="Right" />
</Style>
</Window.Resources>
<Grid>
<DataGrid Name="dataGrid1" AutoGenerateColumns="False"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn
x:Name="col_item_id"
Header="ID"
Binding="{Binding item_id}"
ElementStyle="{StaticResource NumericStyle}"
IsReadOnly="True"
/>
<DataGridTextColumn
x:Name="col_item_name"
Header="商品名"
Binding="{Binding item_name}"
/>
<DataGridComboBoxColumn
x:Name="col_category_id"
Header="カテゴリ"
SelectedValueBinding="{Binding category_id}"
SelectedValuePath="category_id"
DisplayMemberPath="category_name"
/>
<DataGridCheckBoxColumn
x:Name="col_status"
Header="ステータス"
Binding="{Binding status}"
/>
<DataGridTextColumn
x:Name="col_create_date"
Header="作成日時"
Binding="{Binding create_date, StringFormat=yyyy/MM/dd HH:mm:ss}"
IsReadOnly="True"
/>
<DataGridTextColumn
x:Name="col_update_date"
Header="更新日時"
Binding="{Binding update_date, StringFormat=yyyy/MM/dd HH:mm:ss}"
IsReadOnly="True"
/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
</xmp></code></pre>そうすると、次のようなフォームができます。<br />
<img border="0" src="http://lh6.ggpht.com/_uvE5pQDYMns/TSqSyzI0qRI/AAAAAAAAB1s/taIc8rhN1oI/s800/wpf_form.PNG" /><br />
ポイントは、DataGrid.Columnsの中で各カラムを設定するときに、<br />
<ul><li>Header="xxxx"で、カラムのタイトルを設定します。例えば Header="商品名"</li>
<li>Binding="{Binding xxxx}"で、DataTableの列名を指定します。例えば Binding="{Binding item_name}"</li>
</ul>とします。<br />
<br />
次に、MainWindows.xaml.csに次のコードを記述します。<br />
<pre><code><xmp>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Data;
namespace WPFDataGridSample
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
//-------------------------------------------------------------------- メンバ変数
private DataTable m_table_mst_category; // カテゴリマスタテーブル
private DataTable m_table_mst_item; // 商品マスタテーブル
//-------------------------------------------------------------------- コンストラクタ
public MainWindow()
{
InitializeComponent();
// テーブルの初期化
try
{
InitTables();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, Title, MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
// グリッドにバインド
dataGrid1.DataContext = m_table_mst_item;
col_category_id.ItemsSource = m_table_mst_category.DefaultView; // カテゴリ列
}
//-------------------------------------------------------------------- メンバ関数
/// <summary>
/// テーブルの初期化
/// </summary>
private void InitTables()
{
// カテゴリマスタテーブルの生成
m_table_mst_category = new DataTable("mst_category");
m_table_mst_category.Columns.Add(new DataColumn("category_id" , typeof(int) )); // カテゴリID
m_table_mst_category.Columns.Add(new DataColumn("category_name", typeof(string) )); // カテゴリ名
m_table_mst_category.Columns.Add(new DataColumn("create_date" , typeof(DateTime))); // 作成日時
m_table_mst_category.Columns.Add(new DataColumn("update_date" , typeof(DateTime))); // 更新日時
m_table_mst_category.PrimaryKey = new DataColumn[] { m_table_mst_category.Columns["category_id"] }; // プライマリキーの設定
m_table_mst_category.Columns["category_name"].Unique = true; // ユニークキーの設定
m_table_mst_category.Columns["category_id"].AutoIncrement = true; // 自動採番の設定
m_table_mst_category.Columns["category_id"].AutoIncrementSeed = 1; // 自動採番は1から始まり
m_table_mst_category.Columns["category_id"].AutoIncrementStep = 1; // 自動採番は1ずつ増加
m_table_mst_category.TableNewRow += new DataTableNewRowEventHandler(m_table_mst_category_TableNewRow); // TableNewRowイベントの追加
// サンプルデータ追加
foreach (var category_name in new string[] { "液晶テレビ", "ブルーレイレコーダ" })
{
DataRow newRow = m_table_mst_category.NewRow();
newRow["category_name"] = category_name;
m_table_mst_category.Rows.Add(newRow);
}
// 商品マスタテーブルの生成
m_table_mst_item = new DataTable("mst_item");
m_table_mst_item.Columns.Add(new DataColumn("item_id" , typeof(int) )); // 商品ID
m_table_mst_item.Columns.Add(new DataColumn("item_name" , typeof(string) )); // 商品名
m_table_mst_item.Columns.Add(new DataColumn("category_id", typeof(int) )); // カテゴリID
m_table_mst_item.Columns.Add(new DataColumn("status" , typeof(bool) )); // ステータス
m_table_mst_item.Columns.Add(new DataColumn("create_date", typeof(DateTime))); // 作成日時
m_table_mst_item.Columns.Add(new DataColumn("update_date", typeof(DateTime))); // 更新日時
m_table_mst_item.PrimaryKey = new DataColumn[] { m_table_mst_item.Columns["item_id"] }; // プライマリキーの設定
m_table_mst_item.Columns["item_name"].Unique = true; // ユニークキーの設定
m_table_mst_item.Columns["item_id"].AutoIncrement = true; // 自動採番の設定
m_table_mst_item.Columns["item_id"].AutoIncrementSeed = 1; // 自動採番は1から始まり
m_table_mst_item.Columns["item_id"].AutoIncrementStep = 1; // 自動採番は1ずつ増加
m_table_mst_item.TableNewRow += new DataTableNewRowEventHandler(m_table_mst_item_TableNewRow); // TableNewRowイベントの追加
// サンプルデータ追加
DataRow newRowItem;
newRowItem = m_table_mst_item.NewRow();
newRowItem["item_name"] = "LED REGZA ZG1";
newRowItem["category_id"] = m_table_mst_category.Select("category_name='液晶テレビ'")[0]["category_id"];
m_table_mst_item.Rows.Add(newRowItem);
newRowItem = m_table_mst_item.NewRow();
newRowItem["item_name"] = "RD-X10";
newRowItem["category_id"] = m_table_mst_category.Select("category_name='ブルーレイレコーダ'")[0]["category_id"];
m_table_mst_item.Rows.Add(newRowItem);
}
//-------------------------------------------------------------------- イベント関数
// カテゴリマスタのTableNewRowイベント
private void m_table_mst_category_TableNewRow(object sender, DataTableNewRowEventArgs e)
{
e.Row["create_date"] = DateTime.Now;
e.Row["update_date"] = DateTime.Now;
}
// 商品マスタのTableNewRowイベント
private void m_table_mst_item_TableNewRow(object sender, DataTableNewRowEventArgs e)
{
e.Row["status" ] = true;
e.Row["create_date"] = DateTime.Now;
e.Row["update_date"] = DateTime.Now;
}
}
}
</xmp></code></pre>メンバ変数に、m_table_mst_categoryとm_table_mst_itemというDataTableを用意して、InitTables関数で各テーブルを生成しサンプルデータを作成しています。<br />
<br />
ポイントになるのは、コンストラクタの<br />
<pre><code> // グリッドにバインド
dataGrid1.DataContext = m_table_mst_item;
col_category_id.ItemsSource = m_table_mst_category.DefaultView; // カテゴリ列</code></pre>の部分で、<br />
<pre><code> dataGrid1.DataContext = m_table_mst_item;</code></pre>で、DataGridに商品マスタのDataTableをバインドしています。<br />
また、DataGridの列の中で、カテゴリ列はコンボボックスで選択できるようにするため、<br />
<pre><code> col_category_id.ItemsSource = m_table_mst_category.DefaultView;</code></pre>で、col_category_idにバインドしています。さらにMainWindow.xamlの中でカテゴリ列は<br />
<pre><code><xmp> <DataGridComboBoxColumn
x:Name="col_category_id"
Header="カテゴリ"
SelectedValueBinding="{Binding category_id}"
SelectedValuePath="category_id"
DisplayMemberPath="category_name"
/></xmp></code></pre>としているため、カテゴリマスタテーブル(m_table_mst_category)のcategory_idが選択値、category_nameが表示値となります。<br />
※商品マスタのcategory_idとのバインディングは、Binding="{Binding category_id}"ではなく、SelectedValueBinding="{Binding category_id}"で行います。<br />
<br />
ここで注意する点として、col_category_id.ItemsSourceを、<br />
<pre><code> col_category_id.ItemsSource = m_table_mst_category.Rows;</code></pre>とすると、カテゴリ列が空白になって正しく表示されません。ただ入力するときのコンボボックスのドロップダウンする行数はカテゴリマスタの件数と一致しているので、カテゴリ列とカテゴリマスタのDataTableとのバインドはうまくいっていると思いますが、表示がうまくいかないようです(DataGridのバグなのか制約なのかよくわかりません)。<br />
<br />
実行すると、次のような画面が表示されます。<br />
<img src="http://lh4.ggpht.com/_uvE5pQDYMns/TSqYMcaUu5I/AAAAAAAAB1w/nb9vYBJf4NM/s800/wpf_result.png" /><br />
<br />
<br />
個人的な感想として、BindingSourceやBindingNavigationなどが使えないため、Windowsフォームのように簡単には作れない感じです。<br />
あと、データとDataGridのバインディングの方法がわかりにくい(今もまだしっかりわかっていない気がします)です。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-37325282746693778552010-12-06T16:15:00.002+09:002010-12-07T08:15:02.666+09:00pgpool2による負荷分散PostgreSQL 9.0.1でストリーミングレプリケーションしたサーバー群を、pgpool2を使って負荷分散させてみました。<br />
イメージはこんな感じで。<br />
<img height="507" src="http://lh6.ggpht.com/_uvE5pQDYMns/TPyFpCI9iRI/AAAAAAAAB0Q/bju0b4XxAj0/s800/pgpool2.png" width="400" /><br />
<br />
pgpool2は3.0.1を使用します。<br />
またこの時、pgpool2のモードは、マスタースレーブモードに設定します。<br />
<br />
前回のAをマスターとして、BとCにレプリケーションした状態に、Dというpgpool2サーバーを入れます。<br />
また、Dのpgpool2のポートは5432とし、クライアントはDのサーバーをPostgreSQLデータベースサーバーとしてアクセスするようにします。<br />
<br />
pgpool2をマスタースレーブモードで動作させる場合、システムDBは不要ですが、pgpool2をソースからビルドするために、PostgreSQLのライブラリなどが必要になるので、PostgreSQL 9.0.1をソースからビルドしておきます。<br />
<br />
pgpool2をインストールします。<br />
<pre><code>$ sudo mkdir /usr/local/pgpool2
$ sudo chown postgres:postgres /usr/local/pgpool2
$ cd /usr/local/src
$ sudo wget http://pgfoundry.org/frs/download.php/2841/pgpool-II-3.0.1.tar.gz
$ sudo tar zxvf pgpool-II-3.0.1.tar.gz
$ chown -R postgres:postgres pgpool-II-3.0.1
$ sudo chown -R postgres:postgres pgpool-II-3.0.1
$ su - postgres
$ cd /usr/local/src/pgpool-II-3.0.1/
$ ./configure --prefix=/usr/local/pgpool2 -with-pgsql=/usr/local/pgsql
$ make
$ make install
</code></pre><br />
pgpool2を設定します。<br />
<pre><code>$ cd /usr/local/pgpool2/etc/
</code></pre>まずpcp.confにpcp.conf.sampleをコピーして編集します。<br />
<pre><code>$ cp pcp.conf.sample pcp.conf
</code></pre>pcp.confに、<br />
postgres:<i>postgresのパスワードのMD5</i><br />
という記述を追加します。<br />
postgreslのパスワードのMD5で暗号化されたものは、<br />
../bin/pg_md5 <i>ユーザーpostgresのパスワード</i><br />
で表示されます。<br />
<br />
pgpool.confにpgpool.conf.sample-streamをコピーして編集します。<br />
<pre><code>$ cp pgpool.conf.sample-stream pgpool.conf
</code></pre>内容の次の箇所を変更します。<br />
<pre><code>listen_addresses = '*'
port = 5432
pid_file_name = '/var/run/pgpool.pid'
# 次の3行は確認用
log_statement = true # 実行されるSQL文を出力
log_per_node_statement = true # どのノードでSQL文が実行されたかを出力
log_connections = true # ノードに接続されたことを出力
# システムDBは不要だけの一応設定
system_db_hostname = 'localhost'
system_db_port = 5433 # pgpool2を5432ポートで動かすので、PostgreSQLを動かすときは5433ポートにする
system_db_dbname = 'pgpool'
system_db_schema = 'pgpool_catalog'
system_db_user = 'pgpool'
system_db_password = ''
backend_hostname0 = 'マスター(A)のホスト'
backend_port0 = 5432
backend_weight0 = 1
backend_data_directory0 = '/usr/local/pgsql/data'
backend_hostname1 = 'スレーブ(B)のホスト'
backend_port1 = 5432
backend_weight1 = 1
backend_data_directory1 = '/usr/local/pgsql/data'
backend_hostname2 = 'スレーブ(C)のホスト'
backend_port2 = 5432
backend_weight2 = 1
backend_data_directory2 = '/usr/local/pgsql/data'
</code></pre><br />
実際に動かして動作確認してみます。<br />
<pre><code>$ sudo /usr/local/pgpool2/bin/pgpool -n
</code></pre><br />
クライアントから、Dのサーバーにポートを5432でアクセスして、更新クエリと参照クエリを適当に投げてみます。<br />
すると、更新クエリは必ずサーバーAに送られ、参照クエリはA,B,Cのいずれかのサーバーに送られます。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-48110180367462103482010-11-10T15:48:00.001+09:002010-11-10T15:55:15.677+09:00PostgreSQL 9.0でレプリケーション先の追加前回、PostgreSQL 9.0のレプリケーション機能を試したが、今回は、レプリケーション先の追加を試してみました。<br />
イメージはこんな感じで<br />
<img height="280" src="http://1.bp.blogspot.com/_uvE5pQDYMns/TNo6td9H07I/AAAAAAAABz0/uPIlR7ccXvc/s320/replication.png" width="320" border="0" /><br />
サーバーAからサーバーBにレプリケーションされている状態に、新たにサーバーCをレプリケーション先として追加します。<br />
<br />
まず、サーバーCにubuntuを入れてPostgreSQL9.0を<a href="http://90h-tech.blogspot.com/2010/02/postgresql.html">ソースからビルド</a>しておきます。<br />
<br />
次に、サーバーBとサーバーCのPostgreSQLを停止します。<br />
サーバーBで<br />
<pre><code>$ sudo /etc/init.d/postgresql stop
</code></pre>サーバーCで<br />
<pre><code>$ sudo /etc/init.d/postgresql stop
</code></pre><br />
サーバーBの/usr/local/pgsql/dataをサーバーCにコピーします。<br />
サーバーBで<br />
<pre><code>$ su - postgres
$ cd /usr/local/pgsql/
$ tar zcvf data.tar.gz data
$ scp data.tar.gz 192.168.168.238:/usr/local/pgsql/
$ exit
</code></pre>サーバーCで<br />
<pre><code>$ su - postgres
$ cd /usr/local/pgsql/
$ tar zxvf data.tar.gz
$ exit
</code></pre><br />
最後に、サーバーBとサーバーCのPostgreSQLを起動します。<br />
サーバーBで<br />
<pre><code>$ sudo /etc/init.d/postgresql start
</code></pre>サーバーCで<br />
<pre><code>$ sudo /etc/init.d/postgresql start
</code></pre><br />
これで、サーバーCがレプリケーション先として追加できました。<br />
動作を確認してみます。<br />
レプリケーション元のデータを更新します。<br />
サーバーAで<br />
<pre><code>$ su - postgres
$ psql testdb
testdb=# select * from table1;
id | create_date
----+----------------------------
1 | 2010-11-08 11:19:10.162554
(1 row)
testdb=# insert into table1(id) values (2);
INSERT 0 1
testdb=# select * from table1;
id | create_date
----+----------------------------
1 | 2010-11-08 11:19:10.162554
2 | 2010-11-10 14:54:15.611948
(2 rows)
</code></pre>サーバーBとサーバーCで<br />
<pre><code>$ su - postgres
$ psql testdb
testdb=# select * from table1;
id | create_date
----+----------------------------
1 | 2010-11-08 11:19:10.162554
2 | 2010-11-10 14:54:15.611948
(2 rows)
</code></pre>となり、更新内容が反映されていることが確認できました。<br />
<br />
<br />
あと、レプリケーション先のサーバーBとサーバーCのPostgreSQLが停止している間に、サーバーAのデータが更新された場合も確認してみました。<br />
<br />
まず、レプリケーション先のPostgreSQLを停止します。<br />
サーバーBで<br />
<pre><code>$ sudo /etc/init.d/postgresql stop
</code></pre>サーバーCで<br />
<pre><code>$ sudo /etc/init.d/postgresql stop
</code></pre><br />
次に、レプリケーション元のサーバーAのデータを更新します。<br />
<pre><code>$ su - postgres
$ psql testdb
testdb=# select * from table1;
id | create_date
----+----------------------------
1 | 2010-11-08 11:19:10.162554
2 | 2010-11-10 14:54:15.611948
(2 rows)
testdb=# insert into table1(id) values (3);
INSERT 0 1
testdb=# select * from table1;
id | create_date
----+----------------------------
1 | 2010-11-08 11:19:10.162554
2 | 2010-11-10 14:54:15.611948
3 | 2010-11-10 14:59:57.012077
(3 rows)
</code></pre><br />
レプリケーション先のPostgreSQLを起動します。<br />
サーバーBで<br />
<pre><code>$ sudo /etc/init.d/postgresql start
</code></pre>サーバーCで<br />
<pre><code>$ sudo /etc/init.d/postgresql start
</code></pre><br />
レプリケーション元のサーバーAの更新内容が反映されているか確認します。<br />
サーバーBとサーバーCで<br />
<pre><code>$ su - postgres
$ psql testdb
testdb=# select * from table1;
id | create_date
----+----------------------------
1 | 2010-11-08 11:19:10.162554
2 | 2010-11-10 14:54:15.611948
3 | 2010-11-10 14:59:57.012077
(3 rows)
</code></pre>となり、更新内容が反映されていることが確認できました。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-30305277911075766802010-11-05T17:27:00.000+09:002010-11-05T17:27:48.674+09:00PostgreSQL 9.0でレプリケーションPostgreSQL 9.0でレプリケーション機能が追加されたので、試してみました。<br />
<br />
環境は、OSにubuntu 10.04 LTS Serverを使ってみました。<br />
ESXi4.1に仮想PCを2台用意して、それぞれにubuntuを入れて、PostgreSQLを<a href="http://90h-tech.blogspot.com/2010/02/postgresql.html">ソースからビルド</a>しておきます。<br />
<br />
■レプリケーション元のPostgreSQLの設定を変更<br />
まず、PostgreSQLを停止します。<br />
<pre><code>$ sudo /etc/init.d/postgresql stop
</code></pre>PostgreSQLの管理ユーザーになって、設定ファイルを編集します。<br />
<pre><code>$ su - postgres
$ cd /usr/local/pgsql/data
</code></pre>postgresql.confに次の設定を追加します。<br />
<pre><code>listen_addresses = '*'
port = 5432
wal_level = hot_standby
archive_mode = on
archive_command = 'cp %p /usr/local/pgsql/data/pg_archive/%f'
max_wal_senders = 3
</code></pre>pg_hba.confに次の設定を追加します。<br />
<pre><code>host replication all 192.168.168.0/24 trust
</code></pre>アーカイブディレクトリを作成します。<br />
<pre><code>$ mkdir pg_archive
</code></pre>PostgreSQLの管理ユーザーを抜けます。<br />
<pre><code>$ exit
</code></pre><br />
■レプリケーション先のPostgreSQLの設定を変更<br />
まず、PostgreSQLを停止します。<br />
<pre><code>$ sudo /etc/init.d/postgresql stop
</code></pre>PostgreSQLの管理ユーザーになって、設定ファイルを編集します。<br />
<pre><code>$ su - postgres
$ cd /usr/local/pgsql/data
</code></pre>postgresql.confに次の設定を追加します。<br />
<pre><code>listen_addresses = '*'
port = 5432
wal_level = hot_standby
archive_mode = on
archive_command = 'cp %p /usr/local/pgsql/data/pg_archive/%f'
max_wal_senders = 3
hot_standby = on
</code></pre>pg_hba.confに次の設定を追加します。<br />
<pre><code>host replication all 192.168.168.0/24 trust
</code></pre>recovery.confというファイルを作成して、次の設定を記述します。<br />
<pre><code>standby_mode = 'on'
primary_conninfo = 'host=レプリケーション元のホスト port=5432 user=postgres'
restore_command = 'cp /usr/local/pgsql/data/pg_archive/%f %p'
trigger_file = '/usr/localpgsql/data/trigger'
</code></pre>アーカイブディレクトリを作成します。<br />
<pre><code>$ mkdir pg_archive
</code></pre>PostgreSQLの管理ユーザーを抜けます。<br />
<pre><code>$ exit
</code></pre><br />
■レプリケーション元のPostgreSQLを起動して、データベースを作成<br />
<pre><code>$ sudo /etc/init.d/postgresql start
$ su - postgres
$ createdb testdb
$ psql -l
List of databases
Name | Owner | Encoding | Collation | Ctype | Access privileges
-----------+----------+----------+-----------+-------+-----------------------
postgres | postgres | UTF8 | C | C |
template0 | postgres | UTF8 | C | C | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | C | C | =c/postgres +
| | | | | postgres=CTc/postgres
testdb | postgres | UTF8 | C | C |
(4 rows)
</code></pre><br />
■レプリケーション元のデータをレプリケーション先にコピー<br />
レプリケーション元のデータをレプリケーション先にコピーします。<br />
<pre><code>$ cd /usr/local/pgsql
$ tar zcvf data.tar.gz data
$ scp data.tar.gz レプリケーション先のホスト:/usr/local/pgsql/
</code></pre>レプリケーション先で<br />
<pre><code>$ su - postgres
$ cd /usr/local/pgsql
</code></pre>設定ファイルのバックアップを取っておきます。<br />
<pre><code>$ mkdir ~/conf_backup
$ cp -a data/*.conf ~/conf_backup/
</code></pre>レプリケーション元のデータを展開します。<br />
<pre><code>$ tar zxvf data.tar.gz
</code></pre>バックアップした設定ファイルを戻します。<br />
<pre><code>$ cp -a ~/conf_backup/*.conf data/
</code></pre>プロセスIDは削除します。<br />
<pre><code>$ rm -f data/postmaster.pid
</code></pre>PostgreSQLを起動します。<br />
<pre><code>$ exit
$ sudo /etc/init.d/postgresql start
</code></pre><br />
■確認<br />
レプリケーション元でtestdbにテーブルを作成してみます。<br />
<pre><code>$ su - postgres
$ psql testdb
testdb=# create table table1 (id integer, create_date timestamp default current_timestamp);
testdb=# \d
List of relations
Schema | Name | Type | Owner
--------+--------+-------+----------
public | table1 | table | postgres
(1 row)
testdb=# \d table1
Table "public.table1"
Column | Type | Modifiers
-------------+-----------------------------+---------------
id | integer |
create_date | timestamp without time zone | default now()
</code></pre>レプリケーション先で確認します。<br />
<pre><code>$ su - postgres
$ psql testdb
testdb=# \dt
List of relations
Schema | Name | Type | Owner
--------+--------+-------+----------
public | table1 | table | postgres
(1 row)
testdb=# \d table1
Table "public.table1"
Column | Type | Modifiers
-------------+-----------------------------+---------------
id | integer |
create_date | timestamp without time zone | default now()
</code></pre>レプリケーション元でデータベースを作成します。<br />
<pre><code>$ createdb testdb2
$ psql -l
List of databases
Name | Owner | Encoding | Collation | Ctype | Access privileges
-----------+----------+----------+-----------+-------+-----------------------
postgres | postgres | UTF8 | C | C |
template0 | postgres | UTF8 | C | C | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | C | C | =c/postgres +
| | | | | postgres=CTc/postgres
testdb | postgres | UTF8 | C | C |
testdb2 | postgres | UTF8 | C | C |
(5 rows)
</code></pre>レプリケーション先で確認します。<br />
<pre><code>$ psql -l
List of databases
Name | Owner | Encoding | Collation | Ctype | Access privileges
-----------+----------+----------+-----------+-------+-----------------------
postgres | postgres | UTF8 | C | C |
template0 | postgres | UTF8 | C | C | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | C | C | =c/postgres +
| | | | | postgres=CTc/postgres
testdb | postgres | UTF8 | C | C |
testdb2 | postgres | UTF8 | C | C |
(5 rows)
</code></pre><br />
割と簡単に設定できました。<br />
そのうち、レプリケーション先の追加や、レプリケーション先の昇格などやってみたいと思います。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0tag:blogger.com,1999:blog-677207412948384379.post-83760231567033606042010-07-16T16:06:00.000+09:002010-07-16T16:06:40.134+09:00DataTableをBindingSourceのDataSourceに設定したときの注意点ADO.NETのDataTableをBindingSourceのDataSourceに設定して、BindingSourceのFilterに条件を設定したとき、DataTable.DefaultViewのRowFilterに、BindingSourceのFilterの内容が設定されるみたい。<br />
このため、1つのDataTableをBindingSourceのDataSourceやComboBoxのDataSourceなど、複数のコントロールのDataSourceに使用すると、どれか1つのコントロールでFilterを設定すると、すべてのコントロールでFilterがかかった状態になる。<br />
<br />
これを回避するために、1つのDataTableを複数のコントロールのDataSourceとして使いたいときは、各コントロールのDataSourceにそれぞれDataViewを(DataTableから)生成して設定すればよい。kkino90hhttp://www.blogger.com/profile/10505643772954517780noreply@blogger.com0