ruby 数据sql操作
ActiveRecord
ActiveRecord 是 Rails 的 ORM 元件,負責與資料庫溝通,讓我們可以用物件導向的語法操作資料庫。在”打造 CRUD 應用程式”一章中提到的對應概念如下:
- 將資料庫表格(table) 對應到一個類別(classe)
- 類別方法就是操作表格(table)
- 將資料庫一列 (row) 對應到一個物件(object)
- 物件方法就是操作個別的資料(row)
- 將資料庫欄位(column) 對應到物件的屬性(object attribute)
因此,資料庫裡面的資料表,我們用一個 Model 類別來表示,而其中的一筆資料,就是一個 Model 物件。
ActiveRecord 這個函式庫實作了 Martin Fowler 的 Active Record 設計模式(Design Pattern)http://martinfowler.com/eaaCatalog/activeRecord.html
ORM 與抽象滲漏法則
ORM (Object-relational mapping ) 是一種對映設關聯式資料與物件資料的程式技術。物件導向和從數學理論發展出來的關聯式資料庫,有著顯著的區別,而 ORM 正是解決這個不匹配問題所產生的工具。它可以讓你使用物件導向語法來操作關聯式資料庫,非常容易使用、撰碼十分有效率,不需要撰寫繁瑣的SQL語法,同時 也增加了程式碼維護性。
不過,有些熟悉 SQL 語法的程式設計師反對使用這樣的機制,因為直接撰寫 SQL 可以確保操作資料庫的執行效率,畢竟有些時候 ORM 產生出來的 SQL 效率不是最佳解,而你卻不一定有經驗能夠意識到什麼時候需要擔心或處理這個問題。
知名軟體人 Joel Spolsky (他有兩本中文翻譯書值得推薦:約耳趣談軟體和約耳續談軟體,悅知出版) 有個理論:抽象滲漏法則:所有重大的抽象機制在某種程式上都是有漏洞的。有非常多程式設計其實都是在建立抽象機制,C 語言簡化了組合組言的繁雜、動態語言如 Ruby 簡化了 C 語言、TCP 協定簡化了 IP 通訊協定,甚至車子的擋風玻璃跟雨刷也簡化了下雨的事實。
但 是這些抽象機制或多或少都會力有未及的地方,用 C 語言撰寫的 Linux 核心也包括少量組合語言、部分 Ruby 套件用 C 語言撰寫擴充來增加效能、保證訊息會抵達 TCP 訊息,碰到 IP 封包在路由器上隨機遺失的時候,你也只會覺得速度很慢、即使有擋風玻璃跟雨刷,開車還是必須小心路滑。
當 某人發明一套神奇可以大幅提升效率的新程式工具時,就會聽到很多人說:「應該先學會如何手動進行,然後才用這個神奇的工具來節省時間。」任何抽象機制都有 漏洞,而唯一能完美處理漏洞的方法,就是只去弄懂該抽象原理以及所隱藏的東西。這是否表示我們應該永遠只應該使用比較低階的工具呢?不是這樣的。而是應該 依照不同的情境,選擇效益最大的抽象化工具。以商務邏輯為多的 Web 應用程式,選擇動態語言開發就相對合適,用 C 語言開發固然執行效率極高,但是完成相同的功能卻需要極高的人月開發時數。如果是作業系統,使用無法隨意控制記憶體分配的動態語言也顯然不是個好主意。
能 夠意識到什麼時候抽象化工具會產生滲漏,正是”有純熟經驗”的程式設計師和”新手”設計師之間的差別。ORM 雖然替我們節省了工作的時間,不過對資深的程式設計師來說,學習 SQL 的時間還是省不掉的。這一切都似乎表示,即使我們擁有愈來愈高階的程式設計工具,抽象化也做得愈來愈好,要成為一個由高階到低階都純熟的程式設計專家是愈 來愈困難了(也越來越稀有及寶貴)。
建立 Model
首先,讓我們示範如何建立一個 Model:
rails g model Category
這個指令會產生幾個檔案
category.rb
category_test.rb
categories.yml
xxxxxxxx_create_categories.rb
打開 xxxxxxxx_create_categories.rb 你可以看到資料表的定義,讓我們加上幾個欄位吧:
class CreateCategories < ActiveRecord::Migration
def self.up
create_table :categories do |t|
t.string :name
t.integer :position
t.timestamps
end
end
def self.down
drop_table :categories
end
end
接著執行以下指令便會產生出資料庫資料表
rake db:migrate
db:migrate 指令會將上述的 Ruby 程式變成以下 SQL 執行。
CREATE TABLE categories (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"name" varchar(255) DEFAULT NULL,
"position" int(4) DEFAULT NULL,
"created_at" datetime DEFAULT NULL,
"updated_at" datetime DEFAULT NULL);
接著我們打開 category.rb 你可以看到
class Category < ActiveRecord::Base
end
這是一個繼承 ActiveRecord::Base 的 Category 類別。
我們在學習 Ruby 的時候提過 irb 這個互動工具,而 Rails 也提供了特殊的 irb 介面叫做 console,讓我們可以直接與 Rails 程式互動:
rails console (可以簡寫成 rails c)
透過 console,我們可以輕易的練習操作 ActiveRecord。
觀看 Log
不像 rails server 可以直接看到 log,在 Rails 主控台下必須透過觀察 log 檔案。我們可以透過 log 觀察到 Rails 產生出來的 SQL 長的如何。
tail -f log/development.log
Windows 上沒有這個指令,可以安裝 Tail for Win32 這個工具來即時觀察 log 檔案。或是安裝 GNU utilities for Win32 來獲得 tail 指令。
基礎操作
如何新增
ActiveRecord提供了四種API,分別是save、save!、create和create!:
a = Category.new( :name => 'Ruby', :position => 1 )
a.save
b = Category.new( :name => 'Perl', :position => 2 )
b.save!
Category.create( :name => 'Python', :position => 3 )
c = Category.create!( :name => 'PHP', :position => 4 )
其中create和create!就等於new
完就save和save!,有無驚嘆號的差別在於validate資料驗證不正確的動作,無驚嘆號版本會回傳布林值(true或false),有驚嘆號版本則是驗證錯誤會丟出例外。
何 時使用驚嘆號版本呢?save和create通常用在會處理回傳布林值(true/false)的情況下(例如在 controller 裡面根據成功失敗決定 render 或 redirect),否則在預期應該會儲存成功的情況下,請用 save!或create! 來處理,這樣一旦碰到儲存失敗的情形,才好追蹤 bug。
透過 :valiate => false 可以略過驗證
c.save( :validate => false )
在 Rails3 之前的版本是 user.save(false)
如何查詢
ActiveRecord 使用了 Arel 技術來實作查詢功能,你可以自由組合 where、limit、select、order 等條件。
Arel 是 relational algebra” library。但根據 2.0 實作者 tenderlove 的說法,也可以說是一種 SQL compiler。 http://engineering.attinteractive.com/2010/12/architecture-of-arel-2-0/
first, last 和 all
這三個方法可以分別拿出資料庫中的第一筆、最後一筆及全部的資料:
c1 = Category.first
c2 = Category.last
categories = Category.all # 這會是一個陣列
如果資料量較多,請不要在正式上線環境中執行.all 把所有資料拿出來,這樣會耗費非常多的記憶體。請用分頁或縮小查詢範圍。
find
已知資料的主鍵 ID 的值的話,可以使用 find 方法:
c3 = Category.find(1)
c4 = Category.find(2)
find 也可以接受陣列參數,這樣就會一次找尋多個並回傳陣列:
arr = Category.find([1,2])
# 或是
arr = Category.find(1,2)
如果找不到資料的話,會丟 ActiveRecord::RecordNotFound 例外。如果是 find_by_id 就不會丟出例外,而是回傳 nil。
find_by_* 和 find_all_by_*
find_by_* 和 find_all_by_* 是 Rails 的動態方法,可以自由用 and 組合,例如:
c5 = Category.find_by_name('Ruby')
c6 = Category.find_by_name_and_position('Ruby', 1)
c7 = Category.find_all_by_position(2)
find_by_sql
如果需要手動撰寫 SQL,可以使用 find_by_sql,例如:
c8 = Category.find_by_sql("select * from categories")
不過在絕大多數的情況,是不需要手動寫 SQL 的。
where 查詢條件
where 可以非常彈性的組合出 SQL 查詢,例如:
c9 = Category.where( :name => 'Ruby', :position => 1 )
c10 = Category.where( [ "name = ? or position = ?", 'Ruby', 2] )
其中參數有兩種寫法,一種是 Hash,另一種是 Array。前者的寫法雖然比較簡潔,但是就沒辦法寫出 or 的查詢。注意到不要使用字串寫法,例如
Category.where("name = #{params[:name]}") # 請不要這樣寫
這是因為字串寫法會有 SQL injection 的安全性問題,請改用陣列寫法。
另外,where 是 lazy loading,也就是直到真的需要取值的時候,才會跟資料庫拿資料。如果需要立即觸發,可以接著使用 .all, .first, .last,例如
c11 = Category.where( :name => 'Ruby', :position => 1 ).all
limit
limit 可以限制筆數
c = Category.limit(5).all
c.size # 5
order
order 可以設定排序條件
Category.order("position")
Category.order("position DESC")
Category.order("position DESC, name ASC")
如果要消去order條件,可以用reorder
:
Category.order("position").reorder("name") # 改用 name 排序
Category.order("position").reorder(nil) # 取消所有排序
offset
offset 可以設定忽略前幾筆不取出,通常用於資料分頁:
c = Category.limit(2)
c.first.id # 1
Category.limit(2).offset(3)
c.first.id # 4
select
預設的 SQL 查詢會取出資料的所有欄位,有時候你可能不需要所有資料,為了效能我們可以只取出其中特定欄位:
Category.select("id, name")
例如欄位中有 Binary 資料時,你不會希望每次都讀取出龐大的 Binary 資料佔用記憶體,而只希望在使用者要下載的時候才讀取出來。
joins
針對Model中的belongs_to
和has_many
關連,可以使用joins
,也就是INNER JOIN
Category.joins(:events)
# SELECT categories.* FROM categories INNER JOIN events ON events.category_id = categories.id
可以一次關連多個:
Post.joins(:category, :comments)
不過這樣抓出來的category物件是沒有event物件的,如果需要一次載入出來,會使用includes
。joins主要的用途是條件:
Category.joins(:events).where("events.name is NOT NULL")
也可以直接給SQL字串:
Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id = clients.id')
# SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id
includes
includes可以預先將關連的資料讀取出來,避免N+1問題(見效能一章)
Event.includes(:category)
# SELECT * FROM events
# SELECT * FROM categories WHERE categories.id IN (1,2,3...)
同理,也可以一次載入多個關連:
Post.includes(:category, :comments)
includes
方法也可以加上條件:
Event.includes(:category).where( :category => { :position => 1 } )
# 或 Event.includes(:category).where( "categories.position = 1" )
group
(TODO)
lock
(TODO)
readonly
(TODO)
from
(TODO)
having
串接寫法
以上的 where, order , limit, offset, joins, select 等等,都可以自由串接起來組合出最終的 SQL 條件:
c12 = Category.where( :name => 'Ruby' ).order("id desc").limit(3)
find_each 批次處理
如果資料量很大,但是又需要全部拿出來處理,可以使用 find_each 批次處理
Category.where("position > 1").find_each do |category|
category.do_some_thing
end
預設會批次撈 1000 筆,如果需要設定可以加上 :batch_size 參數。
重新載入
如果已經讀取的 AR 資料,需要重新載入,可以用 reload 方法:
p = Category.first
p.reload
如何刪除
一種是先抓到該物件,然後刪除:
c12 = Category.first
c12.destroy
另一種是直接對類別呼叫刪除,傳入 ID 或條件:
Category.delete(2)
Category.delete_all(conditions = nil)
Category.destroy_all(conditions = nil)
delete 不會有 callback 回呼,destroy 有 callback 回呼。什麼是回呼詳見下一章。
統計方法
Category.count
Category.average(:position)
Category.maximum(:position)
Category.sum(:position)
其中我們可以利用上述的 where 條件縮小範圍,例如:
Category.where( :name => "Ruby").count
如何更新
c13 = Category.first
c13.update_attributes(attributes)
c13.update_attributes!(attributes)
c13.update_attribute(attribute_name, value)
注意 update_attribute 會略過 validation 資料驗證 注意 mass assign 安全性問題,可以透過 attr_protected 或 attr_accessor 設定,詳見安全性一章。
Scopes 作用域
Model Scopes是一項非常酷的功能,它可以將常用的查詢條件宣告起來,讓程式變得乾淨易讀,更厲害的是可以串接使用。例如,我們編輯app/models/event.rb,加上兩個Scopes:
class Event < ActiveRecord::Base
scope :public, where( :is_public => true )
scope :recent_three_days, where(["created_at > ? ", Time.now - 3.days ])
end
Event.create( :name => "public event", :is_public => true )
Event.create( :name => "private event", :is_public => false )
Event.create( :name => "private event", :is_public => true )
Event.public
Event.public.recent_three_days
串接的順序沒有影響
接著,我們可以設定一個預設的Scope,通常會拿來設定排序:
class Event < ActiveRecord::Base
default_scope order('id DESC')
end
unscoped
方法可以暫時取消預設的default_scope:
Event.unscoped do
Event.all
# SELECT * FROM events
end
最後,Scope也可以接受參數,例如:
class Event < ActiveRecord::Base
scope :recent, lambda{ |date| where(["created_at > ? ", date ]) }
# 或 scope :recent, Proc.new{ |t| where(["created_at > ? ", t ]) }
end
Event.recent( Time.now - 7.days )
不過,筆者會推薦上述這種帶有參數的Scope,改成如下的類別方法,可以比較明確看清楚參數是什麼,特別是你想給預設值的時候:
class Event < ActiveRecord::Base
def recent(t=Time.now)
where(["created_at > ? ", t ])
end
end
Event.recent( Time.now - 7.days )
這樣的效果是一樣的,也是一樣可以和其他Scope做串接。
scoped
方法可以將Model轉成可以串接的形式,方便依照參數組合出不同查詢,例如
fruits = Fruit.scoped
fruits = fruits.where(:colour => 'red') if options[:red_only]
fruits = fruits.limit(10) if limited?
自定 attribute 與資料庫互動
(TODO)
使用 attr_accessor
可以使用 read_attribute 和 write_attribute 這兩個比較底層的 API
ruby 数据sql操作的更多相关文章
- 在MyBatis中查询数据、涉及多参数的数据访问操作、插入数据时获取数据自增长的id、关联表查询操作、动态SQL、关于配置MyBatis映射没有代码提示的解决方案
1. 单元测试 在单元测试中,每个测试方法都需要执行相同的前置代码和后置代码,则可以自定义2个方法,分别在这2个方法中执行前置代码和后置代码,并为这2个方法添加@Before和@After注解,然后, ...
- 【SQL必知必会笔记(3)】SELECT语句的WHERE子句数据过滤操作
上个笔记主要介绍了利用SELECT语句检索单个/多个/所有列,并利用DISTINCT关键字检索具有唯一性的值.利用LIMIT/OFFSET子句限制结果:以及利用ORDER BY子句排序检索出的数据,主 ...
- MySQL之唯一索引、外键的变种、SQL语句数据行操作补充
0.唯一索引 unique对num进行唯一限制,表示num是独一无二的,uql是唯一索引名称 上面为联合索引:num和xx不能完全一样 1.外键的变种 a. 用户表和部门表 用户: 1 alex 1 ...
- SQL语句学习积累·数据的操作
数据的操作 select 取表中前五条数据 select top 5 from table_name 取表中前50%的数据 select top 50 percent from table_name ...
- JAVASE02-Unit08: 文本数据IO操作 、 异常处理
Unit08: 文本数据IO操作 . 异常处理 * java.io.ObjectOutputStream * 对象输出流,作用是进行对象序列化 package day08; import java.i ...
- MySQL的数据库,数据表,数据的操作
数据库简介 概念 什么是数据库?简单来说,数据库就是存储数据的"仓库", 但是,光有数据还不行,还要管理数据的工具,我们称之为数据库管理系统! 数据库系统 = 数据库管理系统 + ...
- 简单sql操作
----------------------------- 数据库的有关SQL语句 -------------------------1.数据库 创建 create database data_nam ...
- 数据库优化和SQL操作的相关题目
SQL操作 1.有一个数据库表peope,表有字段name,age,address三个属性(注:没有主键).现在如果表中有重复的数据,请删去重复只留下其中的一条.重复的定义就是两条记录的name,ag ...
- 常用SQL操作(MySQL或PostgreSQL)与相关数据库概念
本文对常用数据库操作及相关基本概念进行总结:MySQL和PostgreSQL对SQL的支持有所不同,大部分SQL操作还是一样的. 选择要用的数据库(MySQL):use database_name; ...
随机推荐
- 论文笔记(3)-Extracting and Composing Robust Features with Denoising Autoencoders
这篇文章是Bengio研究的在传统的autoencoder基础上增加了噪声参数,也就是说在输入X的时候,并不直接用X的数据,而是按照一定的概率来清空输入为0.paper中的名词为corrupted.这 ...
- 安装CentOS桌面环境
CentOS 作为服务器的操作系统是很常见的,但是因为需要稳定而没有很时髦的更新,所以很少做为桌面环境.在服务器上通常不需要安装桌面环境,最小化地安装 CentOS(也就是 minimal CentO ...
- leetcode 第三大的数
给定一个非空数组,返回此数组中第三大的数.如果不存在,则返回数组中最大的数.要求算法时间复杂度必须是O(n). 示例 1: 输入: [3, 2, 1] 输出: 1 解释: 第三大的数是 1. 示例 2 ...
- AGC032D Rotation Sort
题目传送门 Description 给定\(N\)的排列(\(N\leq5000\)),将任一区间最左侧的数插到该区间最右边的代价为\(A\),将任一区间最右侧的数插到该区间最左边的代价为\(B\), ...
- [bug]微信小程序使用 <scroll-view> 和 box-shadow 引起页面抖动
背景 为了实现点点点动态loading效果,并且方便使用(只需要给一个空元素加一个.loading),有如下代码: .loader { background-color: #fff; font-siz ...
- Swift 里字符串(四)large sting
对于普通的字符串,对应的_StringObject 有两个存储属性: _countAndFlagsBits: UInt64 _object: Builtin.BridgeObject _countAn ...
- Hbuilder用ajax连接eclipse中的servlet例子以及注意事项
今天用前端神器Hbuilder连接eclipse中的servlet,真是费了九牛二虎之力,才把问题解决 Hbuilder中的代码: test.html <!DOCTYPE html> &l ...
- WebDriverAPI(10)
操作Frame页面元素 测试网址代码 frameset.html: <html> <head> <title>frameset页面</title> &l ...
- laydata 点击日期闪现
因项目需求需要多个日期,然后点击日期就会出现闪现的情况,导致选择不了日期 html代码 <table class="form"> <tr> <th c ...
- Tomcat 访问manager app报403 解决方案(虚拟机可以正常使用,外面访问报错)
虚拟机中Tomcat启动后,可以访问项目(虚拟机里面和外面都可以).虚拟机中能够正常进入manager app进行热部署工作,但是在外面能访问tomcat首页,点击manager app报403错误. ...