<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SQL on Think and Write</title><link>https://kenjiusui.github.io/blog/tags/sql/</link><description>Recent content in SQL on Think and Write</description><generator>Hugo</generator><language>ja</language><lastBuildDate>Fri, 28 Apr 2023 23:18:34 +0900</lastBuildDate><atom:link href="https://kenjiusui.github.io/blog/tags/sql/index.xml" rel="self" type="application/rss+xml"/><item><title>SQLでカテゴリごとに最大・最小の値を持つ行を取得する</title><link>https://kenjiusui.github.io/blog/posts/biz/007_sql%E3%81%A7%E3%82%AB%E3%83%86%E3%82%B4%E3%83%AA%E3%81%94%E3%81%A8%E3%81%AB%E6%9C%80%E5%A4%A7%E6%9C%80%E5%B0%8F%E3%81%AE%E5%80%A4%E3%82%92%E6%8C%81%E3%81%A4%E8%A1%8C%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B/</link><pubDate>Fri, 28 Apr 2023 23:18:34 +0900</pubDate><guid>https://kenjiusui.github.io/blog/posts/biz/007_sql%E3%81%A7%E3%82%AB%E3%83%86%E3%82%B4%E3%83%AA%E3%81%94%E3%81%A8%E3%81%AB%E6%9C%80%E5%A4%A7%E6%9C%80%E5%B0%8F%E3%81%AE%E5%80%A4%E3%82%92%E6%8C%81%E3%81%A4%E8%A1%8C%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B/</guid><description>&lt;p&gt;SQLではよくあるケースだけど案外書くのが難しい書き方。&lt;br&gt;
これが一番簡単だと思いますが、もっといい方法があったら教えてください。&lt;/p&gt;
&lt;h1 id="やりたいこと"&gt;やりたいこと&lt;/h1&gt;
&lt;p&gt;なんらかのカテゴリーごとに順番で並べて一番数値が高い行の他のカラムのデータを取得するクエリです。&lt;br&gt;
たとえば、下記のitemsというテーブルがあったときcategoryごとにpriceが最も高いnameを取得するという問題です。&lt;/p&gt;
&lt;p&gt;itemsテーブル&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;belt&lt;/td&gt;
&lt;td&gt;a&lt;/td&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;belt&lt;/td&gt;
&lt;td&gt;b&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;belt&lt;/td&gt;
&lt;td&gt;c&lt;/td&gt;
&lt;td&gt;4000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wallet&lt;/td&gt;
&lt;td&gt;d&lt;/td&gt;
&lt;td&gt;3000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wallet&lt;/td&gt;
&lt;td&gt;e&lt;/td&gt;
&lt;td&gt;60000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wallet&lt;/td&gt;
&lt;td&gt;f&lt;/td&gt;
&lt;td&gt;15000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wallet&lt;/td&gt;
&lt;td&gt;g&lt;/td&gt;
&lt;td&gt;20000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;求める結果&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;belt&lt;/td&gt;
&lt;td&gt;b&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wallet&lt;/td&gt;
&lt;td&gt;e&lt;/td&gt;
&lt;td&gt;60000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id="クエリの例"&gt;クエリの例&lt;/h1&gt;
&lt;p&gt;この問題はwindow関数でrow_number関数を使うことで記述する方法がオススメです。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;select
category,
name,
price
from (
select
category,
name,
price,
row_number() over (partition by category order by price desc) as price_order
from
items
)
where
price_order = 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;なにをやっているのか簡単に言うと &lt;code&gt;partition by category&lt;/code&gt; でcategoryごとにわけて &lt;code&gt;order by price desc&lt;/code&gt; でprice降順で並びかえたところに &lt;code&gt;row_number()&lt;/code&gt; で順番に番号を割り当てています。&lt;br&gt;
これによってpriceが最も高いとprice_orderカラムに1が入り降順で番号が割り当てられます。&lt;br&gt;
これをサブクエリとして &lt;code&gt;price_order = 1&lt;/code&gt; に絞り込むことで最大の値が手に入るという仕組みです。&lt;br&gt;
注意点として同じpriceの行が複数存在する場合はどれか1行しか得られません。&lt;/p&gt;</description></item><item><title>メッセージの往復回数をカウントするSQLクエリの書き方</title><link>https://kenjiusui.github.io/blog/posts/biz/005_%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%81%AE%E5%BE%80%E5%BE%A9%E5%9B%9E%E6%95%B0%E3%82%92%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%99%E3%82%8Bsql%E3%82%AF%E3%82%A8%E3%83%AA%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9/</link><pubDate>Wed, 15 Mar 2023 01:20:36 +0900</pubDate><guid>https://kenjiusui.github.io/blog/posts/biz/005_%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%81%AE%E5%BE%80%E5%BE%A9%E5%9B%9E%E6%95%B0%E3%82%92%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%99%E3%82%8Bsql%E3%82%AF%E3%82%A8%E3%83%AA%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9/</guid><description>&lt;p&gt;タイトルどおりメッセージの往復回数をカウントするクエリの書き方です。&lt;br&gt;
正確には異なるユーザがメッセージを送った回数をカウントする方法なのでチャットルームが1vs1なら往復回数だし3人以上いたら発信者が切り替わった回数と見ることができます。&lt;br&gt;
最近はチャットサポートが当たり前になってきましたしチャットを提供するサービスも一般化してきたので使う機会は割とあるのではないでしょうか。&lt;br&gt;
環境はBigQueryを想定しています。&lt;/p&gt;
&lt;h1 id="データのイメージ"&gt;データのイメージ&lt;/h1&gt;
&lt;p&gt;こういう感じのデータをイメージしています。&lt;br&gt;
massagesというテーブルに下記のようなデータが入っているとします。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;room_id&lt;/th&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;created_at&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;2023-01-01 00:00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;700&lt;/td&gt;
&lt;td&gt;2023-01-01 01:01:01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;2023-01-01 08:01:02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;2023-02-01 00:10:03&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;900&lt;/td&gt;
&lt;td&gt;2023-03-01 01:00:04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;900&lt;/td&gt;
&lt;td&gt;2023-04-01 0500:05&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;idはmassageのユニークキーです。&lt;br&gt;
room_idはチャットルームごとのユニークキーです。&lt;br&gt;
room_idごとに何往復のチャットがやりとりされたのか数え上げることが目的です。&lt;br&gt;
user_idはチャットルームに参加しているユーザのidでmassageを誰が送ったのかわかります。&lt;br&gt;
ここではチャットルームに2人のユーザがいることを想定しています。&lt;/p&gt;
&lt;h1 id="例文"&gt;例文&lt;/h1&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;with massages_with_lag as (
select
id,
room_id,
user_id,
lag(user_id, 1) over (partition by room_id order by created_at) as lag_user_id
from
massages
)
select
room_id,
ceil(count(case when user_id &amp;lt;&amp;gt; lag_user_id then id end) / 2) as count_round_trips
from
massages_with_lag
group by
room_id
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;このクエリは2つにわかれています。&lt;br&gt;
前半は各メッセージごとに1つ前のメッセージのuser_idを横並びにさせ、後半でuser_idを比較して違ったらカウントが1つ増え、それを半分で割って小数点以下を繰り上げることで往復回数としています。&lt;/p&gt;</description></item><item><title>BigQueryでJSONの配列から行に変換する</title><link>https://kenjiusui.github.io/blog/posts/biz/004_bigquery%E3%81%A7json%E3%81%AE%E9%85%8D%E5%88%97%E3%81%8B%E3%82%89%E8%A1%8C%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B/</link><pubDate>Wed, 07 Dec 2022 11:30:18 +0900</pubDate><guid>https://kenjiusui.github.io/blog/posts/biz/004_bigquery%E3%81%A7json%E3%81%AE%E9%85%8D%E5%88%97%E3%81%8B%E3%82%89%E8%A1%8C%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B/</guid><description>&lt;p&gt;valueに配列を含むJSONから配列を抽出し行へ変換するクエリの書き方&lt;/p&gt;
&lt;h1 id="前提"&gt;前提&lt;/h1&gt;
&lt;p&gt;下記のようなJSONが &amp;ldquo;テーブル名：table_1&amp;rdquo; の &amp;ldquo;列名：item_logs&amp;rdquo; に格納されいてるとき&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{
&amp;#34;user_id&amp;#34;: 1111,
&amp;#34;item_names&amp;#34;: [
&amp;#34;foo&amp;#34;,
&amp;#34;bar&amp;#34;,
&amp;#34;hoge&amp;#34;
]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;このようなテーブルに変換するためのクエリ&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;item&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1111&lt;/td&gt;
&lt;td&gt;foo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1111&lt;/td&gt;
&lt;td&gt;bar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1111&lt;/td&gt;
&lt;td&gt;hoge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;環境はBigQuery&lt;/p&gt;
&lt;h1 id="書き方"&gt;書き方&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;json_query_arrayでほしい配列が格納されているペアのkey &amp;ldquo;$.item_names&amp;rdquo; を指定し目的の配列をとってくる&lt;/li&gt;
&lt;li&gt;unnestで配列を行に変換する（配列の順序が保証されないらしいので必要ならoffsetを使う）&lt;/li&gt;
&lt;li&gt;上記で行に変換したものと元のテーブルをcross joinして目的のテーブルが完成&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;select
user_id,
json_value(items) as item,
from
table_1 as t_1
cross join
unnest(json_query_array(t_1.item_logs, &amp;#34;$.item_names&amp;#34;)) as items
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;これを使ってJSONから &lt;code&gt;item = &amp;quot;foo&amp;quot;&lt;/code&gt; のときだけフラグを建てる処理も簡単に書ける。&lt;br&gt;
JSON値は比較とかできないのでjson_valueで変換する。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;select
id,
json_value(items) = &amp;#34;foo&amp;#34; as is_foo,
from
table_1 as t_1
cross join
unnest(json_query_array(t_1.item_logs, &amp;#34;$.item_names&amp;#34;)) as items
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;変更があったり間違っていたらコメントください。&lt;/p&gt;</description></item></channel></rss>