sequel 5.80.0 → 5.92.0

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.
Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sequel +9 -4
  3. data/lib/sequel/adapters/ado.rb +1 -1
  4. data/lib/sequel/adapters/ibmdb.rb +1 -0
  5. data/lib/sequel/adapters/jdbc/db2.rb +2 -2
  6. data/lib/sequel/adapters/jdbc/derby.rb +3 -3
  7. data/lib/sequel/adapters/jdbc/h2.rb +2 -2
  8. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -2
  9. data/lib/sequel/adapters/jdbc/jtds.rb +2 -2
  10. data/lib/sequel/adapters/jdbc/mysql.rb +1 -1
  11. data/lib/sequel/adapters/jdbc/oracle.rb +5 -5
  12. data/lib/sequel/adapters/jdbc/postgresql.rb +5 -5
  13. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +6 -6
  14. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -2
  15. data/lib/sequel/adapters/jdbc/sqlserver.rb +2 -2
  16. data/lib/sequel/adapters/jdbc.rb +8 -8
  17. data/lib/sequel/adapters/mysql2.rb +8 -1
  18. data/lib/sequel/adapters/shared/access.rb +1 -0
  19. data/lib/sequel/adapters/shared/db2.rb +1 -1
  20. data/lib/sequel/adapters/shared/mssql.rb +18 -5
  21. data/lib/sequel/adapters/shared/mysql.rb +8 -4
  22. data/lib/sequel/adapters/shared/oracle.rb +1 -0
  23. data/lib/sequel/adapters/shared/postgres.rb +106 -13
  24. data/lib/sequel/adapters/shared/sqlite.rb +4 -2
  25. data/lib/sequel/adapters/sqlite.rb +4 -0
  26. data/lib/sequel/adapters/trilogy.rb +1 -2
  27. data/lib/sequel/connection_pool/sharded_threaded.rb +26 -10
  28. data/lib/sequel/connection_pool/threaded.rb +26 -10
  29. data/lib/sequel/connection_pool.rb +2 -2
  30. data/lib/sequel/core.rb +15 -0
  31. data/lib/sequel/database/connecting.rb +20 -26
  32. data/lib/sequel/database/dataset_defaults.rb +3 -3
  33. data/lib/sequel/database/misc.rb +46 -10
  34. data/lib/sequel/database/query.rb +11 -11
  35. data/lib/sequel/database/schema_generator.rb +8 -0
  36. data/lib/sequel/database/schema_methods.rb +17 -1
  37. data/lib/sequel/dataset/actions.rb +9 -1
  38. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +1 -1
  39. data/lib/sequel/dataset/prepared_statements.rb +2 -1
  40. data/lib/sequel/dataset/query.rb +9 -5
  41. data/lib/sequel/dataset/sql.rb +25 -5
  42. data/lib/sequel/extensions/caller_logging.rb +2 -0
  43. data/lib/sequel/extensions/connection_validator.rb +15 -10
  44. data/lib/sequel/extensions/dataset_run.rb +41 -0
  45. data/lib/sequel/extensions/migration.rb +23 -3
  46. data/lib/sequel/extensions/null_dataset.rb +2 -2
  47. data/lib/sequel/extensions/pg_auto_parameterize.rb +1 -1
  48. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +93 -10
  49. data/lib/sequel/extensions/pg_enum.rb +3 -3
  50. data/lib/sequel/extensions/pg_json_ops.rb +642 -9
  51. data/lib/sequel/extensions/pg_row.rb +3 -1
  52. data/lib/sequel/extensions/pg_schema_caching.rb +90 -0
  53. data/lib/sequel/extensions/provenance.rb +2 -0
  54. data/lib/sequel/extensions/query_blocker.rb +172 -0
  55. data/lib/sequel/extensions/schema_caching.rb +24 -9
  56. data/lib/sequel/extensions/schema_dumper.rb +16 -4
  57. data/lib/sequel/extensions/sqlite_json_ops.rb +1 -1
  58. data/lib/sequel/extensions/stdio_logger.rb +48 -0
  59. data/lib/sequel/extensions/string_agg.rb +17 -4
  60. data/lib/sequel/extensions/temporarily_release_connection.rb +178 -0
  61. data/lib/sequel/extensions/virtual_row_method_block.rb +1 -0
  62. data/lib/sequel/model/associations.rb +28 -3
  63. data/lib/sequel/model/base.rb +67 -18
  64. data/lib/sequel/plugins/association_pks.rb +1 -1
  65. data/lib/sequel/plugins/column_encryption.rb +1 -1
  66. data/lib/sequel/plugins/composition.rb +1 -1
  67. data/lib/sequel/plugins/defaults_setter.rb +16 -4
  68. data/lib/sequel/plugins/enum.rb +1 -1
  69. data/lib/sequel/plugins/forbid_lazy_load.rb +14 -1
  70. data/lib/sequel/plugins/input_transformer.rb +1 -1
  71. data/lib/sequel/plugins/inspect_pk.rb +44 -0
  72. data/lib/sequel/plugins/instance_filters.rb +4 -1
  73. data/lib/sequel/plugins/inverted_subsets.rb +1 -0
  74. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  75. data/lib/sequel/plugins/nested_attributes.rb +10 -5
  76. data/lib/sequel/plugins/optimistic_locking.rb +2 -0
  77. data/lib/sequel/plugins/paged_operations.rb +5 -2
  78. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +6 -1
  79. data/lib/sequel/plugins/pg_auto_validate_enums.rb +88 -0
  80. data/lib/sequel/plugins/pg_eager_any_typed_array.rb +95 -0
  81. data/lib/sequel/plugins/rcte_tree.rb +1 -1
  82. data/lib/sequel/plugins/serialization.rb +11 -5
  83. data/lib/sequel/plugins/sql_comments.rb +7 -2
  84. data/lib/sequel/plugins/static_cache_cache.rb +50 -13
  85. data/lib/sequel/plugins/subset_conditions.rb +85 -5
  86. data/lib/sequel/plugins/subset_static_cache.rb +263 -0
  87. data/lib/sequel/plugins/tactical_eager_loading.rb +6 -2
  88. data/lib/sequel/plugins/validate_associated.rb +1 -1
  89. data/lib/sequel/sql.rb +16 -6
  90. data/lib/sequel/version.rb +1 -1
  91. metadata +12 -234
  92. data/CHANGELOG +0 -1355
  93. data/README.rdoc +0 -936
  94. data/doc/advanced_associations.rdoc +0 -884
  95. data/doc/association_basics.rdoc +0 -1859
  96. data/doc/bin_sequel.rdoc +0 -146
  97. data/doc/cheat_sheet.rdoc +0 -255
  98. data/doc/code_order.rdoc +0 -102
  99. data/doc/core_extensions.rdoc +0 -405
  100. data/doc/dataset_basics.rdoc +0 -96
  101. data/doc/dataset_filtering.rdoc +0 -222
  102. data/doc/extensions.rdoc +0 -77
  103. data/doc/fork_safety.rdoc +0 -84
  104. data/doc/mass_assignment.rdoc +0 -98
  105. data/doc/migration.rdoc +0 -660
  106. data/doc/model_dataset_method_design.rdoc +0 -129
  107. data/doc/model_hooks.rdoc +0 -254
  108. data/doc/model_plugins.rdoc +0 -270
  109. data/doc/mssql_stored_procedures.rdoc +0 -43
  110. data/doc/object_model.rdoc +0 -563
  111. data/doc/opening_databases.rdoc +0 -436
  112. data/doc/postgresql.rdoc +0 -611
  113. data/doc/prepared_statements.rdoc +0 -144
  114. data/doc/querying.rdoc +0 -1070
  115. data/doc/reflection.rdoc +0 -120
  116. data/doc/release_notes/5.0.0.txt +0 -159
  117. data/doc/release_notes/5.1.0.txt +0 -31
  118. data/doc/release_notes/5.10.0.txt +0 -84
  119. data/doc/release_notes/5.11.0.txt +0 -83
  120. data/doc/release_notes/5.12.0.txt +0 -141
  121. data/doc/release_notes/5.13.0.txt +0 -27
  122. data/doc/release_notes/5.14.0.txt +0 -63
  123. data/doc/release_notes/5.15.0.txt +0 -39
  124. data/doc/release_notes/5.16.0.txt +0 -110
  125. data/doc/release_notes/5.17.0.txt +0 -31
  126. data/doc/release_notes/5.18.0.txt +0 -69
  127. data/doc/release_notes/5.19.0.txt +0 -28
  128. data/doc/release_notes/5.2.0.txt +0 -33
  129. data/doc/release_notes/5.20.0.txt +0 -89
  130. data/doc/release_notes/5.21.0.txt +0 -87
  131. data/doc/release_notes/5.22.0.txt +0 -48
  132. data/doc/release_notes/5.23.0.txt +0 -56
  133. data/doc/release_notes/5.24.0.txt +0 -56
  134. data/doc/release_notes/5.25.0.txt +0 -32
  135. data/doc/release_notes/5.26.0.txt +0 -35
  136. data/doc/release_notes/5.27.0.txt +0 -21
  137. data/doc/release_notes/5.28.0.txt +0 -16
  138. data/doc/release_notes/5.29.0.txt +0 -22
  139. data/doc/release_notes/5.3.0.txt +0 -121
  140. data/doc/release_notes/5.30.0.txt +0 -20
  141. data/doc/release_notes/5.31.0.txt +0 -148
  142. data/doc/release_notes/5.32.0.txt +0 -46
  143. data/doc/release_notes/5.33.0.txt +0 -24
  144. data/doc/release_notes/5.34.0.txt +0 -40
  145. data/doc/release_notes/5.35.0.txt +0 -56
  146. data/doc/release_notes/5.36.0.txt +0 -60
  147. data/doc/release_notes/5.37.0.txt +0 -30
  148. data/doc/release_notes/5.38.0.txt +0 -28
  149. data/doc/release_notes/5.39.0.txt +0 -19
  150. data/doc/release_notes/5.4.0.txt +0 -80
  151. data/doc/release_notes/5.40.0.txt +0 -40
  152. data/doc/release_notes/5.41.0.txt +0 -25
  153. data/doc/release_notes/5.42.0.txt +0 -136
  154. data/doc/release_notes/5.43.0.txt +0 -98
  155. data/doc/release_notes/5.44.0.txt +0 -32
  156. data/doc/release_notes/5.45.0.txt +0 -34
  157. data/doc/release_notes/5.46.0.txt +0 -87
  158. data/doc/release_notes/5.47.0.txt +0 -59
  159. data/doc/release_notes/5.48.0.txt +0 -14
  160. data/doc/release_notes/5.49.0.txt +0 -59
  161. data/doc/release_notes/5.5.0.txt +0 -61
  162. data/doc/release_notes/5.50.0.txt +0 -78
  163. data/doc/release_notes/5.51.0.txt +0 -47
  164. data/doc/release_notes/5.52.0.txt +0 -87
  165. data/doc/release_notes/5.53.0.txt +0 -23
  166. data/doc/release_notes/5.54.0.txt +0 -27
  167. data/doc/release_notes/5.55.0.txt +0 -21
  168. data/doc/release_notes/5.56.0.txt +0 -51
  169. data/doc/release_notes/5.57.0.txt +0 -23
  170. data/doc/release_notes/5.58.0.txt +0 -31
  171. data/doc/release_notes/5.59.0.txt +0 -73
  172. data/doc/release_notes/5.6.0.txt +0 -31
  173. data/doc/release_notes/5.60.0.txt +0 -22
  174. data/doc/release_notes/5.61.0.txt +0 -43
  175. data/doc/release_notes/5.62.0.txt +0 -132
  176. data/doc/release_notes/5.63.0.txt +0 -33
  177. data/doc/release_notes/5.64.0.txt +0 -50
  178. data/doc/release_notes/5.65.0.txt +0 -21
  179. data/doc/release_notes/5.66.0.txt +0 -24
  180. data/doc/release_notes/5.67.0.txt +0 -32
  181. data/doc/release_notes/5.68.0.txt +0 -61
  182. data/doc/release_notes/5.69.0.txt +0 -26
  183. data/doc/release_notes/5.7.0.txt +0 -108
  184. data/doc/release_notes/5.70.0.txt +0 -35
  185. data/doc/release_notes/5.71.0.txt +0 -21
  186. data/doc/release_notes/5.72.0.txt +0 -33
  187. data/doc/release_notes/5.73.0.txt +0 -66
  188. data/doc/release_notes/5.74.0.txt +0 -45
  189. data/doc/release_notes/5.75.0.txt +0 -35
  190. data/doc/release_notes/5.76.0.txt +0 -86
  191. data/doc/release_notes/5.77.0.txt +0 -63
  192. data/doc/release_notes/5.78.0.txt +0 -67
  193. data/doc/release_notes/5.79.0.txt +0 -28
  194. data/doc/release_notes/5.8.0.txt +0 -170
  195. data/doc/release_notes/5.80.0.txt +0 -40
  196. data/doc/release_notes/5.9.0.txt +0 -99
  197. data/doc/schema_modification.rdoc +0 -679
  198. data/doc/security.rdoc +0 -443
  199. data/doc/sharding.rdoc +0 -286
  200. data/doc/sql.rdoc +0 -648
  201. data/doc/testing.rdoc +0 -190
  202. data/doc/thread_safety.rdoc +0 -15
  203. data/doc/transactions.rdoc +0 -250
  204. data/doc/validations.rdoc +0 -558
  205. data/doc/virtual_rows.rdoc +0 -265
data/doc/querying.rdoc DELETED
@@ -1,1070 +0,0 @@
1
- = Querying in Sequel
2
-
3
- This guide is based on http://guides.rubyonrails.org/active_record_querying.html
4
-
5
- == Purpose of this Guide
6
-
7
- Sequel is a flexible and powerful database library
8
- that supports a wide variety of different querying methods. This guide
9
- aims to be a introduction to Sequel's querying support.
10
-
11
- While you can use raw SQL with Sequel, a large part of the
12
- advantage you get from using Sequel is Sequel's ability to abstract
13
- SQL from you and give you a pure-ruby interface. Sequel also ships with
14
- a {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
15
- which adds methods to core ruby types to work with Sequel.
16
-
17
- == Retrieving Objects
18
-
19
- Sequel provides a few separate methods for retrieving objects from the
20
- database. The underlying method is Sequel::Dataset#each, which yields each
21
- row as the Sequel::Database provides it. However, while Dataset#each can and
22
- often is used directly, in many cases there is a more convenient retrieval
23
- method you can use.
24
-
25
- === Sequel::Dataset
26
-
27
- If you are new to Sequel and aren't familiar with Sequel, you should probably
28
- read the {"Dataset Basics" guide}[rdoc-ref:doc/dataset_basics.rdoc],
29
- then come back here.
30
-
31
- === Retrieving a Single Object
32
-
33
- Sequel offers quite a few ways to to retrieve a single object.
34
-
35
- ==== Using a Primary Key [Sequel::Model]
36
-
37
- The <tt>Sequel::Model.[]</tt> is the easiest method to use to find a model instance
38
- by its primary key value:
39
-
40
- # Find artist with primary key (id) 1
41
- artist = Artist[1]
42
- # SELECT * FROM artists WHERE (id = 1)
43
- # => #<Artist @values={:name=>"YJM", :id=>1}>
44
-
45
- If there is no record with the given primary key, nil will be returned. If you want
46
- to raise an exception if no record is found, you can use <tt>Sequel::Model.with_pk!</tt>:
47
-
48
- artist = Artist.with_pk!(1)
49
-
50
- ==== Using +first+
51
-
52
- If you want the first record in the dataset,
53
- <tt>Sequel::Dataset#first</tt> is probably the most obvious method to use:
54
-
55
- artist = Artist.first
56
- # SELECT * FROM artists LIMIT 1
57
- # => #<Artist @values={:name=>"YJM", :id=>1}>
58
-
59
- Any options you pass to +first+ will be used as a filter:
60
-
61
- artist = Artist.first(name: 'YJM')
62
- # SELECT * FROM artists WHERE (name = 'YJM') LIMIT 1
63
- # => #<Artist @values={:name=>"YJM", :id=>1}>
64
-
65
- artist = Artist.first(Sequel.like(:name, 'Y%'))
66
- # SELECT * FROM artists WHERE (name LIKE 'Y%' ESCAPE '\') LIMIT 1
67
- # => #<Artist @values={:name=>"YJM", :id=>1}>
68
-
69
- If there is no matching row, +first+ will return nil. If you want to
70
- raise an exception instead, use <tt>first!</tt>.
71
-
72
- <tt>Sequel::Dataset#[]</tt> is basically an alias for +first+, except it
73
- requires an argument:
74
-
75
- DB[:artists][{name: 'YJM'}]
76
- # SELECT * FROM artists WHERE (name = 'YJM') LIMIT 1
77
- # => {:name=>"YJM", :id=>1}
78
-
79
- Note that while Model.[] allows you to pass a primary key directly,
80
- Dataset#[] does not (unless it is a model dataset).
81
-
82
- ==== Using +last+
83
-
84
- If you want the last record in the dataset,
85
- <tt>Sequel::Dataset#last</tt> is an obvious method to use. +last+ requires the
86
- dataset be ordered, unless the dataset is a model dataset in which case +last+
87
- will do a reverse order by the primary key field:
88
-
89
- artist = Artist.last
90
- # SELECT * FROM artists ORDER BY id DESC LIMIT 1
91
- # => #<Artist @values={:name=>"YJM", :id=>1}>
92
-
93
- Note:
94
-
95
- 1. +last+ is equivalent to running a +reverse.first+ query, in other words it reverses the order of the dataset and then calls +first+. This is why +last+ raises a Sequel::Error when there is no order on a plain dataset - because it will provide the same record as +first+, and most users will find that confusing.
96
- 2. +last+ is not necessarily going to give you the last record in the dataset unless you give the dataset an unambiguous order.
97
- 3. +last+ will ignore +limit+ if chained together in a query because it sets a limit of 1 if no arguments are given.
98
-
99
- ==== Retrieving a Single Column Value
100
-
101
- Sometimes, instead of wanting an entire row, you only want the value of
102
- a specific column. For this <tt>Sequel::Dataset#get</tt> is the method
103
- you want:
104
-
105
- artist_name = Artist.get(:name)
106
- # SELECT name FROM artists LIMIT 1
107
- # => "YJM"
108
-
109
- ==== Retrieving a Multiple Column Values
110
-
111
- If you want the value for multiple columns, you can pass an array to
112
- <tt>Sequel::Dataset#get</tt>:
113
-
114
- artist_id, artist_name = Artist.get([:id, :name])
115
- # SELECT id, name FROM artists LIMIT 1
116
- # => [1, "YJM"]
117
-
118
- === Retrieving Multiple Objects
119
-
120
- ==== As an Array of Hashes or Model Objects
121
-
122
- In many cases, you want an array of all of the rows associated with the
123
- dataset, in which case <tt>Sequel::Dataset#all</tt> is the method you
124
- want to use:
125
-
126
- artists = Artist.all
127
- # SELECT * FROM artists
128
- # => [#<Artist @values={:name=>"YJM", :id=>1}>,
129
- # #<Artist @values={:name=>"AS", :id=>2}>]
130
-
131
- ==== Using an Enumerable Interface
132
-
133
- <tt>Sequel::Dataset</tt> uses an Enumerable Interface, so it provides a
134
- method named each that yields hashes or model objects as they are retrieved
135
- from the database:
136
-
137
- Artist.each{|x| p x.name}
138
- # SELECT * FROM artists
139
- "YJM"
140
- "AS"
141
-
142
- This means that all of the methods in the Enumerable module are available,
143
- such as +map+:
144
-
145
- artist_names = Artist.map{|x| x.name}
146
- # SELECT * FROM artists
147
- # => ["YJM", "AS"]
148
-
149
- ==== As an Array of Column Values
150
-
151
- Sequel also has an extended +map+ method that takes an argument. If you
152
- provide an argument to +map+, it will return an array of values for the
153
- given column. So the previous example can be handled more easily with:
154
-
155
- artist_names = Artist.map(:name)
156
- # SELECT * FROM artists
157
- # => ["YJM", "AS"]
158
-
159
- One difference between these two ways of returning an array of values is
160
- that providing +map+ with an argument is really doing:
161
-
162
- artist_names = Artist.map{|x| x[:name]} # not x.name
163
-
164
- Note that regardless of whether you provide +map+ with an argument, it
165
- does not modify the columns selected. If you only want to select a
166
- single column and return an array of the columns values, you can use
167
- +select_map+:
168
-
169
- artist_names = Artist.select_map(:name)
170
- # SELECT name FROM artists
171
- # => ["YJM", "AS"]
172
-
173
- It's also common to want to order such a map, so Sequel provides a
174
- +select_order_map+ method as well:
175
-
176
- artist_names = Artist.select_order_map(:name)
177
- # SELECT name FROM artists ORDER BY name
178
- # => ["AS", "YJM"]
179
-
180
- In all of these cases, you can provide an array of column symbols and
181
- an array of arrays of values will be returned:
182
-
183
- artist_names = Artist.select_map([:id, :name])
184
- # SELECT id, name FROM artists
185
- # => [[1, "YJM"], [2, "AS"]]
186
-
187
- ==== As a Hash
188
-
189
- Sequel makes it easy to take an SQL query and return it as a ruby hash,
190
- using the +as_hash+ method:
191
-
192
- artist_names = Artist.as_hash(:id, :name)
193
- # SELECT * FROM artists
194
- # => {1=>"YJM", 2=>"AS"}
195
-
196
- As you can see, the +as_hash+ method uses the first symbol as the key
197
- and the second symbol as the value. So if you swap the two arguments the hash
198
- will have its keys and values transposed:
199
-
200
- artist_names = Artist.as_hash(:name, :id)
201
- # SELECT * FROM artists
202
- # => {"YJM"=>1, "AS"=>2}
203
-
204
- Now what if you have multiple values for the same key? By default, +as_hash+
205
- will just have the last matching value. If you care about all matching values,
206
- use +to_hash_groups+, which makes the values of the array an array of matching
207
- values, in the order they were received:
208
-
209
- artist_names = Artist.to_hash_groups(:name, :id)
210
- # SELECT * FROM artists
211
- # => {"YJM"=>[1, 10, ...], "AS"=>[2, 20, ...]}
212
-
213
- If you only provide one argument to +as_hash+, it uses the entire hash
214
- or model object as the value:
215
-
216
- artist_names = DB[:artists].as_hash(:name)
217
- # SELECT * FROM artists
218
- # => {"YJM"=>{:id=>1, :name=>"YJM"}, "AS"=>{:id=>2, :name=>"AS"}}
219
-
220
- and +to_hash_groups+ works similarly:
221
-
222
- artist_names = DB[:artists].to_hash_groups(:name)
223
- # SELECT * FROM artists
224
- # => {"YJM"=>[{:id=>1, :name=>"YJM"}, {:id=>10, :name=>"YJM"}], ...}
225
-
226
- Model datasets have a +as_hash+ method that can be called without any
227
- arguments, in which case it will use the primary key as the key and
228
- the model object as the value. This can be used to easily create an
229
- identity map:
230
-
231
- artist_names = Artist.as_hash
232
- # SELECT * FROM artists
233
- # => {1=>#<Artist @values={:id=>1, :name=>"YGM"}>,
234
- # 2=>#<Artist @values={:id=>2, :name=>"AS"}>}
235
-
236
- There is no equivalent handling to +to_hash_groups+, since there would
237
- only be one matching record, as the primary key must be unique.
238
-
239
- Note that +as_hash+ never modifies the columns selected. However, just
240
- like Sequel has a +select_map+ method to modify the columns selected and
241
- return an array, Sequel also has a +select_hash+ method to modify the
242
- columns selected and return a hash:
243
-
244
- artist_names = Artist.select_hash(:name, :id)
245
- # SELECT name, id FROM artists
246
- # => {"YJM"=>1, "AS"=>2}
247
-
248
- Likewise, +select_hash_groups+ also exists:
249
-
250
- artist_names = Artist.select_hash_groups(:name, :id)
251
- # SELECT name, id FROM artists
252
- # => {"YJM"=>[1, 10, ...], "AS"=>[2, 20, ...]}
253
-
254
- == Modifying datasets
255
-
256
- Note that the retrieval methods discussed above just return
257
- the row(s) included in the existing dataset. In most cases,
258
- you aren't interested in every row in a table, but in a subset
259
- of the rows, based on some criteria. In Sequel, filtering
260
- the dataset is generally done separately than retrieving
261
- the records.
262
-
263
- There are really two types of dataset methods that you will
264
- be using:
265
-
266
- 1. Methods that return row(s), discussed above
267
- 2. Methods that return modified datasets, discussed below
268
-
269
- Sequel datasets are frozen and use a method chaining, functional style API
270
- that returns modified datasets. Let's start with a simple example.
271
-
272
- This is a basic dataset that includes all records in the
273
- table +artists+:
274
-
275
- ds1 = DB[:artists]
276
- # SELECT * FROM artists
277
-
278
- Let's say we are only interested in the artists whose names
279
- start with "A":
280
-
281
- ds2 = ds1.where(Sequel.like(:name, 'A%'))
282
- # SELECT * FROM artists WHERE (name LIKE 'A%' ESCAPE '\')
283
-
284
- Here we see that +where+ returns a dataset that adds a +WHERE+
285
- clause to the query. It's important to note that +where+ does
286
- not modify the receiver:
287
-
288
- ds1
289
- # SELECT * FROM artists
290
- ds2
291
- # SELECT * FROM artists WHERE (name LIKE 'A%' ESCAPE '\')
292
-
293
- In Sequel, dataset methods do not modify the dataset itself, so you can freely use the dataset in multiple
294
- places without worrying that its usage in one place will affect its usage
295
- in another place. This is what is meant by a functional style API.
296
-
297
- Let's say we only want to select the id and name columns, and that
298
- we want to order by name:
299
-
300
- ds3 = ds2.order(:name).select(:id, :name)
301
- # SELECT id, name FROM artists WHERE (name LIKE 'A%' ESCAPE '\') ORDER BY name
302
-
303
- Note how you don't need to assign the returned value of order to a variable,
304
- and then call select on that. Because order just returns a dataset, you can
305
- call select directly on the returned dataset. This is what is meant by a
306
- method chaining API.
307
-
308
- Also note how you can call methods that modify different clauses in any order.
309
- In this case, the WHERE clause was added first, then the ORDER clause, then the
310
- SELECT clause was modified. This makes for a flexible API, where you can modify
311
- any part of the query at any time.
312
-
313
- == Filters
314
-
315
- Filtering is probably the most common dataset modifying action done in Sequel.
316
- Both the +where+ and +filter+ methods filter the dataset by modifying the
317
- dataset's WHERE clause. Both accept a wide variety of input formats, discussed
318
- below.
319
-
320
- === Hashes
321
-
322
- The most common format for providing filters is via a hash. In general, Sequel
323
- treats conditions specified with a hash as equality, inclusion, or identity. What type
324
- of condition is used depends on the values in the hash.
325
-
326
- Unless Sequel has special support for the value's class, it uses a simple
327
- equality statement:
328
-
329
- Artist.where(id: 1)
330
- # SELECT * FROM artists WHERE (id = 1)
331
-
332
- Artist.where(name: 'YJM')
333
- # SELECT * FROM artists WHERE (name = 'YJM')
334
-
335
- For arrays, Sequel uses the IN operator with a value list:
336
-
337
- Artist.where(id: [1, 2])
338
- # SELECT * FROM artists WHERE (id IN (1, 2))
339
-
340
- For datasets, Sequel uses the IN operator with a subselect:
341
-
342
- Artist.where(id: Album.select(:artist_id))
343
- # SELECT * FROM artists WHERE (id IN (
344
- # SELECT artist_id FROM albums))
345
-
346
- For boolean values such as nil, true, and false, Sequel uses the IS operator:
347
-
348
- Artist.where(id: nil)
349
- # SELECT * FROM artists WHERE (id IS NULL)
350
-
351
- For ranges, Sequel uses a pair of inequality statements:
352
-
353
- Artist.where(id: 1..5)
354
- # SELECT * FROM artists WHERE ((id >= 1) AND (id <= 5))
355
-
356
- Artist.where(id: 1...5)
357
- # SELECT * FROM artists WHERE ((id >= 1) AND (id < 5))
358
-
359
- Finally, for regexps, Sequel uses an SQL regular expression. Note that this
360
- is only supported by default on PostgreSQL and MySQL. It can also be supported
361
- on SQLite when using the sqlite adapter with the :setup_regexp_function
362
- Database option.
363
-
364
- Artist.where(name: /JM$/)
365
- # SELECT * FROM artists WHERE (name ~ 'JM$')
366
-
367
- If there are multiple arguments in the hash, the filters are ANDed together:
368
-
369
- Artist.where(id: 1, name: /JM$/)
370
- # SELECT * FROM artists WHERE ((id = 1) AND (name ~ 'JM$'))
371
-
372
- This works the same as if you used two separate +where+ calls:
373
-
374
- Artist.where(id: 1).where(name: /JM$/)
375
- # SELECT * FROM artists WHERE ((id = 1) AND (name ~ 'JM$'))
376
-
377
- === Array of Two Element Arrays
378
-
379
- If you use an array of two element arrays, it is treated as a hash. The only
380
- advantage to using an array of two element arrays is that it allows you to
381
- duplicate keys, so you can do:
382
-
383
- Artist.where([[:name, /JM$/], [:name, /^YJ/]])
384
- # SELECT * FROM artists WHERE ((name ~ 'JM$')) AND ((name ~ '^YJ'))
385
-
386
- === Virtual Row Blocks
387
-
388
- If a block is passed to a filter, it is treated as a virtual row block:
389
-
390
- Artist.where{id > 5}
391
- # SELECT * FROM artists WHERE (id > 5)
392
-
393
- You can learn more about virtual row blocks in the {"Virtual Rows" guide}[rdoc-ref:doc/virtual_rows.rdoc].
394
-
395
- You can provide both regular arguments and a block, in which case the results
396
- will be ANDed together:
397
-
398
- Artist.where(name: 'A'...'M'){id > 5}
399
- # SELECT * FROM artists WHERE ((name >= 'A') AND (name < 'M') AND (id > 5))
400
-
401
- Using virtual row blocks, what you can do with single entry hash or an array with
402
- a single two element array can also be done using the =~ method:
403
-
404
- Artist.where{id =~ 5}
405
- # SELECT * FROM artists WHERE (id = 5)
406
-
407
- === Symbols
408
-
409
- If you have a boolean column in the database, and you want only true
410
- values, you can just provide the column symbol to filter:
411
-
412
- Artist.where(:retired)
413
- # SELECT * FROM artists WHERE retired
414
-
415
- === SQL::Expression
416
-
417
- Sequel has a DSL that allows easily creating SQL expressions. These SQL
418
- expressions are instances of subclasses of Sequel::SQL::Expression. You've
419
- already seen an example earlier:
420
-
421
- Artist.where(Sequel.like(:name, 'Y%'))
422
- # SELECT * FROM artists WHERE name LIKE 'Y%' ESCAPE '\'
423
-
424
- In this case Sequel.like returns a Sequel::SQL::BooleanExpression object,
425
- which is used directly in the filter.
426
-
427
- You can use the DSL to create arbitrarily complex expressions. SQL::Expression
428
- objects can be created via singleton methods on the Sequel module. The most common
429
- method is Sequel.[], which takes any object and wraps it in a SQL::Expression
430
- object. In most cases, the SQL::Expression returned supports the & operator for
431
- +AND+, the | operator for +OR+, and the ~ operator for inversion:
432
-
433
- Artist.where(Sequel.like(:name, 'Y%') & (Sequel[{b: 1}] | Sequel.~(c: 3)))
434
- # SELECT * FROM artists WHERE ((name LIKE 'Y%' ESCAPE '\') AND ((b = 1) OR (c != 3)))
435
-
436
- You can combine these expression operators with the virtual row support:
437
-
438
- Artist.where{(a > 1) & ~((b(c) < 1) | d)}
439
- # SELECT * FROM artists WHERE ((a > 1) AND (b(c) >= 1) AND NOT d)
440
-
441
- Note the use of parentheses when using the & and | operators, as they have lower
442
- precedence than other operators. The following will not work:
443
-
444
- Artist.where{a > 1 & ~(b(c) < 1 | d)}
445
- # Raises a TypeError
446
-
447
- === Strings with Placeholders
448
-
449
- Assuming you want to get your hands dirty and use SQL fragments in filters, Sequel allows you
450
- to do so if you explicitly mark the strings as literal strings using +Sequel.lit+. You can
451
- use placeholders in the string and pass arguments for the placeholders:
452
-
453
- Artist.where(Sequel.lit("name LIKE ?", 'Y%'))
454
- # SELECT * FROM artists WHERE (name LIKE 'Y%')
455
-
456
- This is the most common type of placeholder, where each question mark is substituted
457
- with the next argument:
458
-
459
- Artist.where(Sequel.lit("name LIKE ? AND id = ?", 'Y%', 5))
460
- # SELECT * FROM artists WHERE (name LIKE 'Y%' AND id = 5)
461
-
462
- You can also use named placeholders with a hash, where the named placeholders use
463
- colons before the placeholder names:
464
-
465
- Artist.where(Sequel.lit("name LIKE :name AND id = :id", name: 'Y%', id: 5))
466
- # SELECT * FROM artists WHERE (name LIKE 'Y%' AND id = 5)
467
-
468
- You don't have to provide any placeholders if you don't want to:
469
-
470
- Artist.where(Sequel.lit("id = 2"))
471
- # SELECT * FROM artists WHERE id = 2
472
-
473
- However, if you are using any untrusted input, you should definitely be using placeholders.
474
- In general, unless you are hardcoding values in the strings, you should use placeholders.
475
- You should never pass a string that has been built using interpolation, unless you are
476
- sure of what you are doing.
477
-
478
- Artist.where(Sequel.lit("id = #{params[:id]}")) # Don't do this!
479
- Artist.where(Sequel.lit("id = ?", params[:id])) # Do this instead
480
- Artist.where(id: params[:id].to_i) # Even better
481
-
482
- === Inverting
483
-
484
- You may be wondering how to specify a not equals condition in Sequel, or the NOT IN
485
- operator. Sequel has generic support for inverting conditions, so to write a not
486
- equals condition, you write an equals condition, and invert it:
487
-
488
- Artist.where(id: 5).invert
489
- # SELECT * FROM artists WHERE (id != 5)
490
-
491
- Note that +invert+ inverts the entire filter:
492
-
493
- Artist.where(id: 5).where{name > 'A'}.invert
494
- # SELECT * FROM artists WHERE ((id != 5) OR (name <= 'A'))
495
-
496
- In general, +invert+ is used rarely, since +exclude+ allows you to invert only specific
497
- filters:
498
-
499
- Artist.exclude(id: 5)
500
- # SELECT * FROM artists WHERE (id != 5)
501
-
502
- Artist.where(id: 5).exclude{name > 'A'}
503
- # SELECT * FROM artists WHERE ((id = 5) AND (name <= 'A')
504
-
505
- So to do a NOT IN with an array:
506
-
507
- Artist.exclude(id: [1, 2])
508
- # SELECT * FROM artists WHERE (id NOT IN (1, 2))
509
-
510
- Or to use the NOT LIKE operator:
511
-
512
- Artist.exclude(Sequel.like(:name, '%J%'))
513
- # SELECT * FROM artists WHERE (name NOT LIKE '%J%' ESCAPE '\')
514
-
515
- You can use Sequel.~ to negate expressions:
516
-
517
- Artist.where(Sequel.~(id: 5))
518
- # SELECT * FROM artists WHERE id != 5
519
-
520
- On Sequel expression objects, you can use ~ to negate them:
521
-
522
- Artist.where(~Sequel.like(:name, '%J%'))
523
- # SELECT * FROM artists WHERE (name NOT LIKE '%J%' ESCAPE '\')
524
-
525
- You can use !~ on Sequel expressions to create negated expressions:
526
-
527
- Artist.where{id !~ 5}
528
- # SELECT * FROM artists WHERE (id != 5)
529
-
530
- === Removing
531
-
532
- To remove all existing filters, use +unfiltered+:
533
-
534
- Artist.where(id: 1).unfiltered
535
- # SELECT * FROM artists
536
-
537
- == Ordering
538
-
539
- Sequel offers quite a few methods to manipulate the SQL ORDER BY clause. The
540
- most basic of these is +order+:
541
-
542
- Artist.order(:id)
543
- # SELECT * FROM artists ORDER BY id
544
-
545
- You can specify multiple arguments to order by more than one column:
546
-
547
- Album.order(:artist_id, :id)
548
- # SELECT * FROM album ORDER BY artist_id, id
549
-
550
- Note that unlike +where+, +order+ replaces an existing order, it does not
551
- append to an existing order:
552
-
553
- Artist.order(:id).order(:name)
554
- # SELECT * FROM artists ORDER BY name
555
-
556
- If you want to add a column to the end of the existing order:
557
-
558
- Artist.order(:id).order_append(:name)
559
- # SELECT * FROM artists ORDER BY id, name
560
-
561
- If you want to add a column to the beginning of the existing order:
562
-
563
- Artist.order(:id).order_prepend(:name)
564
- # SELECT * FROM artists ORDER BY name, id
565
-
566
- === Reversing
567
-
568
- Just like you can invert an existing filter, you can reverse an existing
569
- order, using +reverse+ without an order:
570
-
571
- Artist.order(:id).reverse
572
- # SELECT FROM artists ORDER BY id DESC
573
-
574
- Alternatively, you can provide reverse with the order:
575
-
576
- Artist.reverse(:id)
577
- # SELECT FROM artists ORDER BY id DESC
578
-
579
- To specify a single entry be reversed, <tt>Sequel.desc</tt> can be used:
580
-
581
- Artist.order(Sequel.desc(:id))
582
- # SELECT FROM artists ORDER BY id DESC
583
-
584
- This allows you to easily use both ascending and descending orders:
585
-
586
- Artist.order(:name, Sequel.desc(:id))
587
- # SELECT FROM artists ORDER BY name, id DESC
588
-
589
- === Removing
590
-
591
- Just like you can remove filters with +unfiltered+, you can remove
592
- orders with +unordered+:
593
-
594
- Artist.order(:name).unordered
595
- # SELECT * FROM artists
596
-
597
- == Selected Columns
598
-
599
- Sequel offers a few methods to manipulate the columns selected. As
600
- you may be able to guess, the main method used is +select+:
601
-
602
- Artist.select(:id, :name)
603
- # SELECT id, name FROM artists
604
-
605
- You just specify all of the columns that you are selecting as
606
- arguments to the method.
607
-
608
- If you are dealing with model objects, you'll want to include the
609
- primary key if you want to update or destroy the object. You'll
610
- also want to include any keys (primary or foreign) related to
611
- associations you plan to use.
612
-
613
- If a column is not selected, and you attempt to access it, you will
614
- get nil:
615
-
616
- artist = Artist.select(:name).first
617
- # SELECT name FROM artists LIMIT 1
618
-
619
- artist[:id]
620
- # => nil
621
-
622
- Like +order+, +select+ replaces the existing selected columns:
623
-
624
- Artist.select(:id).select(:name)
625
- # SELECT name FROM artists
626
-
627
- To append to the existing selected columns, use +select_append+:
628
-
629
- Artist.select(:id).select_append(:name)
630
- # SELECT id, name FROM artists
631
-
632
- To prepend to the existing selected columns, use +select_prepend+:
633
-
634
- Artist.select(:id).select_prepend(:name)
635
- # SELECT name, id FROM artists
636
-
637
- To remove specifically selected columns, and default back to all
638
- columns, use +select_all+:
639
-
640
- Artist.select(:id).select_all
641
- # SELECT * FROM artists
642
-
643
- To select all columns from a given table, provide an argument to
644
- +select_all+:
645
-
646
- Artist.select_all(:artists)
647
- # SELECT artists.* FROM artists
648
-
649
- === Distinct
650
-
651
- To treat duplicate rows as a single row when retrieving the records,
652
- use +distinct+:
653
-
654
- Artist.distinct.select(:name)
655
- # SELECT DISTINCT name FROM artists
656
-
657
- Note that DISTINCT is a separate SQL clause, it's not a function
658
- that you pass to select.
659
-
660
- == Limit and Offset
661
-
662
- You can limit the dataset to a given number of rows using +limit+:
663
-
664
- Artist.limit(5)
665
- # SELECT * FROM artists LIMIT 5
666
-
667
- You can provide a second argument to +limit+ to specify an offset:
668
-
669
- Artist.limit(5, 10)
670
- # SELECT * FROM artists LIMIT 5 OFFSET 10
671
-
672
- You can also call the +offset+ method separately:
673
-
674
- Artist.limit(5).offset(10)
675
- # SELECT * FROM artists LIMIT 5 OFFSET 10
676
-
677
- Either of these would return the 11th through 15th records in the original
678
- dataset.
679
-
680
- To remove a limit and offset from a dataset, use +unlimited+:
681
-
682
- Artist.limit(5, 10).unlimited
683
- # SELECT * FROM artists
684
-
685
- == Grouping
686
-
687
- The SQL GROUP BY clause is used to combine multiple rows based on
688
- the values of a given group of columns.
689
-
690
- To modify the GROUP BY clause of the SQL statement, you use +group+:
691
-
692
- Album.group(:artist_id)
693
- # SELECT * FROM albums GROUP BY artist_id
694
-
695
- You can remove an existing grouping using +ungrouped+:
696
-
697
- Album.group(:artist_id).ungrouped
698
- # SELECT * FROM albums
699
-
700
- If you want to add a column to the end of the existing grouping columns:
701
-
702
- Album.group(:artist_id).group_append(:name)
703
- # SELECT * FROM albums GROUP BY artist_id, name
704
-
705
- A common use of grouping is to count based on the number of grouped rows,
706
- and Sequel provides a +group_and_count+ method to make this easier:
707
-
708
- Album.group_and_count(:artist_id)
709
- # SELECT artist_id, count(*) AS count FROM albums GROUP BY artist_id
710
-
711
- This will return the number of albums for each artist_id.
712
-
713
- If you want to select and group on the same columns, you can use +select_group+:
714
-
715
- Album.select_group(:artist_id)
716
- # SELECT artist_id FROM albums GROUP BY artist_id
717
-
718
- Usually you would add a +select_append+ call after that, to add some sort of
719
- aggregation:
720
-
721
- Album.select_group(:artist_id).select_append{sum(num_tracks).as(tracks)}
722
- # SELECT artist_id, sum(num_tracks) AS tracks FROM albums GROUP BY artist_id
723
-
724
- == Having
725
-
726
- The SQL HAVING clause is similar to the WHERE clause, except that
727
- filters the results after the grouping has been applied, instead of
728
- before. One possible use is if you only wanted to return artists
729
- who had at least 10 albums:
730
-
731
- Album.group_and_count(:artist_id).having{count.function.* >= 10}
732
- # SELECT artist_id, count(*) AS count FROM albums
733
- # GROUP BY artist_id HAVING (count(*) >= 10)
734
-
735
- Both the WHERE clause and the HAVING clause are removed by +unfiltered+:
736
-
737
- Album.group_and_count(:artist_id).having{count.function.* >= 10}.
738
- where(:name.like('A%')).unfiltered
739
- # SELECT artist_id, count(*) AS count FROM albums GROUP BY artist_id
740
-
741
- == Joins
742
-
743
- Sequel has support for many different SQL join types.
744
- The underlying method used is +join_table+:
745
-
746
- Album.join_table(:inner, :artists, id: :artist_id)
747
- # SELECT * FROM albums
748
- # INNER JOIN artists ON (artists.id = albums.artist_id)
749
-
750
- In most cases, you won't call +join_table+ directly, as Sequel provides
751
- shortcuts for all common (and most uncommon) join types. For example
752
- +join+ does an inner join:
753
-
754
- Album.join(:artists, id: :artist_id)
755
- # SELECT * FROM albums
756
- # INNER JOIN artists ON (artists.id = albums.artist_id)
757
-
758
- And +left_join+ does a LEFT JOIN:
759
-
760
- Album.left_join(:artists, id: :artist_id)
761
- # SELECT * FROM albums
762
- # LEFT JOIN artists ON (artists.id = albums.artist_id)
763
-
764
- === Table/Dataset to Join
765
-
766
- For all of these specialized join methods, the first argument is
767
- generally the name of the table to which you are joining. However, you
768
- can also provide a dataset, in which case a subselect is used:
769
-
770
- Album.join(Artist.where{name < 'A'}, id: :artist_id)
771
- # SELECT * FROM albums
772
- # INNER JOIN (SELECT * FROM artists WHERE (name < 'A')) AS t1
773
- # ON (t1.id = albums.artist_id)
774
-
775
- === Join Conditions
776
-
777
- The second argument to the specialized join methods is the conditions
778
- to use when joining, which is similar to a filter expression, with
779
- a few minor exceptions.
780
-
781
- ==== Implicit Qualification
782
-
783
- A hash used as the join conditions operates similarly to a filter,
784
- except that unqualified symbol keys are automatically qualified
785
- with the table from the first argument, and unqualified symbol values
786
- are automatically qualified with the last table joined (or the first
787
- table in the dataset if there hasn't been a previous join):
788
-
789
- Album.join(:artists, id: :artist_id)
790
- # SELECT * FROM albums
791
- # INNER JOIN artists ON (artists.id = albums.artist_id)
792
-
793
- Note how the +id+ symbol is automatically qualified with +artists+,
794
- while the +artist_id+ symbol is automatically qualified with +albums+.
795
-
796
- Because Sequel uses the last joined table for implicit qualifications
797
- of values, you can do things like:
798
-
799
- Album.join(:artists, id: :artist_id).
800
- join(:members, artist_id: :id)
801
- # SELECT * FROM albums
802
- # INNER JOIN artists ON (artists.id = albums.artist_id)
803
- # INNER JOIN members ON (members.artist_id = artists.id)
804
-
805
- Note that when joining to the +members+ table, +artist_id+ is qualified
806
- with +members+ and +id+ is qualified with +artists+.
807
-
808
- While a good default, implicit qualification is not always correct:
809
-
810
- Album.join(:artists, id: :artist_id).
811
- join(:tracks, album_id: :id)
812
- # SELECT * FROM albums
813
- # INNER JOIN artists ON (artists.id = albums.artist_id)
814
- # INNER JOIN tracks ON (tracks.album_id = artists.id)
815
-
816
- Note here how +id+ is qualified with +artists+ instead of +albums+. This
817
- is wrong as the foreign key <tt>tracks.album_id</tt> refers to <tt>albums.id</tt>, not
818
- <tt>artists.id</tt>. To fix this, you need to explicitly qualify when joining:
819
-
820
- Album.join(:artists, id: :artist_id).
821
- join(:tracks, album_id: Sequel[:albums][:id])
822
- # SELECT * FROM albums
823
- # INNER JOIN artists ON (artists.id = albums.artist_id)
824
- # INNER JOIN tracks ON (tracks.album_id = albums.id)
825
-
826
- Just like in filters, an array of two element arrays is treated the same
827
- as a hash, but allows for duplicate keys:
828
-
829
- Album.join(:artists, [[:id, :artist_id], [:id, 1..5]])
830
- # SELECT * FROM albums INNER JOIN artists
831
- # ON ((artists.id = albums.artist_id)
832
- # AND (artists.id >= 1) AND (artists.id <= 5))
833
-
834
- And just like in the hash case, unqualified symbol elements in the
835
- array are implicitly qualified.
836
-
837
- By default, Sequel only qualifies unqualified symbols in the conditions. However,
838
- You can provide an options hash with a <tt>qualify: :deep</tt> option to do a deep
839
- qualification, which can qualify subexpressions. For example, let's say you are doing
840
- a JOIN using case insensitive string comparison:
841
-
842
- Album.join(:artists, {Sequel.function(:lower, :name) =>
843
- Sequel.function(:lower, :artist_name)},
844
- qualify: :deep)
845
- # SELECT * FROM albums INNER JOIN artists
846
- # ON (lower(artists.name) = lower(albums.artist_name))
847
-
848
- Note how the arguments to lower were qualified correctly in both cases.
849
-
850
- ==== USING Joins
851
-
852
- The most common type of join conditions is a JOIN ON, as displayed
853
- above. However, the SQL standard allows for join conditions to be
854
- specified with JOIN USING, assuming the column name is the same in
855
- both tables.
856
-
857
- For example, if instead of having a primary
858
- column named +id+ in all of your tables, you use +artist_id+ in your
859
- +artists+ table and +album_id+ in your +albums+ table, you could do:
860
-
861
- Album.join(:artists, [:artist_id])
862
- # SELECT * FROM albums INNER JOIN artists USING (artist_id)
863
-
864
- See here how you specify the USING columns as an array of symbols.
865
-
866
- ==== NATURAL Joins
867
-
868
- NATURAL joins take it one step further than USING joins, by assuming
869
- that all columns with the same names in both tables should be
870
- used for joining:
871
-
872
- Album.natural_join(:artists)
873
- # SELECT * FROM albums NATURAL JOIN artists
874
-
875
- In this case, you don't even need to specify any conditions.
876
-
877
- ==== Join Blocks
878
-
879
- You can provide a block to any of the join methods that accept
880
- conditions. This block should accept 3 arguments: the table alias
881
- for the table currently being joined, the table alias for the last
882
- table joined (or first table), and an array of previous
883
- <tt>Sequel::SQL::JoinClause</tt>s.
884
-
885
- This allows you to qualify columns similar to how the implicit
886
- qualification works, without worrying about the specific aliases
887
- being used. For example, let's say you wanted to join the albums
888
- and artists tables, but only want albums where the artist's name
889
- comes before the album's name.
890
-
891
- Album.join(:artists, id: :artist_id) do |j, lj, js|
892
- Sequel[j][:name] < Sequel[lj][:name]
893
- end
894
- # SELECT * FROM albums INNER JOIN artists
895
- # ON ((artists.id = albums.artist_id)
896
- # AND (artists.name < albums.name))
897
-
898
- Because greater than can't be expressed with a hash in Sequel, you
899
- need to use a block and qualify the tables manually.
900
-
901
- == From
902
-
903
- In general, the FROM table is the first clause populated when creating
904
- a dataset. For a standard Sequel::Model, the dataset already has the
905
- FROM clause populated, and the most common way to create datasets is
906
- with the <tt>Database#[]</tt> method, which populates the FROM clause.
907
-
908
- However, you can modify the tables you are selecting FROM using +from+:
909
-
910
- Album.from(:albums, :old_albums)
911
- # SELECT * FROM albums, old_albums
912
-
913
- Be careful with this, as multiple tables in the FROM clause use a cross
914
- join by default, so the number of rows will be number of albums times the
915
- number of old albums.
916
-
917
- Using multiple FROM tables and setting conditions in the WHERE clause is
918
- an old-school way of joining tables:
919
-
920
- DB.from(:albums, :artists).where{{artists[:id]=>albums[:artist_id]}}
921
- # SELECT * FROM albums, artists WHERE (artists.id = albums.artist_id)
922
-
923
- === Using the current dataset in a subselect
924
-
925
- In some cases, you may want to wrap the current dataset in a subselect.
926
- Here's an example using +from_self+:
927
-
928
- Album.order(:artist_id).limit(100).from_self.group(:artist_id)
929
- # SELECT * FROM (SELECT * FROM albums ORDER BY artist_id LIMIT 100)
930
- # AS t1 GROUP BY artist_id
931
-
932
- This is different than without +from_self+:
933
-
934
- Album.order(:artist_id).limit(100).group(:artist_id)
935
- # SELECT * FROM albums GROUP BY artist_id ORDER BY name LIMIT 100
936
-
937
- Without +from_self+, you are doing the grouping, and limiting the number
938
- of grouped records returned to 100. So assuming you have albums by more
939
- than 100 artists, you'll end up with 100 results.
940
-
941
- With +from_self+, you are limiting the number of records before grouping.
942
- So if the artist with the lowest id had 100 albums, you'd get 1 result,
943
- not 100.
944
-
945
- == Locking for Update
946
-
947
- Sequel allows you to easily add a FOR UPDATE clause to your queries so
948
- that the records returned can't be modified by another query until the
949
- current transaction commits. You just use the +for_update+ dataset
950
- method when returning the rows:
951
-
952
- DB.transaction do
953
- album = Album.for_update.first(id: 1)
954
- # SELECT * FROM albums WHERE (id = 1) FOR UPDATE
955
- album.num_tracks += 1
956
- album.save
957
- end
958
-
959
- This will ensure that no other connection modifies the row between when you select
960
- it and when the transaction ends.
961
-
962
- === Optimistic Locking
963
-
964
- One of the model plugins that ships with Sequel is an optimistic locking plugin, which provides
965
- a database independent way to detect and raise an error if two different connections
966
- modify the same row. It's useful for things like web forms where you cannot keep a
967
- transaction open while the user is looking at the form, because of the web's
968
- stateless nature.
969
-
970
- == Custom SQL
971
-
972
- Sequel makes it easy to use custom SQL for the query by providing it to the <tt>Database#[]</tt>
973
- method as a string:
974
-
975
- DB["SELECT * FROM artists"]
976
- # SELECT * FROM artists
977
-
978
- You can also use the +with_sql+ dataset method to return a dataset that uses that
979
- exact SQL:
980
-
981
- DB[:albums].with_sql("SELECT * FROM artists")
982
- # SELECT * FROM artists
983
-
984
- With either of these methods, you can use placeholders:
985
-
986
- DB["SELECT * FROM artists WHERE id = ?", 5]
987
- # SELECT * FROM artists WHERE id = 5
988
-
989
- DB[:albums].with_sql("SELECT * FROM artists WHERE id = :id", id: 5)
990
- # SELECT * FROM artists WHERE id = 5
991
-
992
- Note that if you specify the dataset using custom SQL, you can still call the dataset
993
- modification methods, but in many cases they will appear to have no affect:
994
-
995
- DB["SELECT * FROM artists"].select(:name).order(:id)
996
- # SELECT * FROM artists
997
-
998
- You can use the implicit_subquery extension to automatically wrap queries that use
999
- custom SQL in subqueries if a method is called that would modify the SQL:
1000
-
1001
- DB.extension :implicit_subquery
1002
- DB["SELECT * FROM artists"].select(:name).order(:id)
1003
- # SELECT name FROM (SELECT * FROM artists) AS t1 ORDER BY id"
1004
-
1005
- If you must drop down to using custom SQL, it's recommended that you only do so for
1006
- specific parts of a query. For example, if the reason you are using custom SQL is
1007
- to use a custom operator in the database in the SELECT clause:
1008
-
1009
- DB["SELECT name, (foo !@# ?) AS baz FROM artists", 'bar']
1010
-
1011
- it's better to use Sequel's DSL, and use a literal string for the custom operator:
1012
-
1013
- DB[:artists].select(:name, Sequel.lit("(foo !@# ?)", 'bar').as(:baz))
1014
-
1015
- That way Sequel's method chaining still works, and it increases Sequel's ability to
1016
- introspect the code.
1017
-
1018
- == Checking for Records
1019
-
1020
- If you just want to know whether the current dataset would return any rows, use <tt>empty?</tt>:
1021
-
1022
- Album.empty?
1023
- # SELECT 1 FROM albums LIMIT 1
1024
- # => false
1025
-
1026
- Album.where(id: 0).empty?
1027
- # SELECT 1 FROM albums WHERE (id = 0) LIMIT 1
1028
- # => true
1029
-
1030
- Album.where(Sequel.like(:name, 'R%')).empty?
1031
- # SELECT 1 FROM albums WHERE (name LIKE 'R%' ESCAPE '\') LIMIT 1
1032
- # => false
1033
-
1034
- == Aggregate Calculations
1035
-
1036
- The SQL standard defines a few helpful methods to get aggreate information about
1037
- datasets, such as +count+, +sum+, +avg+, +min+, and +max+. There are dataset methods
1038
- for each of these aggregate functions.
1039
-
1040
- +count+ just returns the number of records in the dataset.
1041
-
1042
- Album.count
1043
- # SELECT count(*) AS count FROM albums LIMIT 1
1044
- # => 2
1045
-
1046
- If you pass an expression to count, it will return the number of records where
1047
- that expression in not NULL:
1048
-
1049
- Album.count(:artist_id)
1050
- # SELECT count(artist_id) AS count FROM albums LIMIT 1
1051
- # => 1
1052
-
1053
- The other methods take a column argument and call the aggregate function with
1054
- the argument:
1055
-
1056
- Album.sum(:id)
1057
- # SELECT sum(id) AS sum FROM albums LIMIT 1
1058
- # => 3
1059
-
1060
- Album.avg(:id)
1061
- # SELECT avg(id) AS avg FROM albums LIMIT 1
1062
- # => 1.5
1063
-
1064
- Album.min(:id)
1065
- # SELECT min(id) AS min FROM albums LIMIT 1
1066
- # => 1
1067
-
1068
- Album.max(:id)
1069
- # SELECT max(id) AS max FROM albums LIMIT 1
1070
- # => 2