tag:crieit.net,2005:https://crieit.net/tags/SQLite/feed 「SQLite」の記事 - Crieit Crieitでタグ「SQLite」に投稿された最近の記事 2022-01-11T21:08:15+09:00 https://crieit.net/tags/SQLite/feed tag:crieit.net,2005:PublicArticle/17925 2022-01-08T16:27:09+09:00 2022-01-11T21:08:15+09:00 https://crieit.net/posts/bbb085470b763b7e8bcb423f4b235d64 スキャンレーション <h1 id="scanlation について"><a href="#scanlation+%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">scanlation について</a></h1> <p>スキャンレーション( scanlation )で検索すると、その言葉の定義から、ガイドラインやチュートリアル、または役割名称や、その広報的なサイトの情報がたくさんある。</p> <p>今は活動していないスキャンレーションのグループ名と、そのプロファイルのページのデータベース (sqlite file)。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.mediafire.com/file/qlse91vdwyletgx/scanlation-g.db/file">https://www.mediafire.com/file/qlse91vdwyletgx/scanlation-g.db/file</a><br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/85947479-df01-7dac-fbb4-f6d8dc21aed8.jpeg" alt="IMG_20211113_140504_811.jpg" /></p> <p><a target="_blank" rel="nofollow noopener" href="https://www-mangaupdates-com.translate.goog/aboutus.html?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=nui">https://www-mangaupdates-com.translate.goog/aboutus.html?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=nui</a></p> <p>過去に翻訳されたタイトルのオリジナルの和名とそれ以外の対照を作りたい場合は有効かもしれないし、そうでもないかもしれない。</p> <p>著作権がらみで問題のあるコンテンツのダウンロード情報や、データ自体は含まないと宣言されているサイトから全てのスキャンレーショングループのプロファイルページの所在を抽出してから、各プロファイルページにアップデートしてアーカイブされている翻訳済みの漫画の英語タイトルを抽出すれば、プロファイルされたスキャンレーションングループについては、全ての翻訳漫画タイトルを知れるということになる。<br /> 抽出の方法はグループのプロファイルページの所在、 プロファイルページ内にある翻訳したシリーズ名、日時という順にたどると効率が良さそう。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/zbtnk/png">1: タイトルを考えるのが面倒な scanlation groups counter プログラム Python link</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/nekon/png">2: a , b<br /> 2つのデータベースをマージする Lua プログラムサンプル link</a> :[ 依存関係 lsqlite3 https://github.com/LuaDist/lsqlite3/blob/master/examples/smart.lua]</p> <p>1.1.1.1<br /> <a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/1.1.1.1">https://en.m.wikipedia.org/wiki/1.1.1.1</a></p> <p>スキャンレーションは海賊サイトのエコシステムの一環で、フェーズとしてはオンラインでの海賊配信の水際に位置していると考えられる。<br /> 2次創作漫画画像を作るために、漫画をスキャナーでスキャンする必要があるわけだが、漫画を入手してスキャンし、アップロード ① してダウンロードアドレスを告知する ② ということをしているというところが、ボトムで、スキャンレーションの作業のエントリーポイントにもこの行程が不可欠。<br /> スキャンしてアップロード ① する人は必ずしもスキャンレーションチームにしかいないわけではないが、アップロードされたスキャン済み画像をダウングレード ③ して海賊配信 ④ していたり、スキャン画像をクリーンアップして翻訳、タイプセットして、翻訳版の海賊配信サイトにアップロード ⑤ していたりと、それぞれに直接関係があったり無かったりするのだろうけども、スキャンレーションという枠では、ひとまとまとまり情報として関係性が見える。例えば、このグループがここ ④ へアップロード ⑤ した等。</p> <p>④ のサイト群については、正当な方法(カドカワ的に「インターネット上の海賊版対策に関する検討会議 第9回会合 平成30年10月15日 議事録[^0]」)similarweb<sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">1</a></sup><sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">2</a></sup> <sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">3</a></sup>で横並びに見えるもののことを主に指すようだが、ボトムアップから見るとすると、行為とモチベーション度に着目する<sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">4</a></sup>とスキャンレーションのサイドからたどると動線も把握できる。スキャンレーションのさらに二次コピーなどが ④ に回収されて公開されている場合もあるだろう。トラフィックの大きさは、配信自体の技術に左右されるので、着目される経済的損失について語られるのはトラフィックの大きな上流の方のことだと思われる。漫画 BANK は ④ に含まれる(すでにアップロードされて海賊配信されているサイトからの画像流用)。</p> <p>これは想像なのでイマジネーションは図にした方がいい。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/ed3db1b5-2e40-1584-b6a3-4ba59ce3704f.png" alt="oie_1I69MA5YrCfZ.png" /></p> <p><a href="https://crieit.now.sh/upload_images/a4659321b5cc94fb81afc1c7163b2a6761dd7319f33e1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a4659321b5cc94fb81afc1c7163b2a6761dd7319f33e1.png?mw=700" alt="image" /></a></p> <p><a target="_blank" rel="nofollow noopener" href="https://fanlore.org/wiki/Scanlation_Process#General_Process">https://fanlore.org/wiki/Scanlation_Process#General_Process</a></p> <p>参考: https://kknews.cc/comic/moz43xz.html</p> <p><a target="_blank" rel="nofollow noopener" href="https://kknews-cc.translate.goog/comic/moz43xz.html?_x_tr_sl=zh-CN&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=nui">https://kknews-cc.translate.goog/comic/moz43xz.html?_x_tr_sl=zh-CN&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=nui</a></p> <p><a target="_blank" rel="nofollow noopener" href="http://onepiece.ria10.com/Entry/3752/">http://onepiece.ria10.com/Entry/3752/</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://www.similarweb.com/ja/website/mangapanda.onl/">https://www.similarweb.com/ja/website/mangapanda.onl/</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://www2.accsjp.or.jp/thanks/thanks39.php">https://www2.accsjp.or.jp/thanks/thanks39.php</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://www.neliti.com/publications/326921/accuracy-of-english-indonesian-scanlation-of-detective-conan-manga-as-compared-t">https://www.neliti.com/publications/326921/accuracy-of-english-indonesian-scanlation-of-detective-conan-manga-as-compared-t</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/scanlation-titles/png">3: link</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/zbtnk/png">1:link</a> / <a target="_blank" rel="nofollow noopener" href="https://rentry.co/nekon/png">2:link</a> / <a target="_blank" rel="nofollow noopener" href="https://rentry.co/scanlation-titles/png">3:link</a> とプログラムにせっせと、動いてもらって 3 をポチッとしてから翌日起きて、途中で止まってると思ってたら、朝になっても昼前まで止まらない量のデータをせっせと書き込んでいた。<br /> カウンターは 106000 を越えていて、なんか無理しているなぁと思いつつ、そのわきで次のプログラムを書きはじめて、</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/2393c94a-9848-b7d1-d308-367fa26659d2.png" alt="oie_png (1).png" /></p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/oytah/png">4:link</a> を書いた。</p> <p>たぶんこれも明日もお昼までうごいているんではないか。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.oreilly.co.jp/books/9784873113487/">https://www.oreilly.co.jp/books/9784873113487/</a></p> <p>漠然とした中での目的としては、翻訳された(おそらく勝手に)漫画のタイトルからオリジナルのタイトルを知りたいのと、その他の言葉でのタイトルを眺めたいので、そうするとどうやって組み上げたらいいのかなということをやってみたい。</p> <p>ということで、まずスキャンレーショングループが過去から現在まで翻訳したシリーズになった(単行本とかのことだと思われる)ものをカウントアップするために、まずグループのリストを作る。これが <a target="_blank" rel="nofollow noopener" href="https://rentry.co/zbtnk/png">1:link</a> のコード。<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/zbtnk/png">1:link</a> でできたリストのなかには、グループについてのプロファイリングされた個別のページのアドレスがあり、このプロファイリングされたページ上にグループの翻訳したシリーズがリストアップされている。<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/zbtnk/png">1:link</a> で作れるデータベースを複数足し合わせるのが <a target="_blank" rel="nofollow noopener" href="https://rentry.co/nekon/png">2:link</a> のコード。なぜ、Lua なのか、コンパクトで速いから。同じように他の言語で書いて同じような速度かもしれないが、コードを見れば、見える通りのことなのでシンプル。遅くはない。</p> <p>hy (clojure lisp 風 Python) 言語に合わせて <a target="_blank" rel="nofollow noopener" href="https://rentry.co/nekon/png">2:link</a> と同じものを Lua の Lisp 風言語 <a target="_blank" rel="nofollow noopener" href="https://github.com/bakpakin/Fennel/blob/main/reference.md">fennel</a> に直してみると</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/fennel/png">2:link fennel</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/37b80ddab68777414753">Vim</a> だとプラグインを設定しないとちょっと難しい感じだけど、<a target="_blank" rel="nofollow noopener" href="https://kakoune.org/">kakoune</a> だとデフォルトで色分けはしてくれる。どうなってるんでしょう。<a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/c0023fb476c5f708292e">vis editor</a> も。</p> <p>プロファイルされたページのシリーズタイトルを足し合わせて記録していくのが 3:link のコード。Python だが hy 言語。今のところ hy で書くメリットは分かりやすさ。<br /> SQLite のバインドも Python 独自のものなので、select して execute されて戻ってくるデータが空のときどうやって空のデータを比較できるのかというところが言語とバインドによって違うので、そこは注意しなくてはいけない。Lua の場合は、空の場合は素通りで、値を判定する式自体を無視するよう、そのことを分かっていたらシンプル。</p> <p>例えば、</p> <pre><code class="lua">local smt1 = "SELECT id FROM tbl_scanlation ORDER BY id DESC LIMIT 1 ;" local last1 = 0 for id in db1:urows(smt1) do last1 = id end </code></pre> <p>これは Lua バインドで sqlite3 データベースの tbl_scanlation というテーブルのデータの中の primary キー連番 id 番号の最後の int 値を取り出す式を実行して last1 に代入したい箇所だが、テーブルの中にデータが入ってなければ <code>local last1 = 0</code> で初期化している値が入る。 nil とか -1 とかにはならない。python や Ruby 、 go や nim など、またそれぞれ違うので、いろんな別の言語にて書き換える場合は、<strong>無い場合</strong>どういう値が入るのかよく調べなければいけない。SQLite3 自体では返ってくる値は決められているが、その値に対する値は、バインドによって書き換えられて処理されているようなので。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/scanlation-titles/png">3:link</a> で出来上がるデータベースの件数は三万件くらいだろうかと思っていたが、10 万件以上だった。SQLite の場合は扱える数はそれ以下のはずだが、無理でもできるとこまででいいのでがんばってもらう。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/oytah/png">4:link</a> の様子。お昼になっても終わっていない。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/736d3cbd-136e-d509-7247-54a18c2061ff.png" alt="Screenshot_20211120-113635.png" /></p> <p>一週間くらい経ってる気がするが、VALID データが 96,426 。つまり、中国、韓国、日本の漫画がスキャンレーショングループによって、それだけの数は翻訳されている。<br /> sqlite3 database file<br /> <a target="_blank" rel="nofollow noopener" href="https://we.tl/t-xUEh2MPRcF">https://we.tl/t-xUEh2MPRcF</a></p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/27ec591c-9a79-c606-7259-195d0f4161f3.png" alt="Screenshot_20211211-143003_kindlephoto-66542057.png" /></p> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:12" role="doc-endnote"> <p>資料7 https://www.kantei.go.jp/jp/singi/titeki2/tyousakai/kensho_hyoka_kikaku/2018/kaizoku/dai9/gijisidai.html <a href="#fnref:12" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:8" role="doc-endnote"> <p>https://en.globes.co.il/en/article-similarwebs-controversial-route-to-wall-street-1001376912 <a href="#fnref:8" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:9" role="doc-endnote"> <p>These Chrome extensions spy on 8 million users 31 Mar 2016 https://mweissbacher.com/2016/03/31/these-chrome-extensions-spy-on-8-million-users/ <a href="#fnref:9" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:10" role="doc-endnote"> <p>https://www.reddit.com/r/scanlation/about/ <a href="#fnref:10" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> tomato tag:crieit.net,2005:PublicArticle/17923 2022-01-07T12:34:32+09:00 2022-01-31T21:18:21+09:00 https://crieit.net/posts/BANK ある一つのサイト についての <h1 id="思い出"><a href="#%E6%80%9D%E3%81%84%E5%87%BA">思い出</a></h1> <p>過去 6 ヶ月間、ひたすらひとつの違法マンガサイトを見ていた。</p> <p>ずっと見ていたのは公開されたマンガコンテンツではなく、サイト運営者が作ってるデータをひたすら見ていたので、11 月の 4 日、15 時くらいにサイトをたたんだのを確認した。<sup id="fnref:100"><a href="#fn:100" class="footnote-ref" role="doc-noteref">1</a></sup></p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/e0ec8e48-727f-bf85-14b5-bb3ff3625497.png" alt="Screenshot_20211104-164634.png" /></p> <p>おそらくこの翻訳記事 2021年11月04日 14時00分</p> <blockquote> <p>Manga Publisher Wants to Sue Huge Piracy Network, Needs Google's Help * TorrentFreak<br /> <code>https://torrentfreak.com/manga-publisher-wants-to-sue-huge-piracy-network-needs-googles-help-211101/</code></p> </blockquote> <p>注) 'Shueisha’s application and proposed orders/subpoenas can be found here (1,2,3,4 pdf)' の書類が興味深い。</p> <p><a target="_blank" rel="nofollow noopener" href="https://gigazine.net/news/20211104-manga-shueisha-googles-piracy-mangabank/">https://gigazine.net/news/20211104-manga-shueisha-googles-piracy-mangabank/</a></p> <p>が公開されたことで、サイト運営者が違法性を自認して、サイト閉鎖を決定したのではないかと思われる。</p> <p>2021.12月中頃、mangabank.org とはまた別のドメインで再開している。<br /> 画像ファイルのあるアドレスのドメインを whois したところ、cloudflare の管轄のホストではなさそうな感じ。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/119a01df-c5bf-ba57-9f8d-50947320e8ec.jpeg" alt="Screenshot_20211230-001601.jpg" /></p> <hr /> <p>過去に書いたこのような記事は、当時、宣伝につながらないように配慮して明記しなかったが、全て漫画 BANK についてのことだった。</p> <p>以下、2021年11月04日 以前</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/563cbcc9776f66cb672e">https://qiita.com/dauuricus/items/563cbcc9776f66cb672e</a></p> <p>6 月の時点では 5 万ページぼどだと思っていたが 10 月には 6 万ページ以上あることがわかった。</p> <p>この 6 万ページというのはマンガコンテンツの漫画の総ページ数のことではなくて、URL のことで、そのひとつの URL に 30 点から 300 点ぐらいの画像ファイルの URL が埋め込まれている。<br /> その画像一枚が、漫画のスキャン画像ファイルの一点に相当する。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/592fa176-0e08-7dd1-4769-4baef444ba24.png" alt="Screenshot_photo-586353998.png" /></p> <p>画像点数にすると、空想すると数えたことないおおきな数になるので、いずれいつか数えようと数えなかったが、その 6 万ページについては 著者 / 漫画のタイトル / 公開されていた URL / アップデート日時の情報 / 付与されていたタグ のデータのセットを記録した。</p> <p>スキャン画像のファイルが漫画 BANK の見ているページに読み込まれるようにページのソースの中に URL が埋め込まれていて、観測した限りのその URL は、全てが cloudflare がホスティングしているドメインだった<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">2</a></sup> ( 2021年 7月の調べ )</p> <p>これが全てではないが、数千のアドレスから集計すると、こちらのドメインに収束した。</p> <pre><code>0 ssl.appx.buzz 1 ssl.asiax.cloud 2 ssl.stagingy.store 3 ssl.lsw.buzz 4 ssl.advx.cloud 5 ssl.appuru.store 6 ssl.lss.buzz 7 ssl.remon.store 8 ssl.lsq.buzz 9 ssl.lsb.buzz 10 ssl.appsx.cloud 11 ssl.lsh.buzz 12 ssl.raichi.store 13 ssl.lsr.buzz 14 ssl.akaax.com 15 ssl.axax.cloud 16 ssl.lsk.buzz 17 ssl.lsy.buzz 18 ssl.zqap.cloud 19 ssl.skyly.cloud 20 ssl.akax.cloud 21 ssl.zmqx.cloud 22 ssl.lssaq.cloud 23 ssl.lsm.buzz 24 ssl.nexc.store </code></pre> <p>参照: 🥝<a target="_blank" rel="nofollow noopener" href="https://zenn.dev/kurocat/articles/55565f019754cb">ページの中から lazy load の画像 URL を抽出する。</a></p> <p>画像の著作権情報<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">3</a></sup>と、公開されている cloudflare のドメインの画像ファイルアドレスのリストがあれば、すんなり停止できるのだろうなと<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">4</a></sup>考えていたが、ずいぶんとほったらかしているな、あれ ? ゼンゼンナニモシナイノ ? と不思議に思えた。<br /> たしか 2019 年<sup id="fnref:0"><a href="#fn:0" class="footnote-ref" role="doc-noteref">5</a></sup>までサイトブロッキング<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">6</a></sup>しかない!<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">7</a></sup> という主張さえあったのだが<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">8</a></sup>、その前にどこまでどうしたのかは、主張からはさっぱりわからないので、「どこまでどうできるのか」がずっと気にかかっていた。</p> <p>サイトブロッキング法制化、中間まとめは先送り<br /> 浅川 直輝 日経 xTECH/日経コンピュータ<br /> 2018.09.20<br /> <a target="_blank" rel="nofollow noopener" href="https://xtech.nikkei.com/atcl/nxt/column/18/00001/01044/">https://xtech.nikkei.com/atcl/nxt/column/18/00001/01044/</a></p> <p>朝日新聞デジタル<br /> 海賊版サイト対策、まとまらず 検討会議は無期限延期に<br /> 上田真由美 川本裕司 2018年10月16日 0時10分<br /> <a target="_blank" rel="nofollow noopener" href="https://www.asahi.com/articles/ASLBH5W88LBHUCLV00L.html">https://www.asahi.com/articles/ASLBH5W88LBHUCLV00L.html</a></p> <p>過去のしりきれとんぼになっている<strong>インターネット上の海賊版対策に関する検討会議 第9回会合 議事録</strong>を読んでいると <strong>とりまとまらない</strong>報告となった理由が熱くて面白かった。「両論併記をしない」という主張についての理由が議事録には残っている。</p> <p><a href="https://crieit.now.sh/upload_images/1ad698b79781afe59ebc1f9702c5222161dd706b56a71.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1ad698b79781afe59ebc1f9702c5222161dd706b56a71.png?mw=700" alt="image" /></a></p> <p>Rf.インターネット上の海賊版対策に関する検討会議 第9回会合 議事録</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.kantei.go.jp/jp/singi/titeki2/tyousakai/kensho_hyoka_kikaku/index.html">https://www.kantei.go.jp/jp/singi/titeki2/tyousakai/kensho_hyoka_kikaku/index.html</a></p> <hr /> <blockquote> <p>Mangabank “Suffers DDoS Attack” & Disappears Following Legal Action<br /> November 9, 2021 by Andy Maxwell</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://torrentfreak.com/mangabank-suffers-ddos-attack-disappears-following-legal-action-211109/">https://torrentfreak.com/mangabank-suffers-ddos-attack-disappears-following-legal-action-211109/</a></p> <p>注) 'A declaration filed with the court by Shueisha ( pdf ) contains a copy of Cloudflare's response to the DMCA subpoena filed earlier this year.' のところが興味深い。</p> <p><a target="_blank" rel="nofollow noopener" href="https://torrentfreak-com.translate.goog/mangabank-suffers-ddos-attack-disappears-following-legal-action-211109/?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=nui">翻訳</a></p> <p>漫画 BANK のアップロードして公開されていたタイトル名には命名規則があり、文字列抽出して、タイトルから検索して、間違った書籍情報をひっぱってくるものを選んでよくみると、簡単な間違いをしているものがあった。そして、これは間違いようなさそうなスペル間違いだったりなので、なにか、その前の段階 ? のもとで間違っていて、改正すると整合がとれないので、そのままにしているのではないかと思われた。<br /> なにと比べるのか。<br /> つまり、コピーしてきて、世の中では、そのタイトル名が通用していて、それは、オリジナルのタイトルからすると間違っているが、ファイル名としては正しいというような場合が考えられる。</p> <p>つまり、コピーなのだと思う。自らスキャンして、自らファイル名をつけて、アップロードしてるわけではないという場合、そうなるかなと思う。<br /> そう考えると、そのファイル名で、普通に検索すると、同じ間違ったタイトル名がひっかかるので、そいうことなのだ。</p> <p>だとするならば、なんらかの理由で漫画をスキャンしてアップロードする人間がいて、それを告知する、そしてそのデータを加工するなどする人間がいるというエコサイクルのなかで、ネット上のアップロードファイルから回収されたものを展示しているということになるので、そのセレクトセンスによって傾向がみられるということである。<br /> かなり古い漫画も選ばれていて、そのタイトルを知っているということは特徴的でもある。</p> <p>とはいえ、興味は運営者が誰なのか...というところには全く無く、データがどうなってんのかな? どういう風に作ってるのかな? 全部のデータはいくつあるのか知るにはどういうアプローチでやるのかなというところに重点があり、ではどこから来たデータで、どういうモチベーションで出てきたファイルなんだろうかということに疑問をもちはじめた。</p> <p>なんらかのモチベーションで日本の漫画をスキャニングし、ネットワークにアップロードする行為から始まり、ローカライズ(翻訳)し、漫画の言語を替えて、きれいに文字をのせて公開する一連のことを、<a href="https://crieit.net/posts/bbb085470b763b7e8bcb423f4b235d64#fn:10">スキャンレーション</a>と呼んでいるらしい。</p> <p><a href="https://crieit.net/posts/4daedd75f0cec1b328d4e661d9337bd3">ある一つのサイトについての 現在</a> へつづく</p> <p>せきららなコード(ちょっと古くなってるけど、今の状況に合わせて少しだけ修正したら使える)</p> <p><a target="_blank" rel="nofollow noopener" href="https://kuroca.hatenablog.com/">https://kuroca.hatenablog.com/</a></p> <hr /> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:100" role="doc-endnote"> <p>2021/11/17 はこんな状況 https://pastebin.com/fTcHK0ti <a href="#fnref:100" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:5" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://zenn.dev/kurocat/articles/eb233dc31bb285">違法と思われるマンガ Thank(仮称)の HTML の構造をよく確認する</a> <a href="#fnref:5" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:4" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/cd7143f84ad03fe17752">本の ISBN 情報なしに、本の題名から書籍データを抽出したいということ。</a> <a href="#fnref:4" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:6" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://www.nikkei.com/article/DGXMZO3631981010102018CR8000/">権利侵害記事、保存も違法 東京地裁が米社に仮処分 2018年10月10日</a> <a href="#fnref:6" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:0" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://www.kantei.go.jp/jp/singi/titeki2/tyousakai/kensho_hyoka_kikaku/index.html">インターネット上の海賊版対策に関する検討会議</a> <a href="#fnref:0" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:1" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://internet.watch.impress.co.jp/docs/special/1128898.html">海賊版サイトをブロッキングするための5つの手法 Internet Society 日本支部 2018年6月22日</a> <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:2" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://xtech.nikkei.com/atcl/nxt/column/18/00001/00469/">カドカワ川上量生社長が語る、サイトブロッキングの必要性 浅川 直輝 日経xTECH/日経コンピュータ</a> <a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:7" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://www.sankei.com/article/20190114-NB33HYAHTZOORB6CZY7BGLQ36M/">海賊版「ブロッキング」法制化断念 政府、広告抑制など総合対策で対応 2019/1/14</a> <a href="#fnref:7" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> tomato tag:crieit.net,2005:PublicArticle/17047 2021-05-01T13:26:57+09:00 2021-05-01T13:28:14+09:00 https://crieit.net/posts/sqlite-docker-20210501 Docker で PHP + SQLite3 の開発環境を構築する <p>SQLite を試験したくなったので、環境を Docker で作ることにしました。</p> <p>ベースは <a target="_blank" rel="nofollow noopener" href="https://labor.ewigleere.net/2021/02/12/docker_centos7_apache_php_xdebug/">CentOS7 + Apache + PHP7 (Composer, Xdebug) の開発環境を Docker で作る</a> で。</p> <h2 id="構成"><a href="#%E6%A7%8B%E6%88%90">構成</a></h2> <h3 id="ディレクトリ構造"><a href="#%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E6%A7%8B%E9%80%A0">ディレクトリ構造</a></h3> <pre><code>PROJECT_ROOT/ │ # 略 │ ├ sqlite/ │ # 略 </code></pre> <p>SQLite 用のディレクトリを掘っただけで、後は同じです。</p> <h3 id="docker-compose.yml"><a href="#docker-compose.yml">docker-compose.yml</a></h3> <pre><code class="yaml">version: '3.8' services: web: build: context: ./apache/docker dockerfile: Dockerfile args: # 略 SQLITE_TABLENAME: $SQLITE_TABLENAME labels: lamp.sqlite.php: "SQLite PHP" volumes: # 略 # sqlite - ./sqlite:/var/sqlite/ # 略 entrypoint: bash -c "bash /workspace/entrypoint.sh $WEB_ROOT_DIRECTORY $WEB_DOMAIN $WEB_HOST_PORTNUM $WEB_CONTAINER_PORTNUM $WEB_HOST_PORTSSL $WEB_CONTAINER_PORTSSL $SQLITE_TABLENAME && /bin/bash" </code></pre> <p><code>docker-compose.yml</code> も大部分は同じです。変数(<code>SQLITE_TABLENAME</code>)とボリュームがそれぞれ1つ増えたのと、 entrypoint へ渡す引数が1つ増えたくらいです。</p> <h3 id=".env"><a href="#.env">.env</a></h3> <pre><code class="env">WEB_ROOT_DIRECTORY=sample_sqlite WEB_DOMAIN=lvh.me WEB_HOST_PORTNUM=8080 WEB_CONTAINER_PORTNUM=80 WEB_HOST_PORTSSL=4043 WEB_CONTAINER_PORTSSL=443 SQLITE_TABLENAME=maindb </code></pre> <p>上述の通り、 SQLite のファイル名を決める変数が増えました。</p> <h3 id="apache/docker/Dockerfile"><a href="#apache%2Fdocker%2FDockerfile">apache/docker/Dockerfile</a></h3> <pre><code class="dockerfile">FROM centos:centos7 # 略 # enable repository remi & remi-php74 RUN yum-config-manager --enable remi && yum-config-manager --enable remi-php74 # php RUN yum -y install php php-devel php-pdo php-mysqlnd php-mbstring php-gd php-pear php-pecl-apc-devel zlib-devel php-xml php-mcrypt php-pecl-xdebug # sqlite RUN yum -y install sqlite sqlite-devel # disable repository remi & remi-php74 RUN yum-config-manager --disable remi && yum-config-manager --disable remi-php74 # 略 # volume directory RUN mkdir /template RUN mkdir /var/www/${WEB_ROOT_DIRECTORY} RUN mkdir /var/www/${WEB_ROOT_DIRECTORY}/web RUN mkdir /workspace RUN mkdir /var/sqlite </code></pre> <p>こちらも概ね同じ。 PHP のインストール直後に <code>sqlite</code>, <code>sqlite-devel</code> をインストールしています。また、最後にボリュームとしてマウントするディレクトリを作成しています。</p> <h2 id="workspace/entrypoint.sh"><a href="#workspace%2Fentrypoint.sh">workspace/entrypoint.sh</a></h2> <pre><code class="sh"># 略 # make sqlite file & create dummy table if [[ ! -e /var/sqlite/${7}.sqlite3 ]]; then echo ".exit" | echo "create table testtable(one varchar(10), two smallint);" | sqlite3 /var/sqlite/${7}.sqlite3 fi # Apache start /usr/sbin/httpd -DFOREGROUND & </code></pre> <p>entrypoint も同様ほぼ同じですが、 Apache 起動前に「もし SQLite のデータファイルが該当ディレクトリにない場合は作成」の処理を追加しました。テーブルがないとファイルが生成されなかったので、ダミーテーブルも CREATE しています。</p> <h2 id="検証"><a href="#%E6%A4%9C%E8%A8%BC">検証</a></h2> <p>上述で構築された SQLite にダミーデータを流し込んでテストしてみます。</p> <pre><code class="bash"># sqlite3 /var/sqlite/maindb.sqlite3 sqlite> insert into testtable values('hello', 10); sqlite .exit # </code></pre> <p>まずはダミーテーブルにダミーデータを INSERT します。</p> <pre><code class="php"><?php try { $pdo = new PDO('sqlite:/var/sqlite/maindb.sqlite3'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $stmt = $pdo->prepare("SELECT * FROM testtable WHERE two >= :two"); $stmt->execute([ 'two' => 5 ]); $result = $stmt->fetchAll(); var_dump($result); } catch (Exception $e) { echo $e->getMessage() . PHP_EOL; } </code></pre> <p>次に上述のような <code>index.php</code> を作成し、Webアクセスします。</p> <pre><code>/var/www/sample_sqlite/web/index.php:25: array (size=1) 0 => array (size=2) 'one' => string 'hello' (length=5) 'two' => string '10' (length=2) </code></pre> <p>結果。これで PHP から PDOオブジェクト 経由でダミーテーブルに接続できたので、ひとまず環境構築という点では完了です。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://it-afi.com/php/post-1253/">php 7.4をソースからインストール時にchecking for sqlite3 > 3.7.4… no configure: error: Package requirements (sqlite3 > 3.7.4) were not met:</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.agent-grow.com/self20percent/2018/08/06/linux-command-auto-yes/">コマンドの途中で聞いてくる yes を自動入力したい?それ yes で出来るよ – 自主的20%るぅる</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://linuxfan.info/post-1739">シェルスクリプトでファイルの存在を確認する方法 | LFI</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://laboradian.com/how-to-use-sqlite-from-php/">PHP から SQLite を使う手順 – ラボラジアン</a> <ul> <li>最終的には SQLite3オブジェクト からではなく PDOオブジェクト から接続してしまいましたが</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/39_isao/items/a5b4940138bced936de0">PHP + SQLite3 超入門したメモ - Qiita</a></li> </ul> arm-band tag:crieit.net,2005:PublicArticle/15526 2019-11-02T17:42:17+09:00 2019-11-02T17:42:17+09:00 https://crieit.net/posts/Flutter-SQLite Flutterで学ぶSQLite <h2 id="sqfliteの導入"><a href="#sqflite%E3%81%AE%E5%B0%8E%E5%85%A5">sqfliteの導入</a></h2> <p>pubspec.yamlのdependenciesセクションに以下を追記するだけです. pathパッケージはパスの解決を行います.</p> <pre><code class="dart">sqflite: path: </code></pre> <h2 id="前提知識"><a href="#%E5%89%8D%E6%8F%90%E7%9F%A5%E8%AD%98">前提知識</a></h2> <h3 id="String Interpolation"><a href="#String+Interpolation">String Interpolation</a></h3> <p>変数の前にドルマークを付けることで挿入できます.</p> <pre><code class="dart">int result = 10; String result_message = 'The result is $result.' </code></pre> <p>式も挿入できます.</p> <pre><code class="dart">int a = 10; int b = 12; String result_message = 'The sum of a and b is ${a+b}'; </code></pre> <h3 id="パスの生成"><a href="#%E3%83%91%E3%82%B9%E3%81%AE%E7%94%9F%E6%88%90">パスの生成</a></h3> <p>パスは文字列型として表ます. ところがOSによってパスの区切り文字が違うなどプラットフォーム依存性を考慮する必要があります. そうしたOS固有の差異を吸収するライブラリが多くの言語の提供されており, Dartでは<a target="_blank" rel="nofollow noopener" href="https://pub.dev/packages/path">path</a>ライブラリを利用します.</p> <pre><code class="dart">import 'package:path/path.dart' as path; path.join('path_to_directory', 'file.text'); </code></pre> <p>こうすることでプログラムを動かすOSに応じた区切り文字で自動的にパス文字列を生成してくれます.</p> <h2 id="SQLiteとは?"><a href="#SQLite%E3%81%A8%E3%81%AF%3F">SQLiteとは?</a></h2> <p>SQLiteの<a target="_blank" rel="nofollow noopener" href="https://www.sqlite.org/about.html">公式サイト</a>からいくつか引用しましょう.</p> <blockquote> <p>Do not be misled by the "Lite" in the name. SQLite has a full-featured SQL implementation, including:</p> <p>A database in SQLite is a single disk file</p> <p>SQLite is an in-process library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine.</p> <p>SQLite is an embedded SQL database engine.</p> <p>SQLite reads and writes directly to ordinary disk files.</p> </blockquote> <p>まとめると以下のような感じでしょうか.</p> <ul> <li>SQLのサブセットではない.</li> <li>サーバーレス</li> <li>組み込み用</li> <li>単一のディスク・ファイルで構成される</li> <li>通常のディスク・ファイルを利用する</li> </ul> <p>サーバーを立てるなど余計な処理がないのでSQLの入門として良いのかもしれません.</p> <h2 id="FlutterアプリのためのSQLite"><a href="#Flutter%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AESQLite">FlutterアプリのためのSQLite</a></h2> <h3 id="データ型"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B">データ型</a></h3> <p>公式ドキュメントには以下のような説明があります.</p> <blockquote> <p>SQLite uses a more general dynamic type system. The dynamic type system of SQLite is backwards compatible with the more common static type systems of other database engines...</p> </blockquote> <p>SQLiteは動的な型ですので, 厳密にはデータ型と言えるものはないようです. ただ他のSQL文と互換性のためにデータ型を指定できるようです. あまり深く考えずに使っていきましょう.</p> <div class="table-responsive"><table> <thead> <tr> <th>データ型</th> <th>説明</th> </tr> </thead> <tbody> <tr> <td>NULL</td> <td>欠損値ののように値が存在しないことを示す</td> </tr> <tr> <td>INTEGER</td> <td>整数型</td> </tr> <tr> <td>REAL</td> <td>浮動小数点型</td> </tr> <tr> <td>TEXT</td> <td>文字列型</td> </tr> <tr> <td>BLOB (Binary Large OBject)</td> <td>バイナリデータ</td> </tr> </tbody> </table></div> <h4 id="Type Affinity"><a href="#Type+Affinity">Type Affinity</a></h4> <blockquote> <p>The type affinity of a column is the recommended type for data stored in that column.</p> </blockquote> <p>公式の説明によると推奨される型のようです. 推奨される型のようです. SQLite独自っぽいのでそんなのもあるぐらいで良いのかもしれません.</p> <h3><a target="_blank" rel="nofollow noopener" href="https://pub.dev/documentation/sqflite/latest/">sqflite</a></h3> <p>Flutter用のSQLiteプラグインとしてsqfliteがあります.</p> <pre><code class="dart">import 'package:sqflite/sqflite.dart' as sqflite.; </code></pre> <h3 id="データベースの作成と接続"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E4%BD%9C%E6%88%90%E3%81%A8%E6%8E%A5%E7%B6%9A">データベースの作成と接続</a></h3> <p>SQLiteは単一のディスク・ファイルとして作成されます. つまり通常のファイルを開くのと同じような処理になります. すでに同名のデータベースが存在する場合はそのデータベースに接続されます. 生成と接続は区別されません.</p> <pre><code class="sh">sqlite3 my_database.db </code></pre> <p>sqfliteではopenDatabaseを利用します.</p> <pre><code class="dart">Future<Database> db = openDatabase('my_database.db', version: 1); </code></pre> <p>Flutterはマルチ・プラットフォームのUIツールキットです. AndroidとiOSではファイルの構成などは当然違います. getDatabasesPathでデータベースの保存フォルダへのパスを取得できます.</p> <pre><code class="dart">// Create an absolute path to databse final database_name = 'your_database.db'; final database_path = getDatabasesPath(); final String path_to_db = path.join(database_path, database_name); // Open or connect database final Future<Database> database = await openDatabase(path_to_db); </code></pre> <h4 id="データベースの削除"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E5%89%8A%E9%99%A4">データベースの削除</a></h4> <pre><code class="dart">final database_name = 'your_database.db'; final database_path = getDatabasesPath(); final String path_to_db = path.join(database_path, database_name); // Open or connect database final void result = await deleteDatabase(path.join(await getDatabasesPath(), 'doggie_database.db')); </code></pre> <h3 id="テーブルの作成とSQL"><a href="#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90%E3%81%A8SQL">テーブルの作成とSQL</a></h3> <h4 id="Q. テーブルとは?"><a href="#Q.+%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%A8%E3%81%AF%3F">Q. テーブルとは?</a></h4> <p>リレーショナル・データベースのデータはテーブルという形式で表現されます.</p> <div class="table-responsive"><table> <thead> <tr> <th>column 1</th> <th>column 2</th> <th>column 3</th> <th>column 4</th> </tr> </thead> <tbody> <tr> <td>field</td> <td>field</td> <td>field</td> <td>field</td> </tr> </tbody> </table></div> <p>普通のテーブルを想像すれば良いです. ラベルに当たる部分をカラム(Column)と呼びます. これはその下に保存されるデータを表す名前です. カラム1から4までの組みあわせ, つまり一行をロウ(Row)もしくはレコード(Record)と呼びます. そしてレコードの各要素をフィールド(Field)と呼びます.</p> <p>構造体をレコード, 各メンバ変数をフィールドと呼ぶ言語もあったりする馴染みのある人もいるかもしれません. そういう意味ではテーブルは構造体を定義するのに似ています. 構造体やクラスなどのデータをレコードに永続化したものをエンティティと呼んだりするようです.</p> <h4 id="Q. テーブルの作成"><a href="#Q.+%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90">Q. テーブルの作成</a></h4> <p>データベースに対する指示はSQL(Structured Query Language)という言語を用いて行います.</p> <pre><code class="dart">CREATE TABLE Student ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT ) </code></pre> <p>SQLのコマンドは全て大文字で記述されている場合が多いのでそれに習います. sqfliteでSQL分の実行するにはexecuteというメソッドを使います.</p> <pre><code class="dart">final String sql = 'CREATE TABLE Student (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)'; db.execute(sql); </code></pre> <p>openDatabaseにはonCreateという名前付き引数があり, データベースの作成時に実行されるフック関数を渡すことができます. テーブルを作る場合は以下のようになります.</p> <pre><code class="dart">// Create an absolute path to databse final database_name = 'your_database.db'; final database_path = getDatabasesPath(); final String path_to_db = path.join(database_path, database_name); // SQL command literal final String sql = 'CREATE TABLE Student (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)'; // Open or connect database Future<Database> database = openDatabase( path_to_db, // Create table onCreate: (Database db, int versin) async { await db.execute(sql); } ); </code></pre> <h3 id="主キーと外部キー"><a href="#%E4%B8%BB%E3%82%AD%E3%83%BC%E3%81%A8%E5%A4%96%E9%83%A8%E3%82%AD%E3%83%BC">主キーと外部キー</a></h3> <h4 id="Q. 主キーとは?"><a href="#Q.+%E4%B8%BB%E3%82%AD%E3%83%BC%E3%81%A8%E3%81%AF%3F">Q. 主キーとは?</a></h4> <p>Studentテーブルのidにはデータ型以外にPRIMARY KEYという属性が付加されています. 主キーには以下のような特徴があります.</p> <ul> <li>テーブルで1列だけ指定できる</li> <li>データ値の重複を許さない</li> <li>NULL値の格納できない (NOT NULL制約)</li> </ul> <p>これはデータに対して付加される条件ということで主キー制約(Constraint)と呼ばれています.</p> <h4 id="Q. 主キー制約を課す理由"><a href="#Q.+%E4%B8%BB%E3%82%AD%E3%83%BC%E5%88%B6%E7%B4%84%E3%82%92%E8%AA%B2%E3%81%99%E7%90%86%E7%94%B1">Q. 主キー制約を課す理由</a></h4> <p>テーブルのレコードを一意に指定するためのようです. Studentテーブルは学生一覧を保持しているのですが, 学生の名前には同姓同名というケースが稀に存在します. つまり特定の学生個人を特定できなくなってしまいます.</p> <div class="table-responsive"><table> <thead> <tr> <th>name</th> <th>class</th> </tr> </thead> <tbody> <tr> <td>日本太郎</td> <td>3-B</td> </tr> <tr> <td>日本太郎</td> <td>3-B</td> </tr> <tr> <td>西日本花子</td> <td>3-B</td> </tr> <tr> <td>東日本次郎</td> <td>3-B</td> </tr> </tbody> </table></div> <p>カラムを増やして性別や住所といった情報を付加すれば二人の日本太郎を区別することはできますが, 西日本花子や東日本次郎の場合に不要な計算が必要になってしまいます. 主キー制約を課せば重複や欠損値はエラーになるので, 識別子と安心して使えますし不要な条件判断のロジックも不要です.</p> <h4 id="Q. 外部キーとは?"><a href="#Q.+%E5%A4%96%E9%83%A8%E3%82%AD%E3%83%BC%E3%81%A8%E3%81%AF%3F">Q. 外部キーとは?</a></h4> <p>もう一つは参照値として文字列が適さないからです. 部署名(Departure)のようなテーブルは名前だけでも識別が可能です. 以下のような官僚名簿を考えてみましょう.</p> <div class="table-responsive"><table> <thead> <tr> <th>name</th> <th>ministry</th> </tr> </thead> <tbody> <tr> <td>大蔵一郎</td> <td>大蔵省</td> </tr> <tr> <td>財務太郎</td> <td>財務省</td> </tr> <tr> <td>財務次郎</td> <td>財務省</td> </tr> </tbody> </table></div> <p>財務省は少し前まで大蔵省と呼ばれていました. 国土交通省や厚生労働省のような省庁合併を伴わないため, (金融庁の扱いが少し変わったようですが)財務省の前進はそのまま大蔵省と言って良さそうです. この改称後に財務太郎と財務一郎さんが入省しました. 今は大蔵省なのは大蔵一郎さん一人なので大蔵省を財務省に書き換えれば良いですが, 人数はもっと多いはずです. この場合以下の問題が生じます.</p> <ul> <li>大量の書き替え作業(手作業)</li> <li>書き替え時の記入ミスとその修正</li> </ul> <p>偶然何事もなくいく場合もありますが, こうした手動の作業は時間がかかります. ここでministryカラムを省庁(Ministry)テーブルに分離することを考えます.</p> <div class="table-responsive"><table> <thead> <tr> <th>id</th> <th>name</th> </tr> </thead> <tbody> <tr> <td>001</td> <td>大蔵省</td> </tr> <tr> <td>002</td> <td>運輸省</td> </tr> <tr> <td>003</td> <td>郵政省</td> </tr> </tbody> </table></div> <p>省庁テーブルは主キーを持っています. これを官僚(国家公務員)テーブルから参照するようにします.</p> <div class="table-responsive"><table> <thead> <tr> <th>name</th> <th>ministry_code</th> </tr> </thead> <tbody> <tr> <td>大蔵一郎</td> <td>001</td> </tr> <tr> <td>財務太郎</td> <td>001</td> </tr> <tr> <td>財務次郎</td> <td>001</td> </tr> </tbody> </table></div> <p>こうしておけば省庁テーブルを大蔵省から財務省に変更するだけなので変更箇所が一箇所ですみます. また記入ミスがあっても発見は容易になります. 省庁テーブルは一種の定数ファイル(テーブル)と考えれば分かりやすいと思います. ハードコードは厳禁なのです(じゃマジック・ナンバーは良いのかというツッコミはやめましょう).</p> <p>こうした別のテーブルから参照しているカラムを外部キーと言います. この場合ministory_codeは省庁テーブルの主キーを外部キーとして参照しています. 外部キーは主キーである必要はないようですが, 主キーを参照するようにすると重複がないことが保証されるので参照先の候補が多数あるという状況は避けられれそうです.</p> <p>なお外部キーとして指定されたフィールドは削除できなくなるそうです(外部キー制約).</p> <h4 id="Q. AUTOINCREMENTとは?"><a href="#Q.+AUTOINCREMENT%E3%81%A8%E3%81%AF%3F">Q. AUTOINCREMENTとは?</a></h4> <p>もう一つAUTOINCREMENTという属性が付加されています. この属性を付加するとidは自動的に増えていきます. 例えば新設の国土交通省を追加してみましょう.</p> <div class="table-responsive"><table> <thead> <tr> <th>id</th> <th>name</th> </tr> </thead> <tbody> <tr> <td>001</td> <td>大蔵省</td> </tr> <tr> <td>002</td> <td>運輸省</td> </tr> <tr> <td>003</td> <td>郵政省</td> </tr> <tr> <td>004</td> <td>国土交通省</td> </tr> </tbody> </table></div> <p>idが一つ増えて004となりました. AUTOINCREMENTを指定するとSQLiteが勝手に数字を増やしてくれます.</p> <h3 id="CRUDとデータ操作言語(DML)"><a href="#CRUD%E3%81%A8%E3%83%87%E3%83%BC%E3%82%BF%E6%93%8D%E4%BD%9C%E8%A8%80%E8%AA%9E%28DML%29">CRUDとデータ操作言語(DML)</a></h3> <p>永続化に関係する基本的な処理をCRUDと言います. これはCreate, Read, Update, そしてDeleteという四つの処理から作った頭文字語です. CRUD処理とSQLiteの対応関係は以下のようになります.</p> <div class="table-responsive"><table> <thead> <tr> <th>CRUD</th> <th>SQLite</th> </tr> </thead> <tbody> <tr> <td>Create</td> <td>INSERT</td> </tr> <tr> <td>READ</td> <td>SELECT</td> </tr> <tr> <td>Update</td> <td>UPDATE</td> </tr> <tr> <td>Delete</td> <td>DELETE</td> </tr> </tbody> </table></div> <p>SQLではこの右側のコマンドをDML(Data Manipulation Language)と呼んでいます.</p> <h4 id="Q. INSERT"><a href="#Q.+INSERT">Q. INSERT</a></h4> <p>レコードをテーブルに挿入します.</p> <pre><code class="sql">INSERT INTO Student (id, name) VALUES (1, '東京太郎') INSERT INTO Student (name) VALUES ('東京太郎') </code></pre> <p>AUTOINCREMENT指定したフィールドは値を無視してもいいです. sqfliteではrawInsertとinsertという二つのメソッドが用意されています. rawInsertにはSQLリテラルを渡します.</p> <pre><code class="dart">final Database db = await database; final name = '東京太郎'; final String sql = 'INSERT INTO Student (name) VALUES ${name}'; final int result = await db.rawInsert(sql); </code></pre> <p>insetは以下のように使います.</p> <pre><code class="dart">final Database db = await database; final String table_name = 'Student'; final String name = '東京太郎'; Map<String, dynamic> record = { 'name': name }; final int result = await db.insert(table_name, record) ; </code></pre> <h4 id="Q. DELETE"><a href="#Q.+DELETE">Q. DELETE</a></h4> <p>順番的にはSELECTですが, DELETEの方がシンプルなのでこちらを先に紹介します.</p> <pre><code class="sql">DELETE FROM Student </code></pre> <p>これを実行すると全てのレコードが削除されます. レコードが一つしかないので同じことですが, 特定のレコードを削除する場合はWHERE句を末尾に付け足します.</p> <pre><code class="sql">DELETE FROM Student WHERE id=1 </code></pre> <p>WHERE句は等号以外にも不等号などの条件も指定できます. INSERT同様にDELETEにも二つのメソッドがあります.</p> <pre><code class="dart">final Database db = await database; final int id = 1; final String sql = 'DELETE FROM Student WHERE id = ${id}'; final int result = await db.rawDelete(sql); </code></pre> <p>あるいは疑問符を使って, 引数を配列として渡すこともできます.</p> <pre><code class="dart">final Database db = await database; final String sql = 'DELETE FROM Student WHERE id = ?'; final int id = 1; final int result = await db.rawDelete(sql, [id]); </code></pre> <p>何れにしてもrawDeleteはSQLリテラルを引数に取ることができます. 一方deleteメソッドは以下のようにします.</p> <pre><code class="dart">final String table_name = 'Student'; final int id = 1; final int result = await database.delete(table_name, where: 'id = ?', whereArgs: [id]); </code></pre> <h4 id="Q. UPDATE"><a href="#Q.+UPDATE">Q. UPDATE</a></h4> <pre><code class="sql">UPDATE Student SET name = "京都太郎" WHERE id = 1 </code></pre> <p>sqfliteではrawUpdateかupdateメソッドを使います.</p> <pre><code class="dart">final int id = 1; final String name = '京都太郎'; final String sql = 'UPDATE Student SET name = ${name} WHERE id = ${id}'; database.rawUpdate(sql); </code></pre> <p>あるいは,</p> <pre><code class="dart">final int id = 1; final String name = '京都太郎'; final String sql = 'UPDATE Student SET name = ? WHERE id = ?'; database.rawUpdate(sql, [name, id]); </code></pre> <p>DELETE同様に配列として渡すこともできます. updateメソッドを使うと以下のようになります.</p> <pre><code class="dart">final String table_name = 'Student'; final int id = 1; final String name = '京都太郎'; final Map<String, dynamic> new_recode = { 'id': id, 'name': name }; database.update(table_name, new_recode, where: "id = ?", whereArgs: [id]); </code></pre> <h4 id="Q. SELECT"><a href="#Q.+SELECT">Q. SELECT</a></h4> <p>テーブルからデータ取り出す操作です.</p> <pre><code class="sql">SELECT * FROM Student SELECT (id, name) FROM Student </code></pre> <p>この二つの文は同じ意味で全てのカラムを対象にStudentテーブルからデータを読み取る, となります. SELECT 以下は対象となるカラムを指定します. 名前だけの一覧ならnameカラムだけを指定します.</p> <pre><code class="sql">SELECT name FROM Student </code></pre> <p>sqfliteの場合はrawQueryかqueryメソッドを使います.</p> <pre><code class="dart">final Database db = await database; final String sql = 'SELECT * FROM Student'; final List<Map<String, dynamic>> result = await db.rawQuery(sql); </code></pre> <p>他のメソッド戻り値が少し複雑になります. あるいはqueryメソッドを使います.</p> <pre><code class="dart">final String table_name = 'Student'; final List<Map<String, dynamic>> result = await db.query(table_name) // final List<Map<String, dynamic>> result = await db.query(table_name, columns: ['id', 'name']); // final List<Map<String, dynamic>> result = await db.query(table_name, where: 'id = ?', wehreArgs: [1]); </code></pre> <h3 id="トランザクション"><a href="#%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3">トランザクション</a></h3> <p>トランザクションは複数の処理を一つの単位として実行することを指すようです. この一連の処理が失敗した場合はロールバックといってトランザクションが始まる前の状態にデータベースを復元します. つまりトランザクションに含まれる処理が全て実行された場合のみデータベースが更新されます.</p> <p>INSERTで複数のレコード挿入してみましょう.</p> <pre><code class="dart">await db.transaction((txn) async{ int id1 = txn.rawInsert('INSERT INTO Student VALUES ?', ['東京太郎']); int id2 = txn.rawInsert('INSERT INTO Student VALUES ?', ['京都太郎']); }); </code></pre> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>簡単でしたがsqfliteの使い方でした.</p> <h2 id="Reference"><a href="#Reference">Reference</a></h2> <h3 id="sqflite"><a href="#sqflite">sqflite</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://pub.dev/packages/sqflite#-readme-tab-">sqflite</a><br /> <a target="_blank" rel="nofollow noopener" href="https://flutter.dev/docs/cookbook/persistence/sqlite">Persist data with SQLite</a><br /> <a target="_blank" rel="nofollow noopener" href="https://www.techiediaries.com/flutter-sqlite-crud-tutorial/">Flutter & SQLite Tutorial: CRUD Operations with sqflite</a><br /> <a target="_blank" rel="nofollow noopener" href="https://alvinalexander.com/flutter/sqflite-escaping-quotes-sql-insert-update-statements">Flutter, sqflite, and escaping quotes with SQL INSERT and UPDATE statements</a></p> <h3 id="SQLite"><a href="#SQLite">SQLite</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://www.sqlitetutorial.net">SQLite Tutorial</a><br /> <a target="_blank" rel="nofollow noopener" href="https://www.dbonline.jp/sqlite/">SQLite入門</a></p> <h3 id="その他"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96">その他</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/334183/what-is-the-most-efficient-way-to-store-tags-in-a-database">What is the most efficient way to store tags in a database?</a><br /> <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/47881198/how-to-design-a-relational-database-for-associating-multiple-tags-with-id">How to design a relational database for associating multiple tags with id?</a><br /> <a target="_blank" rel="nofollow noopener" href="https://charlesleifer.com/blog/a-tour-of-tagging-schemas-many-to-many-bitmaps-and-more/">A Tour of Tagging Schemas: Many-to-many, Bitmaps and More</a><br /> <a target="_blank" rel="nofollow noopener" href="https://support.airtable.com/hc/en-us/articles/218734758-A-beginner-s-guide-to-many-to-many-relationships">A beginner's guide to many-to-many relationships</a></p> ブレイン tag:crieit.net,2005:PublicArticle/15519 2019-10-30T22:41:45+09:00 2019-10-30T22:41:45+09:00 https://crieit.net/posts/SQLite SQLiteでカラムを削除する簡単な方法 <p><a href="https://crieit.now.sh/upload_images/4451f2f15b0ced1d1bd2bd311c46645c5db9920731dc7.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/4451f2f15b0ced1d1bd2bd311c46645c5db9920731dc7.jpg?mw=700" alt="SQLiteでカラムを削除する簡単な方法" /></a><br /> こんにちは。ケイジです。</p> <p>Laravelでウェブ開発をしています。<br /> ローカル環境のデータベースはSQLiteを使用することが多いです。</p> <p>テストなどで作ったカラムをちょこっと削除したい場面がたまにあるのですが、SQLiteにはカラムを削除するコマンドがありません。<br /> MySQLやPostgreSQLはdrop columnで簡単にできるのに。※</p> <p>SQLiteでカラムを削除しようとすると非常に面倒で、「新規にテーブルを作り、古いテーブルからデータを移し、古いテーブルを削除してから新規作成したテーブルを元のテーブルと同じ名前にリネームする」ということをしなければなりません。</p> <p>そんなときは「DB Browser for SQLite」を利用してカラム削除をすればとても楽チン。</p> <p>DB Browser for SQLiteはGUIでSQLiteを操作するアプリケーションです。<br /> ダウンロードは以下のサイトから。</p> <p><a target="_blank" rel="nofollow noopener" href="https://sqlitebrowser.org/dl/">Downloads - DB Browser for SQLite</a></p> <p>今回の例では「articles」テーブルの「published_at」カラムを削除します。</p> <p>ダウンロードしたアプリケーションを起動するとウィンドウが表示されるので「Open Database」をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/9a3ac0970f7eec762a1a157437141ee35db992b5a304a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9a3ac0970f7eec762a1a157437141ee35db992b5a304a.png?mw=700" alt="ss01-2-1024x634.png" /></a></p> <p>操作したい拡張子「.sqlite」のSQLiteのデータファイルを選択します。<br /> <a href="https://crieit.now.sh/upload_images/c94b641907cbe0da2d32ba39785335b25db992cd24f2c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c94b641907cbe0da2d32ba39785335b25db992cd24f2c.png?mw=700" alt="ss02-2.png" /></a></p> <p>カラムを削除したいテーブルを選択し、「Modify Table」をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/d1845b8b1b11d2500e662108c15295c35db992de01c8a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d1845b8b1b11d2500e662108c15295c35db992de01c8a.png?mw=700" alt="ss03-2-1024x634.png" /></a></p> <p>すると次のような画面が表示されるので削除する「published_at」カラムを選択して「Remove field」をクリック。<br /> <a href="https://crieit.now.sh/upload_images/6b90fafd4e6dbc75883699a8107bb07b5db992e96ea35.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6b90fafd4e6dbc75883699a8107bb07b5db992e96ea35.png?mw=700" alt="ss04-2.png" /></a></p> <p>確認画面が表示されるので「Yes」をクリックしてカラムを削除します。<br /> <a href="https://crieit.now.sh/upload_images/80f3ca4e2936641376189300eaf5d7165db992f6db8d3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/80f3ca4e2936641376189300eaf5d7165db992f6db8d3.png?mw=700" alt="ss05-2.png" /></a></p> <p>以上です。<br /> 簡単ですね。</p> <p>それでは、よいプログラミングライフを!</p> <p>※ Laravelを使っていればmigrationを用いて<br /> <code>$table->dropColumn(‘hoge’);</code><br /> 一行でできます。</p> ケイジ tag:crieit.net,2005:PublicArticle/15510 2019-10-27T15:10:52+09:00 2019-10-30T22:31:59+09:00 https://crieit.net/posts/1fed4cfffba68fc79557e060d24d5a18 個人開発でサイトリリースした理由とやったこと <p><a href="https://crieit.now.sh/upload_images/5b0212fe52d73d692cba535132678c2c5db990c43e5a5.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b0212fe52d73d692cba535132678c2c5db990c43e5a5.jpg?mw=700" alt="個人開発でサイトリリースした理由とやったこと" /></a><br /> こんにちは。ケイジです。<br /> ボルジムというボルダリングジム検索ウェブサービスを制作し運営しています。</p> <p>今回、このサービスをリリースするまでにやったことを綴ることによって、誰かの役に立つのではと思い記事を書きました。</p> <h1 id="なぜ創ったか"><a href="#%E3%81%AA%E3%81%9C%E5%89%B5%E3%81%A3%E3%81%9F%E3%81%8B">なぜ創ったか</a></h1> <p>この数年、ボルダリング沼にとっぷりと浸かってしまい、週イチの社内ボルダリング部活動と週末の自主活動の週2回ボル活をするのが習慣になっています。たまにボルダリングができない週末があると気持ちも体も落ち着かずムズムズし週明けの仕事に差し支えるまでに。</p> <p>また個人開発もしばらくご無沙汰していて何か創りたい欲がフツフツと湧いてきたタイミングでもあったので、エイヤ!とボルダリングジム検索ウェブサービスを創りました。<br /> 首都圏近郊のボルダリングジムの口コミなどの情報が閲覧、投稿できるウェブサービスになります。</p> <p><a target="_blank" rel="nofollow noopener" href="https://boulgym.com/?ref=crieit">ボルジム・首都圏近郊ボルダリングジム検索サイト</a></p> <p>好きなこと ✕ 好きなことの掛け合わせだから、それはそれは楽しいだろうと。</p> <h1 id="やったことリスト"><a href="#%E3%82%84%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8%E3%83%AA%E3%82%B9%E3%83%88">やったことリスト</a></h1> <p>ボルジムを立ち上げるために、したことは以下のようになります。</p> <h2 id="プログラミング言語、フレームワークを選ぶ"><a href="#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E8%A8%80%E8%AA%9E%E3%80%81%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%92%E9%81%B8%E3%81%B6">プログラミング言語、フレームワークを選ぶ</a></h2> <p><a href="https://crieit.now.sh/upload_images/fe96233f19a7998d16e424d92bfc351c5db53460a28f2.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/fe96233f19a7998d16e424d92bfc351c5db53460a28f2.jpg?mw=700" alt="logos01.jpg" /></a></p> <p>以前はCakePHPでの開発経験はあったものの、ここ数年はWordPressのカスタマイズをメインに仕事をしているため、どの言語とフレームワークで開発するのかをまず選定しました。</p> <p>候補に挙がったのは以下のフレームワークになります。<br /> ※カッコ内はプログラミング言語</p> <ul> <li>Laravel(PHP)</li> <li>WordPress(PHP)</li> <li>Ruby on Rails(Ruby)</li> <li>Django(Python)</li> </ul> <p>選定にあたってはGoogleトレンドでまずざっくりと最近の検索結果の多いものをリストアップしました。</p> <p>また、求人が多いフレームワークは今勢いのあるものだろうと推測し、国内と海外のリクルートサイトで求人数の多いもので絞り込みもしました。</p> <p>結果、学習期間にあまり時間を使いたくないということで個人的なPHP慣れもある、LaravelとWordPressの二択になったのですが、開発の自由度の高いLaravelに決定しました。</p> <h1 id="環境づくり"><a href="#%E7%92%B0%E5%A2%83%E3%81%A5%E3%81%8F%E3%82%8A">環境づくり</a></h1> <p><a href="https://crieit.now.sh/upload_images/5396069f54a02c0404723081c571a00a5db5348995da3.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5396069f54a02c0404723081c571a00a5db5348995da3.jpg?mw=700" alt="logos02.jpg" /></a><br /> 環境づくりはできるだけ仕事で使い慣れているものを選択。</p> <h2 id="ローカル開発環境"><a href="#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83">ローカル開発環境</a></h2> <p>Vagrant一択でした。</p> <h2 id="ソースコード管理"><a href="#%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E7%AE%A1%E7%90%86">ソースコード管理</a></h2> <p>BitbucketでGitコードを管理。<br /> 現在はGitHubでプライベートリポジトリを作成できるとのことなので今選ぶならそっちになるかもしれません。</p> <h2 id="ウェブサーバ"><a href="#%E3%82%A6%E3%82%A7%E3%83%96%E3%82%B5%E3%83%BC%E3%83%90">ウェブサーバ</a></h2> <p>さくらのレンタルサーバを使ってます。</p> <h2 id="サイトデザイン"><a href="#%E3%82%B5%E3%82%A4%E3%83%88%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3">サイトデザイン</a></h2> <p>いつも使い慣れているものを使いました。<br /> パーツ、ロゴやイラスト制作は、Adobe IllustratorとPhotoshop。<br /> サイト全体とUIデザインにはSketchを使用。</p> <h1 id="学習方法"><a href="#%E5%AD%A6%E7%BF%92%E6%96%B9%E6%B3%95">学習方法</a></h1> <h2 id="Laravel学習法"><a href="#Laravel%E5%AD%A6%E7%BF%92%E6%B3%95">Laravel学習法</a></h2> <p>Laravelについてはまったく触ったことがなかったので、ドットインストールのプレミアム会員になり、動画学習しました。</p> <p>まず、Laravelをローカル環境で使うにあたって必要な<a target="_blank" rel="nofollow noopener" href="https://dotinstall.com/lessons/basic_sqlite_v2">SQLite入門</a>をざっと一周します。</p> <p>次に、<a target="_blank" rel="nofollow noopener" href="https://dotinstall.com/lessons/basic_laravel_v2">Laravel 5.5入門レッスン</a>をざっと1周して概要をつかみ、次に実際にローカル環境で手を動かしてチュートリアルウェブアプリをつくりながらもう1周しました。</p> <p>ドットインストールは構成が初学者にとって非常に分かりやすく、動画一つが3分前後ととっつきやすくておすすめです。<br /> Vagrantについても最初はこのサイトで学習しました。</p> <p>プレミアム会員になると女性ボイスが選択できるのと、再生スピードが変更できるのが個人的に良い機能だと思います。</p> <p>という感じでざっくりとLaravelについて理解が深まったところで、<a target="_blank" rel="nofollow noopener" href="https://laravel10.wordpress.com/category/%e3%81%af%e3%81%98%e3%82%81%e3%81%a6%e3%81%ae-laravel/">ララ帳「はじめてのLaravel」</a>を参考にしつつ、実際にウェブサイト開発をしていきました。</p> <p>分からないところは随時ググりながら進めました。<br /> 頻繁に使ったのは<a target="_blank" rel="nofollow noopener" href="https://readouble.com/laravel/">readouble</a>(日本語版Laravelドキュメンテーション)になります。翻訳してくれている有志の方々ありがとう。</p> <p>英語に抵抗がなければ<a target="_blank" rel="nofollow noopener" href="https://laravel.com/docs/6.x">laravel documentation</a>の方が情報も早く、デザインも見やすいためおすすめです。</p> <h1 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h1> <p>という感じのことを、休日や出勤前や後に細々と続けていき大体6ヶ月ほどかけてリリースまでこぎつけました。</p> <p>この記事が少しでも読まれた誰かの一助になれば幸いです。</p> <p>では、よいものづくりライフを!</p> ケイジ tag:crieit.net,2005:PublicArticle/15448 2019-10-03T09:28:32+09:00 2019-10-03T09:28:32+09:00 https://crieit.net/posts/VB-NET-DataGridView-5d9540b08ea4f 【VB.NET】DataGridView の行を動的に追加してみる <p>引き続き、DataGridView関連の話し。<br /> 今回は行を動的に追加する方法を試してみてました。</p> <p>プログラムは前回のものを流用します。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/12/27/post-3564/" target="_blank" rel="noopener noreferrer" data-blogcard="1">【VB.NET】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%E5%87%A6%E7%90%86">追加処理</a></h3> <p>追加ボタンが押されたタイミングで、データグリッドビューの空行を作成して追加します。</p> <p>今回は、1行追加したら「更新」するまで追加できないようにしました。<br /> (グローバルでフラグを持たせています)</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 SQLiteConnection("Data Source=SampleDb.sqlite") ' データベースオープン conn.Open() ' データ追加 Using con As New DataContext(conn) Dim idx = dgvCat.Rows.Count() ' 行追加 dgvCat.Rows.Add() ' 種別マスタ取得 Dim kinds As Table(Of Kind) = con.GetTable(Of Kind) Dim kindResult As IQueryable(Of Kind) = From x In kinds Order By x.KindCd Select x Dim kindList = kindResult.ToList() ' 猫一覧取得 Dim tblCat As Table(Of Cat) = con.GetTable(Of Cat) Dim newNo = 0 ' 使用できるNoを判定 For i As Integer = 1 To tblCat.ToList().Count() + 1 Dim selectNo = i If tblCat.SingleOrDefault(Function(x As Cat) x.No = selectNo) Is Nothing 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>更新処理では、データグリッドビューから直接値を取得して、データベースに存在しなければ追加、存在すれば更新というようにしました。</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 As New SQLiteConnection("Data Source=SampleDb.sqlite") ' データベースオープン conn.Open() ' データ更新 Using con As New DataContext(conn) ' 対象のテーブルオブジェクトを取得 Dim Table = con.GetTable(Of Cat) ' 選択されているデータを取得 For i = 0 To dgvCat.Rows.Count - 1 ' テーブルから対象のデータを取得 Dim no As Integer = dgvCat(0, i).Value Dim target As Cat = Table.SingleOrDefault(Function(x As Cat) x.No = no) If (target Is Nothing) Then ' データ作成 Dim Cat As New Cat() Cat.No = dgvCat(0, i).Value Cat.Name = dgvCat(1, i).Value Cat.Sex = dgvCat(2, i).Value Cat.Age = dgvCat(3, i).Value Cat.KindCd = dgvCat(4, i).Value Cat.Favorite = dgvCat(5, i).Value Table.InsertOnSubmit(Cat) Else ' データ変更 target.Name = dgvCat(1, i).Value target.Sex = dgvCat(2, i).Value target.Age = dgvCat(3, i).Value target.KindCd = dgvCat(4, i).Value target.Favorite = dgvCat(5, i).Value End If ' DBの変更を確定 con.SubmitChanges() Next End Using conn.Close() End Using AddRowFlg = False ' データ再検索 search() MessageBox.Show("データを更新しました。") 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/2017/12/VbSQLite5_000.jpg" alt="検索後" /></p> <p>とりあえず検索します。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite5_001.jpg" alt="追加ボタンクリック" /></p> <p>追加ボタンをクリックすると空行が追加されます。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite5_002.jpg" alt="データを入力" /></p> <p>追加された行に値を設定します。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite5_003.jpg" alt="更新ボタンをクリック" /></p> <p>更新ボタンをクリック、再検索後の画面が表示されます。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>年の瀬となり忙しくなってまいりましたが<br /> なんとか更新していきたいと思います。</p> <p>次回はどうしようかな。。</p> <p>ではでは。</p> doraxdora tag:crieit.net,2005:PublicArticle/15446 2019-10-02T09:28:19+09:00 2019-10-02T09:28:19+09:00 https://crieit.net/posts/VB-NET-DataGridView 【VB.NET】DataGridView を直接変更してデータを更新してみる <p>今回は、データグリッドビューの内容を直接編集してデータを更新したいと思います。</p> <p>プログラムは例によって前回のものを。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/12/24/post-3541/" target="_blank" rel="noopener noreferrer" data-blogcard="1">【VB.NET】SQLiteに接続してデータを登録してみる</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="#%E6%A4%9C%E7%B4%A2%E5%87%A6%E7%90%86">検索処理</a></h3> <p>取得したデータを走査して1行ずつ作成していくように変更します。</p> <pre><code> ''' <summary> ''' 検索処理 ''' </summary> Private Sub search() Using conn As New SQLiteConnection("Data Source=SampleDb.sqlite") ' データベースオープン conn.Open() ' 検索条件を指定してデータを取得 Using con As New DataContext(conn) Dim searchName As String = txtName.Text Dim searchKind As String = CType(cmbKind.SelectedValue, Kind).KindCd ' 種別マスタ取得 Dim kinds As Table(Of Kind) = con.GetTable(Of Kind) Dim kindResult As IQueryable(Of Kind) = From x In kinds Order By x.KindCd Select x Dim kindList = kindResult.ToList() ' 猫一覧取得 Dim tblCat As Table(Of Cat) = con.GetTable(Of Cat) Dim result As IQueryable(Of Cat) If (searchKind = "") Then ' 種別が選択されていなければ名前のみ前方一致指定 result = From x In tblCat Where x.Name.StartsWith(searchName) Order By x.No Select x Else ' 種別が選択されていれば名前+種別で検索 result = From x In tblCat Where x.Name.StartsWith(searchName) &amp; x.KindCd = searchKind Order By x.No Select x End If ' データグリッドビューに設定 'dgvCat.DataSource = result.ToList() Dim list As List(Of Cat) = result.ToList() Dim i As Integer = 0 dgvCat.Rows.Clear() For i = 0 To list.Count() - 1 Dim cat = list(i) ' 行追加 dgvCat.Rows.Add() ' No(プライマリなので編集不可) Dim no = New DataGridViewTextBoxCell() no.Value = cat.No dgvCat(0, i) = no dgvCat(0, i).ReadOnly = True ' 名前 Dim name = New DataGridViewTextBoxCell() name.Value = cat.Name dgvCat(1, i) = name ' 性別 Dim sex = New DataGridViewComboBoxCell() sex.Items.AddRange({"♂", "♀"}) sex.DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox dgvCat(2, i) = sex dgvCat(2, i).Value = cat.Sex ' 年齢 Dim age = New DataGridViewTextBoxCell() age.Value = cat.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 = cat.KindCd ' 好物 Dim favorite = New DataGridViewTextBoxCell() favorite.Value = cat.Favorite dgvCat(5, i) = favorite Next End Using ' データベースクローズ conn.Close() End Using End Sub </code></pre> <p> </p> <h3 id="更新処理"><a href="#%E6%9B%B4%E6%96%B0%E5%87%A6%E7%90%86">更新処理</a></h3> <p>データグリッドビューに表示されているデータを全て更新するように変更します。</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 As New SQLiteConnection("Data Source=SampleDb.sqlite") ' データベースオープン conn.Open() ' データ更新 Using con As New DataContext(conn) ' 対象のテーブルオブジェクトを取得 Dim Table = con.GetTable(Of Cat) ' 選択されているデータを取得 For i = 0 To dgvCat.Rows.Count - 1 ' テーブルから対象のデータを取得 Dim no As Integer = dgvCat(0, i).Value Dim target As Cat = Table.Single(Function(x As Cat) x.No = no) ' データ変更 target.Name = dgvCat(1, i).Value target.Sex = dgvCat(2, i).Value target.Age = dgvCat(3, i).Value target.KindCd = dgvCat(4, i).Value target.Favorite = dgvCat(5, i).Value ' DBの変更を確定 con.SubmitChanges() Next End Using conn.Close() End Using ' データ再検索 search() MessageBox.Show("データを更新しました。") End Sub </code></pre> <h3 id="コンボボックスの制御"><a href="#%E3%82%B3%E3%83%B3%E3%83%9C%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AE%E5%88%B6%E5%BE%A1">コンボボックスの制御</a></h3> <p>以前の記事でもやりましたが、<br /> コンボボックスがワンクリックで開くように仕込みを入れます。</p> <pre><code> ''' <summary> ''' データグリッドビューセルクリックイベント ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Sub dgvCat_CellClick(sender As Object, e As DataGridViewCellEventArgs) Handles dgvCat.CellClick Dim dgv As DataGridView = CType(sender, DataGridView) gridComboHandle(dgv, e) End Sub ''' <summary> ''' データグリッドビューのコンボボックス制御 ''' </summary> ''' <param name="dgv"></param> ''' <param name="e"></param> Private Sub gridComboHandle(dgv As DataGridView, e As DataGridViewCellEventArgs) ' 対象の列だった場合 If dgv.Columns(e.ColumnIndex).Name = "ColSex" Or dgv.Columns(e.ColumnIndex).Name = "ColKind" Then SendKeys.SendWait("{F4}") End If End Sub </code></pre> <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/12/VbSQLite4_000.jpg" alt="データ検索後" /></p> <p>データ検索後の画面。<br /> 無事にデータが表示され、データグリッドビューにコンボボックスも表示されています。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite4_001.jpg" alt="データ変更" /></p> <p>1行目の種別を変更して、「更新」ボタンをクリックします。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite4_002.jpg" alt="データ変更後" /></p> <p>無事に変更できました。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>やり方としてはいまいちで、WPFみたいにMVVMとかバインディングとかできるかと思いますが、これはこれでもしかしたら需要があるかもしれません。</p> <p>次回は追加処理をしてみます。</p> <p>ではでは。</p> doraxdora tag:crieit.net,2005:PublicArticle/15439 2019-09-30T09:58:53+09:00 2019-09-30T09:58:53+09:00 https://crieit.net/posts/VB-NET-SQLite-5d91534d804b7 【VB.NET】SQLiteに接続してデータを登録してみる <p>VBでSQLite、データ検索までしたので続いて登録、更新、削除をしてみました。<br /> C#からの移植ではありますが、微妙に違うところもありますね。</p> <p>プログラムは前回のものを流用します。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/12/22/post-3533/" target="_blank" rel="noopener noreferrer" data-blogcard="1">【VB.NET】SQLiteからデータを取得して表示する</a></p> <h2 id="画面の修正"><a href="#%E7%94%BB%E9%9D%A2%E3%81%AE%E4%BF%AE%E6%AD%A3">画面の修正</a></h2> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite3_000-1.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="#%E6%A4%9C%E7%B4%A2%E5%87%A6%E7%90%86%E3%81%AE%E4%BF%AE%E6%AD%A3">検索処理の修正</a></h3> <p>追加更新削除後に再検索するため、別メソッドに切り出します。</p> <pre><code> ''' <summary> ''' 検索ボタンクリックイベント. ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click search() End Sub ''' <summary> ''' 検索処理 ''' </summary> Private Sub search() Using conn As New SQLiteConnection("Data Source=SampleDb.sqlite") ' データベースオープン conn.Open() ' 検索条件を指定してデータを取得 Using con As New DataContext(conn) Dim searchName As String = txtName.Text Dim searchKind As String = CType(cmbKind.SelectedValue, Kind).KindCd Dim tblCat As Table(Of Cat) = con.GetTable(Of Cat) Dim result As IQueryable(Of Cat) If (searchKind = "") Then ' 種別が選択されていなければ名前のみ前方一致指定 result = From x In tblCat Where x.Name.StartsWith(searchName) Order By x.No Select x Else ' 種別が選択されていれば名前+種別で検索 result = From x In tblCat Where x.Name.StartsWith(searchName) &amp; x.KindCd = searchKind Order By x.No Select x End If ' データグリッドビューに設定 dgvCat.DataSource = result.ToList() End Using ' データベースクローズ conn.Close() End Using End Sub </code></pre> <h3 id="データ追加"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E8%BF%BD%E5%8A%A0">データ追加</a></h3> <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 Using conn As New SQLiteConnection("Data Source=SampleDb.sqlite") ' データベースオープン conn.Open() ' データ追加 Using con As New DataContext(conn) ' 対象のテーブルオブジェクトを取得 Dim Table = con.GetTable(Of Cat) ' データ作成 Dim Cat As New Cat() Cat.No = 5 Cat.Name = "こなつ" Cat.Sex = "&#x2640;" Cat.Age = 7 Cat.KindCd = "01" Cat.Favorite = "布団" ' データ追加 Table.InsertOnSubmit(Cat) ' DBの変更を確定 con.SubmitChanges() End Using conn.Close() End Using ' データ再検索 search() MessageBox.Show("データを追加しました。") End Sub </code></pre> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite3_003.jpg" alt="データ追加" /></p> <p>追加ボタンをクリックすると、固定ではありますがデータが追加されます。</p> <h3 id="データ更新"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E6%9B%B4%E6%96%B0">データ更新</a></h3> <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 As New SQLiteConnection("Data Source=SampleDb.sqlite") ' データベースオープン conn.Open() ' データ更新 Using con As New DataContext(conn) ' 対象のテーブルオブジェクトを取得 Dim Table = con.GetTable(Of Cat) ' 選択されているデータを取得 For Each r As DataGridViewRow In dgvCat.SelectedRows Dim Cat As Cat = CType(dgvCat.DataSource(), List(Of Cat)).Item(r.Index) ' テーブルから対象のデータを取得 Dim target As Cat = Table.Single(Function(x As Cat) x.No = Cat.No) ' データ変更 target.Favorite = "高いところ" ' DBの変更を確定 con.SubmitChanges() Next End Using conn.Close() End Using ' データ再検索 search() MessageBox.Show("データを更新しました。") End Sub </code></pre> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite3_004.jpg" alt="データ修正" /></p> <p>更新ボタンをクリックすると、選択された行の好物が更新されます。</p> <h3 id="データ削除"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E5%89%8A%E9%99%A4">データ削除</a></h3> <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 As New SQLiteConnection("Data Source=SampleDb.sqlite") ' データベースオープン conn.Open() ' データ削除 Using con As New DataContext(conn) ' 対象のテーブルオブジェクトを取得 Dim Table = con.GetTable(Of Cat) ' 選択されているデータを取得 For Each r As DataGridViewRow In dgvCat.SelectedRows Dim Cat As Cat = CType(dgvCat.DataSource(), List(Of Cat)).Item(r.Index) ' テーブルから対象のデータを取得 Dim target As Cat = Table.Single(Function(x As Cat) x.No = Cat.No) ' データ削除 Table.DeleteOnSubmit(target) ' DBの変更を確定 con.SubmitChanges() Next End Using conn.Close() End Using ' データ再検索 search() MessageBox.Show("データを削除しました。") End Sub </code></pre> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite3_005.jpg" alt="データ削除" /></p> <p>削除ボタンをクリックすると、選択されている行のデータが削除されます。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>とりあえず簡単ではありますが追加、更新、削除ができました。</p> <p>次回はデータグリッドを直接編集してデータの更新等が行えるようにしたいと思います。</p> <p>ではでは。</p> <p> </p> doraxdora tag:crieit.net,2005:PublicArticle/15429 2019-09-27T13:09:52+09:00 2019-09-27T13:09:52+09:00 https://crieit.net/posts/VB-NET-SQLite-5d8d8b90b1414 【VB.NET】SQLiteに接続してデータを取得してみる <p>今回はデータベースから取得したデータを表示したいと思います。</p> <p>プログラムは前回のものを流用します。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/12/21/post-3519/" target="_blank" rel="noopener noreferrer" data-blogcard="1">【VB.NET】SQLiteのデータベース及びテーブルを動的に作成してみる</a></p> <h2 id="データの追加"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E8%BF%BD%E5%8A%A0">データの追加</a></h2> <p>前回作成したテーブルにデータをツール、もしくはコマンドラインから追加します。</p> <pre><code> INSERT INTO MSTKIND VALUES ("01", "キジトラ"); INSERT INTO MSTKIND VALUES ("02", "長毛種(不明)"); INSERT INTO MSTKIND VALUES ("03", "ミケ(っぽい)"); INSERT INTO MSTKIND VALUES ("04", "サビ"); INSERT INTO MSTKIND VALUES ("09", "その他"); INSERT INTO TBLCAT VALUES('1','そら','♂','6','01','犬の人形'); INSERT INTO TBLCAT VALUES('2','りく','♂','5','02','人間'); INSERT INTO TBLCAT VALUES('3','うみ','♀','4','03','高級ウェットフード'); INSERT INTO TBLCAT VALUES('4','こうめ','♀','2','04','横取りフード'); </code></pre> <p>追加したデータを確認します。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite2_001.jpg" alt="A5m2SQLで確認" /></p> <h2 id="Linq で データを取得、設定する"><a href="#Linq+%E3%81%A7+%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E5%8F%96%E5%BE%97%E3%80%81%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B">Linq で データを取得、設定する</a></h2> <h3 id="参照の追加"><a href="#%E5%8F%82%E7%85%A7%E3%81%AE%E8%BF%BD%E5%8A%A0">参照の追加</a></h3> <p>ソリューション・エクスプローラーからプロジェクトの「参照」を右クリックし、「参照の追加」を選択します。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite2_000-1.jpg" alt="参照の追加" /></p> <p>「System.Data.Linq」で検索、チェックしてOKボタンをクリックします。</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="#%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88%E5%AE%A3%E8%A8%80%E3%81%AE%E8%BF%BD%E5%8A%A0">インポート宣言の追加</a></h3> <p>次の記述を先頭に追加します。</p> <pre><code> Imports System.Data.Linq </code></pre> <h3 id="フォームロード時にコンボボックスにデータを設定する"><a href="#%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%83%AD%E3%83%BC%E3%83%89%E6%99%82%E3%81%AB%E3%82%B3%E3%83%B3%E3%83%9C%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AB%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B">フォームロード時にコンボボックスにデータを設定する</a></h3> <p>次の記述をフォームロード時のイベントメソッドに追加します。</p> <pre><code> ' 種別コンボボックスの内容をデータベースから取得して設定 Using con As New DataContext(conn) Dim kinds As Table(Of Kind) = con.GetTable(Of Kind) Dim result As IQueryable(Of Kind) = From x In kinds Order By x.KindCd Select x Dim empty As New Kind() empty.KindCd = "" empty.KindName = "指定なし" Dim list = result.ToList() list.Insert(0, empty) ' コンボボックスに設定 cmbKind.DataSource = list cmbKind.DisplayMember = "kindName" End Using </code></pre> <h3 id="検索ボタンのクリックイベント追加"><a href="#%E6%A4%9C%E7%B4%A2%E3%83%9C%E3%82%BF%E3%83%B3%E3%81%AE%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E8%BF%BD%E5%8A%A0">検索ボタンのクリックイベント追加</a></h3> <p>デザイナー画面にて配置した「検索」ボタンをダブルクリックします。</p> <p>検索処理の実装</p> <p>自動生成されたメソッドに次の処理を記述します。</p> <pre><code> ''' <summary> ''' 検索ボタンクリックイベント. ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click Using conn As New SQLiteConnection("Data Source=SampleDb.sqlite") ' データベースオープン conn.Open() ' 検索条件を指定してデータを取得 Using con As New DataContext(conn) Dim searchName As String = txtName.Text Dim searchKind As String = CType(cmbKind.SelectedValue, Kind).KindCd Dim tblCat As Table(Of Cat) = con.GetTable(Of Cat) Dim result As IQueryable(Of Cat) If (searchKind = "") Then ' 種別が選択されていなければ名前のみ前方一致指定 result = From x In tblCat Where x.Name.StartsWith(searchName) Order By x.No Select x Else ' 種別が選択されていれば名前+種別で検索 result = From x In tblCat Where x.Name.StartsWith(searchName) &amp; x.KindCd = searchKind Order By x.No Select x End If ' データグリッドビューに設定 dgvCat.DataSource = result.ToList() End Using ' データベースクローズ conn.Close() End Using End Sub </code></pre> <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/12/VbSQLite009-1.jpg" alt="起動後の画面" /></p> <p>起動後の画面。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite010.jpg" alt="コンボボックスにデータ設定" /></p> <p>コンボボックスに無事にデータが設定されました。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite2_003.jpg" alt="検索した結果" /></p> <p>検索ボタンをクリックして、データグリッドビューにも無事にデータが表示されました。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>C#とほぼ同じなので難しくはないですね。</p> <p>次回はデータの追加・更新・削除なんかもやってみます。</p> <p>ではでは。</p> <p> </p> doraxdora tag:crieit.net,2005:PublicArticle/15423 2019-09-25T09:35:38+09:00 2019-09-25T09:35:38+09:00 https://crieit.net/posts/VB-NET-SQLite 【VB.NET】SQLiteのデータベース及びテーブルを動的に作成してみる <p>今回はSQLiteを使ったプログラムを作っていきます。</p> <p>プログラムは前回のものを流用しませんが、WPFのものを移植する感じになります。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/06/09/post-1184/" target="_blank" rel="noopener noreferrer">【WPF】SQLiteを使ってデータを DataGrid に表示してみる</a></p> <h2 id="SQLiteのパッケージをインストール"><a href="#SQLite%E3%81%AE%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">SQLiteのパッケージをインストール</a></h2> <p>SQLite を利用するためのパッケージをインストールします。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite000.jpg" alt="NuGetパッケージ管理" /></p> <p>ソリューションエクスプローラーからプロジェクトを選択、右クリックし「Nuget パッケージの管理」を選択します。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite001.jpg" alt="パッケージの追加" /></p> <p>Nuget パッケージ管理画面が表示されるので、検索窓に「SQLite」を入力し「System.Data.SQLite」を選択、インストールボタンをクリックします。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite002.jpg" alt="確認ダイアログ" /></p> <p>変更の確認ダイアログが表示されるので、「OK」ボタンをクリックします。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite003.jpg" alt="出力ビュー" /></p> <p>出力ビューに「終了」が出力されればOKです。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite004.jpg" alt="ソリューションエクスプローラー" /></p> <p>ソリューションエクスプローラーの「参照」を開くと「System.Data.*」が追加されていることが確認できます。</p> <h2 id="ビルドを実行する"><a href="#%E3%83%93%E3%83%AB%E3%83%89%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B">ビルドを実行する</a></h2> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite005.jpg" alt="リビルドの実行" /></p> <p>上部メニューの「ビルド」>「ソリューションのリビルド」を選択します。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite006.jpg" alt="ファイルが出力される" /></p> <p>プロジェクトのディレクトリ>bin>Target>x86 、x64 に「SQLite.Interop.dll」が出力されていることが確認できます。</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="#%E3%83%AD%E3%83%BC%E3%83%89%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%81%AE%E8%BF%BD%E5%8A%A0">ロードイベントの追加</a></h3> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite007.jpg" alt="フォームのプロパティ" /></p> <p>デザイナー画面を開き、フォームを選択した状態でプロパティウィンドウの「Load」欄をダブルクリックします。</p> <p>次のようにプログラムが自動生成されます。</p> <p>Form1.vb</p> <pre><code> Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load End Sub End Class </code></pre> <h3 id="フォームロード時にテーブルを作成する"><a href="#%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%83%AD%E3%83%BC%E3%83%89%E6%99%82%E3%81%AB%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">フォームロード時にテーブルを作成する</a></h3> <p>フォームロード(アプリケーション起動時)に、テーブルを作成するようにプログラムを修正、<br /> 先程追加したイベントに次の処理を記述します。</p> <p>Form1.vb</p> <pre><code> Imports System.Data.SQLite Public Class Form1 ''' <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 SQLiteConnection("Data Source=SampleDb.sqlite") ' データベースオープン conn.Open() ' テーブルが存在しなければ作成 Using command = conn.CreateCommand() Dim sb As New System.Text.StringBuilder() sb.Append("CREATE TABLE IF NOT EXISTS MSTKIND (") sb.Append(" KIND_CD NCHAR NOT NULL") sb.Append(" , KIND_NAME NVARCHAR") 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 INT NOT NULL") sb.Append(" , NAME NVARCHAR NOT NULL") sb.Append(" , SEX NVARCHAR NOT NULL") sb.Append(" , AGE INT DEFAULT 0 NOT NULL") sb.Append(" , KIND_CD NCHAR DEFAULT 0 NOT NULL") sb.Append(" , FAVORITE NVARCHAR") sb.Append(" , primary key (NO)") sb.Append(")") command.CommandText = sb.ToString() command.ExecuteNonQuery() End Using ' データベースクローズ conn.Close() End Using End Sub End Class </code></pre> <h2 id="アプリケーションの実行"><a href="#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E5%AE%9F%E8%A1%8C">アプリケーションの実行</a></h2> <p>上部メニューの開始ボタンをクリックし、アプリケーションを実行すると「MSTKIND」、「TBLCAT」テーブルが作成されます。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/12/VbSQLite008.jpg" alt="A5m2で確認" /></p> <p>A5m2などを使って確認をしてください。</p> <p>以上でひとまず完了です。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>少し長くなったのでここまにして続きは次回にします。</p> <p>次回はコンボボックス、データグリッドにデータベースから取得したデータを表示したいと思います。</p> <p>ではでは。</p> doraxdora tag:crieit.net,2005:PublicArticle/15237 2019-07-14T11:29:04+09:00 2019-07-14T11:29:04+09:00 https://crieit.net/posts/WPF-SQLite-5d2a9370e7173 【WPF】SQLiteのインサートが遅いので速度アップの方法を試してみる <p>SQLiteって手軽で便利なんですが、<br /> 大量のデータを扱うとなるとそれなりに速度が気になってきます。</p> <p>試しに何万件のインサート処理をやってみたら案の定とても待ち切れる時間で処理が終わらなかったのでちょっと調べてみました。</p> <p>プログラムは次の記事のものを流用します。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/06/29/post-1354/" target="_blank" rel="noopener noreferrer" data-blogcard="1">【WPF】TextFieldParser で CSVファイルを読み込む</a></p> <h2 id="元の処理"><a href="#%E5%85%83%E3%81%AE%E5%87%A6%E7%90%86">元の処理</a></h2> <p>CSVを読み込んで一括登録する処理を変更してみます。</p> <p>MainWindow.xaml.cs</p> <pre><code> /// <summary> /// CSV読込ボタンクリックイベント. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void imp_button_Click(object sender, RoutedEventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); ofd.FileName = ""; ofd.DefaultExt = "*.csv"; if (ofd.ShowDialog() == false) { return; } List<Cat> list = readFile(ofd.FileName); // 接続 int count = 0; // データを追加する using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) { using (DataContext context = new DataContext(conn)) { foreach (Cat cat in list) { // 対象のテーブルオブジェクトを取得 var table = context.GetTable<Cat>(); // データが存在するかどうか判定 if (table.SingleOrDefault(x => x.No == cat.No) == null) { // データ追加 table.InsertOnSubmit(cat); // DBの変更を確定 context.SubmitChanges(); count++; } } } conn.Close(); } MessageBox.Show(count + " / " + list.Count + " 件 のデータを取り込みました。"); // データ再検索 searchData(); } </code></pre> <h2 id="プラグマステートメントを設定"><a href="#%E3%83%97%E3%83%A9%E3%82%B0%E3%83%9E%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88%E3%83%A1%E3%83%B3%E3%83%88%E3%82%92%E8%A8%AD%E5%AE%9A">プラグマステートメントを設定</a></h2> <p>まずはプラグマというステートメントを設定してみます。<br /> (プラグマステートメントとは、SQLite ライブラリの動作を変更するためのものです。)</p> <p>次の記事に色々と詳しく書いてありますので参考にしてみてください。</p> <blockquote> <p>参考 : <a target="_blank" rel="nofollow noopener" href="http://devlights.hatenablog.com/entry/2014/02/01/151642">http://devlights.hatenablog.com/entry/2014/02/01/151642</a></p> </blockquote> <pre><code> using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite;version=3;synchronous=Normal;journal mode=Wal")) { using (DataContext context = new DataContext(conn)) { foreach (Cat cat in list) { // 対象のテーブルオブジェクトを取得 var table = context.GetTable<Cat>(); // データが存在するかどうか判定 if (table.SingleOrDefault(x => x.No == cat.No) == null) { // データ追加 table.InsertOnSubmit(cat); // DBの変更を確定 context.SubmitChanges(); count++; } } } conn.Close(); } </code></pre> <p>接続文字列にオプションを追加する方式でやってみました。</p> <p>詳細に処理時間を載せませんが、処理速度の改善が見られました。</p> <h2 id="明示的にトランザクションを開始する"><a href="#%E6%98%8E%E7%A4%BA%E7%9A%84%E3%81%AB%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E9%96%8B%E5%A7%8B%E3%81%99%E3%82%8B">明示的にトランザクションを開始する</a></h2> <p>これは結構有名な話しですが、<br /> 一括処理する際にちゃんと明示的にトランザクションを制御しないと、<br /> 1件毎にそれなりに時間のかかるトランザクション処理が実行されてしまうので、明示的にトランザクションを開始するように変更します。</p> <pre><code> using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite;version=3;synchronous=Normal;journal mode=Wal")) { using (DataContext context = new DataContext(conn)) { using (var ts = conn.BeginTransaction()) { foreach (Cat cat in list) { // 対象のテーブルオブジェクトを取得 var table = context.GetTable<Cat>(); // データが存在するかどうか判定 if (table.SingleOrDefault(x => x.No == cat.No) == null) { // データ追加 table.InsertOnSubmit(cat); // DBの変更を確定 context.SubmitChanges(); count++; } } ts.Commit(); } } conn.Close(); } </code></pre> <p>これも処理速度アップとしては有効な手段でした。</p> <h2 id="Sqlコマンドに変更する"><a href="#Sql%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B">Sqlコマンドに変更する</a></h2> <p>データコンテキストを使って更新や追加を行うのも結構時間がかかるようなので、SQLクエリを直書きして実行するようにしてみます。</p> <pre><code> using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite;version=3;synchronous=Normal;journal mode=Wal")) { using (DataContext context = new DataContext(conn)) { using (var ts = conn.BeginTransaction()) { foreach (Cat cat in list) { using (SQLiteCommand cmd = conn.CreateCommand()) { // 対象のテーブルオブジェクトを取得 var table = context.GetTable<Cat>(); // データが存在するかどうか判定 if (table.SingleOrDefault(x => x.No == cat.No) == null) { String sql = @"INSERT INTO CAT (" + " NO" + ", NAME" + ", SEX" + ", AGE" + ", KIND_CD" + ", FAVORITE" + ") VALUES (" + cat.No + "', '" + cat.Name + "', '" + cat.Sex + "', '" + cat.Age + "', '" + cat.Kind + "', '" + cat.Favorite + "')"; // データ追加 cmd.CommandText = sql; cmd.ExecuteNonQuery(); count++; } } } ts.Commit(); } } conn.Close(); } </code></pre> <p>こちらも勿論速度アップしました。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>ちょっと殴り書きみたいな記事ですが、とりあえず上記のような対策を施すとひとまず何万件のインサートも問題なく実行できるかと思います。</p> <p>もっと劇的に速度アップさせる必要がある場合は、<br /> 登録するデータの存在確認をしないようにするといいと思います。<br /> (この場合はテーブル定義や、別で一時テーブルを用意するなど別途検討が必要となります)</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> ウラル tag:crieit.net,2005:PublicArticle/14883 2019-03-26T11:36:13+09:00 2019-03-27T10:35:50+09:00 https://crieit.net/posts/WPF-SQLite 【WPF】SQLiteに接続してデータを登録してみる <p>前回に引き続き、SQLiteネタです。<br /> やっぱりデータ検索をしたら登録、更新、削除までやらないとですよね。</p> <p>ということでプログラムは前回のものを流用します。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/06/09/post-1184/" target="_blank" rel="noopener" data-blogcard="1">【WPF】SQLite から Linq でデータを取得してDataGrid に表示してみる</a></p> <p>画面の修正<br /> まず、画面にボタンを追加します。</p> <p>MainWindow.xaml</p> <pre><code><DataGrid Name="dataGrid" HorizontalAlignment="Left" Margin="10,43,0,0" Width="497" Height="225" Style="{StaticResource grid-normal}"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding No}" ClipboardContentBinding="{x:Null}" Header="No" IsReadOnly="True" Width="50"/> <DataGridTextColumn Binding="{Binding Name}" ClipboardContentBinding="{x:Null}" Header="名前" IsReadOnly="True" Width="100"/> <DataGridTextColumn Binding="{Binding Sex}" ClipboardContentBinding="{x:Null}" Header="性別" IsReadOnly="True" Width="40"/> <DataGridTextColumn Binding="{Binding Age}" ClipboardContentBinding="{x:Null}" Header="年齢" IsReadOnly="True" Width="40"/> <DataGridTextColumn Binding="{Binding Kind}" ClipboardContentBinding="{x:Null}" Header="種別" IsReadOnly="True" Width="120"/> <DataGridTextColumn Binding="{Binding Favorite}" ClipboardContentBinding="{x:Null}" Header="好物" IsReadOnly="True" Width="*"/> </DataGrid.Columns> </DataGrid> <Button x:Name="add_button" Content="追加" HorizontalAlignment="Left" Margin="10,273,0,0" VerticalAlignment="Top" Width="75" Height="30" Click="add_button_Click"/> <Button x:Name="upd_button" Content="更新" HorizontalAlignment="Left" Margin="90,273,0,0" VerticalAlignment="Top" Width="75" Height="30" Click="upd_button_Click"/> <Button x:Name="del_button" Content="削除" HorizontalAlignment="Left" Margin="172,273,0,0" VerticalAlignment="Top" Width="75" Height="30" Click="del_button_Click"/> </code></pre> <p>データグリッドの高さを調整し、画面の下部にボタンを追加し、<br /> 更に追加したボタンにクリックイベントを追加。<br /> 検索処理の修正<br /> 追加、更新、削除後にデータを再検索するために、<br /> 検索処理を検索ボタンクリックイベントの処理から分離します。</p> <p>MainWindow.xaml.cs</p> <pre><code> /// <summary> /// 検索ボタンクリックイベント. /// /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void search_button_Click(object sender, RoutedEventArgs e) { logger.Info("検索ボタンクリック"); searchData(); } /// <summary> /// データ検索処理. /// </summary> private void searchData() { using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) { conn.Open(); // 猫データマスタを取得してコンボボックスに設定する using (DataContext con = new DataContext(conn)) { String searchName = this.search_name.Text; String searchKind = (this.search_kind.SelectedValue as Kind).KindCd; // データを取得 Table<Cat> tblCat = con.GetTable<Cat>(); // サンプルなので適当に組み立てる 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(); } conn.Close(); } } </code></pre> <p>データ追加(INSERT)<br /> 追加ボタンのクリックイベントに処理を実装します。</p> <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("追加ボタンクリック"); // 接続 using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) { conn.Open(); // データを追加する using (DataContext context = new DataContext(conn)) { // 対象のテーブルオブジェクトを取得 var table = context.GetTable<Cat>(); // データ作成 Cat cat = new Cat(); cat.No = 5; cat.Name = "こなつ"; cat.Sex = "&#x2640;"; cat.Age = 7; cat.Kind = "01"; cat.Favorite = "布団"; // データ追加 table.InsertOnSubmit(cat); // DBの変更を確定 context.SubmitChanges(); } conn.Close(); } // データ再検索 searchData(); MessageBox.Show("データを追加しました。"); } </code></pre> <p>今回は簡単に試してみたかったので<br /> フォームを用意せずに固定でデータを追加します。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSqliteIud000.jpg" alt="image" /></p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSqliteIud001.jpg" alt="image" /></p> <p>データ更新(UPDATE)</p> <p>更新ボタンのクリックイベントに処理を実装します。</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; } // 接続 using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) { conn.Open(); // データを追加する using (DataContext context = new DataContext(conn)) { // 対象のテーブルオブジェクトを取得 var table = context.GetTable<Cat>(); // 選択されているデータを取得 Cat cat = this.dataGrid.SelectedItem as Cat; // テーブルから対象のデータを取得 var target = table.Single(x => x.No == cat.No); // データ変更 target.Favorite = "高いところ"; // DBの変更を確定 context.SubmitChanges(); } conn.Close(); } // データ再検索 searchData(); MessageBox.Show("データを更新しました。"); } </code></pre> <p>今回は簡単に・・・(略</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSqliteIud002.jpg" alt="image" /></p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSqliteIud003.jpg" alt="image" /></p> <p>データ削除(DELETE)<br /> 削除ボタンのクリックイベントに処理を実装します。</p> <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; } // 接続 using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) { conn.Open(); // データを削除する using (DataContext context = new DataContext(conn)) { // 対象のテーブルオブジェクトを取得 var table = context.GetTable<Cat>(); // 選択されているデータを取得 Cat cat = this.dataGrid.SelectedItem as Cat; // テーブルから対象のデータを取得 var target = table.Single(x => x.No == cat.No); // データ削除 table.DeleteOnSubmit(target); // DBの変更を確定 context.SubmitChanges(); } conn.Close(); } // データ再検索 searchData(); MessageBox.Show("データを削除しました。"); } </code></pre> <p>削除は単純ですね。</p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSqliteIud004.jpg" alt="image" /></p> <p><img src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSqliteIud005.jpg" alt="image" /></p> <p>まとめ<br /> 前回に引き続き、LINQを使ってのデータ追加、更新、削除をしてみましたが<br /> 分かってしまえばとても楽に実装できるもんですね。</p> <p>もう少し複雑なことをやろうとしたら<br /> またそれなりに学ぶ必要がありそうですが、しばらくさわってみようと思います。</p> <p>ではでは。</p> doraxdora tag:crieit.net,2005:PublicArticle/14762 2019-01-30T14:19:12+09:00 2019-01-30T14:19:12+09:00 https://crieit.net/posts/WPF-SQLite-Linq-DataGrid 【WPF】SQLite から Linq でデータを取得してDataGrid に表示してみる <p>今日も引き続き WPF@C#の話しです。</p> <p>その他リソースの外部ファイル化をやろうかと思ったのですが、<br /> DataGridにちゃんとデータベースからデータを表示したかったのでちょっと寄り道。</p> <p>とりあえずサンプルなんかで無料のデータベースを使うのあれば SQLite で十分なので、<br /> SQLiteを使ってデータの検索、DataGridへの表示をやってみようと思います。</p> <p>プログラムは前回のものを修正します。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/06/08/post-1155/" target="_blank" rel="noopener noreferrer" data-blogcard="1">【WPF】スタイルをCSSみたいに外部ファイルに定義する</a></p> <p>まずは SQLite に接続するための準備から。</p> <p>Nuget でパッケージをダウンロード<br /> <a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite000.jpg"><img class="wp-image-1187 size-large" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite000-715x1024.jpg" alt="プロジェクトのコンテキストメニュー" /><br /> </a><br /> ソリューションエクスプローラーからプロジェクトを選択、右クリックし<br /> 「Nuget パッケージの管理」を選択します。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite001.jpg"><img class="wp-image-1188 size-large" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite001-1024x767.jpg" alt="Nuget パッケージ管理画面" /><br /> </a><br /> Nuget パッケージ管理画面が表示されるので、<br /> 検索窓に「SQLite」を入力し、「System.Data.SQLite」を選択、<br /> インストールボタンをクリックします。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite002.jpg"><img class="size-full wp-image-1189" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite002.jpg" alt="変更確認画面" /><br /> </a><br /> 変更の確認ダイアログが表示されるので、<br /> 「OK」ボタンをクリックします。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite003.jpg"><img class="size-full wp-image-1190" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite003.jpg" alt="出力ビュー" /><br /> </a></p> <p>出力ビューに「終了」が出力されればOKです。</p> <p><img class="size-full wp-image-1191" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite004.jpg" alt="参照の確認" /></p> <p>ソリューションエクスプローラーの「参照」を開き<br /> System.Data.* が追加されていることが確認できれば追加完了です。<br /> ビルド<br /> 一度ビルドを実行します。<br /> 上部メニューの「ビルド」>「ソリューションのリビルド」を選択します。</p> <p><img class="size-full wp-image-1192" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite005.jpg" alt="エクスプローラ" /></p> <p>プロジェクトのディレクトリ>bin>Target>x86 、x64 に「SQLite.Interop.dll」が<br /> 出力されていることを確認します。<br /> 起動時にテーブルを作成する<br /> プログラム修正<br /> アプリケーション起動時に、テーブルを作成するようにプログラムを修正します。</p> <p>MainWindows.xaml.cs</p> <pre><code> using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; 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; // SQLite利用のため追加 using System.Data.SQLite; namespace WpfApp1 { /// &lt;summary&gt; /// MainWindow.xaml の相互作用ロジック /// &lt;/summary&gt; public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); // SampleDb.sqlite を作成(存在しなければ) using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) { // データベースに接続 conn.Open(); // コマンドの実行 using (var command = conn.CreateCommand()) { // テーブルが存在しなければ作成する // 種別マスタ StringBuilder sb = new StringBuilder(); sb.Append("CREATE TABLE IF NOT EXISTS MSTKIND ("); sb.Append(" KIND_CD NCHAR NOT NULL"); sb.Append(" , KIND_NAME NVARCHAR"); 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 INT NOT NULL"); sb.Append(" , NAME NVARCHAR NOT NULL"); sb.Append(" , SEX NVARCHAR NOT NULL"); sb.Append(" , AGE INT DEFAULT 0 NOT NULL"); sb.Append(" , KIND_CD NCHAR DEFAULT 0 NOT NULL"); sb.Append(" , FAVORITE NVARCHAR"); sb.Append(" , primary key (NO)"); sb.Append(")"); command.CommandText = sb.ToString(); command.ExecuteNonQuery(); } // 切断 conn.Close(); } } } } </code></pre> <p> <br /> アプリケーションの実行<br /> 上部メニューの開始ボタンをクリックし、アプリケーションを実行すると<br /> 「MSTKIND」、「TBLCAT」テーブルが作成されます。</p> <p><img class="size-full wp-image-1193" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite006.jpg" alt="SQLite Manager" /></p> <p>SQLite のデータベースを操作できるツール等で、テーブルが作成されていることを確認します。</p> <p>今回は Firefox のアドオン「SQLite Manager」で確認しました。<br /> データの追加<br /> 次のデータをツール、もしくはコマンドラインから追加します。</p> <pre><code> INSERT INTO MSTKIND VALUES ("01", "キジトラ"); INSERT INTO MSTKIND VALUES ("02", "長毛種(不明)"); INSERT INTO MSTKIND VALUES ("03", "ミケ(っぽい)"); INSERT INTO MSTKIND VALUES ("04", "サビ"); INSERT INTO MSTKIND VALUES ("09", "その他"); INSERT INTO TBLCAT VALUES('1','そら','&#x2642;','6','01','犬の人形'); INSERT INTO TBLCAT VALUES('2','りく','&#x2642;','5','02','人間'); INSERT INTO TBLCAT VALUES('3','うみ','&#x2640;','4','03','高級ウェットフード'); INSERT INTO TBLCAT VALUES('4','こうめ','&#x2640;','2','04','横取りフード'); </code></pre> <p>今度はコマンドラインから追加してみました。</p> <p><img class="size-full wp-image-1195" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite008.jpg" alt="SQLite Manager" /></p> <p>更に SQLite Manager にて確認。<br /> Linq で データを取得、設定する<br /> Linqとは</p> <blockquote>オブジェクトやデータベース、データセット、エンティティ、XML文書など、アプリケーションで扱うさまざまなデータソースに対して、統一的な手段でアクセスするしくみ</blockquote> <p>Linq用パッケージの追加<br /> Linqを利用するため、ライブラリの追加を行います。</p> <p>ソリューションエクスプローラに表示されている「参照」を右クリックし<br /> 参照の追加画面を開きます。</p> <p><img class="size-full wp-image-1197" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite010.jpg" alt="参照の追加" /></p> <p>「System.Data.Linq」を検索し、表示されたものを選択、「OK」ボタンをクリックします。</p> <p>プログラム修正<br /> クラス追加<br /> 種別クラス</p> <p>Kind.cs</p> <pre><code><br />using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.Linq.Mapping; namespace WpfApp1 { [Table(Name = "mstkind")] public class Kind { [Column(Name = "kind_cd", IsPrimaryKey = true)] public String KindCd { get; set; } [Column(Name = "kind_name")] public String KindName { get; set; } } } </code></pre> <p>猫データクラス</p> <p>Cat.cs</p> <pre><code>using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.Linq.Mapping; namespace WpfApp1 { [Table(Name = "tblcat")] public class Cat { [Column(Name = "no", IsPrimaryKey = true)] public int No { get; set; } [Column(Name = "name")] public String Name { get; set; } [Column(Name = "sex")] public String Sex { get; set; } [Column(Name = "age")] public int Age { get; set; } [Column(Name = "kind_cd")] public String Kind { get; set; } [Column(Name = "favorite")] public String Favorite { get; set; } } } </code></pre> <p>宣言の追加<br /> MainWindow.xaml.cs</p> <p>Linqを利用するためにパッケージ利用宣言を追加<br /> (System.Data.Linq.Mapping)</p> <pre><code>省略 . . using System.Data.SQLite; using System.Data.Linq; // ←を追記する using System.Data.Linq.Mapping; // ←を追記する namespace WpfApp1 { 省略 } </code></pre> <p>データ取得処理の追加<br /> 「MainWindow」メソッドに次の記述を追加</p> <p>MainWindow.xaml.cs</p> <pre><code><br /> // 種別マスタを取得してコンボボックスに設定する using (DataContext con = new DataContext(conn)) { // データを取得 Table&lt;Kind&gt; mstKind = con.GetTable&lt;Kind&gt;(); IQueryable&lt;Kind&gt; result = from x in mstKind orderby x.KindCd select x; // 最初の要素は「指定なし」とする Kind empty = new Kind(); empty.KindCd = ""; empty.KindName = "指定なし"; var list = result.ToList(); list.Insert(0, empty); // コンボボックスに設定 this.search_kind.ItemsSource = list; this.search_kind.DisplayMemberPath = "KindName"; } </code></pre> <p>新規メソッドの追加(クリックイベント)</p> <pre><code><br /> /// &lt;summary&gt; /// 検索ボタンクリックイベント. /// /// &lt;/summary&gt; /// &lt;param name="sender"&gt;&lt;/param&gt; /// &lt;param name="e"&gt;&lt;/param&gt; private void search_button_Click(object sender, RoutedEventArgs e) { using (var conn = new SQLiteConnection("Data Source=SampleDb.sqlite")) { conn.Open(); // 猫データ一覧を取得して DataGrid に設定 using (DataContext con = new DataContext(conn)) { String searchName = this.search_name.Text; String searchKind = (this.search_kind.SelectedValue as Kind).KindCd; // データを取得 Table&lt;Cat&gt; tblCat = con.GetTable&lt;Cat&gt;(); // サンプルなので適当に組み立てる IQueryable&lt;Cat&gt; 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(); } conn.Close(); } } </code></pre> <p>ビューの修正<br /> 検索ボタンにクリックイベントを追加します。</p> <p>MainWindows.xaml</p> <pre><code><br /> &lt;Button x:Name="search_button" Content="検索" Margin="432,12,0,0" Style="{StaticResource btn-normal}" Click="search_button_Click"/&gt; </code></pre> <p>起動<br /> <img class="size-full wp-image-1198" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite011.jpg" alt="修正後画面" /><br /> 全件検索<br /> <img class="size-full wp-image-1199" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite012.jpg" alt="全件検索" /></p> <p>全データが表示されました。<br /> 名前指定検索<br /> <img class="size-full wp-image-1200" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite013.jpg" alt="名前指定検索" /></p> <p>名前を指定したので、そらのみ表示されました。<br /> 種別指定検索<br /> <img class="size-full wp-image-1201" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite014.jpg" alt="種別指定検索" /></p> <p>みけっぽい、「うみ」のみ表示されました。<br /> 存在しない組み合わせで検索<br /> <img class="size-full wp-image-1202" src="https://www.doraxdora.com/wp-content/uploads/2017/06/WpfSQLite015.jpg" alt="存在しない組み合わせ" /></p> <p>こうめはサビなので、なにも表示されません。<br /> まとめ</p> <p>ひとまず、DataGrid に表示できました。<br /> サンプルなのでソースはまあまあ適当ですが、なんとなく使い方がわかってきました。(実は Linq も初)</p> <p>日々新しい技術が出てくるのでついていくのが大変ですね。<br /> 頑張ります。</p> <p>次回(未定)は Log4net を使ってみたいと思います。</p> <p>ではでは。</p> <p>ソースはこちら</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/doraxdora/SampleWpfSQLite1" target="_blank" rel="noopener">GitHub</a></p> doraxdora