setStateで古いstateを見てしまった

2020/12/28 17:41


<p>基本的なところだけど、少し悩んでしまったのでメモ。</p> <p>アプリのタイマー機能を実装しようとした際、setIntervalを使用しても時間が加算されない問題が発生した。 原因としては、<b>setStateにオブジェクトを渡した場合、stateを即座にアップデートすることを保証しない</b>というもの。</p> <p><a href="https://ja.reactjs.org/docs/faq-state.html#how-do-i-update-state-with-values-that-depend-on-the-current-state">&#x30B3;&#x30F3;&#x30DD;&#x30FC;&#x30CD;&#x30F3;&#x30C8;&#x306E; state &ndash; React</a></p> <p>今回のような、setIntervalでsetStateが呼ばれるなど参照したいstateが絶えず更新されているものである場合、「setStateに更新用の関数を渡し、その関数内の引数で最新のstateにアクセスして操作する」のが正しい実装内容となる。</p> <h4>駄目な例</h4> <p>下記の例だと、<code>setState</code>にtimeに1を足した値を引数として渡しているが、この時にtimeが参照しているのは更新前のtimeである。 つまり、0秒から時間の加算が始まったとして、</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink> <span class="synComment">// timeは親のコンポーネントからもらってくる数値</span> <span class="synStatement">const</span> <span class="synIdentifier">[</span>time, setTime<span class="synIdentifier">]</span> = useState(props.time || <span class="synStatement">null</span>); useEffect(() =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> <span class="synIdentifier">{</span> taskId, recordingTaskId <span class="synIdentifier">}</span> = props; <span class="synStatement">if</span> (taskId &amp;&amp; taskId === recordingTaskId) <span class="synIdentifier">{</span> <span class="synComment">// setIntervalで時間加算の関数(addSecond)を1秒毎に実行させる</span> setTimerId(setInterval(addSecond, 1000)); <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synStatement">return</span>; <span class="synIdentifier">}</span>, <span class="synIdentifier">[</span>props.recordingTaskId<span class="synIdentifier">]</span>); <span class="synStatement">const</span> addSecond = () =&gt; <span class="synIdentifier">{</span> <span class="synComment">// ここでsetStateに「time + 1」という値を渡してしまっているため、常に古いstateを見てしまっている</span> setTime(time + 1); <span class="synIdentifier">}</span>; </pre> <h4>良い例</h4> <pre class="code lang-javascript" data-lang="javascript" data-unlink> <span class="synComment">// timeは親のコンポーネントからもらってくる数値</span> <span class="synStatement">const</span> <span class="synIdentifier">[</span>time, setTime<span class="synIdentifier">]</span> = useState(props.time || <span class="synStatement">null</span>); useEffect(() =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> <span class="synIdentifier">{</span> taskId, recordingTaskId <span class="synIdentifier">}</span> = props; <span class="synStatement">if</span> (taskId &amp;&amp; taskId === recordingTaskId) <span class="synIdentifier">{</span> <span class="synComment">// setIntervalで時間加算の関数(addSecond)を1秒毎に実行させる</span> setTimerId(setInterval(addSecond, 1000)); <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synStatement">return</span>; <span class="synIdentifier">}</span>, <span class="synIdentifier">[</span>props.recordingTaskId<span class="synIdentifier">]</span>); <span class="synStatement">const</span> addSecond = () =&gt; <span class="synIdentifier">{</span> <span class="synComment">// 更新関数を渡しているため、この中のtは最新のstateが入っていることが保証されている</span> setTime(t =&gt; t + 1); <span class="synIdentifier">}</span>; </pre>

親から子コンポーネントのstateにcreateRefを使ってアクセスする

2020/12/02 10:54


<p>表題の通りだけど、個人的におすすめしない。</p> <p>refでDOM要素にも<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>にも自由にアクセスできるようにはなるが、せっかくpropsで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>間のやり取りができるようになっているので、あまりそれを崩したくない。 ちなみに、reactのリファレンスにはrefを使うのに適したケースとして以下が挙げられている。</p> <ul> <li>フォーカス、テキストの選択およびメディアの再生の管理</li> <li>アニメーションの発火</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B5%A1%BC%A5%C9%A5%D1%A1%BC%A5%C6%A5%A3">サードパーティ</a>の DOM ライブラリとの統合</li> </ul> <p><a href="https://ja.reactjs.org/docs/refs-and-the-dom.html">Ref &#x3068; DOM &ndash; React</a></p> <p>が、仕方ない場面はあるのでこういった方法を採用することもある。</p> <p>下記のようにして、子<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>のstateにアクセスできる。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// クラスA内にBという子コンポーネントがあるとする</span> <span class="synStatement">class</span> A <span class="synStatement">extends</span> React.Component <span class="synIdentifier">{</span> constructor(props) <span class="synIdentifier">{</span> <span class="synStatement">super</span>(props); <span class="synComment">// stateを取得したい子コンポーネントにtestRefを渡すために、ここで作成</span> <span class="synIdentifier">this</span>.testRef = React.createRef(); <span class="synIdentifier">}</span> <span class="synComment">// クラスB内に作成したtestRefを渡す</span> &lt;B ref=<span class="synIdentifier">{this</span>.testRef<span class="synIdentifier">}</span> /&gt; <span class="synComment">// Bコンポーネント内の value というstateにアクセスできる</span> handleBstate = () =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> test = <span class="synIdentifier">this</span>.testRef.current.state.value <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> </pre> <p>繰り返すが、あまりおすすめはしない。 瞬間的に子<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の値を取得したい、関数を利用したいというケースでもなければあまり使わない方が無難だと思う。</p>

dayjsのduration.format()まだマージされていない

2020/11/27 22:53


<p>JS系で、時間・日時を取り扱うライブラリにdayjsというものがある。(他の有名どころだとMoment.js<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>とか)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fday.js.org%2F" title="Day.js · 2kB JavaScript date utility library" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://day.js.org/">day.js.org</a></cite> <br></p> <p>ある時、formatを使って秒数を「HH:mm:ss」の形式で表現したくなった。 例えば、144,930秒で表現すると「40:15:30」となる。</p> <p>momentだと上記のようなケースに対応したライブラリがある。</p> <p><a href="https://github.com/jsmreese/moment-duration-format">GitHub - jsmreese/moment-duration-format: Format function plugin for the Moment Duration object.</a></p> <p>momentと似た感じで、 <code>dayjs.duration({ seconds: time }).format('HH:mm:ss') ※timeは任意の秒数</code> でフォーマットできないかと思ったけど、まだdurationでformatは使えないらしい。 ただ、最近devブランチに <code>duration().format</code>を使えるようにしたブランチがマージされたらしいので、これがmasterにマージされれば晴れて上記のようなフォーマットができるようになると思われる。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fiamkun%2Fdayjs%2Fissues%2F965" title="How we can format the duration in dayjs. · Issue #965 · iamkun/dayjs" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/iamkun/dayjs/issues/965">github.com</a></cite></p> <p>ちなみに、dayjs()で生成したものにシンプルにformatを使用しただけだと、24時間ごとに一日とカウントされ、24時間以上のカウントには対応できない。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink> <span class="synStatement">const</span> time = 144930 * 1000 <span class="synComment">// dayjs(time)のtimeはミリ秒で計算されるため、秒数をミリ秒に変換している</span> dayjs(time).format(<span class="synConstant">'HH:mm:ss'</span>) =&gt; 16:15:30 </pre> <p><br></p> <h4>2021/2/11追記</h4> <p>どうやら上記のような対応の予定は無いらしい。 残念。</p> <p><a href="https://github.com/iamkun/dayjs/issues/1376">Request - Format duration days as more than 24h &middot; Issue #1376 &middot; iamkun/dayjs &middot; GitHub</a></p> <div class="footnotes"> <hr/> <ol> <li id="fn:1"> <p>ただし、Moment.jsは近い将来新しい開発が停止になるそう <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcpoint-lab.co.jp%2Farticle%2F202009%2F17020%2F" title="【JavaScript】Moment.js の新規機能開発が停止。新規プロジェクトへの採用は非推奨へ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://cpoint-lab.co.jp/article/202009/17020/">cpoint-lab.co.jp</a></cite><a href="#fnref:1" rev="footnote">&#8617;</a></p></li> </ol> </div>

非同期処理のデバッグ(Rails)

2020/07/25 21:54


<p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>における非同期処理の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>方法を備忘録としてまとめた。<br /> なお、非同期処理はSidekiqを用いて実現しているものとする。</p> <ul> <li>gemを使って処理を止める</li> </ul><p>非同期処理中でも以下のgemを使用すれば、バックグラウンドで動いているプロセスを<code>binding.remote_pry</code>で止められるらしい。(まだ試したことはない)<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2FMon-Ouie%2Fpry-remote" title="Mon-Ouie/pry-remote" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/Mon-Ouie/pry-remote">github.com</a></cite></p><p></p> <ul> <li>Workerを直接実行させたうえで、binding.pryを使って処理を止める</li> </ul><p>例えば<code>TestWorker</code>というWorkerがあるとして、そいつを直接実行するには<a class="keyword" href="http://d.hatena.ne.jp/keyword/rails">rails</a> consoleで以下を実行すればいい。(非同期処理を無理矢理同期的に実行させている)<br /> この方法であれば、<code>binding.pry</code>でも処理を一時停止させることができる。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synType">TestWorker</span>.new.perform </pre>

ActiveSupport一時調べ

2020/07/11 09:45


<h3>Active Supportとは</h3> <p>Active Supportは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>であり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>言語の拡張、ユーティリティ、その他横断的な作業を担っています。</p> <p>Active Supportは言語レベルで基本部分を底上げして豊かなものにし、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>アプリケーションの開発と<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a>それ自体の開発に役立てるべく作られています。 (引用:Active Support コア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%C8%C4%A5%B5%A1%C7%BD">拡張機能</a>) <a href="https://railsguides.jp/active_support_core_extensions.html">https://railsguides.jp/active_support_core_extensions.html</a></p> <p>StringやInteger、Date、Datetimeなどのコアなクラスのメソッドを拡張してくれるもの</p> <hr /> <h3>拡張対象</h3> <p>Active Supportが拡張する対象を ざっと挙げてみました(全部ではないです)</p> <ul> <li>Object</li> <li>Module</li> <li>Class</li> <li>String</li> <li>Numeric <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/BigDecimal">BigDecimal</a></li> <li>Integer</li> </ul> </li> <li>Enumerable</li> <li>Array</li> <li>Hash</li> <li>Range</li> <li>Date</li> <li>Time</li> <li>File</li> <li>NameError</li> <li>LoadError</li> </ul> <p>...</p> <hr /> <h3>全てのオブジェクトで使える拡張</h3> <ul> <li>Object#blank?</li> <li>Object#present?</li> </ul> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/ruby">ruby</a>にあるのは<code>nil?</code>と<code>empty?</code>のみ</p> <pre class="code" data-lang="" data-unlink>nil.nil? =&gt; true [].empty? =&gt; true &#39;&#39;.empty? =&gt; true</pre> <hr /> <ul> <li>Object#blank?</li> </ul> <p><code>nil?</code>と<code>empty?</code>の合わせ技</p> <pre class="code" data-lang="" data-unlink>nil.blank? =&gt; true [].blank? =&gt; true &#39;&#39;.blank? =&gt; true nil.blank? =&gt; true</pre> <ul> <li>Object#present?</li> </ul> <p><code>!blank?</code>と同じ意味</p> <pre class="code" data-lang="" data-unlink>nil.present? =&gt; false [].present? =&gt; false [1,2,3].present? =&gt; true</pre> <p>blank?とpresent?の定義は下のURL先 <a href="https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/blank.rb">https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/blank.rb</a></p> <hr /> <ul> <li>Object#try</li> </ul> <p>おなじみtryメソッド。 レシーバが<a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a>、もしくはメソッドが定義されていない時には<a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a>を返す。<a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a>でないなら引数で渡されたメソッドを実行。</p> <pre class="code" data-lang="" data-unlink>&#34;test&#34;.try(:name) =&gt; nil &#34;test&#34;.try(:size) =&gt; 4</pre> <ul> <li>Object#try!</li> </ul> <p><code>try</code>と違う点は、レシーバが<a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a>なら<code>nil</code>でなく例外を返すところ。</p> <pre class="code" data-lang="" data-unlink>&#34;test&#34;.try!(:name) NoMethodError: undefined method `name&#39; for &#34;test&#34;:String # NoMethodErrorクラスの例外が発生</pre> <hr /> <ul> <li>Object#with_options</li> </ul> <p>オプションを一つにまとめられる。 <code>validates</code>メソッドのオプションを一つにまとめたりとかもできると思うよ。</p> <pre class="code" data-lang="" data-unlink>class Company &lt; ApplicationRecord ... has_many :contracts, dependent: :destroy has_one :billing_address, dependent: :destroy has_many :billings, dependent: :destroy ... end ↓ class Company &lt; ApplicationRecord ... with_options dependent: :destroy to |comp| comp.has_many :contracts comp.has_one :billing_address comp.has_many :billings ... end </pre> <hr /> <ul> <li>tapの補足</li> </ul> <p>ブロックを渡した場合には、<a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a>でない場合のみブロックの評価を行い、tryの返り値はブロックの評価結果となる。</p> <pre class="code" data-lang="" data-unlink>array =&gt; [[&#34;test1&#34;, 1], [&#34;test2&#34;, 2]] array.try do |a| a.to_h end =&gt; {&#34;test1&#34;=&gt;1, &#34;test2&#34;=&gt;2} array.try do |a| a.to_h end.values =&gt; [1, 2] # Object#yield_selfも同様の挙動 array.yield_self do |a| a.to_h end.values =&gt; [1, 2]</pre> <hr /> <h3>Classの拡張</h3> <h4>class_attribute</h4> <p>継承可能なクラス属性を定義する。 以下のようなことができるようになる。</p> <ul> <li>子クラスで上書きが可能</li> <li>アクセサメソッドを自動で定義</li> <li>子クラスで定義されていなければ親クラスの値を参照</li> </ul> <p>親クラスの定数を汚染する恐れがない。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>メソッド内からでもアクセスできる。</p> <pre class="code" data-lang="" data-unlink>class A class_attribute :x end class B &lt; A; end class C &lt; B; end A.x = :a B.x # =&gt; :a C.x # =&gt; :a B.x = :b A.x # =&gt; :a C.x # =&gt; :b C.x = :c A.x # =&gt; :a B.x # =&gt; :b # インスタンスからもアクセス・書き換え可能 # インスタンスからクラスのclass_attributeを書き換えることはない test = B.new test.x = :t test.x # =&gt; :t B.x = :b </pre> <hr /> <h3>Stringの拡張</h3> <ul> <li>活用系</li> </ul> <p>レシーバに対して何かしら操作を行う系</p> <pre class="code" data-lang="" data-unlink>&#34;tomato&#34;.pluralize =&gt; &#34;tomatoes&#34; &#34;katsui&#34;.pluralize =&gt; &#34;katsuis&#34;</pre> <hr /> <p>活用系の続き</p> <pre class="code" data-lang="" data-unlink>&#34;admin_user&#34;.camelize =&gt; &#34;AdminUser&#34; &#34;admin_user&#34;.camelize(:lower) =&gt; &#34;adminUser&#34; &#34;account/financial_account&#34;.camelize =&gt; &#34;Account::FinancialAccount&#34; # camelizeと反対の操作を行うのがunderscore &#34;Backoffice::UsersController&#34;.demodulize =&gt; &#34;UsersController&#34; &#34;::UsersController&#34;.demodulize =&gt; &#34;UsersController&#34;</pre> <hr /> <h3>Numericの拡張</h3> <ul> <li>バイト</li> </ul> <p>全ての数値が以下のメソッドに対応している。</p> <pre class="code" data-lang="" data-unlink>bytes kilobytes megabytes gigabytes terabytes petabytes exabytes</pre> <pre class="code" data-lang="" data-unlink>2.bytes =&gt; 2 2.kilobytes =&gt; 2048 2.megabytes =&gt; 2097152</pre> <hr /> <ul> <li>書式設定</li> </ul> <p>指定した形式で数値をフォーマットできる</p> <pre class="code" data-lang="" data-unlink># 電話番号 1234567.to_s(:phone) =&gt; &#34;123-4567&#34; 1234567891.to_s(:phone) =&gt; &#34;123-456-7891&#34; 1234567891.to_s(:phone, area_code: true) =&gt; &#34;(123) 456-7891&#34; // 桁区切り 12345678.to_s(:delimited) =&gt; &#34;12,345,678&#34; pry(main)&gt; 12345678.to_s(:delimited, delimiter: &#34;.&#34;) =&gt; &#34;12.345.678&#34; // 丸める 111.2345.to_s(:rounded) =&gt; &#34;111.235&#34; 111.2345.to_s(:rounded, precision: 2) =&gt; &#34;111.23&#34; 1112903.to_s(:rounded, precision: 3) =&gt; &#34;1112903.000&#34;</pre> <hr /> <h3>Enumerableの拡張</h3> <pre class="code" data-lang="" data-unlink># sum # 要素の合計値を返す # 数値でない場合は+を使ってsumを計算する [2,3,4].sum =&gt; 9 (1..10).sum =&gt; 55 %w(rb yml haml).sum =&gt; &#34;rbymlhaml&#34; # many? # collection.size &gt; 1 の短縮系 [1,2,3,4].many? =&gt; true # many?ではブロックの評価も行える # ブロックの場合、評価結果のcollection &gt; 1かどうか判定する [1,2,3,4].many? { |a| a % 2 == 0 } =&gt; true [1,2,3,4].many? { |a| a % 3 == 0 } =&gt; false # pluck # 指定したkeyに基づく配列を返す [ { id: 1 }, { id: 2 }, { id: 3 } ].pluck(:id) =&gt; [1, 2, 3]</pre> <hr /> <h3>Hashの拡張</h3> <pre class="code" data-lang="" data-unlink># deep_merge # レシーバと引数に同じkeyが存在し、かつ値がHashの時に下位のHashをmergeする {a: {b: 1}}.deep_merge({a: {c: 2}}) =&gt; {:a=&gt;{:b=&gt;1, :c=&gt;2}} # exceptとexcept! # 引数で指定したkeyがあればレシーバのHashから取り除く {a: 1, b: 2}.except(:a) =&gt; {:b=&gt;2} # stringify_keysとsymbolize_keys # レシーバのHashのkeyをシンボル→文字列に変換(stringify_keys) # レシーバのHashのkeyを文字列→シンボルに変換(symbolize_keys) {nil =&gt; nil, 1 =&gt; 1, a: :a}.stringify_keys =&gt; {&#34;&#34;=&gt;nil, &#34;1&#34;=&gt;1, &#34;a&#34;=&gt;:a} {nil =&gt; nil, 1 =&gt; 1, &#34;a&#34; =&gt; &#34;a&#34;}.symbolize_keys =&gt; {nil=&gt;nil, 1=&gt;1, :a=&gt;&#34;a&#34;} </pre> <hr /> <h3>まとめ</h3> <ul> <li><p>あなたが実装しようとしているメソッドは、既にActive Supportが提供しているかもしれないので、一旦落ち着いて探しましょう</p></li> <li><p>比較的Active Supportのドキュメントは読みやすく、発見も色々とあるので読んでみるといいよ!</p></li> </ul>

tap,yeild_selfについて

2020/07/11 09:43


<p>tapとyield_selfの使い分けについて</p> <ul> <li>tapについて</li> </ul> <p>tapはレシーバ自身を返すため、元々のレシーバの値を変更することなくメソッドチェーンをつなげることができる。 他の処理を行いながら、メソッドチェーンを行いたい時などに便利。</p> <pre class="code" data-lang="" data-unlink>result = [] test = [1, 2, 3] kekka = test.tap { |t| result &lt;&lt; t.sum }.join(&#39;/&#39;) result =&gt; [6] kekka =&gt; &#34;1/2/3&#34;</pre> <ul> <li>yield_selfについて</li> </ul> <p>yield_selfはブロック内の処理結果を返すため、</p> <p>他の処理を行いながら、メソッドチェーンを行いたい時などに便利。</p>

SQL一時調べ

2020/07/11 09:42


<p>目次</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>とは</li> <li>用語</li> <li>基本的な構文</li> <li>Select構文</li> <li>句・式・文について</li> <li>内部結合・外部結合</li> <li>集計関数</li> <li>case式</li> <li>サブクエリ</li> <li>Active Record</li> <li>Active Recordとは</li> <li>Active Recordの設計</li> <li>Active Recordから<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>を直接発行</li> </ul> <hr /> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>とは</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%D8%B7%B8%A5%C7%A1%BC%A5%BF%A5%D9%A1%BC%A5%B9%B4%C9%CD%FD%A5%B7%A5%B9%A5%C6%A5%E0">関係データベース管理システム</a> (<a class="keyword" href="http://d.hatena.ne.jp/keyword/RDBMS">RDBMS</a>) において、データの操作や定義を行うためのデータベース言語(問い合わせ言語)、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>固有言語である。プログラミングにおいてデータベースへのアクセスのために、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0%B8%C0%B8%EC">プログラミング言語</a>と併用されるが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>そのものは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0%B8%C0%B8%EC">プログラミング言語</a>ではない。</p> <p>(引用:<a class="keyword" href="http://d.hatena.ne.jp/keyword/wikipedia">wikipedia</a> <a href="https://ja.wikipedia.org/wiki/SQL">https://ja.wikipedia.org/wiki/SQL</a>)</p> <hr /> <h2>基本的な用語</h2> <ul> <li><p>クエリ(問合せ) データベースへ要求を出すこと</p></li> <li><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/DML">DML</a>(データ操作言語) ★今日のメイン データベースにに対する操作を行う言語。SELECT(検索)、INSERT(挿入)、UPDATE(更新)、DELETE(削除)など</p></li> <li><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/DDL">DDL</a>(データ定義言語) データ構造を定義する言語。代表的な言語はCREATE(表領域作成)、<a class="keyword" href="http://d.hatena.ne.jp/keyword/DROP">DROP</a>(表領域の削除)、ALTER(変更),TRUNCATE(表領域内のレコード削除)など</p></li> <li><p>DCL(データ制御言語) <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>の制御を行うための言語。BEGIN、COMMIT、ROLLBACKなど</p></li> </ul> <hr /> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>の構文について</h2> <p>↓<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>構文逆引き辞典 <a href="http://www.sql-reference.com/">http://www.sql-reference.com/</a></p> <hr /> <h4>Select構文</h4> <p>テーブルからレコードを取得するための命令文。</p> <pre class="code" data-lang="" data-unlink>SELECT [STRAIGHT_JOIN] [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT] [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS] [HIGH_PRIORITY] [DISTINCT| DISTINCTROW | ALL] フィールド名 / 式 [ AS 別名 ], ... [INTO {OUTFILE | DUMPFILE} &#39;ファイル名&#39; オプション] [FROM テーブル名 [WHERE 条件式] [GROUP BY {整数 | フィールド名| 式} [ASC | DESC], ... [WITH ROLLUP]] [HAVING 条件式] [ORDER BY {整数 | フィールド名| 式} [ASC | DESC] ,...] [LIMIT [オフセット,] 行指定] [PROCEDURE 手続き名(属性一覧)] [FOR UPDATE | LOCK IN SHARE MODE] ]</pre> <p>(引用:<a href="https://rfs.jp/sb/sql/s03/03-15.html">https://rfs.jp/sb/sql/s03/03-15.html</a>)</p> <hr /> <h4>Select句の例</h4> <pre class="code" data-lang="" data-unlink>/* budgetsテーブルの全レコードの取得・表示 */ select * from budgets; /* 重複の排除(distinct) */ SELECT DISTINCT id, management_item_id, value from budgets /* 実績の金額順でソート */ SELECT id, management_item_id, value AS &#34;金額&#34; from budgets WHERE value &gt; 30000 ORDER BY value;</pre> <hr /> <h2>句・式・文の区別について</h2> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243803/2cd3b4f9-5b6b-4087-0e92-9be7f9e77529.png" alt="image.png" /></p> <h4>句(clause)</h4> <p>節とも呼ぶ。文、またはクエリの構成要素。 省略可な句もある。</p> <p>(例) UPDATE句、FROM句、WHERE句、EXISTS句</p> <h4>式(experssion)</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AB%A5%E9%A1%BC">スカラー</a>値などを表す '1', 'katsui', trueなど</p> <h4>文(statement)</h4> <p>命令文ともいう。 一つの実行の単位となる<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>。 文の末尾には';'を付けることができる。 (例) SELECT文 UPDATE文</p> <p>参考: <a href="https://qiita.com/gooddoog/items/a12d500b331f2ecf718f">https://qiita.com/gooddoog/items/a12d500b331f2ecf718f</a> <a href="https://en.wikipedia.org/wiki/SQL">https://en.wikipedia.org/wiki/SQL</a></p> <hr /> <h2>内部結合・外部結合</h2> <p>内部結合(INNER JOIN): 複数のテーブルを結合し、条件が一致したもののみを取得する。 結合条件に合わないものは弾かれる。</p> <p>左外部結合(LEFT OUTER JOIN) 右外部結合(RIGHT OUTER JOIN) 完全外部結合(FULL OUTER JOIN): それぞれ、まず内部結合が行われる。その後、対象とするテーブルに対して結合できるデータをくっつける。完全外部結合は「内部結合 + 左外部結合 + 右外部結合」を行う。</p> <pre class="code" data-lang="" data-unlink>/* 内部結合 */ SELECT * from base_achievements INNER JOIN management_items on base_achievements.management_item_id = management_items.id; /* 左外部結合(実績に対して、結合条件に合う予算細目を結合し、なければNULLを入れておく) */ SELECT * from base_achievements LEFT OUTER JOIN management_items on base_achievements.management_item_id = management_items.id; /* 右外部結合(予算細目に対して、結合条件に合う実績を結合し、なければNULLを入れておく) */ SELECT * from base_achievements RIGHT OUTER JOIN management_items on base_achievements.management_item_id = management_items.id;</pre> <hr /> <h2>集計関数について</h2> <p>GroupBy: GROUP BY句に記述された列のリスト(集約キー)を基準に、同じ値を持つ行を同じグループに振り分ける Having: グルーピングした結果から、Having句で指定した抽出条件を実行する</p> <pre class="code" data-lang="" data-unlink>/* 実績のmanagement_idに対して単純にグループ化 */ SELECT management_item_id from base_achievements GROUP BY management_item_id ORDER BY management_item_id; /* 実績を紐づく細目ごとにグループ化→実績値の合計を出力 */ SELECT management_item_id, sum(value) from base_achievements GROUP BY management_item_id ORDER BY management_item_id; /* 実績を紐づく細目ごとにグループ化→実績値の合計を出力→合計金額6000000以下は弾く */ SELECT management_item_id, sum(value) AS &#34;合計金額&#34; from base_achievements GROUP BY management_item_id HAVING sum(value) &gt; 6000000 ORDER BY management_item_id;</pre> <hr /> <h4>集計関数を実行する上でのメモ</h4> <ul> <li><p>whereとHavingとの違いについて Where句はselect句の結果からwhere句で指定した抽出条件を実行する Having句はGroupBy句でグルーピングした結果からHaving句で指定した抽出条件を実行する</p></li> <li><p>GroupByでは、GroupByで指定したカラムしかSelect句に指定できない</p></li> </ul> <hr /> <h2>case式</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>内で条件分岐を行うための構文。 値と式による分岐があるが、値の方はあまり使わないのではないか。</p> <pre class="code" data-lang="" data-unlink>/* (条件式による分岐)実績のvalueに応じて、判定結果をそれぞれ「継続」「保留」「棄却」とする) 空文字の時には&#34;/&#34;を外す */ SELECT id, summary, value AS &#34;金額&#34;, CASE WHEN value &lt; 500000 THEN CASE summary WHEN &#39;&#39; THEN &#39;棄却&#39; ELSE summary || &#39;/棄却&#39; END WHEN value &gt; 1000000 THEN CASE summary WHEN &#39;&#39; THEN &#39;継続&#39; ELSE summary || &#39;/継続&#39; END ELSE CASE summary WHEN &#39;&#39; THEN &#39;保留&#39; ELSE summary || &#39;/保留&#39; END END &#34;判定結果&#34; FROM base_achievements ORDER BY value DESC</pre> <hr /> <h2>副問合せ(サブクエリ)</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>文の中に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%FE%A4%EC%BB%D2">入れ子</a>で<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>文を指定すること。Select文の結果を別の<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>文で利用する。 from句配下でも、where句配下でも、select句のすぐ後に使用することもできる。 from句配下で使用されるケースはイメージがつかない。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243803/62b944ec-f291-3d13-6f77-3dd74156255a.png" alt="image.png" /> (引用:<a href="https://www.atmarkit.co.jp/ait/articles/1208/06/news118.html">https://www.atmarkit.co.jp/ait/articles/1208/06/news118.html</a>)</p> <p> </p> <hr /> <h4>副問合せの例</h4> <pre class="code" data-lang="" data-unlink>/* サブクエリ:勘定科目に紐づく細目に紐づく実績のみを取得する */ SELECT id, summary, management_item_id, value AS &#34;金額&#34; from base_achievements WHERE management_item_id IN ( SELECT management_items.id from management_items INNER JOIN accounts on management_items.account_id = accounts.id WHERE accounts.type = &#39;FinancialAccount&#39;) ORDER BY base_achievements.id;</pre> <p>便利ではあるが、ネストしまくると途端に可読性が悪くなるやつでもあると思う。</p> <hr /> <h2>Active Recordとは</h2> <p>「Active Recordパターン」という<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%B6%A5%A4%A5%F3%A5%D1%A5%BF%A1%BC%A5%F3">デザインパターン</a>に由来した、<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>を意識せずにデータベースアクセスを行えるライブラリ。</p> <p>→ Active Recordパターンを<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>で実装したライブラリが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>で使われているActive Recordライブラリ</p> <p>(参考:<a href="https://www.techscore.com/tech/Ruby/Rails/other/designpattern/1/">https://www.techscore.com/tech/Ruby/Rails/other/designpattern/1/</a>)</p> <hr /> <h4>Active Recordの設計</h4> <p>Active Recordパターンについて → 「データベーステーブルまたはビューの行をラップし、データベースアクセスを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A5%D7%A5%BB%A5%EB%B2%BD">カプセル化</a>してデータに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>ロジックを追加するオブジェクト」</p> <p>これを<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>で実装すると下記のようなクラス設計になる。</p> <ul> <li>クラスは1つのテーブルを表す</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>はテーブルの1行を表す</li> <li>クラス、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>メソッドで関連するデータ処理を行う</li> </ul> <hr /> <pre class="code" data-lang="" data-unlink># application_record.rb class ApplicationRecord &lt; ActiveRecord::Base … end # ApplicationRecordを継承したbase_achievement.rb(ActiveRecord::Baseを継承したモデルクラス) class BaseAchievement &lt; ApplicationRecord … end </pre> <hr /> <h4>Active Recordで<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>を直接発行したい時</h4> <p>「<a class="keyword" href="http://d.hatena.ne.jp/keyword/ActiveRecord">ActiveRecord</a>::Base.connection」を使用する</p> <pre class="code" data-lang="" data-unlink>con = ActiveRecord::Base.connection con.execute(&#34;SELECT DISTINCT id, management_item_id, value from budgets WHERE value &gt; 30000;&#34;) // 返り値(DBアダプタ依存のオブジェクト) #&lt;PG::Result:0x00005627b5dd0080 status=PGRES_TUPLES_OK ntuples=11 nfields=3 cmd_tuples=11&gt;</pre> <hr /> <h4>直接検索とかしたい時とか</h4> <ul> <li>select_all(検索結果)</li> </ul> <p>これ自体の返り値は<code>ActiveRecord::Result</code>オブジェクトで、to_hashとかでハッシュの配列が取得できる。</p> <pre class="code" data-lang="" data-unlink>con = ActiveRecord::Base.connection con.select_all(&#34;SELECT DISTINCT id, management_item_id, value from budgets WHERE value &gt; 30000;&#34;).to_hash // 返り値 [{&#34;id&#34;=&gt;1166, &#34;management_item_id&#34;=&gt;471, &#34;value&#34;=&gt;42433.0}, {&#34;id&#34;=&gt;1165, &#34;management_item_id&#34;=&gt;471, &#34;value&#34;=&gt;34243.0}, {&#34;id&#34;=&gt;1155, &#34;management_item_id&#34;=&gt;474, &#34;value&#34;=&gt;42342.0}, {&#34;id&#34;=&gt;1173, &#34;management_item_id&#34;=&gt;472, &#34;value&#34;=&gt;909090.0}, {&#34;id&#34;=&gt;1161, &#34;management_item_id&#34;=&gt;476, &#34;value&#34;=&gt;625656.0}, {&#34;id&#34;=&gt;1171, &#34;management_item_id&#34;=&gt;481, &#34;value&#34;=&gt;34242.0}, {&#34;id&#34;=&gt;1156, &#34;management_item_id&#34;=&gt;474, &#34;value&#34;=&gt;35252.0}, {&#34;id&#34;=&gt;1174, &#34;management_item_id&#34;=&gt;472, &#34;value&#34;=&gt;454545.0}, {&#34;id&#34;=&gt;1206, &#34;management_item_id&#34;=&gt;492, &#34;value&#34;=&gt;245345.0}, {&#34;id&#34;=&gt;1159, &#34;management_item_id&#34;=&gt;476, &#34;value&#34;=&gt;55245.0}, {&#34;id&#34;=&gt;1210, &#34;management_item_id&#34;=&gt;480, &#34;value&#34;=&gt;345345.0}]</pre> <p>select_oneとかselect_rowsなどいくつかメソッドがある。</p> <p>参考: <a href="https://qiita.com/yut_h1979/items/4cb3d9a3b3fc87ca0435">https://qiita.com/yut_h1979/items/4cb3d9a3b3fc87ca0435</a></p> <p> </p>