tag:crieit.net,2005:https://crieit.net/tags/PostgreSQL/feed 「PostgreSQL」の記事 - Crieit Crieitでタグ「PostgreSQL」に投稿された最近の記事 2023-05-07T08:55:07+09:00 https://crieit.net/tags/PostgreSQL/feed tag:crieit.net,2005:PublicArticle/18427 2023-05-07T08:47:15+09:00 2023-05-07T08:55:07+09:00 https://crieit.net/posts/resolve-key-is-stored-in-legacy-truste-gpg-keyring-issue 【Ubuntu】apt updateでKey is stored in legacy trusted.gpg keyringと注意されたので対応する <p>こんにちは、しきゆらです。<br /> 今回は公式のドキュメントを見ながら対応したのに、<code>apt update</code>で怒られるようになったので<br /> なぜなのか調べつつ、対応方法を見つけて対応したのでメモしておきます。</p> <h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2> <p>今回起こったのはPostgreSQLのインストール時のこと。<br /> PostgreSQLをインストールするためにリポジトリ追加を行った。 手順はこれ。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.postgresql.org/download/linux/ubuntu/">PostgreSQL: Linux downloads (Ubuntu) </a></p> <p>該当箇所は以下の通り。</p> <pre><code class="zsh"># Create the file repository configuration: $ sudo sh -c 'echo "deb <http://apt.postgresql.org/pub/repos/apt> $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' # Import the repository signing key: $ wget --quiet -O - <https://www.postgresql.org/media/keys/ACCC4CF8.asc> | sudo apt-key add - # Update the package lists: $ sudo apt-get update # <= ここでWarningが出た ... Key is stored in legacy trusted.gpg keyring </code></pre> <p>ということで、記載されているが、そもそも何を怒っているのかを調べてみました。</p> <h2 id="何が起きているのか"><a href="#%E4%BD%95%E3%81%8C%E8%B5%B7%E3%81%8D%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE%E3%81%8B">何が起きているのか</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://gihyo.jp/admin/serial/01/ubuntu-recipe/0675">apt-keyはなぜ廃止予定となったのか</a><br /> <a target="_blank" rel="nofollow noopener" href="https://salsa.debian.org/apt-team/apt/-/commit/ee284d5917d09649b68ff1632d44e892f290c52f">Fully deprecate apt-key, schedule removal for Q2/2022 (ee284d59) · Commits · APT Developers / apt · GitLab</a></p> <p>apt-keyコマンドがだいぶ前の2020年8月ころから廃止予定で、2022年Q2に廃止となっていたようです。<br /> 手元の環境はWSL2上のUbuntu 22.10でしたが、まだ残っているようでした。<br /> 今後はなくなるんでしょう。</p> <blockquote> <p>セキュリティ上の懸念点からくるもので、簡単にまとめると次の2点が理由です。</p> <ol> <li>apt-key addは単一ファイル(/etc/apt/trusted.gpg)に鍵を追加していくため、複数のリスクの異なるリポジトリの鍵を同じ権限で管理しなくてはならない。</li> <li>リポジトリ鍵として追加した鍵は、すべてのリポジトリに対して適用される。</li> </ol> <p>引用元: https://gihyo.jp/admin/serial/01/ubuntu-recipe/0675</p> </blockquote> <p>ということで、リポジトリごとにリスクが異なるカギを全体で使うのよくいないよね、ということでこれをやめた、という感じなのかなと。<br /> やめたはいいが、この記事時点では代替となるものが用意されていないようです。 今後は、各リポジトリで利用するカギを/usr/share/keyrings/配下に設置して個別に指定する運用が良いだろう、として締めています。</p> <h2 id="対応方法"><a href="#%E5%AF%BE%E5%BF%9C%E6%96%B9%E6%B3%95">対応方法</a></h2> <p>大きく分けると以下の通り。</p> <ol> <li>既存のカギを削除する</li> <li>別途カギを取得し設定しなおす</li> <li>リポジトリとカギを紐づける</li> </ol> <h3 id="1. 既存のカギを削除する"><a href="#1.+%E6%97%A2%E5%AD%98%E3%81%AE%E3%82%AB%E3%82%AE%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B">1. 既存のカギを削除する</a></h3> <pre><code class="zsh">$ apt-key list Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)). /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg # <= これを削除する --------------------------------------------- pub rsa4096 2011-10-13 [SC] B97B 0AFC AA1A 47F0 44F2 44A0 7FCC 7D46 ACCC 4CF8 uid [ unknown] PostgreSQL Debian Repository ... # 鍵の削除はfinger printの最後の8文字のようです $ sudo apt-key del ACCC4CF8 Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)). OK # 消えたことを確認 $ apt-key list Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)). ... </code></pre> <h3 id="2. 別途カギを取得し設定しなおす"><a href="#2.+%E5%88%A5%E9%80%94%E3%82%AB%E3%82%AE%E3%82%92%E5%8F%96%E5%BE%97%E3%81%97%E8%A8%AD%E5%AE%9A%E3%81%97%E3%81%AA%E3%81%8A%E3%81%99">2. 別途カギを取得し設定しなおす</a></h3> <p>カギについては、ここに記載されているので取得して入れなおします。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.postgresql.org/download/linux/ubuntu/">PostgreSQL: Linux downloads (Ubuntu) </a></p> <pre><code class="zsh"># カギの取得 $ wget <https://www.postgresql.org/media/keys/ACCC4CF8.asc> # カギをgpgコマンドを使って変換 $ gpg --no-default-keyring -o postgresql-keyring.gpg --dearmor ACCC4CF8.asc # カギを確認 $ file postgres-keyring.gpg postgres-keyring.gpg: GPG keybox database version 1 ... # 変換したカギを/usr/share/keyrings/へ設置する # 手元ではフォルダがなかったので手動で作成 $ sudo mkdir -p /usr/share/keyrings $ sudo cp postgres-keyring.gpg /usr/local/share/keyrings </code></pre> <h3 id="3. リポジトリとカギを紐づける"><a href="#3.+%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%A8%E3%82%AB%E3%82%AE%E3%82%92%E7%B4%90%E3%81%A5%E3%81%91%E3%82%8B">3. リポジトリとカギを紐づける</a></h3> <p>リポジトリは/etc/apt/sources.list.d/pgdg.listに作成しているので、このファイルを以下のように変更。</p> <pre><code class="zsh"># deb [signed-by=/path/to/gpg_key] URLの形式でカギの場所を記載 deb [signed-by=/usr/local/share/keyrings/postgresql-keyring.gpg] <http://apt.postgresql.org/pub/repos/apt> kinetic-pgdg main </code></pre> <p>これにて対応完了です。<br /> Warningが出ないことを確認しましょう。</p> <pre><code class="zsh"># warningが出ないことを確認 $ sudo apt update ... Hit:7 <http://apt.postgresql.org/pub/repos/apt> kinetic-pgdg InRelease </code></pre> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>今回は、apt-keyコマンドが廃止となっているために、ドキュメント通りに対応してもWarningが出てしまっていたので、諸々調べつつ対応してみました。 そもそもの廃止から知らなかったので勉強になりました。</p> <p>なお、DockerやGithub CLIでは /etc/apt/keyrings 配下に設置しているようです。<br /> <a target="_blank" rel="nofollow noopener" href="https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository">Install Docker Engine on Ubuntu | Docker Documentation</a><br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/cli/cli/issues/5122">Linux installation instructions recommend storing GPG keyring in the wrong location · Issue #5122 · cli/cli · GitHub</a></p> <p>管理をユーザ側でやる、という形であれば1か所にまとめたほうが良いので どこか1か所を決めて、リポジトリ追加時にそこへ追加していく方法をとったほうが良いかもしれません。<br /> DebianのWikiにも /etc/apt/keyrings 配下に設置しよう、となっているのでこっちに合わせたほうが良いかもしれません。<br /> <a target="_blank" rel="nofollow noopener" href="https://wiki.debian.org/DebianRepository/UseThirdParty">DebianRepository/UseThirdParty - Debian Wiki</a></p> <p>今回はここまで。<br /> おわり</p> しきゆら tag:crieit.net,2005:PublicArticle/15752 2020-03-09T00:34:26+09:00 2020-03-09T00:34:26+09:00 https://crieit.net/posts/mysql-postgresql-null MySQLやPostgreSQLにおけるNULLは、不明な値であり計算に使うと結果がNULLになってしまうことがある <p>私はプログラミング言語におけるNULLは、「何もない値」または「何も示していない値」という定義で理解しています。<br /> ところがSQLにおけるNULLでは、この単純な理解が通用しないことを知りました。</p> <p>先に結論を言ってしまうと、<strong>「NULLとは不明な値のことである」</strong>という解釈で落ち着いたのですが。<br /> これの何が問題なのか、解説します。</p> <h2 id="MySQLにおけるNULLとは、「存在しない不明な値」のことである"><a href="#MySQL%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8BNULL%E3%81%A8%E3%81%AF%E3%80%81%E3%80%8C%E5%AD%98%E5%9C%A8%E3%81%97%E3%81%AA%E3%81%84%E4%B8%8D%E6%98%8E%E3%81%AA%E5%80%A4%E3%80%8D%E3%81%AE%E3%81%93%E3%81%A8%E3%81%A7%E3%81%82%E3%82%8B">MySQLにおけるNULLとは、「存在しない不明な値」のことである</a></h2> <p>MySQLのリファレンスを読むと、NULLとは不明な値であることがわかります。</p> <blockquote> <p>NULL値に慣れるまでは驚くかもしれません。<br /> 概念的には、NULLは<strong>「存在しない不明な値」</strong>を意味し、ほかの値とは多少異なる方法で扱われます。<br /> <a target="_blank" rel="nofollow noopener" href="https://dev.mysql.com/doc/refman/5.6/ja/working-with-null.html">https://dev.mysql.com/doc/refman/5.6/ja/working-with-null.html</a> より</p> </blockquote> <p>不明な値を直訳で解釈すると、<strong>「何が入っているのか分からない値」</strong>という意味になります。</p> <h2 id="NULLを算術(計算)で使うとNULLになる"><a href="#NULL%E3%82%92%E7%AE%97%E8%A1%93%EF%BC%88%E8%A8%88%E7%AE%97%EF%BC%89%E3%81%A7%E4%BD%BF%E3%81%86%E3%81%A8NULL%E3%81%AB%E3%81%AA%E3%82%8B">NULLを算術(計算)で使うとNULLになる</a></h2> <p>一般的なプログラミング言語では、NULLを使った計算では警告や例外が発生するか、もしくは「0」という値に自動変換されて計算されます。</p> <pre><code class="php"><?php $a = 10 - null; echo $a; // 10 </code></pre> <p>ところがMySQLでは「10」にもならないし、エラーにもなりません。</p> <pre><code class="sql">mysql> SELECT 10 - NULL; +-----------+ | 10 - NULL | +-----------+ | NULL | +-----------+ 1 row in set (0.00 sec) </code></pre> <p>この挙動はPostgreSQLでも同様で、「10 - NULL」はNULLになります。</p> <pre><code class="sql">postgres=# \pset null '(null)' Null display is "(null)". postgres=# SELECT 10 - NULL; ?column? ---------- (null) (1 row) </code></pre> <h3 id="NULLは算術比較できない"><a href="#NULL%E3%81%AF%E7%AE%97%E8%A1%93%E6%AF%94%E8%BC%83%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84">NULLは算術比較できない</a></h3> <p>MySQLのリファレンスを読み進めていくと、<strong>算術比較はできません</strong>と書いてあります。</p> <blockquote> <p>=、<br /> <a target="_blank" rel="nofollow noopener" href="https://dev.mysql.com/doc/refman/5.6/ja/working-with-null.html">https://dev.mysql.com/doc/refman/5.6/ja/working-with-null.html</a> より</p> </blockquote> <p>つまりNULLは不明な値なので、算術比較の結果もまた、不明になってしまうのだろうと私は解釈しました。</p> <h2 id="NULLとなりえる値を、計算で使うのはやめよう"><a href="#NULL%E3%81%A8%E3%81%AA%E3%82%8A%E3%81%88%E3%82%8B%E5%80%A4%E3%82%92%E3%80%81%E8%A8%88%E7%AE%97%E3%81%A7%E4%BD%BF%E3%81%86%E3%81%AE%E3%81%AF%E3%82%84%E3%82%81%E3%82%88%E3%81%86">NULLとなりえる値を、計算で使うのはやめよう</a></h2> <p>MySQLやPostgreSQLにおけるNULLの挙動を総括すると、たとえテーブル定義の型が整数型(INT型)であったとしても、<strong>「NOT NULL」制約のないカラムをSQLでそのまま計算に使わないほうが良い</strong>と言えます。</p> <p>ただ計算に使えない数値というのも困りますので、対処法を考えてみます。<br /> 私が思いつく限り、対処法は大きく分けて2つあります。</p> <h3 id="テーブル定義(DDL)にNOT NULLを追加する"><a href="#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E5%AE%9A%E7%BE%A9%EF%BC%88DDL%EF%BC%89%E3%81%ABNOT+NULL%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B">テーブル定義(DDL)にNOT NULLを追加する</a></h3> <p>SQL上で計算に使う値であれば、テーブル定義を「NOT NULL DEFAULT 0」にして、必ず数値がある状態にします。</p> <p>値が「0」であれば算術に使うことができるため、想定外の計算トラブルは抑制できます。</p> <h3 id="IFNULL()などのSQL関数を使って、NULL値を回避する"><a href="#IFNULL%28%29%E3%81%AA%E3%81%A9%E3%81%AESQL%E9%96%A2%E6%95%B0%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%80%81NULL%E5%80%A4%E3%82%92%E5%9B%9E%E9%81%BF%E3%81%99%E3%82%8B">IFNULL()などのSQL関数を使って、NULL値を回避する</a></h3> <p>MySQLにはIFNULL()という、SQL関数があります。<br /> IFNULLとは、「もしNULLであれば」を判定する関数です。</p> <p>これを使って、NULLであれば「0」に変換して計算します。</p> <pre><code class="sql">mysql> SELECT 10 - IFNULL(NULL, 0); +----------------------+ | 10 - IFNULL(NULL, 0) | +----------------------+ | 10 | +----------------------+ </code></pre> <p>テーブル定義で「NOT NULL」制約を使えないのであれば、NULL判定をしてデフォルト値を与えましょう。<br /> NULLを回避することによって、計算結果がNULLになってしまう問題を防げます。</p> <h2 id="さいごに"><a href="#%E3%81%95%E3%81%84%E3%81%94%E3%81%AB">さいごに</a></h2> <p>MySQLやPostgreSQLにおけるNULLとは、特異な値であることを学びました。<br /> だからMySQLのリファレンスにも、<strong>「NULL値に慣れるまでは驚くかもしれません。」</strong>と、あえて書いてあるのだと推測します。</p> <p>なおデータベースにおけるNULLの扱い多くのRDBMSで共通ですが、Oracleだけは特別みたいです。</p> <blockquote> <p>以上の NULL に関する事柄のほとんどは、PostgreSQL に限らず、多くの RDBMS で共通ですが、重要な例外が1つあります。<br /> <strong>Oracleでは、NULL を含む文字列結合において NULL を返しません。</strong><br /> つまり select 'abc' || null from dual; の結果は abc になります。<br /> <a target="_blank" rel="nofollow noopener" href="https://oss-db.jp/dojo/dojo_08">https://oss-db.jp/dojo/dojo_08</a> より</p> </blockquote> <p>本記事ではデータベースにおけるNULLを取り上げましたが、NULL値の挙動や扱い方は、プログラミング言語によっても変わります。<br /> プログラム内でNULLを使うときは、<strong>「各プログラミング言語の参考書やリファレンスで、NULLの挙動を確認しておくのが良さそう」</strong>です。</p> このすみ tag:crieit.net,2005:PublicArticle/15593 2019-12-08T18:59:22+09:00 2019-12-09T10:28:21+09:00 https://crieit.net/posts/PostgreSQL PostgreSQL: とにかく簡単にトレンドやホットランキングを出したい <p>CGMなどを作っていると、現在人気の投稿をトレンドとしてランキング表示したいときがある<br /> これをSQLだけで実装する<br /> パフォーマンスも何も考えていないしテーブル構成にも依存しているし、さらには手動で調整するマジックナンバーさえある<br /> だが、簡単なのでとりあえずそれっぽい機能を付けたいとなったときに役立つかもしれない</p> <h1 id="環境"><a href="#%E7%92%B0%E5%A2%83">環境</a></h1> <p>PostgreSQL</p> <h1 id="テーブル構成1"><a href="#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E6%A7%8B%E6%88%901">テーブル構成1</a></h1> <p>ランク付けの対象として投稿を表すPOSTテーブルがあり、閲覧数を表すVIEWテーブルと一対多のリレーションになっているとする(閲覧されるたびにVIEWテーブルのレコードが増えるパターン)</p> <p>また、各テーブルには作成日時created_atと更新日時updated_atがあるとする</p> <p><a href="https://crieit.now.sh/upload_images/eb23ee475f9ebc4e2f26928c82bdba9a5decb796a57a9.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/eb23ee475f9ebc4e2f26928c82bdba9a5decb796a57a9.png?mw=700" alt="Entity Relationship Diagram.png" /></a></p> <h1 id="SQL"><a href="#SQL">SQL</a></h1> <pre><code class="sql">SELECT "post".*, (COUNT(DISTINCT view)) as rank_point FROM "post" LEFT OUTER JOIN "view" ON "view"."post_id" = "post"."id" AND "view"."created_at" >= now() - interval '1 hour' GROUP BY post.id ORDER BY rank_point DESC; </code></pre> <h2 id="説明"><a href="#%E8%AA%AC%E6%98%8E">説明</a></h2> <p>LEFT OUTER JOINで結合する時にVIEWテーブルの条件を調整している<br /> <code>"view"."created_at" >= now() - interval '1 hour'</code>は現在時刻から1時間前までの間に作成されたVIEWのレコードのみ結合するという条件で、これをカウントしてrank_pointとし、post.idでグルーピングしたものを降順に並べれば1時間のうちで閲覧が多い投稿が順に並ぶはずである<br /> <code>interval '1 hour'</code>の部分は好きなように調整できる</p> <h1 id="テーブル構成2"><a href="#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E6%A7%8B%E6%88%902">テーブル構成2</a></h1> <p>他の指標を使いたくなったらいくらでも追加できる<br /> 例えば投稿には一対多でコメントがあって、コメントの多さでも人気なことを表したいとする<br /> <a href="https://crieit.now.sh/upload_images/eb23ee475f9ebc4e2f26928c82bdba9a5decc0f9a666b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/eb23ee475f9ebc4e2f26928c82bdba9a5decc0f9a666b.png?mw=700" alt="Entity Relationship Diagram.png" /></a></p> <h1 id="SQL"><a href="#SQL">SQL</a></h1> <pre><code class="sql">SELECT "post".*, ( (COUNT(DISTINCT view) * 10) + (COUNT(DISTINCT comment) * 1000 ) ) / 2 as rank_point FROM "post" LEFT OUTER JOIN "view" ON "view"."post_id" = "post"."id" AND "view"."created_at" >= now() - interval '1 hour' LEFT OUTER JOIN "comment" ON "comment"."post_id" = "post"."id" AND "comment"."created_at" >= now() - interval '1 hour' GROUP BY post.id ORDER BY rank_point DESC; </code></pre> <h2 id="説明"><a href="#%E8%AA%AC%E6%98%8E">説明</a></h2> <p>VIEWに加えてCOMMENTも同じように結合する<br /> あとは適当に掛け算して桁を合わせてから指標の数で割ったものをrank_pointとするだけだ<br /> 桁を合わせるとは書いたが、重視したい指標に多めの数値を掛けるなりすれば重み付けもできる</p> <h1 id="蛇足"><a href="#%E8%9B%87%E8%B6%B3">蛇足</a></h1> <p>最初はちゃんと移動平均を出そうと思ってWindow関数とか調べたが、複数テーブルが絡むと面倒な記述になりそうでやめてしまった</p> <h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://www.postgresql.jp/document/11/html/functions-datetime.html">9.9. 日付/時刻関数と演算子</a></p> sink tag:crieit.net,2005:PublicArticle/15523 2019-11-01T02:12:38+09:00 2019-11-01T20:22:07+09:00 https://crieit.net/posts/AWX-Server-Error AWX をインストールした後の Server Error を解決したかった話 <p>この記事は、 <a target="_blank" rel="nofollow noopener" href="https://github.com/ansible/awx">Ansible AWX</a> をインストールしたときに、 Server Error に <strong>なったりならなかったりする</strong> 問題に対処したときのポエムだ。</p> <p><a href="https://crieit.now.sh/upload_images/e835b521a9d07ba5a1f19762bb3928fe5dbb159674c75.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e835b521a9d07ba5a1f19762bb3928fe5dbb159674c75.png?mw=700" alt="1921_ansible_awx_error_00.png" /></a></p> <p>はじめに断っておくが、最終的に AWX 8.0.0 で解消しているっぽいものの、 原因や正確な条件などは不明なままである。<br /> また後述するが、 (タイトルに反して)おそらく Ansible AWX の問題ではなく、 postgres:9.6 の docker イメージの問題ではないかと思われる。</p> <h2 id="発生した問題の状況"><a href="#%E7%99%BA%E7%94%9F%E3%81%97%E3%81%9F%E5%95%8F%E9%A1%8C%E3%81%AE%E7%8A%B6%E6%B3%81">発生した問題の状況</a></h2> <p>まず、 問題が起きている状況をまとめると、以下のような状況だ。</p> <ul> <li>AWX を <a target="_blank" rel="nofollow noopener" href="https://github.com/ansible/awx/blob/7.0.0/INSTALL.md#docker-compose">Docker-Compose</a> を使ってインストール</li> <li>AWX のバージョンは、 7.0.0 または 6.1.0 (どちらでも発生する)</li> <li>インストール先は CentOS 7.7 1908 の VirtualBox のゲストマシン</li> <li>どういうわけか、 <strong>問題が起きる場合と起きない場合がある</strong> <ul> <li>仮想ストレージが (SSD ではなく) HDD 上にあると発生しやすい気がする</li> <li>VM のホストが、 バックエンドで I/O をガリガリやっていると発生しやすい気がする</li> <li>VM のホストが、 (バッテリー駆動などで)で省電力モードになっていると発生しやすい気がする</li> </ul></li> <li>問題が発生するかどうかは、インストール後の最初の起動で決まる。 <ul> <li>最初の起動で問題が発生すれば、その後も発生し続ける。</li> <li>逆に、最初の起動で問題が発生しなければ、その後は発生しない。</li> </ul></li> </ul> <p>最後の2つが非常に厄介で、全て VirtualBox で同じリソースを割り当てた VM のゲストマシンに、 同じ設定で CentOS をインストールしているのにもかからず、問題が発生したりしなかったりする。<br /> 訳がわからない。</p> <p>VM のホストをいろいろ買えつつ試してみたところ、上記のような状態の時に問題が派生しやすい気がするが、ハッキリ「そうだ」と言えるほどの回数は試せていない。</p> <p>最初、 良くある SELinux 関係の問題かと思って SELinux を切って AWX のインストールをやり直してみたが、何も変わらなかった。</p> <h2 id="AWX エラーの内容"><a href="#AWX+%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%AE%E5%86%85%E5%AE%B9">AWX エラーの内容</a></h2> <p>上記スクショの "Server Error" "A server error has occurred" のメッセージだけでは、どんな問題が発生しているのか全くわからないので、とりあえず各コンテナのログをみてみる。</p> <p>どうやら、 awx_postgres コンテナで、以下のようなエラーが発生しているようだ。</p> <pre><code>FATAL: no pg_hba.conf entry for host "172.18.0.2", user "awx", database "awx", SSL off </code></pre> <p>問題が起きていない環境と、起きている環境で pg_hba.conf (※) を比べると、<br /> 問題が起きていない環境では、このファイルの末尾に</p> <pre><code>host all all all md5 </code></pre> <p>が追記されているという違いがある。</p> <p>(※: pg_hba.conf は awx_postgres コンテナの <code>/var/lib/postgresql/data/pgdata/pg_hba.conf</code> にあるほか、 docker ホスト側でも <code>/tmp/pgdocker/pgdata/pg_hba.conf</code> として永続化されている。 (postgres_data_dir 変数が初期値の場合。))</p> <p>この行がないため、 WEB サービスを走らせているコンテナから PostgreSQL を動かしているコンテナへのアクセスが拒否されているようだ。</p> <p>しかし、問題となっている環境で pg_hba.conf を書き換えても、別のエラーが発生して Server Error が表示される問題は解決しない。</p> <p>問題が起きる環境と起きない環境を更に詳しく比べると、 PostgreSQL の DB が作成すらされていないことがわかった。<br /> 本来なら、 awx_postgres コンテナ内に awx ユーザで DB が作成されている筈なのにもかかわらず、以下のように コンテナに入って psql の --list オプションを実行すると、エラーになってしまう。</p> <pre><code class="bash">$ sudo docker exec -it awx_postgres /bin/bash root@xxxxxxxxxxxx:/# psql --list -U awx psql: FATAL: database "postgres" does not exist </code></pre> <p>以上のことから、 pg_hba.conf の書き換えや、 PostgreSQL DB の初期化といった、 初期化プロセスが実行されずに「初期化済み」扱いされているのが原因ではないかと、予想がつく。</p> <p>awx リポジトリ内で同様の不具合が挙がっていないか確認してみたが、 <a target="_blank" rel="nofollow noopener" href="https://github.com/ansible/awx/issues/4736">似たような問題は挙がっている</a> ものの、それそのものズバリの回答は見つからなかった。</p> <h2 id="pg_hba.conf を書き換えているのは誰だ"><a href="#pg_hba.conf+%E3%82%92%E6%9B%B8%E3%81%8D%E6%8F%9B%E3%81%88%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE%E3%81%AF%E8%AA%B0%E3%81%A0">pg_hba.conf を書き換えているのは誰だ</a></h2> <p>audit などを使って pg_hba.conf に関するシステムコールを監視して、 何が host all all all md5 と追記しているのかを確認してみる。</p> <p>その結果、書き換えを行っているのは</p> <pre><code class="bash">bash /usr/local/bin/docker-entrypoint.sh postgres </code></pre> <p>のプロセスだとわかった。</p> <p>AWX 7.0.0 インストール時の <a target="_blank" rel="nofollow noopener" href="https://github.com/ansible/awx/blob/7.0.0/installer/roles/local_docker/templates/docker-compose.yml.j2#L131">docker-compose.yml</a> も、 AWX 6.1.0 インストール時の <a target="_blank" rel="nofollow noopener" href="https://github.com/ansible/awx/blob/7.0.0/installer/roles/local_docker/templates/docker-compose.yml.j2#L131">docker-compose.yml</a> も、 postgres:9.6 の docker イメージを使っていることがわかる。<br /> このため、 上記の pg_hba.conf の書き換えを行っている docker-entrypoint.sh は、 postgres:9.6 イメージの Dockerfile のエントリーポイントである <a target="_blank" rel="nofollow noopener" href="https://github.com/docker-library/postgres/blob/3610f1e45365fb09c0fea29fa387b35f0efdb3a1/9.6/docker-entrypoint.sh#L119-L122">この docker-entrypoint.sh</a> だろう。</p> <p>前後の PostgreSQL の初期化プロセスも働いていないことを考えると、</p> <pre><code class="bash"> # look specifically for PG_VERSION, as it is expected in the DB dir if [ ! -s "$PGDATA/PG_VERSION" ]; then # "initdb" is particular about the current user existing in "/etc/passwd", so we use "nss_wrapper" to fake that if necessary # see https://github.com/docker-library/postgres/pull/253, https://github.com/docker-library/postgres/issues/359, https://cwrap.org/nss_wrapper.html </code></pre> <p>この条件分岐の部分が、意図通り働かないことがあるのではないかと思う。</p> <p>しかし、私は docker に関してはほとんどシロウトなので、ここから docker イメージの定義ファイルを辿って、どこがおかしいのかを調査するのはちょっと難しい。。。</p> <h2 id="AWX 8.0.0 がリリースされたら解決してた"><a href="#AWX+8.0.0+%E3%81%8C%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%81%95%E3%82%8C%E3%81%9F%E3%82%89%E8%A7%A3%E6%B1%BA%E3%81%97%E3%81%A6%E3%81%9F">AWX 8.0.0 がリリースされたら解決してた</a></h2> <p>…とかなんとかやってるうちに、 <a target="_blank" rel="nofollow noopener" href="https://github.com/ansible/awx/releases/tag/8.0.0">AWX 8.0.0 がリリース</a> されてしまった。</p> <p>8.0.0 で AWX のインストールを試してみると、ぱったりと問題が発生しなくなった。</p> <p>awx_postgres コンテナを作成する docker イメージが、 <a target="_blank" rel="nofollow noopener" href="https://github.com/ansible/awx/blob/8.0.0/installer/roles/local_docker/templates/docker-compose.yml.j2#L131">postgres:9.6 から postgres:10 に変更</a> になったからではないかと考えているが、具体的にどう変わったかは正直わからない。</p> <p>ひょんなことから、原因は不明ながら問題が解決してしまったので、これ以上調査する気力も失せて、私は考えるのをやめた。</p> <p><strong>追記:</strong> そして、記事を書いた直後に <a target="_blank" rel="nofollow noopener" href="https://github.com/ansible/awx/releases/tag/9.0.0">AWX 9.0.0 がリリース</a> された。ちょっとペース早すぎない!?</p> advanceboy tag:crieit.net,2005:PublicArticle/15493 2019-10-20T10:36:15+09:00 2019-10-20T10:36:15+09:00 https://crieit.net/posts/VB-NET-PostgreSQL 【VB.NET】PostgreSQL に接続してデータ操作(追加・更新・削除)をしてみる <p>おはようございます。</p> <p>今回はPostgreSQLで追加、更新、削除をやってみましたが、<br /> Oracle、MySQLと変わらず、クラスを変更するだけでほぼ対応ができてしまいました。</p> <p>プログラムは前回のものを参考にしてください。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2018/01/24/post-3760/" target="_blank" rel="noopener noreferrer" data-blogcard="1">【VB.NET】PostgreSQL に接続してデータを DataGridView に表示してみる</a></p> <h2 id="プログラム修正"><a href="#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E4%BF%AE%E6%AD%A3">プログラム修正</a></h2> <h3 id="追加(行追加)処理"><a href="#%E8%BF%BD%E5%8A%A0%EF%BC%88%E8%A1%8C%E8%BF%BD%E5%8A%A0%EF%BC%89%E5%87%A6%E7%90%86">追加(行追加)処理</a></h3> <p>Form1.vb</p> <pre><code> ''' <summary> ''' 追加ボタンクリックイベント ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click ' 追加後に更新されていなければ処理しない If (AddRowFlg) Then Return End If AddRowFlg = True Using conn As New NpgsqlConnection("Server=localhost; Port=5432; User Id=USER01;Password=USER01;Database=DB01") ' データベースオープン conn.Open() ' データ追加 Using con As New DataContext(conn) Dim idx = dgvCat.Rows.Count() ' 行追加 dgvCat.Rows.Add() ' 種別マスタ取得 ' 種別コンボボックスの内容をデータベースから取得して設定 ' データを取得 Dim cmd As NpgsqlCommand = New NpgsqlCommand("SELECT * FROM MSTKIND", conn) Dim da As NpgsqlDataAdapter = New NpgsqlDataAdapter(cmd) Dim dt As DataTable = New DataTable() da.Fill(dt) Dim kindList As List(Of Kind) = New List(Of Kind) Dim k As Kind = New Kind() For Each row As DataRow In dt.Rows k = New Kind() k.KindCd = row("KIND_CD").ToString() k.KindName = row("KIND_NAME").ToString() kindList.Add(k) Next ' 猫一覧取得 cmd = New NpgsqlCommand("SELECT * FROM TBLCAT", conn) da = New NpgsqlDataAdapter(cmd) dt = New DataTable() da.Fill(dt) Dim newNo = 0 ' 使用できるNoを判定 For i As Integer = 1 To dt.Rows.Count() + 1 Dim selectNo = i If dt.Select("No = '" + selectNo.ToString + "'").Length = 0 Then newNo = selectNo End If Next ' No(プライマリなので編集不可) Dim no = New DataGridViewTextBoxCell() no.Value = newNo dgvCat(0, idx) = no dgvCat(0, idx).ReadOnly = True ' 名前 Dim name = New DataGridViewTextBoxCell() dgvCat(1, idx) = name ' 性別 Dim sex = New DataGridViewComboBoxCell() sex.Items.AddRange({"&#x2642;", "&#x2640;"}) sex.DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox dgvCat(2, idx) = sex ' 年齢 Dim age = New DataGridViewTextBoxCell() dgvCat(3, idx) = age ' 種別 Dim kind = New DataGridViewComboBoxCell() kind.DataSource = kindList kind.DisplayMember = "KindName" kind.ValueMember = "KindCd" kind.DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox dgvCat(4, idx) = kind ' 好物 Dim favorite = New DataGridViewTextBoxCell() dgvCat(5, idx) = favorite End Using conn.Close() End Using End Sub </code></pre> <h3 id="更新処理"><a href="#%E6%9B%B4%E6%96%B0%E5%87%A6%E7%90%86">更新処理</a></h3> <p>Form1.vb</p> <pre><code> ''' <summary> ''' 更新ボタンクリックイベント ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Sub btnUpdate_Click(sender As Object, e As EventArgs) Handles btnUpdate.Click Using conn = New OracleConnection("Data Source=localhost;User Id=USER01;Password=USER01;") ' データベースオープン conn.Open() ' データ更新 For i = 0 To dgvCat.Rows.Count - 1 ' テーブルから対象のデータを取得 Dim no As Integer = dgvCat(0, i).Value ' シーケンス列としていないので、 ' 最初に件数を取得して No を計算する Dim cmd As OracleCommand = New OracleCommand("SELECT COUNT(*) FROM TBLCAT WHERE No = '" + no.ToString() + "'", conn) Dim cnt As Decimal = cmd.ExecuteScalar() If (cnt = 0) Then ' データ追加 Dim query = "INSERT INTO TBLCAT VALUES (" + (cnt + 1) + ", '" + dgvCat(1, i).Value + "'" + ", '" + dgvCat(2, i).Value + "'" + ", " + dgvCat(3, i).Value + "'" + ", '" + dgvCat(4, i).Value + "'" + ", '" + dgvCat(5, i).Value + "')" cmd = New OracleCommand(query, conn) cmd.ExecuteNonQuery() Else ' データ変更 Dim query = "UPDATE TBLCAT SET" + " NAME = '" + dgvCat(1, i).Value.ToString() + "'" + ", SEX = '" + dgvCat(2, i).Value.ToString() + "'" + ", AGE = '" + dgvCat(3, i).Value.ToString() + "'" + ", KIND_CD = '" + dgvCat(4, i).Value.ToString() + "'" + ", FAVORITE = '" + dgvCat(5, i).Value.ToString() + "' " + "WHERE NO = '" + dgvCat(0, i).Value.ToString() + "'" cmd = New OracleCommand(query, conn) cmd.ExecuteNonQuery() End If Next conn.Close() End Using AddRowFlg = False ' データ再検索 search() MessageBox.Show("データを更新しました。") End Sub </code></pre> <p> </p> <h3 id="削除処理"><a href="#%E5%89%8A%E9%99%A4%E5%87%A6%E7%90%86">削除処理</a></h3> <p>Form1.vb</p> <pre><code> ''' <summary> ''' 削除ボタンクリックイベント ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Sub btnDelete_Click(sender As Object, e As EventArgs) Handles btnDelete.Click Using conn = New OracleConnection("Data Source=localhost;User Id=USER01;Password=USER01;") ' データベースオープン conn.Open() ' 選択されている行 For Each r As DataGridViewRow In dgvCat.SelectedRows Dim Cat As Cat = CType(dgvCat.DataSource(), List(Of Cat)).Item(r.Index) ' データ削除 Dim query As String = "DELETE FROM TBLCAT WHERE NO = " + Cat.No ' クエリ実行 Dim cmd As OracleCommand = New OracleCommand(query, conn) cmd.ExecuteNonQuery() Next conn.Close() End Using ' データ再検索 search() MessageBox.Show("データを削除しました。") End Sub </code></pre> <p> </p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>Oracle、MySQLからPostgreSQLに変更するのも簡単にできそうですね。<br /> 次回以降はまた別の言語をやっていこうかと思います。</p> <p>ではでは。</p> doraxdora tag:crieit.net,2005:PublicArticle/15491 2019-10-18T10:31:07+09:00 2019-10-18T10:31:07+09:00 https://crieit.net/posts/VB-NET-PostgreSQL-DataGridView 【VB.NET】PostgreSQL に接続してデータを DataGridView に表示してみる <p>おはようございます。</p> <p>今回はVB.NETでPostgreSQLに接続、データの取得をやってみます。</p> <p>プログラムは前回のものを流用します。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2018/01/18/3718/" target="_blank" rel="noopener noreferrer" data-blogcard="1">【VB.NET】MySQL(MariaDB)に接続してデータ操作(追加・更新・削除)をしてみる</a></p> <p>また、PostgreSQL環境の作成は以下の記事を参考にしてください<br /> <a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/07/25/post-1748/" target="_blank" rel="noopener noreferrer" data-blogcard="1">PostgreSQL 9.6.3 インストールからテーブル作成まで</a></p> <h2 id="Nuget でパッケージをダウンロード"><a href="#Nuget+%E3%81%A7%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%82%92%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89">Nuget でパッケージをダウンロード</a></h2> <p><img src="https://www.doraxdora.com/wp-content/uploads/2018/01/VbPostgreSQL000.jpg" alt="NuGetパッケージ管理の選択" /></p> <p>ソリューションエクスプローラーからプロジェクトを選択、右クリックし<br /> 「Nuget パッケージの管理」を選択します。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2018/01/VbPostgreSQL001.jpg" alt="NuGetパッケージ管理画面" /></p> <p>Nuget パッケージ管理画面が表示されるので、<br /> 検索窓に「npgsql」を入力し、「Npgsql」を選択、インストールボタンをクリックします。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2018/01/VbPostgreSQL002.jpg" alt="プレビュー画面" /></p> <p>プレビュー画面が表示された場合は、「OK」ボタンをクリックします。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2018/01/VbPostgreSQL003.jpg" alt="ライセンス同意画面" /></p> <p>ライセンス同意画面が表示されるので、「同意する」ボタンをクリックします。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2018/01/VbPostgreSQL004.jpg" alt="出力ビュー" /></p> <p>出力ビューに「終了」が表示されれば完了です。</p> <h2 id="プログラム修正"><a href="#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E4%BF%AE%E6%AD%A3">プログラム修正</a></h2> <h3 id="宣言の追加"><a href="#%E5%AE%A3%E8%A8%80%E3%81%AE%E8%BF%BD%E5%8A%A0">宣言の追加</a></h3> <p>ダウンロードしたパッケージを利用するための宣言を追加します。</p> <p>Form1.vb</p> <pre><code>Imports Npgsql </code></pre> <h3 id="初期表示処理の修正"><a href="#%E5%88%9D%E6%9C%9F%E8%A1%A8%E7%A4%BA%E5%87%A6%E7%90%86%E3%81%AE%E4%BF%AE%E6%AD%A3">初期表示処理の修正</a></h3> <p>MySQLに接続していた部分を PostgreSQLに接続するように修正し、<br /> MySqlCommand、MySqlDataAdapterもそれぞれ PostgreSQL用のクラスに変更します。</p> <p>Form1.vb</p> <pre><code> ''' <summary> ''' フォームロード時の処理 ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Using conn As New NpgsqlConnection("Server=localhost; Port=5432; User Id=USER01;Password=USER01;Database=DB01") ' データベースオープン conn.Open() ' 種別コンボボックスの内容をデータベースから取得して設定 ' データを取得 Dim cmd As NpgsqlCommand = New NpgsqlCommand("SELECT * FROM MSTKIND", conn) Dim da As NpgsqlDataAdapter = New NpgsqlDataAdapter(cmd) Dim dt As DataTable = New DataTable() da.Fill(dt) ' コンボボックスに値を設定 Dim sources As List(Of Kind) = New List(Of Kind) Dim k As Kind = New Kind() k.KindCd = "" k.KindName = "指定なし" sources.Add(k) For Each row As DataRow In dt.Rows k = New Kind() k.KindCd = row("KIND_CD").ToString() k.KindName = row("KIND_NAME").ToString() sources.Add(k) Next Me.cmbKind.DataSource = sources Me.cmbKind.DisplayMember = "KindName" ' データベースクローズ conn.Close() End Using End Sub </code></pre> <h3 id="検索処理の修正"><a href="#%E6%A4%9C%E7%B4%A2%E5%87%A6%E7%90%86%E3%81%AE%E4%BF%AE%E6%AD%A3">検索処理の修正</a></h3> <p>初期表示と同様、接続の処理とMysqlのクラスをPostgreSQL用に変更します。</p> <p>Form1.vb</p> <pre><code> ''' <summary> ''' 検索処理 ''' </summary> Private Sub search() Using conn As New NpgsqlConnection("Server=localhost; Port=5432; User Id=USER01;Password=USER01;Database=DB01") ' データベースオープン conn.Open() ' 検索条件を指定してデータを取得 Using con As New DataContext(conn) Dim searchName As String = txtName.Text Dim searchKind As String = CType(cmbKind.SelectedValue, Kind).KindCd ' 種別マスタ取得 ' 種別コンボボックスの内容をデータベースから取得して設定 ' データを取得 Dim cmd As NpgsqlCommand = New NpgsqlCommand("SELECT * FROM MSTKIND", conn) Dim da As NpgsqlDataAdapter = New NpgsqlDataAdapter(cmd) Dim dt As DataTable = New DataTable() da.Fill(dt) Dim kindList As List(Of Kind) = New List(Of Kind) Dim k As Kind = New Kind() For Each row As DataRow In dt.Rows k = New Kind() k.KindCd = row("KIND_CD").ToString() k.KindName = row("KIND_NAME").ToString() kindList.Add(k) Next ' 猫一覧取得 Dim Sql As String = "SELECT * FROM TBLCAT" Dim where As String = "" If (searchName <> "") Then where = " WHERE NAME LIKE '" + searchName + "%'" End If If (searchKind <> "") Then If (where <> "") Then where += " AND" Else where = " WHERE" End If where += " KIND_CD = '" + searchKind + "'" End If Sql += where cmd = New NpgsqlCommand(Sql, conn) da = New NpgsqlDataAdapter(cmd) dt = New DataTable() da.Fill(dt) ' データグリッドビューに設定 Dim i As Integer = 0 dgvCat.Rows.Clear() For i = 0 To dt.Rows.Count() - 1 ' 行追加 dgvCat.Rows.Add() ' No(プライマリなので編集不可) Dim no = New DataGridViewTextBoxCell() no.Value = dt.Rows(i)("No") dgvCat(0, i) = no dgvCat(0, i).ReadOnly = True ' 名前 Dim name = New DataGridViewTextBoxCell() name.Value = dt.Rows(i)("Name") dgvCat(1, i) = name ' 性別 Dim sex = New DataGridViewComboBoxCell() sex.Items.AddRange({"&#x2642; ", "&#x2640; "}) sex.DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox dgvCat(2, i) = sex dgvCat(2, i).Value = dt.Rows(i)("Sex") ' 年齢 Dim age = New DataGridViewTextBoxCell() age.Value = dt.Rows(i)("Age") dgvCat(3, i) = age ' 種別 Dim kind = New DataGridViewComboBoxCell() kind.DataSource = kindList kind.DisplayMember = "KindName" kind.ValueMember = "KindCd" kind.DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox dgvCat(4, i) = kind dgvCat(4, i).Value = dt.Rows(i)("Kind_Cd") ' 好物 Dim favorite = New DataGridViewTextBoxCell() favorite.Value = dt.Rows(i)("Favorite") dgvCat(5, i) = favorite Next End Using ' データベースクローズ conn.Close() End Using End Sub </code></pre> <p> </p> <h2 id="起動してみる"><a href="#%E8%B5%B7%E5%8B%95%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">起動してみる</a></h2> <p><img src="https://www.doraxdora.com/wp-content/uploads/2018/01/VbPostgreSQL005.jpg" alt="起動後の画面" /></p> <p>種別にデータが設定されました。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2018/01/VbPostgreSQL006.jpg" alt="検索結果" /></p> <p>検索ボタン押下でデータがグリッドに表示されました。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>PostgreSQLもほとんど変更無しで対応することができました。</p> <p>実は EntityFrameworkを利用する予定だったのですが、<br /> 依存ファイルが見つからないエラーで嵌ってしまったので断念。</p> <p>いずれ時間のある時にでも。。</p> <p>ではでは。</p> doraxdora tag:crieit.net,2005:PublicArticle/15168 2019-06-26T09:51:09+09:00 2019-06-26T09:51:09+09:00 https://crieit.net/posts/WPF-PostgreSQL-5d12c17d61f60 【WPF】PostgreSQL に接続してデータを操作(登録、更新、削除)してみる <p>おはようございます。</p> <p>前回に引き続き PostgreSQL を使って、登録、更新、削除をしてみたいと思います。</p> <p>前回の記事はこちら。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/08/08/post-1948/" target="_blank" rel="noopener noreferrer" data-blogcard="1">【WPF】PostgreSQL に接続してデータを取得して表示する</a></p> <h2 id="プログラムの修正"><a href="#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E4%BF%AE%E6%AD%A3">プログラムの修正</a></h2> <h3 id="追加処理の修正"><a href="#%E8%BF%BD%E5%8A%A0%E5%87%A6%E7%90%86%E3%81%AE%E4%BF%AE%E6%AD%A3">追加処理の修正</a></h3> <p>MainWindow.xaml.cs</p> <pre><code> /// <summary> /// 追加ボタンクリックイベント /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void add_button_Click(object sender, RoutedEventArgs e) { logger.Info("追加ボタンクリック"); // データを追加する // PgDbContext に変更 //using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) using (var context = new PgDbContext()) { // データ作成 // テーブルはコンテキスト経由でアクセスする //var table = context.GetTable<Cat>(); Cat cat = new Cat(); cat.No = 5; cat.No = 5; cat.Name = "こなつ"; cat.Sex = "♀"; cat.Age = 7; cat.Kind = "01"; cat.Favorite = "布団"; // データ追加 // コンテキスト経由でエンティティを追加 //table.InsertOnSubmit(cat); context.Cats.Add(cat); // DBの変更を確定 // メソッド変更 //context.SubmitChanges(); context.SaveChanges(); } // データ再検索 searchData(); MessageBox.Show("データを追加しました。"); } </code></pre> <h3 id="更新処理"><a href="#%E6%9B%B4%E6%96%B0%E5%87%A6%E7%90%86">更新処理</a></h3> <p>MainWindow.xaml.cs</p> <pre><code> /// <summary> /// 更新ボタンクリックイベント /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void upd_button_Click(object sender, RoutedEventArgs e) { logger.Info("更新ボタンクリック"); // 選択チェック if (this.dataGrid.SelectedItem == null) { MessageBox.Show("更新対象を選択してください。"); return; } // データを更新する // PgDbContext に変更 //using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) using (var context = new PgDbContext()) { // 対象のテーブルオブジェクトを取得 // テーブルはコンテキスト経由でアクセスする //var table = context.GetTable<Cat>(); var table = context.Cats; // 選択されているデータを取得 Cat cat = this.dataGrid.SelectedItem as Cat; // テーブルから対象のデータを取得 var target = table.Single(x => x.No == cat.No); // データ変更 target.Favorite = "高いところ"; // DBの変更を確定 // メソッド変更 //context.SubmitChanges(); context.SaveChanges(); } // データ再検索 searchData(); MessageBox.Show("データを更新しました。"); } </code></pre> <p> </p> <h3 id="削除処理"><a href="#%E5%89%8A%E9%99%A4%E5%87%A6%E7%90%86">削除処理</a></h3> <p>MainWindow.xaml.cs</p> <pre><code> /// <summary> /// 削除ボタンクリックイベント /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void del_button_Click(object sender, RoutedEventArgs e) { logger.Info("追加ボタンクリック"); // 選択チェック if (this.dataGrid.SelectedItem == null) { MessageBox.Show("削除対象を選択してください。"); return; } // データを削除する // PgDbContext に変更 //using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) using (var context = new PgDbContext()) { // 対象のテーブルオブジェクトを取得 // テーブルはコンテキスト経由でアクセスする //var table = context.GetTable<Cat>(); var table = context.Cats; // 選択されているデータを取得 Cat cat = this.dataGrid.SelectedItem as Cat; // テーブルから対象のデータを取得 var target = table.Single(x => x.No == cat.No); // データ削除 // メソッド変更 //table.DeleteOnSubmit(target); table.Remove(target); // DBの変更を確定 context.SaveChanges(); } // データ再検索 searchData(); MessageBox.Show("データを削除しました。"); } </code></pre> <p> </p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>サクッとした記事になりましたが、ひとまず PostgreSQL はここまでとして次回は Oracle を試してみたいと思います。</p> <p>ソースはこちら</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/doraxdora/SampleWpfPostgreSQL1" target="_blank" rel="noopener noreferrer">GitHub</a></p> <p>ではでは。</p> doraxdora tag:crieit.net,2005:PublicArticle/15166 2019-06-25T10:05:02+09:00 2019-06-25T10:05:02+09:00 https://crieit.net/posts/WPF-PostgreSQL 【WPF】PostgreSQL に接続してデータを取得して表示する <p>おはようございます。</p> <p>今回は、PostgreSQLを使ってデータを検索、データグリッドに表示してみます。<br /> お決まりですが、プログラムは前回までのものを流用します。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/07/28/post-1873/" target="_blank" rel="noopener" data-blogcard="1">【WPF】ProgressRingを使って時間のかかる処理を分かりやすくする</a></p> <p>また、PostgreSQLをインストールしていない場合は次の記事を参照して、パソコンにインストールしてください。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/07/25/post-1748/" target="_blank" rel="noopener" data-blogcard="1">PostgreSQL 9.6.3 をインストールしてテーブルを作成する</a></p> <h2 id="Nuget でパッケージをダウンロード"><a href="#Nuget+%E3%81%A7%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%82%92%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89">Nuget でパッケージをダウンロード</a></h2> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/08/WpfPostgreSQL000.jpg" alt="NuGetパッケージ管理を開く" /></p> <p>ソリューションエクスプローラーからプロジェクトを選択、右クリックし<br /> 「Nuget パッケージの管理」を選択</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/08/WpfPostgreSQL001.jpg" alt="NuGetパッケージ管理" /></p> <p>Nuget パッケージ管理画面が表示されるので、<br /> 検索窓に「Npgsql 6」を入力し、「EntityFramework6.Npgsql」を選択、<br /> インストールボタンをクリックします。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/08/WpfPostgreSQL002.jpg" alt="プレビュー画面" /></p> <p>プレビュー画面が表示される場合は「OK」ボタンをクリックします。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/08/WpfPostgreSQL003.jpg" alt="出力ビュー" /></p> <p>出力ビューに「終了」が表示されれば完了です。</p> <h2 id="スキーマの作成"><a href="#%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%9E%E3%81%AE%E4%BD%9C%E6%88%90">スキーマの作成</a></h2> <p>PostgreSQL ではスキーマ単位の管理をしておかないと困ることになります。</p> <p>スキーマ指定しないでテーブル作成すると「public」スキーマに作成されるのですが、<br /> C#からの接続時には必ず(やってみた限りでは)スキーマ指定しないといけないので、まずはスキーマを作成しておきます。</p> <pre><code>CREATE SCHEMA dora; </code></pre> <h2 id="テーブルの作成"><a href="#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90">テーブルの作成</a></h2> <p>作成したスキーマににテーブルを作成<br /> SQLite の時と同じテーブル、データを用意します。</p> <p>このとき、テーブル名やカラム名は小文字にしておくこと。</p> <pre><code>CREATE TABLE IF NOT EXISTS dora.mstkind ( kind_cd CHAR(2) NOT NULL , kind_name VARCHAR(20) , primary key (kind_cd) ); CREATE TABLE IF NOT EXISTS dora.tblcat ( no INTEGER NOT NULL , name VARCHAR(20) NOT NULL , sex CHAR(3) NOT NULL , age INTEGER DEFAULT 0 NOT NULL , kind_cd CHAR(2) DEFAULT '00' NOT NULL , favorite VARCHAR(40) , PRIMARY KEY (no) ); INSERT INTO DORA.MSTKIND VALUES ('01', 'キジトラ'); INSERT INTO DORA.MSTKIND VALUES ('02', '長毛種(不明)'); INSERT INTO DORA.MSTKIND VALUES ('03', 'ミケ(っぽい)'); INSERT INTO DORA.MSTKIND VALUES ('04', 'サビ'); INSERT INTO DORA.MSTKIND VALUES ('09', 'その他'); INSERT INTO DORA.TBLCAT VALUES('1','そら','&#x2642;','6','01','犬の人形'); INSERT INTO DORA.TBLCAT VALUES('2','りく','&#x2642;','5','02','人間'); INSERT INTO DORA.TBLCAT VALUES('3','うみ','&#x2640;','4','03','高級ウェットフード'); INSERT INTO DORA.TBLCAT VALUES('4','こうめ','&#x2640;','2','04','横取りフード'); </code></pre> <h2 id="設定ファイルの修正"><a href="#%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E4%BF%AE%E6%AD%A3">設定ファイルの修正</a></h2> <p>次のようにします。<br /> 重要な部分は、「entityFramework」タグと「system.data」タグ。</p> <p>App.config</p> <pre><code><?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> <parameters> <parameter value="v13.0" /> </parameters> </defaultConnectionFactory> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> <provider invariantName="Npgsql" type="Npgsql.NpgsqlServices, EntityFramework6.Npgsql" /> </providers> </entityFramework> <system.data> <DbProviderFactories> <remove invariant="Npgsql" /> <add name="Npgsql Data Provider" invariant="Npgsql" support="FF" description=".Net Framework Data Provider for Postgresql" type="Npgsql.NpgsqlFactory, Npgsql" /> </DbProviderFactories> </system.data> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Npgsql" publicKeyToken="5d8b90d52f46fda7" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-3.2.5.0" newVersion="3.2.5.0" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration> </code></pre> <h2 id="プログラム修正"><a href="#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E4%BF%AE%E6%AD%A3">プログラム修正</a></h2> <h3 id="新規クラスの追加"><a href="#%E6%96%B0%E8%A6%8F%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E8%BF%BD%E5%8A%A0">新規クラスの追加</a></h3> <p>DbContextを継承したクラスを作成します。<br /> このクラスでデータベースへの接続、Entityへのマッピングなどを行います。<br /> デフォルトのスキーマを変更する場合、「OnModelCreating」メソッドにて設定可能です。</p> <p>PgDbContext.cs</p> <pre><code>using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.Entity; using Npgsql; namespace WpfApp1 { class PgDbContext : DbContext { private const string ConnectionString = "Server=localhost;User ID=USER01;Password=USER01;Database=DB01;port=5432"; // コンストラクタにて接続文字列を設定 public PgDbContext() : base(new NpgsqlConnection(ConnectionString), true) { } public DbSet<Kind> Kinds { get; set; } public DbSet<Cat> Cats { get; set; } // スキーマを変更する場合にはここに設定 protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("dora"); } } } </code></pre> <h3 id="Entityクラスの修正"><a href="#Entity%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E4%BF%AE%E6%AD%A3">Entityクラスの修正</a></h3> <p>アノテーションが今までのとちょっと違うので修正します。</p> <p>Kind.cs</p> <pre><code>using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace WpfApp1 { [Table("mstkind")] class Kind { [Key] [Column("kind_cd")] public String KindCd { get; set; } [Column("kind_name")] public String KindName { get; set; } } } </code></pre> <p>Cat.cs</p> <pre><code>using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace WpfApp1 { [Table("tblcat")] class Cat { [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] [Column("no")] public int No { get; set; } [Column("name")] public String Name { get; set; } [Column("sex")] public String Sex { get; set; } [Column("age")] public int Age { get; set; } [Column("kind_cd")] public String Kind { get; set; } [Column("favorite")] public String Favorite { get; set; } } } </code></pre> <h3 id="宣言の追加"><a href="#%E5%AE%A3%E8%A8%80%E3%81%AE%E8%BF%BD%E5%8A%A0">宣言の追加</a></h3> <p>MainWindow.xaml.cs</p> <pre><code>using Npgsql; using System.ComponentModel; </code></pre> <h3 id="接続処理の変更"><a href="#%E6%8E%A5%E7%B6%9A%E5%87%A6%E7%90%86%E3%81%AE%E5%A4%89%E6%9B%B4">接続処理の変更</a></h3> <p>テーブル作成以外、全ての箇所を変更します。</p> <p>MainWindow.xaml.cs</p> <pre><code>//using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) using (var context = new PgDbContext()) </code></pre> <p>テーブル作成は今まで通りコマンドで行います。<br /> (また別途、DbContext でのやり方は紹介します)</p> <h3 id="テーブル作成クエリの変更"><a href="#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E4%BD%9C%E6%88%90%E3%82%AF%E3%82%A8%E3%83%AA%E3%81%AE%E5%A4%89%E6%9B%B4">テーブル作成クエリの変更</a></h3> <p>SQLite とは微妙に違うところ(型、長さ)を変更。</p> <p>MainWindow.xaml.cs</p> <pre><code>// テーブルが存在しなければ作成する // 種別マスタ StringBuilder sb = new StringBuilder(); sb.Append("CREATE TABLE IF NOT EXISTS mstkind ("); sb.Append(" kind_cd CHAR(2) NOT NULL"); sb.Append(" , kind_name VARCHAR(20)"); sb.Append(" , PRIMARY KEY (kind_cd)"); sb.Append(")"); command.CommandText = sb.ToString(); command.ExecuteNonQuery(); // 猫テーブル sb.Clear(); sb.Append("CREATE TABLE IF NOT EXISTS tblcat ("); sb.Append(" no INTEGER NOT NULL"); sb.Append(" , name VARCHAR(20) NOT NULL"); sb.Append(" , sex CHAR(3) NOT NULL"); sb.Append(" , age INTEGER DEFAULT 0 NOT NULL"); sb.Append(" , kind_cd CHAR(2) DEFAULT '00' NOT NULL"); sb.Append(" , favorite VARCHAR(40)"); sb.Append(" , PRIMARY KEY (no)"); sb.Append(")"); command.CommandText = sb.ToString(); command.ExecuteNonQuery(); </code></pre> <h3 id="検索処理の修正"><a href="#%E6%A4%9C%E7%B4%A2%E5%87%A6%E7%90%86%E3%81%AE%E4%BF%AE%E6%AD%A3">検索処理の修正</a></h3> <p>MainWindow.xaml.cs</p> <pre><code> /// <summary> /// 検索処理(非同期) /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SearchProcess(object sender, DoWorkEventArgs e) { // 猫データマスタを取得してコンボボックスに設定する //using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) // PgDbContext に変更 using (var context = new PgDbContext()) { String searchName = this.search_name.Text; String searchKind = (this.search_kind.SelectedValue as Kind).KindCd; // データを取得 // context から DbSet を取得 //Table<Cat> tblCat = con.GetTable<Cat>(); var tblCat = context.Cats; // サンプルなので適当に組み立てる IQueryable<Cat> result; if (searchKind == "") { // 名前は前方一致のため常に条件していしても問題なし result = from x in tblCat where x.Name.StartsWith(searchName) orderby x.No select x; } else { result = from x in tblCat where x.Name.StartsWith(searchName) &amp; x.Kind == searchKind orderby x.No select x; } this.dataGrid.ItemsSource = result.ToList(); } } </code></pre> <h3 id="コンバーターの修正"><a href="#%E3%82%B3%E3%83%B3%E3%83%90%E3%83%BC%E3%82%BF%E3%83%BC%E3%81%AE%E4%BF%AE%E6%AD%A3">コンバーターの修正</a></h3> <p>データグリッドに表示する際に値を変換している箇所も、DB接続しているため変更します。</p> <p>KindConverter.cs</p> <pre><code>using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.Linq; using System.Data.SQLite; using System.Data.SQLite.Linq; // Converter 用 // IValueConverter、CultureInfo using System.Windows.Data; using System.Globalization; namespace WpfApp1 { /// <summary> /// 種別コンバータークラス. /// </summary> public class KindConverter : IValueConverter { /// <summary> /// データ変換処理 /// </summary> /// <param name="value"></param> /// <param name="targetType"></param> /// <param name="parameter"></param> /// <param name="culture"></param> /// <returns></returns> public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { //using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) // PgDbContext に変更 using (var context = new PgDbContext()) { // データを取得 //Table<Kind> tblCat = con.GetTable<Kind>(); //Kind k = tblCat.Single(c => c.KindCd == value as String); var kind = context.Kinds.SingleOrDefault(c => c.KindCd == (String)value); if (kind != null) { return kind.KindName; } return ""; } } /// <summary> /// データ復元処理 /// </summary> /// <param name="value"></param> /// <param name="targetType"></param> /// <param name="parameter"></param> /// <param name="culture"></param> /// <returns></returns> public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { using (var context = new PgDbContext()) { // データを取得 //Table<Kind> tblCat = con.GetTable<Kind>(); //Kind k = tblCat.Single(c => c.KindCd == value as String); var kind = context.Kinds.SingleOrDefault(c => c.KindName == value as String); if (kind != null) { return kind.KindCd; } } return ""; } } } </code></pre> <p> </p> <h2 id="起動してみる"><a href="#%E8%B5%B7%E5%8B%95%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">起動してみる</a></h2> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/08/WpfPostgreSQL004.jpg" alt="検索結果" /></p> <p>無事に起動、検索することができました。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>ひとまず、元々の処理(SQLite、MySQL)とあまり変更せずに実装することができました。</p> <p>ちょっと長くなってしまったので、<br /> 登録、更新、削除はまた次回とさせていただきます。</p> <p>ではでは。</p> <p> </p> doraxdora tag:crieit.net,2005:PublicArticle/14900 2019-04-02T18:05:06+09:00 2019-04-02T18:09:36+09:00 https://crieit.net/posts/SQL-NULL-0 SQLでNULLを0として出力する方法 <p>MySQLで外部結合したときに、COUNT()の結果が0件だとNULL値が返却されてしまったため、NULLを0として表示する関数を調べました。<br /> ついでに他のDBについても書きました。</p> <h2 id="関数"><a href="#%E9%96%A2%E6%95%B0">関数</a></h2> <p><strong>IFNULL (a, 0)</strong><br /> <a target="_blank" rel="nofollow noopener" href="https://dev.mysql.com/doc/refman/5.6/ja/control-flow-functions.html#function_ifnull">MySQL</a>、<a target="_blank" rel="nofollow noopener" href="http://www.sqlitetutorial.net/sqlite-functions/sqlite-ifnull/">SQLite</a></p> <p><strong>COALESCE (a, 0)</strong><br /> <a target="_blank" rel="nofollow noopener" href="https://dev.mysql.com/doc/refman/5.6/ja/comparison-operators.html#function_coalesce">MySQL</a>、<a target="_blank" rel="nofollow noopener" href="http://www.sqlitetutorial.net/sqlite-functions/sqlite-coalesce/">SQLite</a>、<a target="_blank" rel="nofollow noopener" href="https://www.postgresql.jp/document/10/html/functions-conditional.html">PostgreSQL</a>、<a target="_blank" rel="nofollow noopener" href="https://docs.oracle.com/cd/E96517_01/sqlrf/COALESCE.html#GUID-3F9007A7-C0CA-4707-9CBA-1DBF2CDE0C87">Oracle</a></p> <p><strong>NVL (a, 0)</strong><br /> <a target="_blank" rel="nofollow noopener" href="https://docs.oracle.com/cd/E96517_01/sqlrf/NVL.html#GUID-3AB61E54-9201-4D6A-B48A-79F4C4A034B2">Oracle</a></p> <p><strong>ISNULL (a, 0)</strong><br /> <a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/sql/t-sql/functions/isnull-transact-sql?view=sql-server-2017">SQL Server</a></p> <h2 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h2> <p>第1引数(この場合カラムa)がNULLだった場合、関数は第2引数(この場合は0)を返します。<br /> 第1引数がNULLでなければ、関数はそのまま第1引数を返します。</p> <p>これらは同じような使い方ができますが、COALESCE()だけ仕様が異なり、引数を無限に設定することができます。<br /> COALESCE()は第1引数から順に「その値がNULLかどうか」を判定し、NULLであれば次の引数へ、NULLでなければその値を返却します。<br /> 全ての引数がNULLだった場合は、諦めてNULLを返却します。</p> ウラル