當前位置:首頁 » 編程語言 » sqlvar
擴展閱讀
webinf下怎麼引入js 2023-08-31 21:54:13
堡壘機怎麼打開web 2023-08-31 21:54:11

sqlvar

發布時間: 2023-08-30 22:34:37

『壹』 靜態sql和動態SQL的區別和測試實例

所謂SQL的動態和靜態,是指SQL語句在何時被編譯和執行,二者都是用在SQL嵌入式編程中的。
靜態SQL:在高級語言中,如果嵌入了SQL語句,而這個SQL語句的主體結構已經明確,例如在c的一段代碼中有一個待執行的SQL「select * from t1 where c1>5」,在編譯階段,就可以將這段SQL交給資料庫管理系統去分析,資料庫軟體可以對這段SQL進行語法解析,生成資料庫方面的可執行代碼,這樣的SQL稱為靜態SQL,即在編譯階段就可以確定資料庫要做什麼事情。
動態SQL:如果嵌入的SQL沒有明確給出,如在c中定義了一個字元數組類型的變數name:char name[32];,然後採用prepared Statement對象的execute方法去執行這個sql,該sql的值可能等於從文本框中讀取的一個SQL或者從鍵盤輸入的SQL,但具體是什麼,在編譯時無法確定,只有等到程序運行起來,在執行的過程中才能確定,這種SQL叫做動態SQL。例如每一種資料庫軟體都有能夠執行SQL語句的界面,那個界面接收的SQL就是動態SQL,因為資料庫廠商在做這個界面時,並不知道用戶會輸入哪些SQL,只有在該界面執行後,接收了用戶的實際輸入,才知道SQL是什麼。
注意:在SQL中如果某些參數沒有確定,如」select * from t1 where c1>? and c2#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>

EXEC SQL INCLUDE SQLCA;
EXEC SQL INCLUDE SQLDA;
/*此函數屬於靜態SQL編程*/
int DBSelect_static(){
EXEC SQL BEGIN DECLARE SECTION;
char _typename[32];
short _length;
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT typename,length
INTO :_typename,:_length
FROM syscat.columns
WHERE tabname='SYSCOLUMNS' and colname='DEFAULT';
printf("【typename:%s】【length:%d】\n",_typename,_length);
}
/*此函數屬於靜態SQL編程:指定函數參數作為SQL語句的變數*/
int DBSelect_static_param(char *tbl_str,char *col_str){
EXEC SQL BEGIN DECLARE SECTION;
char _typename1[32];
short _length1;
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT typename,length
INTO :_typename1,:_length1
FROM syscat.columns
WHERE tabname='tbl_str' and colname='col_str';
printf("【typename:%s】【length:%d】\n",_typename1,_length1);
}
/*此函數屬於動態SQL編程:SQL語句的結構是不確定的,需要根據用戶的輸入補全SQL語句*/
int DBUpdate_dynamic(){
EXEC SQL BEGIN DECLARE SECTION;
char _address1[32];
char _tablename[32];
char _tmp[32];
char buf[256];
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT count(address1) INTO :_tmp FROM cisaddressinfo WHERE customid='100000100000000000178';
if(0==_tmp) {printf("Not Found!\n");return -1;}
memset(buf,0x00,sizeof(buf));
sprintf(buf,"update ");
printf("Pls input : tablename ->");
scanf("%s",&_tablename);
strcat(buf,_tablename);
strcat(buf," set address1=? where customid='100000100000000000178'");
EXEC SQL PREPARE project FROM :buf;
if(sqlca.sqlcode) perror("PREPARE");
printf("Pls input : address1 ->");
scanf("%s",&_address1);
EXEC SQL EXECUTE project USING :_address1;
if(sqlca.sqlcode) perror("EXECUTE");
EXEC SQL COMMIT WORK;
if(sqlca.sqlcode) perror("COMMIT");
}
/*此函數屬於動態SQL編程:使用sqlda數據結構組裝復雜多變的動態SQL*/
int DBSelect_dynamic(){
EXEC SQL BEGIN DECLARE SECTION;
char hostVarStmt[256];
EXEC SQL END DECLARE SECTION;
// 聲明兩個 SQLDA 指針,minsqlda 將是一個最小的 SQLDA 結構,用於 PREPARE 語句,
// 此時結果集的欄位數量未知,所以只需一個最小的 SQLDA,即包含 HEADER 和一個 SQLVAR
struct sqlda * minsqlda = (struct sqlda*)malloc(SQLDASIZE(1));
struct sqlda * fulsqlda = NULL;

strcpy(hostVarStmt, "select WUID from workprocess where muid = '185001'");
// PREPARE 將填寫 minsqlda 的 header,sqldabc 為 SQLDA 總長度,sqln 為 SQLVAR 數量,即欄位數量
EXEC SQL PREPARE STMT INTO :*minsqlda FROM :hostVarStmt;
// 根據從 minsqlda 中獲取的長度,分配完整的 SQLDA 結構 fulsqlda,其中將包括合適數量的 SQLVAR 結構
//結構sqlda的成員變數sqld返回select查詢語句的欄位的數目,可以根據此變數分配內存
fulsqlda = (struct sqlda *)malloc(SQLDASIZE(minsqlda->sqld));
// 使用 DESCRIBE 語句,獲取結果集中每個欄位的描述信息,包括各欄位的類型 (sqltype) 和長度 (sqllen)
EXEC SQL DESCRIBE STMT INTO :*fulsqlda;
int i;
for(i=0;i<minsqlda->sqld;i++)
{
// 根據每個欄位的長度,分配內存,將地址存儲在對應 SQLVAR 的 sqldata 中
// fulsqlda->sqlvar[i].sqldata=malloc(fulsqlda->sqlvar[i].sqllen);
fulsqlda->sqlvar[i].sqldata=malloc(32);
fulsqlda->sqlvar[i].sqlind=malloc(sizeof(short));
}

// 聲明游標
EXEC SQL DECLARE c1 CURSOR FOR STMT;
EXEC SQL OPEN c1;

EXEC SQL WHENEVER not found goto no_more_data;
// 讀取記錄,記錄中每個欄位的內容將寫入 fulsqlda 中對應 SQLVAR 結構的 sqldata 指向的內存
// EXEC SQL FETCH c1 USING DESCRIPTOR :*fulsqlda;
// 循環讀取所有記錄
for (;;)
{
EXEC SQL FETCH c1 USING DESCRIPTOR :*fulsqlda;
for(i=0;i<minsqlda->sqld;i++){
printf("%d %s\n",fulsqlda->sqlvar[i].sqltype,fulsqlda->sqlvar[i].sqldata);
usleep(10000);
}
}
return 0;
no_more_data:
printf("\nEND of Data\n");
free(minsqlda);
free(fulsqlda);
EXEC SQL CLOSE c1;
return 0;
}
int main(){
/*連接資料庫*/
EXEC SQL CONNECT TO ezeelink USER ezeelink USING EA704075ezeelink;
DBSelect_static();
DBSelect_static_param("SYSCOLUMNS","DEFAULT");
DBUpdate_dynamic();
DBSelect_dynamic();

no_more_data:
;

return 0;
}

案例輸出結果如下:
【typename:VARCHAR】【length:254】
【typename:VARCHAR】【length:254】
Pls input : tablename ->cisaddressinfo
Pls input : address1 ->ShangHai
452 cis505
452 cis506
452 pub806
452 ips007
452 ips032
452 dps302
END of Data
注意:
如果使用動態SQL編程編寫select查詢語句並保存結果,需要使用sqlda數據結構的,同時使用SQL的特性和功能,如:PREPARE ,EXECUTE ,DESCRIBE , DECLARE CURSE C1 FOR … , OPEN CURSE , CLOSE CURSE ….等等
建議:
動態SQL適用於表名及查詢欄位名未知的情況。在已知查詢欄位名及表名的情況下,使用動態SQL(字元串拼接方式)會增加硬解析的開銷,在這種情況下,建議使用靜態SQL,這樣可以提高執行效率。在過程過程用拼湊的動態sql效率並不高,有時候還不如程序直接傳遞sql.靜態SQL是前置編譯綁定,動態SQL是後期執行時才編譯綁定

『貳』 SQL 有哪些函數SQL中有哪些函數

聚合函數是對一組值執行計算並返回單一的值的函數,它經常與SELECT語句的GROUP BY子句一同使用,SQL SERVER 中具體有哪些聚合函數呢?我們來一一看一下:

  1. AVG 返回指定組中的平均值,空值被忽略。

  2. 例:select prd_no,avg(qty) from sales group by prd_no

  3. 2. COUNT 返回指定組中項目的數量。

  4. 例:select count(prd_no) from sales

  5. 3. MAX 返回指定數據的最大值。

  6. 例:select prd_no,max(qty) from sales group by prd_no

  7. 4. MIN 返回指定數據的最小值。

  8. 例:select prd_no,min(qty) from sales group by prd_no

  9. 5. SUM 返回指定數據的和,只能用於數字列,空值被忽略。

  10. 例:select prd_no,sum(qty) from sales group by prd_no

  11. 6. COUNT_BIG 返回指定組中的項目數量,與COUNT函數不同的是COUNT_BIG返回bigint值,而COUNT返回的是int值。

  12. 例:select count_big(prd_no) from sales

  13. 7. GROUPING 產生一個附加的列,當用CUBE或ROLLUP運算符添加行時,輸出值為1.當所添加的行不是由CUBE或ROLLUP產生時,輸出值為0.

  14. 例:select prd_no,sum(qty),grouping(prd_no) from sales group by prd_no with rollup

  15. 8. BINARY_CHECKSUM 返回對表中的行或表達式列表計算的二進制校驗值,用於檢測表中行的更改。

  16. 例:select prd_no,binary_checksum(qty) from sales group by prd_no

  17. 9. CHECKSUM_AGG 返回指定數據的校驗值,空值被忽略。

  18. 例:select prd_no,checksum_agg(binary_checksum(*)) from sales group by prd_no

  19. 10. CHECKSUM 返回在表的行上或在表達式列表上計算的校驗值,用於生成哈希索引。

  20. 11. STDEV 返回給定表達式中所有值的統計標准偏差。

  21. 例:select stdev(prd_no) from sales

  22. 12. STDEVP 返回給定表達式中的所有值的填充統計標准偏差。

  23. 例:select stdevp(prd_no) from sales

  24. 13. VAR 返回給定表達式中所有值的統計方差。

  25. 例:select var(prd_no) from sales

  26. 14. VARP 返回給定表達式中所有值的填充的統計方差。

  27. 例:select varp(prd_no) from sales

『叄』 sql里的var函數是干什麼的

var...開頭的數據類型用來保存可變長度的數據的,以VARCHAR型和CHAR舉例來說:假如你向一個長度為四十個字元的VARCHAR型欄位中輸入數據BIll GAtES。當你以後從這個欄位中取出此數據時,你取出的數據其長度為十個字元——字元串Bill Gates的長度。 現在假如你把字元串輸入一個長度為四十個字元的CHAR型欄位中,那麼當你取出數據時,所取出的數據長度將是四十個字元。字元串的後面會被附加多餘的空格。VARCHAR型欄位較突出的好處是它可以比CHAR型欄位佔用更少的內存和硬碟空間。當你的資料庫很大時,這種內存和磁碟空間的節省會變得非常重要。不知這么解釋能否明白?

『肆』 SQL優化(二)

SQL優化一: sql優化(一)

上片文章已經詳細介紹了explain各個欄位的含義,以及什麼情況應該建立索引,什麼情況不需要建立索引以及sql語句性能的判斷依據,接下來我介紹下如何合理的建立索引。

sql語句:select id,author_id from article where category_id = 1 and comments>1 order by views desc limit 1;

分析:首先我們根據where後面的條件建立符合索引,然後根據order by後面的欄位建立索引,因此建立索引idx_article_ccv,即以(category_id,comments,views)數據列建立復合索引,但由於comments是一個范圍,按照BTree索引的原理,先排序category_id,如果遇到相同的category_id則再排序comments,如果遇到相同的comments則再排序views,又因為comments欄位在復合索引里處於中間位置,而comments>1是一個條件(是一個范圍值),在復合索引的一個范圍值的數據列後面的索引全部失效,mysql無法利用索引再對後面的views部分進行檢索,也就是說views無法按照索引排序,所以explain下此sql語句,type為range,extra使用的是Using filesort,這是比較糟糕的。所以我們放棄comments這個范圍欄位,建立索引idx_article_cv,即以(category_id,views)數據列建立復合索引,explain 此sql,type變成了ref,extra的using filesort也變成了using index,這就變得好多了。

索引:idx_article_cv,即以(category_id,views)數據列建立復合索引

前段時間做了一個銷售精細化項目,是公司crm項目的一個大模塊,大致就是為銷售人員制定指標,實現銷售目標從區域到團到業務員到客戶,實時跟蹤業務員所負責客戶的下單量的情況。這就存在許多關聯關系,區域-團,團-業務員,業務員-客戶,這使得sql常常需要關聯多張表。

sql語句:SELECT

tu.fuserid,

tu.faccount,

tu.fphone,

tu.fcertificationtype,

tu.fcertificatename,

tu.fkeyarea,

tu.fkeyareatext,

DATE_FORMAT(tcr.fupdatetime,'%Y-%m-%d %H:%i:%s') as fupdatetime,

tag.forggroupid,

tag.forggroupname,

tug.forguserid,

tug.fusername,

tug.fuserphone,

tag.fcitycode

FROM t_finedt_user AS tu

LEFT JOIN t_finedt_customer_relation AS tcr

ON tu.fuserid = tcr.fuserid

LEFT JOIN t_finedt_usergroup AS tug

ON tcr.forguserid = tug.forguserid

and tcr.forggroupid = tug.forggroupid

LEFT JOIN t_finedt_areagroup AS tag

ON tug.forggroupid = tag.forggroupid

where tu.fkeyarea=? and tu.fuserid=? and tug.forggroupid = ?

分析:上面的sql是左連接,左邊的表一定是全表查詢,所以要建立右邊表對應關聯欄位的索引,在表t_finedt_user上建立tu_fuserid_fkeyarea索引,即以(fuserid,fkeyarea)欄位建立索引,在表t_finedt_customer_relation 上建立tcr_forguserid_forggroupid索引,即以(forguserid,forggroupid)欄位建立索引,在表t_finedt_usergroup 上建立tug_forguserid_forggroupid索引,即以(forguserid,forggroupid)欄位建立索引,在表t_finedt_areagroup上建立tag_forggroupid索引,即以(forggroupid)欄位建立索引。建立索引後,sql查詢速度明顯快了很多

索引:tcr_forguserid_forggroupid,tu_fuserid_fkeyarea,tug_forguserid_forggroupid,tag_forggroupid

1、盡可能減少join語句中的NestedLoop的循環次數,永遠用小結果集驅動大結果集

2、優先優化NestedLoop的內層循環

3、保證join語句總被驅動表上的join欄位已經被索引

4、當無法保證被驅動表join條件欄位被索引,且內存資源充足的前提下,不要太吝嗇joinBuffer的設置

1、全值匹配我最愛

2、最佳左前綴原則——如果索引了多列,要遵守最左前綴原則,指的是查詢從索引的最左前列開始並且不跳過索引中的列

3、並在索引列上做任何操作(計算、函數、自動or手動類型轉換),這些會導致索引失效而轉向全表掃描

4、存儲引擎不能使用索引中范圍條件右邊的列,范圍之後的索引全失效

5、盡量使用覆蓋索引(之訪問索引的查詢(索引列和查詢的列一致)),減少select *

6、mysql在使用不等於(!=、>、<)的時候無法使用索引會導致全表掃描。

7、is null、is not null也無法使用索引。

8、like以通配符開頭("%abc.."),mysql索引失效也會變成全表掃描的操作。

9、字元串不加單引號也會引起索引失效

10、少用or,用它來連接時會索引失效。

1、對於單值索引,盡量選擇針對當前query過濾性更好的索引

2、在選擇組合索引的時候,當前query中過濾性最好的欄位在索引欄位順序中,位置越靠前越好

3、在選擇組合索引的時候,盡量選擇盡可能包含當前query中的where字句中更多欄位的索引

4、盡可能通過分析統計信息和調整query的寫法來達到選擇合適索引的目的。

全值匹配我最愛,最左前綴要遵守

帶頭大哥不能死,中間兄弟不能斷

索引列上少計算,范圍之後全失效

like百分寫最右,覆蓋索引不寫里

不等空值還有or,索引失效要少用

var引號不可丟,sql高級也不難

『伍』 關於SQL中的方差函數VAR()相關問題!

var函數
var函數,計算指定欄位之中符合查詢條件的變異數估計值。
語法為var(運算式)。運算式,可為欄位名稱、運算式、或一個函數,此函數可
以是一個內部或使用者定義的,但不能為其它的sql函數。
如果符合查詢條件的記錄為兩個以下時,var函數將傳回一個null
值,該表示不
能計算變異數。

『陸』 如何使用sql函數平均值、總數、最小值、最大值、總和、標准差

avg函數:計算查詢中某一特定欄位資料的算術平均值。
count函數:計算符合查詢條件的記錄數。
min, max函數:傳回指定欄位值中符合查詢條件的第一條、最末條記錄的資料。
first, last函數:傳回指定欄位值中符合查詢條件的最小值、最大值。
stdev函數:計算指定欄位值中符合查詢條件的標准差。
sum函數:計算指定欄位值中符合查詢條件的資料總和。
var,函數:計算指定欄位值中符合查詢條件的變異數估計值。

『柒』 SQL函數的函數介紹

Aggregate函數的操作面向一系列的值,並返回一個單一的值。
注釋:如果在 SELECT 語句的項目列表中的眾多其它表達式中使用 SELECT 語句,則這個 SELECT 必須使用 GROUP BY 語句! 函數 描述 AVG(column) 返回某列的平均值 COUNT(column) 返回某列的行數(不包括 NULL 值) COUNT(*) 返回被選行數 FIRST(column) 返回在指定的域中第一個記錄的值 LAST(column) 返回在指定的域中最後一個記錄的值 MAX(column) 返回某列的最高值 MIN(column) 返回某列的最低值 STDEV(column) 返回某列的標准偏差 STDEVP(column) 返回某列總體的標准偏差 SUM(column) 返回某列的總和 VAR(column) 返回某列非NULL值的方差 VARP(column) 返回某列所有非NULL值的總體方差 【示例】 Name Age Adams, John 38 Bush, George 33 Carter, Thomas 28 AVG 函數返回數值列的平均值。NULL 值不包括在計算中。
SELECT AVG(column_name) FROM table_name
我們擁有 Orders 表(示例1):
我們希望計算 OrderPrice 欄位的平均值。
我們使用如下 SQL 語句:
SELECT AVG(OrderPrice) AS OrderAverage FROM Orders結果集類似這樣: OrderAverage 950 示例2
我們希望找到 OrderPrice 值高於 OrderPrice 平均值的客戶。
我們使用如下 SQL 語句:
SELECT Customer FROM OrdersWHERE OrderPrice>(SELECT AVG(OrderPrice) FROM Orders)結果集類似這樣: Customer Bush Carter Adams COUNT() 函數COUNT() 函數返回匹配指定條件的行數。 FORMAT 函數用於對欄位的顯示進行格式化。
SELECT FORMAT(column_name,format) FROM table_name 參數 描述 column_name 必需。要格式化的欄位。 format 必需。規定格式。 我們擁有下面這個 Procts 表: Prod_Id ProctName Unit UnitPrice 1 gold 1000 g 32.35 2 silver 1000 g 11.56 3 copper 1000 g 6.85 我們希望顯示每天日期所對應的名稱和價格(日期的顯示格式是 YYYY-MM-DD)。
我們使用如下 SQL 語句:
SELECT ProctName, UnitPrice, FORMAT(Now(),'YYYY-MM-DD') as PerDateFROM Procts結果集類似這樣: ProctName UnitPrice PerDate gold 32.35 12/29/2008 silver 11.56 12/29/2008 copper 6.85 12/29/2008 LAST() 函數返回指定的欄位中最後一個記錄的值。
提示:可使用 ORDER BY 語句對記錄進行排序。
SELECT LAST(column_name) FROM table_name
實例1 Orders 表:
我們希望查找 OrderPrice 列的最後一個值。
我們使用如下 SQL 語句:
SELECT LAST(OrderPrice) AS LastOrderPrice FROM Orders結果集類似這樣: LastOrderPrice 100 LCASE 函數把欄位的值轉換為小寫。
SELECT LCASE(column_name) FROM table_name
我們擁有下面這個 Persons 表: Id LastName FirstName Address City 1 Adams John Oxford Street London 2 Bush George Fifth Avenue New York 3 Carter Thomas Changan Street Beijing 我們希望選取 LastName 和 FirstName 列的內容,然後把 LastName 列轉換為小寫。
我們使用如下 SQL 語句:
SELECT LCASE(LastName) as LastName,FirstName FROM Persons結果集類似這樣: LastName FirstName adams John bush George carter Thomas LEN 函數返迴文本欄位中值的長度。
SELECT LEN(column_name) FROM table_name
同上有 Persons 表:
我們希望取得 City 列中值的長度。
我們使用如下 SQL 語句:
SELECT LEN(City) as LengthOfCity FROM Persons結果集類似這樣: LengthOfCity 6 8 7 MAX 函數返回一列中的最大值。NULL 值不包括在計算中。
SELECT MAX(column_name) FROM table_name注釋:MIN 和 MAX 也可用於文本列,以獲得按字母順序排列的最高或最低值。
實例1Orders 表:
我們希望查找 OrderPrice 列的最大值。
我們使用如下 SQL 語句:
SELECT MAX(OrderPrice) AS LargestOrderPrice FROM Orders結果集類似這樣: LargestOrderPrice 2000 MID 函數用於從文本欄位中提取字元。
SELECT MID(column_name,start[,length]) FROM table_name 參數 描述 column_name 必需。要提取字元的欄位。 start 必需。規定開始位置(起始值是 1)。 length 可選。要返回的字元數。如果省略,則 MID() 函數返回剩餘文本。 同上有 Persons 表:
我們希望從 City 列中提取前 3 個字元。
我們使用如下 SQL 語句:
SELECT MID(City,1,3) as SmallCity FROM Persons結果集類似這樣: SmallCity Lon New Bei MIN 函數返回一列中的最小值。NULL 值不包括在計算中。
SELECT MIN(column_name) FROM table_name注釋:MIN 和 MAX 也可用於文本列,以獲得按字母順序排列的最高或最低值。
實例1 Orders 表:
我們希望查找 OrderPrice 列的最小值。
我們使用如下 SQL 語句:
SELECT MIN(OrderPrice) AS SmallestOrderPrice FROM Orders結果集類似這樣: SmallestOrderPrice 100 NOW 函數返回當前的日期和時間間
SELECT NOW() FROM table_name
貌似沒有這函數。
我們擁有下面這個 Procts 表: Prod_Id ProctName Unit UnitPrice 1 gold 1000 g 32.35 2 silver 1000 g 11.56 3 copper 1000 g 6.85 我們希望顯示當天的日期所對應的名稱和價格。
我們使用如下 SQL 語句:
SELECT ProctName, UnitPrice, Now() as PerDate FROM Procts結果集類似這樣: ProctName UnitPrice PerDate gold 32.35 12/29/2008 11:36:05 AM silver 11.56 12/29/2008 11:36:05 AM copper 6.85 12/29/2008 11:36:05 AM ROUND 函數用於把數值欄位舍入為指定的小數位數。
SELECT ROUND(column_name,decimals) FROM table_name 參數 描述 column_name 必需。要舍入的欄位。 decimals 必需。規定要返回的小數位數。 Procts 表:
我們希望把名稱和價格舍入為最接近的整數。
我們使用如下 SQL 語句:
SELECT ProctName, ROUND(UnitPrice,0) as UnitPrice FROM Procts結果集類似這樣: ProctName UnitPrice gold 32 silver 12 copper 7 SUM 函數返回數值列的總數(總額)。
SELECT SUM(column_name) FROM table_name
實例1 Orders 表:
我們希望查找 OrderPrice 欄位的總數。
我們使用如下 SQL 語句:
SELECT SUM(OrderPrice) AS OrderTotal FROM Orders結果集類似這樣: OrderTotal 5700 GROUP BY 語句合計函數 (比如SUM) 常常需要添加 GROUP BY 語句。 UCASE 函數把欄位的值轉換為大寫。
SELECT UCASE(column_name) FROM table_name
Persons 表:
我們希望選取 LastName 和 FirstName 列的內容,然後把 LastName 列轉換為大寫。
我們使用如下 SQL 語句:
SELECT UCASE(LastName) : LastName FirstName ADAMS John BUSH George CARTER Thomas