rabbit-slide-yancya-ruby_seen_from_sqlish_brain 2015.11.08.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rabbit +1 -0
- data/README.rd +24 -0
- data/README.rd~ +24 -0
- data/Rakefile +17 -0
- data/config.yaml +19 -0
- data/hena30_1.png +0 -0
- data/ooedo-2015.rab +769 -0
- data/pdf/ruby_seen_from_sqlish_brain-ruby_seen_from_sqlish_brain.pdf +0 -0
- data/sql_machi.png +0 -0
- data/yancya.jpg +0 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bb4d6b7773858ab1928decb70a8f0d2e0442a9e5
|
4
|
+
data.tar.gz: e6cafe066e872dcd2298f8b775cb27abaf315a9b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 693b54c64d40816946155d9a3ccacd4ef17694b654e09801b7c1e97ab06c2fab30826beb9558d885e3f001441b77cf9d1e1f16690fa9fdb28ecb48302ffc8032
|
7
|
+
data.tar.gz: 606e5580043f4ea6ec008de4be827a40ae6a784a919323821f915b0e3059c0c86a98d6f6b0c4ca207b24bd9d84f2e1312b156b27fbc7094288f1dee096937b1f
|
data/.rabbit
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ooedo-2015.rab
|
data/README.rd
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= SQL 脳から見た Ruby
|
2
|
+
|
3
|
+
大江戸 Ruby 会議 05 の Ninja Talks で話した内容のスライドです
|
4
|
+
|
5
|
+
== 作者向け
|
6
|
+
|
7
|
+
=== 表示
|
8
|
+
|
9
|
+
rake
|
10
|
+
|
11
|
+
=== 公開
|
12
|
+
|
13
|
+
rake publish
|
14
|
+
|
15
|
+
== 閲覧者向け
|
16
|
+
|
17
|
+
=== インストール
|
18
|
+
|
19
|
+
gem install rabbit-slide-yancya-ruby_seen_from_sqlish_brain
|
20
|
+
|
21
|
+
=== 表示
|
22
|
+
|
23
|
+
rabbit rabbit-slide-yancya-ruby_seen_from_sqlish_brain
|
24
|
+
|
data/README.rd~
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= TODO: スライドのタイトル
|
2
|
+
|
3
|
+
TODO: スライドの説明
|
4
|
+
|
5
|
+
== 作者向け
|
6
|
+
|
7
|
+
=== 表示
|
8
|
+
|
9
|
+
rake
|
10
|
+
|
11
|
+
=== 公開
|
12
|
+
|
13
|
+
rake publish
|
14
|
+
|
15
|
+
== 閲覧者向け
|
16
|
+
|
17
|
+
=== インストール
|
18
|
+
|
19
|
+
gem install rabbit-slide-yancya-ooedo-2015
|
20
|
+
|
21
|
+
=== 表示
|
22
|
+
|
23
|
+
rabbit rabbit-slide-yancya-ooedo-2015.gem
|
24
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "rabbit/task/slide"
|
2
|
+
|
3
|
+
# Edit ./config.yaml to customize meta data
|
4
|
+
|
5
|
+
spec = nil
|
6
|
+
Rabbit::Task::Slide.new do |task|
|
7
|
+
spec = task.spec
|
8
|
+
# spec.files += Dir.glob("doc/**/*.*")
|
9
|
+
# spec.files -= Dir.glob("private/**/*.*")
|
10
|
+
# spec.add_runtime_dependency("YOUR THEME")
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Tag #{spec.version}"
|
14
|
+
task :tag do
|
15
|
+
sh("git", "tag", "-a", spec.version.to_s, "-m", "Publish #{spec.version}")
|
16
|
+
sh("git", "push", "--tags")
|
17
|
+
end
|
data/config.yaml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
---
|
2
|
+
id: ruby_seen_from_sqlish_brain
|
3
|
+
base_name: ruby_seen_from_sqlish_brain
|
4
|
+
tags: []
|
5
|
+
presentation_date: 2015/11/08
|
6
|
+
version: 2015.11.08.1
|
7
|
+
licenses: []
|
8
|
+
slideshare_id: yancyajp
|
9
|
+
speaker_deck_id: yancya
|
10
|
+
ustream_id:
|
11
|
+
vimeo_id:
|
12
|
+
youtube_id:
|
13
|
+
author:
|
14
|
+
markup_language: :rd
|
15
|
+
name: yancya
|
16
|
+
email: yancya@upec.jp
|
17
|
+
rubygems_user: yancya
|
18
|
+
slideshare_user: yancyajp
|
19
|
+
speaker_deck_user: yancya
|
data/hena30_1.png
ADDED
Binary file
|
data/ooedo-2015.rab
ADDED
@@ -0,0 +1,769 @@
|
|
1
|
+
= SQL 脳から見た Ruby
|
2
|
+
|
3
|
+
: author
|
4
|
+
@yancya
|
5
|
+
: content-source
|
6
|
+
大江戸 Ruby 会議 05
|
7
|
+
: date
|
8
|
+
2015-11-08
|
9
|
+
: allotted-time
|
10
|
+
18m
|
11
|
+
: theme
|
12
|
+
rabbit
|
13
|
+
|
14
|
+
= これから話すひと
|
15
|
+
|
16
|
+
# image
|
17
|
+
# src = yancya.jpg
|
18
|
+
# caption = @yancya
|
19
|
+
# relative_height = 30
|
20
|
+
|
21
|
+
* 3児の父 Rubyist
|
22
|
+
* PostgreSQL と BigQuery が好き
|
23
|
+
* つくばエクスプレスユーザー
|
24
|
+
|
25
|
+
= 大江戸 Ruby 会議 04
|
26
|
+
|
27
|
+
# image
|
28
|
+
# src = sql_machi.png
|
29
|
+
# relative_height = 55
|
30
|
+
|
31
|
+
* ビッグっぽいデータについて
|
32
|
+
* すごく良かったです
|
33
|
+
|
34
|
+
= 大江戸 Ruby 会議 04
|
35
|
+
|
36
|
+
* つまり
|
37
|
+
* Ruby 会議で SQL の話をするのは間違ってなかった
|
38
|
+
|
39
|
+
= 大江戸 Ruby 会議 04
|
40
|
+
|
41
|
+
* SQL 枠が出来たと見て、よろしいですね?
|
42
|
+
|
43
|
+
= SQL と Ruby
|
44
|
+
|
45
|
+
* SQL におけるリレーションの実体はタプルの集合(ということにしておく)
|
46
|
+
* タプルの集合っぽい2次元配列なら Ruby でも扱えるハズ
|
47
|
+
* 同等の事が出来るかどうか、ちょっとくらべてみよう
|
48
|
+
|
49
|
+
= SQL と Ruby
|
50
|
+
|
51
|
+
# RT
|
52
|
+
caption = サンプルテーブル users
|
53
|
+
|
54
|
+
name, birthday
|
55
|
+
|
56
|
+
ちゆ, 1999-02-14
|
57
|
+
ちゆ, 2003-02-14
|
58
|
+
もなみ, 2002-02-09
|
59
|
+
もなみ, 2006-02-09
|
60
|
+
メモリ, 2003-11-02
|
61
|
+
メモリ, 2004-11-02
|
62
|
+
|
63
|
+
= SQL と Ruby
|
64
|
+
|
65
|
+
# coderay ruby
|
66
|
+
|
67
|
+
users = [
|
68
|
+
['ちゆ', '1999-02-14'],
|
69
|
+
['ちゆ', '2003-02-14'],
|
70
|
+
['もなみ', '2002-02-09'],
|
71
|
+
['もなみ', '2006-02-09'],
|
72
|
+
['メモリ', '2003-11-02'],
|
73
|
+
['メモリ', '2004-11-02'],
|
74
|
+
]
|
75
|
+
|
76
|
+
= SQL と Ruby
|
77
|
+
|
78
|
+
# coderay sql
|
79
|
+
|
80
|
+
SELECT name, birthday
|
81
|
+
FROM users
|
82
|
+
|
83
|
+
* SQL の SELECT
|
84
|
+
|
85
|
+
= SQL と Ruby
|
86
|
+
|
87
|
+
# coderay ruby
|
88
|
+
|
89
|
+
users.
|
90
|
+
map { |name, birthday| [name, birthday] }
|
91
|
+
|
92
|
+
* SELECT 相当の処理は Ruby ではこう書く
|
93
|
+
|
94
|
+
= SQL と Ruby
|
95
|
+
|
96
|
+
# coderay sql
|
97
|
+
|
98
|
+
SELECT name, birthday
|
99
|
+
FROM users
|
100
|
+
WHERE name = 'もなみ'
|
101
|
+
|
102
|
+
* SQL の WHERE
|
103
|
+
|
104
|
+
= SQL と Ruby
|
105
|
+
|
106
|
+
# coderay ruby
|
107
|
+
|
108
|
+
users.select { |name, _| name == 'もなみ' }
|
109
|
+
#=> [["もなみ", "2002-02-09"], ["もなみ", "2006-02-09"]]
|
110
|
+
|
111
|
+
* WHERE 相当の処理は Ruby ではこう書く
|
112
|
+
|
113
|
+
= SQL と Ruby
|
114
|
+
|
115
|
+
# coderay sql
|
116
|
+
|
117
|
+
-- name 毎の件数
|
118
|
+
SELECT name, COUNT(*)
|
119
|
+
FROM users
|
120
|
+
GROUP BY name
|
121
|
+
|
122
|
+
* SQL の GROUP BY
|
123
|
+
|
124
|
+
= SQL と Ruby
|
125
|
+
|
126
|
+
# coderay ruby
|
127
|
+
|
128
|
+
users.
|
129
|
+
group_by { |name, _| name }.
|
130
|
+
map { |name, tuples| [name, tuples.size] }
|
131
|
+
#=> [["ちゆ", 2], ["もなみ", 2], ["メモリ", 2]]
|
132
|
+
|
133
|
+
* GROUP BY 相当の処理は Ruby ではこう書く
|
134
|
+
|
135
|
+
= SQL と Ruby
|
136
|
+
|
137
|
+
# coderay sql
|
138
|
+
|
139
|
+
-- birthday で並び替え
|
140
|
+
SELECT name, birthday
|
141
|
+
FROM users
|
142
|
+
ORDER BY birthday
|
143
|
+
|
144
|
+
* SQL の ORDER BY
|
145
|
+
|
146
|
+
= SQL と Ruby
|
147
|
+
|
148
|
+
# coderay ruby
|
149
|
+
|
150
|
+
users.sort_by { |_, birthday| birthday }
|
151
|
+
#=> [["ちゆ", "1999-02-14"],
|
152
|
+
#=> ["もなみ", "2002-02-09"],
|
153
|
+
#=> ["ちゆ", "2003-02-14"],
|
154
|
+
#=> ["メモリ", "2003-11-02"],
|
155
|
+
#=> ["メモリ", "2004-11-02"],
|
156
|
+
#=> ["もなみ", "2006-02-09"]]
|
157
|
+
|
158
|
+
* ORDER BY 相当の処理は Ruby ではこう書く
|
159
|
+
|
160
|
+
= SQL と Ruby
|
161
|
+
|
162
|
+
# coderay sql
|
163
|
+
|
164
|
+
-- 件数が2件以上の行に絞り込む
|
165
|
+
SELECT name
|
166
|
+
FROM users
|
167
|
+
GROUP BY name
|
168
|
+
HAVING count(*) > 1
|
169
|
+
|
170
|
+
* SQL の HAVING
|
171
|
+
|
172
|
+
= SQL と Ruby
|
173
|
+
|
174
|
+
# coderay ruby
|
175
|
+
|
176
|
+
users.
|
177
|
+
group_by { |name, _| name }.
|
178
|
+
select { |_, tuples| tuples.size > 1 }.
|
179
|
+
map { |name, _| [name] }
|
180
|
+
|
181
|
+
* HAVING 相当の処理は Ruby ではこう書く
|
182
|
+
|
183
|
+
= SQL と Ruby
|
184
|
+
|
185
|
+
# coderay sql
|
186
|
+
|
187
|
+
SELECT *
|
188
|
+
FROM users
|
189
|
+
LIMIT 2
|
190
|
+
|
191
|
+
* SQL の LIMIT
|
192
|
+
|
193
|
+
= SQL と Ruby
|
194
|
+
|
195
|
+
# coderay ruby
|
196
|
+
|
197
|
+
users.take(2)
|
198
|
+
|
199
|
+
* LIMIT 相当の処理は Ruby ではこう書く
|
200
|
+
|
201
|
+
= SQL と Ruby
|
202
|
+
|
203
|
+
# coderay sql
|
204
|
+
|
205
|
+
SELECT users.name, users.birthday, urls.url
|
206
|
+
FROM users
|
207
|
+
JOIN urls
|
208
|
+
ON users.name = urls.name
|
209
|
+
|
210
|
+
* SQL の JOIN
|
211
|
+
* INNER JOIN は属性を付加するだけじゃなくて、絞り込みにも使える
|
212
|
+
|
213
|
+
= SQL と Ruby
|
214
|
+
|
215
|
+
# coderay ruby
|
216
|
+
|
217
|
+
urls = [
|
218
|
+
['ちゆ', 'http://tiyu.to'],
|
219
|
+
['もなみ', 'http://www.tokyo-nazo.net/tester/'],
|
220
|
+
['メモリ', 'http://homepage1.nifty.com/GANTSU/memo/index.html'],
|
221
|
+
]
|
222
|
+
|
223
|
+
users.
|
224
|
+
product(urls).
|
225
|
+
select { |(users_name, _), (urls_name, _)| users_name == urls_name }.
|
226
|
+
map { |(name, birthday), (_, url)| [name, birthday, url] }
|
227
|
+
|
228
|
+
* JOIN 相当の処理は Ruby ではこう書く
|
229
|
+
* 結構キツい
|
230
|
+
|
231
|
+
= SQL と Ruby
|
232
|
+
|
233
|
+
# coderay sql
|
234
|
+
|
235
|
+
-- name 毎に、birthday 順で1つ前の行と name を '+' で結合
|
236
|
+
SELECT string_agg(name, '+')
|
237
|
+
over(partition by name order by birthday rows 1 preceding)
|
238
|
+
FROM users
|
239
|
+
-- ちゆ
|
240
|
+
-- ちゆ+ちゆ
|
241
|
+
-- もなみ
|
242
|
+
-- もなみ+もなみ
|
243
|
+
-- メモリ
|
244
|
+
-- メモリ+メモリ
|
245
|
+
|
246
|
+
* SQL の WINDOW 関数
|
247
|
+
|
248
|
+
= SQL と Ruby
|
249
|
+
|
250
|
+
# coderay ruby
|
251
|
+
|
252
|
+
users.
|
253
|
+
group_by { |name, _| name }.
|
254
|
+
map { |_, tuples|
|
255
|
+
tuples.
|
256
|
+
sort_by { |_, birthday| birthday }.
|
257
|
+
map.with_index { |tuple, i|
|
258
|
+
[tuples[(i.zero? ? i : (i-1))..i].map { |name, _| name }.join("+")]
|
259
|
+
}
|
260
|
+
}.
|
261
|
+
flatten(1)
|
262
|
+
|
263
|
+
* WINDOW 関数は Ruby ではこう書く
|
264
|
+
* かなりキツい
|
265
|
+
|
266
|
+
= どう書く勉強会
|
267
|
+
|
268
|
+
* 話は変わって
|
269
|
+
* オフラインリアルタイムどう書く勉強会
|
270
|
+
* http://nabetani.sakura.ne.jp/hena/ord30taxi/
|
271
|
+
* 鍋谷さんの良問を味わう会
|
272
|
+
|
273
|
+
= どう書く勉強会
|
274
|
+
|
275
|
+
# image
|
276
|
+
# src = hena30_1.png
|
277
|
+
# relative_height = 100
|
278
|
+
|
279
|
+
= どう書く勉強会
|
280
|
+
|
281
|
+
* 第30回
|
282
|
+
* 架空のタクシー料金体系について、経路情報に応じた料金を計算
|
283
|
+
* 初乗り料金があり、距離や課金地点のエリアによって異なる課金額
|
284
|
+
|
285
|
+
= どう書く勉強会
|
286
|
+
|
287
|
+
# coderay sql
|
288
|
+
|
289
|
+
CREATE OR REPLACE FUNCTION pg_temp.calc_charge(route text) RETURNS table(num bigint) AS
|
290
|
+
$$
|
291
|
+
BEGIN
|
292
|
+
RETURN QUERY
|
293
|
+
|
294
|
+
* 当日は Ruby で解いたが、後日 SQL で解いてみた
|
295
|
+
* SQL で書いた function プロシージャ(経路文字列を受け取る)
|
296
|
+
|
297
|
+
= どう書く勉強会
|
298
|
+
|
299
|
+
# coderay sql
|
300
|
+
|
301
|
+
WITH point_vias AS (
|
302
|
+
SELECT row_number() over() as no, *
|
303
|
+
FROM unnest(string_to_array(route, NULL)) AS point
|
304
|
+
|
305
|
+
* 受け取った経路文字列を1文字ずつに分解して配列にする
|
306
|
+
* 配列を行に展開する
|
307
|
+
* 行番号を着けたリレーションを返す
|
308
|
+
|
309
|
+
= どうかく勉強会
|
310
|
+
|
311
|
+
* WITH ?
|
312
|
+
* サブクエリに名前を付けられる仕組み
|
313
|
+
* Common Table Expressions(共通表式)と呼ばれる(CTE と略す)
|
314
|
+
* メインの SELECT の前に書いておくと、そのクエリ中にだけ有効な VIEW が見えるようになるというイメージ
|
315
|
+
|
316
|
+
= どう書く勉強会
|
317
|
+
|
318
|
+
# coderay sql
|
319
|
+
|
320
|
+
), point_pairs AS (
|
321
|
+
SELECT no, point AS f, lead(point) over(order by no) AS t
|
322
|
+
FROM point_vias
|
323
|
+
|
324
|
+
* LEAD 関数で、直後の地点の値を呼び出す
|
325
|
+
|
326
|
+
= どう書く勉強会
|
327
|
+
|
328
|
+
# coderay sql
|
329
|
+
|
330
|
+
), point_pairs AS (
|
331
|
+
|
332
|
+
* WITH ?(2)
|
333
|
+
* WITH はカンマ区切りで、いくらでも書ける(何億とかは無理だと思うので空気読んで)
|
334
|
+
* それ以前の CTE を参照する事が可能(逆は無理)
|
335
|
+
|
336
|
+
= どう書く勉強会
|
337
|
+
|
338
|
+
# coderay sql
|
339
|
+
|
340
|
+
), paths AS (
|
341
|
+
SELECT no, f, t
|
342
|
+
FROM point_pairs
|
343
|
+
WHERE t IS NOT NULL
|
344
|
+
|
345
|
+
* 直後の地点が無い行(最終地点)は課金されないので除く
|
346
|
+
|
347
|
+
= どう書く勉強会
|
348
|
+
|
349
|
+
# coderay sql
|
350
|
+
|
351
|
+
), distances AS (
|
352
|
+
SELECT no, path, distance
|
353
|
+
FROM paths
|
354
|
+
JOIN path_patterns ON ARRAY[paths.f, paths.t] <@ string_to_array(path_patterns.path, NULL)
|
355
|
+
|
356
|
+
* path_patterns はマスタとして永続化済みのテーブル
|
357
|
+
* 地点間の距離と、地点の地域が入ってる
|
358
|
+
* それを JOIN して、経路に距離情報を付与する
|
359
|
+
|
360
|
+
= どう書く勉強会
|
361
|
+
|
362
|
+
# coderay sql
|
363
|
+
|
364
|
+
), distance_and_prices AS (
|
365
|
+
SELECT no, distances.distance, price
|
366
|
+
FROM distances
|
367
|
+
JOIN path_patterns using(path)
|
368
|
+
JOIN extend_charges using(city)
|
369
|
+
|
370
|
+
* 地点毎に違う追加料金単価が入ったテーブルを JOIN する
|
371
|
+
|
372
|
+
= どう書く勉強会
|
373
|
+
|
374
|
+
# coderay sql
|
375
|
+
|
376
|
+
), distance_progress AS (
|
377
|
+
SELECT sum(distance) over(order by no) AS terminattion, price
|
378
|
+
FROM distance_and_prices
|
379
|
+
|
380
|
+
* 地点毎の積算距離と追加料金単価を算出
|
381
|
+
|
382
|
+
= どう書く勉強会
|
383
|
+
|
384
|
+
# coderay sql
|
385
|
+
|
386
|
+
), extend_charge_terms AS (
|
387
|
+
SELECT int8range(coalesce(lag(terminattion) over(), 0) + 1, terminattion, '[]') AS term, price
|
388
|
+
FROM distance_progress
|
389
|
+
|
390
|
+
* 地点毎の積算距離の範囲(範囲型の値)と追加料金単価のペア
|
391
|
+
|
392
|
+
= どう書く勉強会
|
393
|
+
|
394
|
+
# coderay sql
|
395
|
+
|
396
|
+
), extend_charge_start AS (
|
397
|
+
SELECT distance + 1 AS distance
|
398
|
+
FROM first_charges
|
399
|
+
JOIN points USING(city)
|
400
|
+
JOIN paths ON points.point = paths.f AND paths.no = 1
|
401
|
+
|
402
|
+
* 初乗り料金が終わって、追加料金が発生し始めるる地点を算出
|
403
|
+
|
404
|
+
= どう書く勉強会
|
405
|
+
|
406
|
+
# coderay sql
|
407
|
+
|
408
|
+
), extend_charge_points AS (
|
409
|
+
SELECT generate_series(
|
410
|
+
(SELECT distance FROM extend_charge_start),
|
411
|
+
(SELECT sum(distance) FROM distances),
|
412
|
+
200
|
413
|
+
) AS point
|
414
|
+
|
415
|
+
* 追加料金が発生する地点から終点までの間をを追加料金が発生する距離(200m)毎に区切って課金地点にする
|
416
|
+
|
417
|
+
= どう書く勉強会
|
418
|
+
|
419
|
+
# coderay sql
|
420
|
+
|
421
|
+
), extend_charges AS (
|
422
|
+
SELECT price AS charge
|
423
|
+
FROM extend_charge_points AS ecp
|
424
|
+
JOIN extend_charge_terms AS ect
|
425
|
+
on ecp.point <@ ect.term
|
426
|
+
|
427
|
+
* 課金地点テーブルに追加料金単価のテーブルを JOIN
|
428
|
+
* 課金地点の適用単価を算出
|
429
|
+
|
430
|
+
= どう書く勉強会
|
431
|
+
|
432
|
+
# coderay sql
|
433
|
+
|
434
|
+
), first_charge AS (
|
435
|
+
SELECT price AS charge
|
436
|
+
FROM first_charges
|
437
|
+
JOIN points using(city)
|
438
|
+
JOIN paths ON paths.f = points.point AND paths.no = 1
|
439
|
+
|
440
|
+
* 初乗り料金
|
441
|
+
|
442
|
+
= どう書く勉強会
|
443
|
+
|
444
|
+
# coderay sql
|
445
|
+
|
446
|
+
), all_charges AS (
|
447
|
+
SELECT charge FROM first_charge
|
448
|
+
UNION ALL
|
449
|
+
SELECT charge FROM extend_charges
|
450
|
+
)
|
451
|
+
|
452
|
+
* 初乗り料金と追加料金を UNION
|
453
|
+
|
454
|
+
= どう書く勉強会
|
455
|
+
|
456
|
+
# coderay sql
|
457
|
+
|
458
|
+
SELECT sum(charge) FROM all_charges;
|
459
|
+
|
460
|
+
* 全ての料金を足すと最終料金が算出される
|
461
|
+
|
462
|
+
= どう書く勉強会
|
463
|
+
|
464
|
+
# coderay sql
|
465
|
+
|
466
|
+
END
|
467
|
+
$$ LANGUAGE plpgsql;
|
468
|
+
|
469
|
+
* function プロシージャー終わり
|
470
|
+
|
471
|
+
= どう書く勉強会
|
472
|
+
|
473
|
+
# coderay sql
|
474
|
+
|
475
|
+
SELECT id, route, pg_temp.calc_charge(route) as actual, expected
|
476
|
+
FROM test_cases;
|
477
|
+
-- id | route | actual | expected
|
478
|
+
-- ----+-----------+--------+----------
|
479
|
+
-- 0 | ADFC | 510 | 510
|
480
|
+
-- 1 | CFDA | 500 | 500
|
481
|
+
-- 2 | AB | 460 | 460
|
482
|
+
-- 3 | BA | 460 | 460
|
483
|
+
-- 4 | CD | 400 | 400
|
484
|
+
-- 5 | DC | 350 | 350
|
485
|
+
-- 6 | BG | 520 | 520
|
486
|
+
-- 7 | GB | 530 | 530
|
487
|
+
-- 8 | FDA | 450 | 450
|
488
|
+
-- 9 | ADF | 450 | 450
|
489
|
+
|
490
|
+
= どう書く勉強会
|
491
|
+
|
492
|
+
# coderay sql
|
493
|
+
|
494
|
+
-- 10 | FDACB | 750 | 750
|
495
|
+
-- 11 | BCADF | 710 | 710
|
496
|
+
-- 12 | EDACB | 800 | 800
|
497
|
+
-- 13 | BCADE | 810 | 810
|
498
|
+
-- 14 | EGFCADE | 920 | 920
|
499
|
+
-- 15 | EDACFGE | 910 | 910
|
500
|
+
-- 16 | ABCDA | 960 | 960
|
501
|
+
-- 17 | ADCBA | 1000 | 1000
|
502
|
+
-- 18 | BADCFGB | 1180 | 1180
|
503
|
+
-- 19 | BGFCDAB | 1180 | 1180
|
504
|
+
-- 20 | CDFC | 460 | 460
|
505
|
+
-- 21 | CFDC | 450 | 450
|
506
|
+
|
507
|
+
= どう書く勉強会
|
508
|
+
|
509
|
+
# coderay sql
|
510
|
+
|
511
|
+
-- 22 | ABGEDA | 1420 | 1420
|
512
|
+
-- 23 | ADEGBA | 1470 | 1470
|
513
|
+
-- 24 | CFGB | 640 | 640
|
514
|
+
-- 25 | BGFC | 630 | 630
|
515
|
+
-- 26 | ABGEDFC | 1480 | 1480
|
516
|
+
-- 27 | CFDEGBA | 1520 | 1520
|
517
|
+
-- 28 | CDFGEDABG | 1770 | 1770
|
518
|
+
-- 29 | GBADEGFDC | 1680 | 1680
|
519
|
+
|
520
|
+
* テストケースを全部パスした
|
521
|
+
|
522
|
+
= どう書く勉強会
|
523
|
+
|
524
|
+
* ファイルはこちらです
|
525
|
+
* https://gist.github.com/yancya/a437cf424242dbe9cab9
|
526
|
+
* ご興味があれば動かしてみてください
|
527
|
+
* PostgreSQL に喰わせるだけで動きます
|
528
|
+
|
529
|
+
= ActiveRecord
|
530
|
+
|
531
|
+
* 話を Ruby っぽいところに戻す
|
532
|
+
* 有名な ORM
|
533
|
+
* 他にも Sequel などがある
|
534
|
+
|
535
|
+
= ActiveRecord
|
536
|
+
|
537
|
+
# RT
|
538
|
+
caption = サンプルテーブル users
|
539
|
+
|
540
|
+
name, birthday
|
541
|
+
|
542
|
+
ちゆ, 1999-02-14
|
543
|
+
ちゆ, 2003-02-14
|
544
|
+
もなみ, 2002-02-09
|
545
|
+
もなみ, 2006-02-09
|
546
|
+
メモリ, 2003-11-02
|
547
|
+
メモリ, 2004-11-02
|
548
|
+
|
549
|
+
= ActiveRecord
|
550
|
+
|
551
|
+
* たとえば「ちゆ12歳」と「もなみ9歳」だけを抜き出したいとする
|
552
|
+
|
553
|
+
= ActiveRecord
|
554
|
+
|
555
|
+
# coderay sql
|
556
|
+
SELECT *
|
557
|
+
FROM users
|
558
|
+
WHERE (name = 'ちゆ' AND date_part('year', age(birthday)) = 12)
|
559
|
+
OR (name = 'もなみ' AND date_part('year', age(birthday)) = 9)
|
560
|
+
|
561
|
+
* SQL で書くと、こんなかんじ
|
562
|
+
* 対象が増える程 OR が増える
|
563
|
+
* Repeat myself 感(Not DRY)
|
564
|
+
|
565
|
+
= ActiveRecord
|
566
|
+
|
567
|
+
# coderay sql
|
568
|
+
SELECT *
|
569
|
+
FROM users
|
570
|
+
WHERE (name = 'ちゆ' AND date_part('year', age(birthday)) = 12)
|
571
|
+
OR (name = 'もなみ' AND date_part('year', age(birthday)) = 9)
|
572
|
+
|
573
|
+
* "(〜 AND 〜)" みたいなのを生成して join(" OR ") とかで結合すれば、なんとかできそう
|
574
|
+
* ただ、他の方法を考えてみよう
|
575
|
+
|
576
|
+
= Shapeshifter
|
577
|
+
|
578
|
+
# coderay ruby
|
579
|
+
|
580
|
+
class CreateShapeshifters < ActiveRecord::Migration
|
581
|
+
def change
|
582
|
+
create_table :shapeshifters do |t|
|
583
|
+
end
|
584
|
+
|
585
|
+
# INSERT forbidden
|
586
|
+
execute <<-SQL
|
587
|
+
ALTER TABLE shapeshifters
|
588
|
+
ADD CONSTRAINT forbid_to_insert_shapeshifters
|
589
|
+
CHECK (false)
|
590
|
+
SQL
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
= Shapeshifter
|
595
|
+
|
596
|
+
# coderay ruby
|
597
|
+
|
598
|
+
class Shapeshifter < ActiveRecord::Base
|
599
|
+
def self.metamorphose(schema:, tuples:)
|
600
|
+
with(table_name => sanitize_sql_array(<<-SQL.chomp, tuples.flatten))
|
601
|
+
SELECT *
|
602
|
+
FROM #{to_values(tuples)}
|
603
|
+
AS t(#{primary_key}, #{schema.join(', ')}) -- SQL インジェクションやべぇ
|
604
|
+
SQL
|
605
|
+
end
|
606
|
+
|
607
|
+
def self.to_values(tuples)
|
608
|
+
tuples.
|
609
|
+
map.with_index(1) { |tuple, i| "(#{i}, #{tuple.map { '?' }.join(', ')})" }.
|
610
|
+
join(", ").
|
611
|
+
tap { |values| break "(VALUES #{values})" }
|
612
|
+
end
|
613
|
+
|
614
|
+
def self.sanitize_sql_array(sql, values)
|
615
|
+
ActiveRecord::Base.send(:sanitize_sql_array, [sql, *values])
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
= Shapeshifter
|
620
|
+
|
621
|
+
# coderay ruby
|
622
|
+
|
623
|
+
with(table_name => sanitize_sql_array(<<-SQL.chomp, tuples.flatten))
|
624
|
+
|
625
|
+
* その with は一体...
|
626
|
+
* postgres_ext という gem の機能です
|
627
|
+
* https://github.com/dockyard/postgres_ext
|
628
|
+
* Arel::Nodes::As のインスタンスか { tablename => sql } みたいな Hash を渡します
|
629
|
+
|
630
|
+
= Shapeshifter
|
631
|
+
|
632
|
+
# coderay ruby
|
633
|
+
|
634
|
+
Shapeshifter.all
|
635
|
+
#=> #<ActiveRecord::Relation []>
|
636
|
+
|
637
|
+
Shapeshifter.create
|
638
|
+
#=> ActiveRecord::StatementInvalid:
|
639
|
+
#=> PG::CheckViolation: ERROR: new row for relation "shapeshifters"
|
640
|
+
#=> violates check constraint "forbid_to_insert_shapeshifters"
|
641
|
+
|
642
|
+
* 中身も無い、INSERT も出来ない、属性が id のみのモデル
|
643
|
+
* これをこうじゃ
|
644
|
+
|
645
|
+
= Shapeshifter
|
646
|
+
|
647
|
+
# coderay ruby
|
648
|
+
|
649
|
+
ar = Shapeshifter.
|
650
|
+
metamorphose(
|
651
|
+
schema: %w{code message},
|
652
|
+
tuples: [[404, 'Not Found'], [200, 'OK']]
|
653
|
+
)
|
654
|
+
#=> #<ActiveRecord::Relation [#<Shapeshifter id: 1>, #<Shapeshifter id: 2>]>
|
655
|
+
|
656
|
+
ar.map(&:attributes)
|
657
|
+
#=> [{"id"=>1, "code"=>404, "message"=>"Not Found"},
|
658
|
+
#=> {"id"=>2, "code"=>200, "message"=>"OK"}]
|
659
|
+
|
660
|
+
ar.where(code: 200)
|
661
|
+
#=> #<ActiveRecord::Relation [#<Shapeshifter id: 2>]>
|
662
|
+
|
663
|
+
* アプリケーションから注入したタプルが SQL 界を旅してから AR として戻ってくる
|
664
|
+
|
665
|
+
= Shapeshifter
|
666
|
+
|
667
|
+
# coderay SQL
|
668
|
+
|
669
|
+
-- 発行される SQL
|
670
|
+
WITH "shapeshifters" AS (
|
671
|
+
SELECT *
|
672
|
+
FROM (VALUES (1, 404, 'Not Found'), (2, 200, 'OK'))
|
673
|
+
AS t(id, code, message) )
|
674
|
+
SELECT "shapeshifters".*
|
675
|
+
FROM "shapeshifters"
|
676
|
+
-- id | code | message
|
677
|
+
-- ----+------+-----------
|
678
|
+
-- 1 | 404 | Not Found
|
679
|
+
-- 2 | 200 | OK
|
680
|
+
|
681
|
+
* 一時的に共通表式(CTE)を参照
|
682
|
+
|
683
|
+
= User モデル
|
684
|
+
|
685
|
+
# coderay ruby
|
686
|
+
|
687
|
+
class User < ActiveRecord::Base
|
688
|
+
scope :on_name_and_ages, -> (tuples) {
|
689
|
+
with(
|
690
|
+
target_users: Shapeshifter.
|
691
|
+
metamorphose(schema: %w{name age}, tuples: tuples)
|
692
|
+
).
|
693
|
+
joins(<<-SQL)
|
694
|
+
JOIN target_users
|
695
|
+
ON users.name = target_users.name
|
696
|
+
AND age = date_part('year', age(birthday))::integer
|
697
|
+
SQL
|
698
|
+
}
|
699
|
+
end
|
700
|
+
|
701
|
+
* scopeにShapeshifterを仕込む
|
702
|
+
|
703
|
+
= User モデル
|
704
|
+
|
705
|
+
# coderay ruby
|
706
|
+
|
707
|
+
User.on_name_and_ages(
|
708
|
+
[['ちゆ', 12], ['もなみ', 9]]
|
709
|
+
).map(&:attributes)
|
710
|
+
#=> [{"id"=>2, "name"=>"ちゆ", "birthday"=>Fri, 14 Feb 2003},
|
711
|
+
#=> {"id"=>4, "name"=>"もなみ", "birthday"=>Thu, 09 Feb 2006}]
|
712
|
+
|
713
|
+
* タプルを使った絞り込みをする scope になってる
|
714
|
+
|
715
|
+
= User モデル
|
716
|
+
|
717
|
+
# coderay sql
|
718
|
+
-- 発行される SQL
|
719
|
+
WITH "target_users" AS (
|
720
|
+
WITH "shapeshifters" AS (
|
721
|
+
SELECT *
|
722
|
+
FROM (VALUES (1, 'ちゆ', 12), (2, 'もなみ', 9))
|
723
|
+
AS t(id, name, age))
|
724
|
+
SELECT "shapeshifters".*
|
725
|
+
FROM "shapeshifters")
|
726
|
+
SELECT "users".*
|
727
|
+
FROM "users"
|
728
|
+
JOIN target_users
|
729
|
+
ON users.name = target_users.name
|
730
|
+
AND age = date_part('year', age(birthday))::integer
|
731
|
+
|
732
|
+
= まとめ
|
733
|
+
|
734
|
+
* SQL で出来る事の殆どは Ruby でもできる
|
735
|
+
* VALUES 句を使うとクエリに任意のリレーションを埋め込める
|
736
|
+
* Rails を騙して共通表式(CTE)を読ませたった
|
737
|
+
* 以上、ご質問があればどうぞ
|
738
|
+
|
739
|
+
= どれくらい入れられるの
|
740
|
+
|
741
|
+
# coderay ruby
|
742
|
+
Shapeshifter.
|
743
|
+
metamorphose(
|
744
|
+
schema: [:name],
|
745
|
+
tuples: 1_000_000.times.map { [%i{a b c}.sample] }
|
746
|
+
).
|
747
|
+
group(:name).
|
748
|
+
count
|
749
|
+
#=> {"c"=>333407, "b"=>332869, "a"=>333724}
|
750
|
+
|
751
|
+
* 100 万件の GRUOP BY 集計
|
752
|
+
* ローカルホストで5秒くらい
|
753
|
+
|
754
|
+
= どれくらい入れられるの
|
755
|
+
|
756
|
+
# comment
|
757
|
+
QUERY PLAN
|
758
|
+
----------------------------------------------------------------------------------------------------------------------------------
|
759
|
+
HashAggregate (cost=37500.00..37502.00 rows=200 width=32) (actual time=630.957..630.958 rows=3 loops=1)
|
760
|
+
CTE shapeshifters
|
761
|
+
-> Values Scan on "*VALUES*" (cost=0.00..12500.00 rows=1000000 width=36) (actual time=0.003..168.647 rows=1000000 loops=1)
|
762
|
+
-> CTE Scan on shapeshifters (cost=0.00..20000.00 rows=1000000 width=32) (actual time=0.005..448.934 rows=1000000 loops=1)
|
763
|
+
Total runtime: 680.796 ms
|
764
|
+
(5 rows)
|
765
|
+
|
766
|
+
* クエリの実行自体は 0.7 秒
|
767
|
+
* 14MB あるとパースが重いかも
|
768
|
+
* EXPLAIN に出てこないっぽい
|
769
|
+
* パース時間プロファイリングの方法を調べたい
|
Binary file
|
data/sql_machi.png
ADDED
Binary file
|
data/yancya.jpg
ADDED
Binary file
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rabbit-slide-yancya-ruby_seen_from_sqlish_brain
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2015.11.08.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- yancya
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rabbit
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.0.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.0.2
|
27
|
+
description: "大江戸 Ruby 会議 05 の Ninja Talks で話した内容のスライドです"
|
28
|
+
email:
|
29
|
+
- yancya@upec.jp
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".rabbit"
|
35
|
+
- README.rd
|
36
|
+
- README.rd~
|
37
|
+
- Rakefile
|
38
|
+
- config.yaml
|
39
|
+
- hena30_1.png
|
40
|
+
- ooedo-2015.rab
|
41
|
+
- pdf/ruby_seen_from_sqlish_brain-ruby_seen_from_sqlish_brain.pdf
|
42
|
+
- sql_machi.png
|
43
|
+
- yancya.jpg
|
44
|
+
homepage: http://slide.rabbit-shocker.org/authors/yancya/ruby_seen_from_sqlish_brain/
|
45
|
+
licenses: []
|
46
|
+
metadata: {}
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 2.4.5.1
|
64
|
+
signing_key:
|
65
|
+
specification_version: 4
|
66
|
+
summary: SQL 脳から見た Ruby
|
67
|
+
test_files: []
|