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 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
+ * パース時間プロファイリングの方法を調べたい
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: []