列出存儲過程
一、存儲過程的概念
T-SQl和C語言一樣 ,是一門結構化的語言。
什麼是存儲過程?
存儲過程是SQL查詢語句與控制流程語句的預編譯集合,並以特定的名稱保存在資料庫中。存儲過程也是資料庫對象
分類:
系統存儲過程: 以sp_或xp_打頭
用戶自定義 :以proc_打頭
存儲過程的優點:
執行速度快 效率高
模塊式編程
減少網路流量
提高安全性
二、系統存儲過程
SQl server 的系統存儲過程保存在master資料庫中,且所有命名的系統存儲過程命名以「Sp_」開頭。在master資料庫中,
系統存儲過程數量如下:
代碼如下 復制代碼
select count([name])as '系統存儲數量' from sysobjects
where [name] like 'sp_%'
EXECUTE 用來表示調用存儲過程,也可以縮寫為EXEC,
調用存儲的語法如下:
EXECUTE 『存儲過程名』 『參數』 ---如果沒有參數則省略參數
常用的系統存儲過程
EXEC sp_databases 列出當前系統中的資料庫
EXEC sp_renamedb 'Northwind','Northwind1' 修改資料庫的名稱(單用戶訪問)
USE stuDB GO EXEC sp_tables 返回某個表列的信息
EXEC sp_columns 查看指定列的信息
EXEC sp_help 查看某個表的所有信息
EXEC sp_helpconstraint '表名' 查看某個表的約束
EXEC sp_helpdb '資料庫名' 或 EXEC sp_helpdb 查看指定資料庫或所有資料庫信息
EXEC sp_helptext '對象名稱' 顯示資料庫對象(存儲過程、觸發器、試圖)的定義文本
EXEC sp_helpindex '表名' 查看指定表的索引信息
EXEC sp_renamedb '原名稱','新名稱' 更改資料庫名稱
EXEC sp_stored_proceres 列出當前環境可用的所有存儲過程
除了系統存儲過程,SQL Server 還提供以Xp_開頭的擴展存儲過程,如可以調用DOS命名的,XP_cmdshell 存儲過程
用法如下:
代碼如下 復制代碼
EXEC Xp_cmdshell DOS 命名 [NO_OUTPUT]
NO_OUTPUT 為可選參數,表示是否輸入存儲過程返回的信息
三、用戶自定義存儲過程
1、語法
代碼如下 復制代碼
create procere 存儲過程名
@參數1名 數據類型 [=默認值] [參數類型(輸入/輸出)]
... ...
@參數n名 數據類型 [=默認值] [參數類型(輸入/輸出)]
as
begin
sql語句
end;
go
參數類型分為輸入參數和輸出參數,默認為輸入參數,使用OUTPUT表示輸出參數。創建存儲過程最好以proc開頭
2、創建不帶參數的存儲過程
代碼如下 復制代碼
--判斷存儲過程是否存在
if object_id('proc_student','procere') is not null
drop procere proc_student
go
create procere proc_student
as
begin
select pcid as '電腦編號',
case pcuse
when 0 then '空閑'
when 1 then '忙碌'
end as '使用狀態' from pc
end;
--調用存儲過程
execute proc_student select * from pc
go
3、創建帶輸入參數的存儲過程
語法:
代碼如下 復制代碼
create procere 存儲過程名
@參數1名 數據類型 [=默認值]
....
@參數2名 數據類型[=默認值]
as
SQl與語句
...
go
--例如
--創建帶輸入參數的存儲過程
代碼如下 復制代碼
if object_id('proc_stu','procere') is not null
drop procere proc_stu
go
create procere proc_stu
@pcuse int
as
begin
select pcid as '電腦編號',
case pcuse
when 0 then '空閑'
when 1 then '忙碌'
end as '使用狀態' from pc where pcuse=@pcuse end;
--調用存儲過程
execute proc_stu @pcuse=1
4、創建帶輸出參數的存儲過程
代碼如下 復制代碼
--創建帶輸出參數的存儲過程
if OBJECT_ID('proc_s','procere') is not null
drop procere proc_s
go
create procere proc_s
@pcid int,
@pcus int output
as
begin
select @pcus=pcuse from pc where pcid=@pcid end;
--調用存儲過程
declare @pcus int execute proc_s 5,@pcus output
四、處理錯誤信息
當存儲過程的語句十分復雜時,可以在存儲過程中加入錯誤語言。SQL Server中可以使用RAISERROR 返回用戶自定義的錯誤信息。
RAISERROR 語法如下:
RAISERROR (自定義的錯誤信息,錯誤的嚴重級別,錯誤狀態)
自定義錯誤信息:表示輸出信息:表示輸出的錯誤提示文本
錯誤的嚴重級別:表示用戶自定義錯誤的嚴重性級別。(0-18極)
錯誤的狀態:表示自定義錯誤的狀態,值的范圍在1-127
Ⅱ 怎麼用SQL語句列出所有資料庫裡面的存儲過程的名稱
可以用Toad 或者pl/sql dev 可以直接激衡談看到存攔飢儲過程定義。 也可以通明碰過SQL 來查看: 如: SELECT * FROM ALL_SOURCE where TYPE='PROCEDURE' AND NAME ='ADDCUSTBUSS'; 更換name就可以了
Ⅲ DB2如何查看當前當前資料庫有哪些存儲過程
可以這么考慮!db2ilist:列出db2所有實例db2getinstance:顯示當前實例db2listdbdirectory:列出當前實例下的所有資料庫db2listactivedatabases:列出當前連接的資料庫db2listapplications:列出所有對資料庫的連接。(這個應該就是你要的,直接列出連接的詳細信息,包括哪些內容自己看吧)
Ⅳ 列出存儲過程的SQL語句如關聯二個表和關聯三個表
根據存儲過程更新三沒彎個表ABC,DEF,EFG:
CREATE PROCEDURE Abc_Update
@W_AbcCode varchar(1),
@AbcWorthRate integer,
@AbcKindRate integer
AS
update Abc set
AbcKindRate=@AbcKindRate,
AbcWorthRate=@AbcWorthRate
where AbcCode=@W_AbcCode
update DEF set
DEFKindRate=@AbcKindRate,
DEFWorthRate=@ABCWorthRate
where DEFCode=@W_AbcCode
update EFG set
EFGKindRate=@AbcKindRate,
EFGWorthRate=@AbcWorthRate
where EFGCode=@W_AbcCode
--通過主鍵ABCCODE,DEFCODE,EFGCODE關聯三個表並同時枯態悶顯示三個表的數據:
SELECT * FROM ABC,DEF,EFG WHERE ABCCODE=DEFCODE AND DEFCODE=EFGCODE
GO
--使用舉例:將ABC,DEF,EFG三個表CODE欄位為A001的記錄另外兩個欄位改為2和3.然後在界面中顯示關聯後的三個表的數據閉襲。
EXEC Abc_Update 'A001',2,3
Ⅳ 簡單sql存儲過程實例、儲過程實戰
實例1:只返回單一記錄集的存儲過程。
銀行存款表(bankMoney)的內容如下
Id
userID
Sex
Money
001
Zhangsan
男
30
002
Wangwu
男
50
003
Zhangsan
男
40
要求1:查詢表bankMoney的內容的存儲過程
create
procere
sp_query_bankMoney
as
select
*
from
bankMoney
go
exec
sp_query_bankMoney
注*
在使用過程中只需要把中的SQL語句替換為存儲過程名,就可以了很方便吧!
實例2(向存儲過程中傳遞參數):
加入一筆記錄到表bankMoney,並查詢此表中userID=
Zhangsan的所有存款的總金額。
Create
proc
insert_bank
@param1
char(10),@param2
varchar(20),@param3
varchar(20),@param4
int,@param5
int
output
with
encryption
---------加密
as
insert
bankMoney
(id,userID,sex,Money)
Values(@param1,@param2,@param3,
@param4)
select
@param5=sum(Money)
from
bankMoney
where
userID='Zhangsan'
go
在SQL
Server查詢分析器中執行該存儲過程的方法是:
declare
@total_price
int
exec
insert_bank
'004','Zhangsan','男',100,@total_price
output
print
'總余額為'+convert(varchar,@total_price)
go
在這里再啰嗦一下存儲過程的3種傳回值(方便正在看這個例子的朋友不用再去查看語法內容):
1.以Return傳回整數
2.以output格式傳回參數
3.Recordset
傳回值的區別:
output和return都可在批次程式中用變數接收,而recordset則傳回到執行批次的客戶端中。
實例3:使用帶有復雜
SELECT
語句的簡單過程
下面的存儲過程從四個表的聯接中返回所有作者(提供了姓名)、出版的書籍以及出版社。該存儲過程不使用任何參數。
USE
pubs
IF
EXISTS
(SELECT
name
FROM
sysobjects
WHERE
name
=
'au_info_all'
AND
type
=
'P')
DROP
PROCEDURE
au_info_all
GO
CREATE
PROCEDURE
au_info_all
AS
SELECT
au_lname,
au_fname,
title,
pub_name
FROM
authors
a
INNER
JOIN
titleauthor
ta
ON
a.au_id
=
ta.au_id
INNER
JOIN
titles
t
ON
t.title_id
=
ta.title_id
INNER
JOIN
publishers
p
ON
t.pub_id
=
p.pub_id
GO
au_info_all
存儲過程可以通過以下方法執行:
EXECUTE
au_info_all
實例4:使用帶有參數的簡單過程
CREATE
PROCEDURE
au_info
@lastname
varchar(40),
@firstname
varchar(20)
AS
SELECT
au_lname,
au_fname,
title,
pub_name
FROM
authors
a
INNER
JOIN
titleauthor
ta
ON
a.au_id
=
ta.au_id
INNER
JOIN
titles
t
ON
t.title_id
=
ta.title_id
INNER
JOIN
publishers
p
ON
t.pub_id
=
p.pub_id
WHERE
au_fname
=
@firstname
AND
au_lname
=
@lastname
GO
au_info
存儲過程可以通過以下方法執行:
EXECUTE
au_info
'Dull',
'Ann'
--
Or
EXECUTE
au_info
@lastname
=
'Dull',
@firstname
=
'Ann'
--
Or
EXECUTE
au_info
@firstname
=
'Ann',
@lastname
=
'Dull'
實例5:使用帶有通配符參數的簡單過程
CREATE
PROCEDURE
au_info2
@lastname
varchar(30)
=
'D%',
@firstname
varchar(18)
=
'%'
AS
SELECT
au_lname,
au_fname,
title,
pub_name
FROM
authors
a
INNER
JOIN
titleauthor
ta
ON
a.au_id
=
ta.au_id
INNER
JOIN
titles
t
ON
t.title_id
=
ta.title_id
INNER
JOIN
publishers
p
ON
t.pub_id
=
p.pub_id
WHERE
au_fname
LIKE
@firstname
AND
au_lname
LIKE
@lastname
GO
au_info2
存儲過程可以用多種組合執行。下面只列出了部分組合:
EXECUTE
au_info2
--
Or
EXECUTE
au_info2
'Wh%'
--
Or
EXECUTE
au_info2
@firstname
=
'A%'
--
Or
EXECUTE
au_info2
'[CK]ars[OE]n'
--
Or
EXECUTE
au_info2
'Hunter',
'Sheryl'
--
Or
EXECUTE
au_info2
'H%',
'S%'
Ⅵ 有哪些最常用的系統存儲過程
sp_databases 列出伺服器上的所有資料庫
sp_helpdb 報告有關指定資料庫或所有資料庫的信息
sp_renamedb 更改資料庫的名稱
sp_tables 返回當前環境下可查詢的對象的列表
sp_columns 返回某個表列的信息
sp_help 查看某個表的所有信息
sp_helpconstraint 查看某個表的約束
sp_helpindex 查看某個表的索引
sp_stored_proceres 列出當前環境中的所有存儲過程
sp_password 添加或修改登錄帳戶的密碼
sp_helptext 顯示默認值、未加密的存儲過程、用戶定義的存儲過程、觸發器或視圖的實際文本
Ⅶ Java資料庫程序中的存儲過程設計
本文闡述了怎麼使用DBMS存儲過程 闡述了使用存儲過程的基本的和高級特性 比如返回ResultSet 本文假設你對DBMS和JDBC已經非常熟悉 也假設你能夠毫無障礙地閱讀其它語言寫成的代碼(即不是Java的語言) 但是 並不要求你有任何存儲過程的編程經歷
存儲過程是指保存在數據豎拿庫並在資料庫端執行的程序 你可以使用特殊的語法在Java類中調用存儲過程 在調用時 存儲過程的名稱及指定的參數通過JDBC連接發送給DBMS 執行存儲過程並通過連接(如果有)返回結果
使用存儲過程擁有和使用基於EJB或CORBA這樣的應用伺服器一樣的好處 區別是存儲過程可以從很多流行的DBMS中免費使用 而應用伺服器大都非常昂貴 這並不只是許可證費用的問題 使用應用伺服器所需要花費的管理 編寫代碼的費用 以及客戶程序所增加的復雜性 都可以通過DBMS中的存儲過程所整個地替代
你可以使用Java Python Perl或C編寫存儲過程 但是通常使用你的DBMS所指定的特定語言 Oracle使用PL/SQL PostgreSQL使用pl/pgsql DB 使用Proceral SQL 這些語言都非常相似 在它們之間移植存儲過程並不比在Sun的EJB規范不同實現版本之間移植Session Bean困難 並且 存儲過程是為嵌入SQL所設計 這使得它們比Java或C等語言更加友好地方式表達資料庫的機制
因為存儲過程運行在DBMS自身 這可以幫助減少應用程序中的等待時間 不是在Java代碼中執行 個或 個SQL語句 而只需要在伺服器端執行 個存儲過程 網路上的數據往返次數的減少可以戲劇性地優化性能
使用存儲過程
簡單的老的JDBC通過CallableStatement類支持存儲過程的調用 該類實際上是PreparedStatement的一個子類 假設我們有一個poets資料庫 資料庫中有一個設置詩人逝世年齡的存儲過程 下面是對老酒鬼Dylan Thomas(old soak Dylan Thomas 不指定是否有關典故跡灶 文化 請批評指正 譯注)進行調用的詳細代碼
try{int age = ;String poetName = dylan thomas ;CallableStatement proc = connection prepareCall( { call set_death_age(? ?) } );proc setString( poetName);proc setInt( age);cs execute();}catch (SQLException e){// }
傳給prepareCall方法的字串是存儲過程調用的書寫規范 它指定了存儲過程的名稱 ?代表了你需要指定的參數
和JDBC集成是存儲過程的一個很大的便利 為了從應用中調用存儲過程 不需要存根(stub)類或者配置文件 除了你的DBMS的JDBC驅動程序外什麼也不需要
當這段代碼執行時 資料庫的存儲過程就被調用 我們沒有去獲取結果 因為該存儲過程並不返回結果 執行成功或失敗將通過例外得知 失敗可能意味著調用存儲姿纖扮過程時的失敗(比如提供的一個參數的類型不正確) 或者一個應用程序的失敗(比如拋出一個例外指示在poets資料庫中並不存在 Dylan Thomas )
結合SQL操作與存儲過程
映射Java對象到SQL表中的行相當簡單 但是通常需要執行幾個SQL語句 可能是一個SELECT查找ID 然後一個INSERT插入指定ID的數據 在高度規格化(符合更高的範式 譯注)的資料庫模式中 可能需要多個表的更新 因此需要更多的語句 Java代碼會很快地膨脹 每一個語句的網路開銷也迅速增加
將這些SQL語句轉移到一個存儲過程中將大大簡化代碼 僅涉及一次網路調用 所有關聯的SQL操作都可以在資料庫內部發生 並且 存儲過程語言 例如PL/SQL 允許使用SQL語法 這比Java代碼更加自然 下面是我們早期的存儲過程 使用Oracle的PL/SQL語言編寫
create procere set_death_age(poet VARCHAR poet_age NUMBER)poet_id NUMBER;beginSELECT id INTO poet_id FROM poets WHERE name = poet;INSERT INTO deaths (mort_id age) VALUES (poet_id poet_age);end set_death_age;
很獨特?不 我打賭你一定期待看到一個poets表上的UPDATE 這也暗示了使用存儲過程實現是多麼容易的一件事情 set_death_age幾乎可以肯定是一個很爛的實現 我們應該在poets表中添加一列來存儲逝世年齡 Java代碼中並不關心資料庫模式是怎麼實現的 因為它僅調用存儲過程 我們以後可以改變資料庫模式以提高性能 但是我們不必修改我們代碼
下面是調用上面存儲過程的Java代碼
public static void setDeathAge(Poet dyingBard int age)throws SQLException{Connection con = null;CallableStatement proc = null;
try{con = connectionPool getConnection();proc = con prepareCall( { call set_death_age(? ?) } );proc setString( dyingBard getName());proc setInt( age);proc execute();}finally{try{proc close();}catch (SQLException e) {}con close();}}
為了確保可維護性 建議使用像這兒這樣的static方法 這也使得調用存儲過程的代碼集中在一個簡單的模版代碼中 如果你用到許多存儲過程 就會發現僅需要拷貝 粘貼就可以創建新的方法 因為代碼的模版化 甚至也可以通過腳本自動生產調用存儲過程的代碼
Functions
存儲過程可以有返回值 所以CallableStatement類有類似getResultSet這樣的方法來獲取返回值 當存儲過程返回一個值時 你必須使用registerOutParameter方法告訴JDBC驅動器該值的SQL類型是什麼 你也必須調整存儲過程調用來指示該過程返回一個值
下面接著上面的例子 這次我們查詢Dylan Thomas逝世時的年齡 這次的存儲過程使用PostgreSQL的pl/pgsql
create function snuffed_it_when (VARCHAR) returns integer declarepoet_id NUMBER;poet_age NUMBER;begin first get the id associated with the poet SELECT id INTO poet_id FROM poets WHERE name = $ ; get and return the age SELECT age INTO poet_age FROM deaths WHERE mort_id = poet_id;return age;end; language pl/pgsql ;
另外 注意pl/pgsql參數名通過Unix和DOS腳本的$n語法引用 同時 也注意嵌入的注釋 這是和Java代碼相比的另一個優越性 在Java中寫這樣的注釋當然是可以的 但是看起來很凌亂 並且和SQL語句脫節 必須嵌入到Java String中
下面是調用這個存儲過程的Java代碼
connection setAutoCommit(false);CallableStatement proc =connection prepareCall( { ? = call snuffed_it_when(?) } );proc registerOutParameter( Types INTEGER);proc setString( poetName);cs execute();int age = proc getInt( );
如果指定了錯誤的返回值類型會怎樣?那麼 當調用存儲過程時將拋出一個RuntimeException 正如你在ResultSet操作中使用了一個錯誤的類型所碰到的一樣
復雜的返回值
關於存儲過程的知識 很多人好像就熟悉我們所討論的這些 如果這是存儲過程的全部功能 那麼存儲過程就不是其它遠程執行機制的替換方案了 存儲過程的功能比這強大得多
當你執行一個SQL查詢時 DBMS創建一個叫做cursor(游標)的資料庫對象 用於在返回結果中迭代每一行 ResultSet是當前時間點的游標的一個表示 這就是為什麼沒有緩存或者特定資料庫的支持 你只能在ResultSet中向前移動
某些DBMS允許從存儲過程中返回遊標的一個引用 JDBC並不支持這個功能 但是Oracle PostgreSQL和DB 的JDBC驅動器都支持在ResultSet上打開到游標的指針(pointer)
設想列出所有沒有活到退休年齡的詩人 下面是完成這個功能的存儲過程 返回一個打開的游標 同樣也使用PostgreSQL的pl/pgsql語言
create procere list_early_deaths () return refcursor as declaretoesup refcursor;beginopen toesup forSELECT poets name deaths ageFROM poets deaths all entries in deaths are for poets but the table might bee generic WHERE poets id = deaths mort_idAND deaths age < ;return toesup;end; language plpgsql ;
下面是調用該存儲過程的Java方法 將結果輸出到PrintWriter
PrintWriter:
static void sendEarlyDeaths(PrintWriter out){Connection con = null;CallableStatement toesUp = null;try{con = ConnectionPool getConnection();
// PostgreSQL needs a transaction to do this con setAutoCommit(false);
// Setup the call CallableStatement toesUp= connection prepareCall( { ? = call list_early_deaths () } );toesUp registerOutParameter( Types OTHER);getResults execute();
ResultSet rs = (ResultSet) getResults getObject( );while (rs next()){String name = rs getString( );int age = rs getInt( );out println(name + was + age + years old );}rs close();}catch (SQLException e){// We should protect these calls toesUp close();con close();}}
因為JDBC並不直接支持從存儲過程中返回遊標 我們使用Types OTHER來指示存儲過程的返回類型 然後調用getObject()方法並對返回值進行強制類型轉換
這個調用存儲過程的Java方法是mapping的一個好例子 Mapping是對一個集上的操作進行抽象的方法 不是在這個過程上返回一個集 我們可以把操作傳送進去執行 本例中 操作就是把ResultSet列印到一個輸出流 這是一個值得舉例的很常用的例子 下面是調用同一個存儲過程的另外一個方法實現
public class ProcessPoetDeaths{public abstract void sendDeath(String name int age);}
static void mapEarlyDeaths(ProcessPoetDeaths mapper){Connection con = null;CallableStatement toesUp = null;try{con = ConnectionPool getConnection();con setAutoCommit(false);
CallableStatement toesUp= connection prepareCall( { ? = call list_early_deaths () } );toesUp registerOutParameter( Types OTHER);getResults execute();
ResultSet rs = (ResultSet) getResults getObject( );while (rs next()){String name = rs getString( );int age = rs getInt( );mapper sendDeath(name age);}rs close();}catch (SQLException e){// We should protect these calls toesUp close();con close();}}
這允許在ResultSet數據上執行任意的處理 而不需要改變或者復制獲取ResultSet的方法
static void sendEarlyDeaths(final PrintWriter out){ProcessPoetDeaths myMapper = new ProcessPoetDeaths(){public void sendDeath(String name int age){out println(name + was + age + years old );}};mapEarlyDeaths(myMapper);}
這個方法使用ProcessPoetDeaths的一個匿名實例調用mapEarlyDeaths 該實例擁有sendDeath方法的一個實現 和我們上面的例子一樣的方式把結果寫入到輸出流 當然 這個技巧並不是存儲過程特有的 但是和存儲過程中返回的ResultSet結合使用 是一個非常強大的工具
結論
存儲過程可以幫助你在代碼中分離邏輯 這基本上總是有益的 這個分離的好處有
快速創建應用 使用和應用一起改變和改善的資料庫模式
資料庫模式可以在以後改變而不影響Java對象 當我們完成應用後 可以重新設計更好的模式
存儲過程通過更好的SQL嵌入使得復雜的SQL更容易理解
編寫存儲過程比在Java中編寫嵌入的SQL擁有更好的工具——大部分編輯器都提供語法高亮!
存儲過程可以在任何SQL命令行中測試 這使得調試更加容易
並不是所有的資料庫都支持存儲過程 但是存在許多很棒的實現 包括免費/開源的和非免費的 所以移植並不是一個問題 Oracle PostgreSQL和DB 都有類似的存儲過程語言 並且有在線的社區很好地支持
存儲過程工具很多 有像TOAD或TORA這樣的編輯器 調試器和IDE 提供了編寫 維護PL/SQL或pl/pgsql的強大的環境
lishixin/Article/program/Java/hx/201311/25906
Ⅷ MySQL存儲過程二
上一節存儲過程封裝的都是簡單的select語句,直接使用被封裝的語句就能完成。所以存儲過程往往應用於更復雜的業務規則處理時更有效
看一個例子
這個例子使用元素比較多,解釋一下:
COMMENT為表添加了一句注釋;
-- 單行注釋,注釋跟在後面的內容,需要注意-- 後需要加一個空格才能生效;
(#注釋內容 /*注釋內容*/ 這兩種方法也能進行注釋)
Declare用來聲明變數,一句declare只能聲明一個變數,變數必須先聲明後使用
If...Then是進行條件判斷的,基本語句如下:
If ... Then ... Else ... End If;
這個存儲過程完成了訂單合計,並判斷該訂單是否需要增加營業稅。taxable是一個布爾值(如果要增稅為真,否則為假)。在存儲體中定義了兩個局部變數。並將結果存儲到局部變數total中。if語句檢查taxable是否為真,如果為真,則用另一條select語句增加營業稅。最後將total結果保存到ototal中。
調用結果如下:
檢查存儲過程
SHOW CREATE PROCEDURE 過程名;
為了獲得包括何時、由誰創建等詳細信息的存儲過程列表,使用
Show procere status; -- 會列出所有存儲過程
可以添加過濾,比如
存儲過程循環語句
1. while
WHILE (表達式) DO
...
END WHILE;
看一個例子
創建了一個循環存儲過程,重復向human插入5條記錄。下面調用看一下結果
2.repeat
基本語句:
Repeat ...until 條件...END Repeat;
同樣操作,使用repeat執行如下