2013-07-10 74 views
6

編輯:下面是一組更完整的代碼,它顯示了每個答案下的具體情況。在SAS中編寫高效查詢在Teradata中使用Proc sql

libname output '/data/files/jeff' 
%let DateStart = '01Jan2013'd; 
%let DateEnd = '01Jun2013'd; 
proc sql; 
CREATE TABLE output.id AS (
    SELECT DISTINCT id 
    FROM mydb.sale_volume AS sv 
    WHERE sv.category IN ('a', 'b', 'c') AND 
    sv.trans_date BETWEEN &DateStart AND &DateEnd 
) 
CREATE TABLE output.sums AS (
    SELECT id, SUM(sales) 
    FROM mydb.sale_volue AS sv 
    INNER JOIN output.id AS ids 
    ON ids.id = sv.id 
    WHERE sv.trans_date BETWEEN &DateStart AND &DateEnd 
    GROUP BY id 
) 
run; 

目標是簡單地根據類別成員資格查詢某個ID的表。然後我彙總這些成員在所有類別中的活動。

上述方法是遠慢於:

  1. 運行第一查詢以獲得所述子集
  2. 運行的第二查詢的總和每個ID
  3. 運行該內連接兩個結果的第三查詢集。

如果我的理解正確,確保所有代碼完全通過而不是交叉加載可能更有效。


發佈提問昨天之後,成員建議我可能會問,從性能上一個單獨的問題,這是更具體的,以我的情況中受益。

我正在使用SAS Enterprise Guide編寫一些程序/數據查詢。我無權修改存儲在「Teradata」中的基礎數據。

我的基本問題是在這個環境中編寫高效的SQL查詢。例如,我查詢一個大表(有數千萬條記錄),用於一小部分ID。然後,我用這個子集再次查詢大表:

proc sql; 
CREATE TABLE subset AS (
    SELECT 
    id 
    FROM 
    bigTable 
    WHERE 
    someValue = x AND 
    date BETWEEN a AND b 

) 

這個作品在幾秒鐘之內,並返回90K的ID。接下來,我想在大表中查詢這組ID,然後出現問題。我想隨着時間的推移,總結值的ID的:

proc sql; 
CREATE TABLE subset_data AS (
    SELECT 
    bigTable.id, 
    SUM(bigTable.value) AS total 
    FROM 
    bigTable 
    INNER JOIN subset 
    ON subset.id = bigTable.id 
    WHERE 
    bigTable.date BETWEEN a AND b 
    GROUP BY 
    bigTable.id 
) 

無論出於何種原因,這需要很長的時間。區別在於第一個查詢標誌爲'someValue'。第二看看所有的活動,不管'someValue'中有什麼。例如,我可以標記每個訂購披薩的顧客。然後,我會查看所有訂購披薩的顧客的每次購買。

我對SAS並不太熟悉,所以我正在尋找關於如何更有效地完成這項任務或加快速度的建議。我願意接受任何想法或建議,請告訴我是否可以提供更多細節。我想我很驚訝第二個查詢花了很長時間來處理。

回答

8

使用SAS訪問Teradata(或任何其他外部數據庫)中的數據時,最關鍵的一點是SAS軟件準備好SQL並將其提交給數據庫。這個想法是試圖讓你(用戶)從所有數據庫的具體細節中解脫出來。 SAS使用稱爲「implict pass-through」的概念來做這件事,這只是意味着SAS將SAS代碼轉換爲DBMS代碼。發生的許多事情之一是數據類型轉換:SAS只有兩個(且只有兩個)數據類型,數字和字符。

SAS爲您處理翻譯事情,但它可能會造成混淆。例如,我已經看到用VARCHAR(400)列定義的「惰性」數據庫表的值不會超過一些較小的值(如人名的列)。在數據庫中,這不是什麼大問題,但是由於SAS沒有VARCHAR數據類型,因此它會爲每行創建400個字符的變量。即使使用數據集壓縮,這也可以使得生成的SAS數據集非常大。

另一種方法是使用「顯式傳遞」,您可以在其中使用相關DBMS的實際語法編寫本機查詢。這些查詢完全在DBMS上執行,並將結果返回給SAS(它仍然爲您進行數據類型轉換。例如,這裏是一個「傳遞」查詢,它執行到兩個表的連接並創建一個SAS數據集作爲結果:

proc sql; 
    connect to teradata (user=userid password=password mode=teradata); 
    create table mydata as 
    select * from connection to teradata (
     select a.customer_id 
      , a.customer_name 
      , b.last_payment_date 
      , b.last_payment_amt 
     from base.customers a 
     join base.invoices b 
     on a.customer_id=b.customer_id 
     where b.bill_month = date '2013-07-01' 
     and b.paid_flag = 'N' 
    ); 
quit; 

注意,對括號中一切都是原生的Teradata SQL和連接操作本身是在數據庫內部運行

你已經在你的問題中顯示的示例代碼是。一個完整的SAS/Teradata程序的實例爲了更好的協助,你需要展示真正的程序,包括任何庫引用,例如,su使您的真實程序如下所示:

proc sql; 
    CREATE TABLE subset_data AS 
    SELECT bigTable.id, 
      SUM(bigTable.value) AS total 
    FROM TDATA.bigTable bigTable 
    JOIN TDATA.subset subset 
    ON  subset.id = bigTable.id 
    WHERE bigTable.date BETWEEN a AND b 
    GROUP BY bigTable.id 
    ; 

這將指示SAS通過其連接到Teradata的以前分配的LIBNAME語句。如果SAS甚至能夠將完整查詢傳遞給Teradata,則該WHERE子句的語法將非常相關。 (您的示例沒有顯示「a」和「b」指的是什麼,SAS可以執行連接的唯一方式很可能是將這兩個表拖回本地工作會話並在SAS服務器上執行連接

我強烈建議的一件事是,您試圖說服您的Teradata管理員允許您在某些實用程序數據庫中創建「驅動程序」表。想法是,您將在Teradata中創建一個包含ID你想提取,然後使用該表來執行顯式連接,我相信你會需要一些更正式的數據庫培訓來做到這一點(比如如何定義合適的索引以及如何「收集統計數據」),但是,知識和能力,你的工作只會飛。

我可以繼續下去,但我會在這裏停下來。我每天都廣泛地使用SAS和Teradata,而我被告知這是全球最大的Teradata環境之一。我喜歡兩種編程。

+1

優秀的答案。 –

+0

這正是我正在尋找的。非常感謝你提供的信息。我仍然在學習SAS和Teradata,因爲我對這兩者都是新手,在我以前的工作中完成了幾乎所有的R到專有數據庫格式。我將發佈一個更完整的代碼示例,以編輯我的原始問題。我很好奇看到你做了什麼。我也會試圖找出驅動程序表的狀態。這似乎是一個好主意,尤其是我需要查詢這些數據的方式。再次,非常感謝您的建議,我真的很感激並從中吸取教訓。 –

+1

Jeff,您的更新問題顯示您確實希望使用傳遞查詢。您現在的做法是保證整個表格必須從Teradata下載,因爲您正在加入SAS數據集。我建議你問一個新的問題,因爲這個問題的答案已經略微超出了範圍。當你這樣做時,看看你能否找到定義'mydb'的原始LIBNAME語句。 – BellevueBob

1

您意味着您的第一個查詢中的90k條記錄都是唯一的id s。這是確定的嗎?

我問,因爲你的第二個查詢的含義是它們不是唯一的。
- 一個id可以有多個值隨着時間的推移,並有不同的somevalue小號

如果id s爲沒有在第一數據集中是唯一,你需要GROUP BY id或使用DISTINCT,在第一個查詢。

想象的是,90K行由30K獨特id s,而每id所以有3行的平均值。

然後想象那些30k獨特的id s在您的時間窗口中實際上有9條記錄,其中包括行數somevalue <> x

然後,您將根據id獲取3x9條記錄。

隨着這兩個數字的增長,您的第二個查詢中的記錄數以幾何級數增長。


替代查詢

如果這不是問題,另一種查詢(這是不理想的,但有可能)會......

SELECT 
    bigTable.id, 
    SUM(bigTable.value) AS total 
FROM 
    bigTable 
WHERE 
    bigTable.date BETWEEN a AND b 
GROUP BY 
    bigTable.id 
HAVING 
    MAX(CASE WHEN bigTable.somevalue = x THEN 1 ELSE 0 END) = 1 
+0

這是一個很好的觀點,感謝您的支持。 ID的確是獨一無二的。實際的子集查詢比較複雜,但最終的輸出是一個「DISTINCT」列表。 我之前沒有使用'HAVING'聲明,我會試一試,看看它是否有幫助。 –

+0

只是爲了測試一些東西,我在查詢日期範圍的一小部分中添加了一個時間戳。如果沒有加入,則需要13.57(0.41 CPU)秒才能返回250k條記錄。 加入後,花費了29.09(1.12 CPU)秒來獲取1800行。多麼奇怪。 –

+0

另外,首先運行主查詢,然後應用後續的聯接(子查詢)將在15.97秒內運行。它能夠計算出所有的總和,然後比削減和然後求和更快。 –

1

如果ID是唯一的和單一的價值,那麼你可以嘗試構建一個格式。

創建一個數據集,看起來像這樣:

fmtname, start, label

其中fmtname是所有記錄是相同的,合法的格式名(開始並以字母結尾,包含字母數字或_);開始是ID值;並且標籤是1.然後爲fmtname添加一行,空白開頭,標籤爲0,另一個變量hlo='o'(for'other')。然後使用CNTLIN選項導入到proc格式,並且您現在具有1/0值轉換。

下面是使用SASHELP.CLASS的簡單示例。這裏的ID是名稱,但它可以是數字或字符 - 無論哪個都適合您的使用。

data for_fmt; 
set sashelp.class; 
retain fmtname '$IDF'; *Format name is up to you. Should have $ if ID is character, no $ if numeric; 
start=name; *this would be your ID variable - the look up; 
label='1'; 
output; 
if _n_ = 1 then do; 
    hlo='o'; 
    call missing(start); 
    label='0'; 
    output; 
end; 
run; 
proc format cntlin=for_fmt; 
quit; 

現在,而不是做一個連接,你可以做您的查詢「正常」但有一個附加的其中條款and put(id,$IDF.)='1'。這不會使用索引或任何內容進行優化,但可能比聯接更快。 (它也可能不會更快 - 取決於SQL優化器的工作方式。)

+0

謝謝喬,我明白你要去哪裏。基本思想是將標籤應用於大集合,然後簡單地過濾掉該標籤,而不是進行連接。 不幸的是,它不是一個單一的值,所以我不能真的這樣應用它。但是,記住這是一件有趣的事情。 –

1

如果該ID是唯一的,則可以爲該表添加一個UNIQUE PRIMARY INDEX(id),否則它將默認爲非唯一PI 。瞭解uniquenes有助於優化器制定更好的計劃。

沒有像Explain這樣的更多信息(只需在SELECT前面放置EXPLAIN)就很難說出如何改進。

0

另一個解決方案是使用SAS程序。我不知道你的實際的SQL是幹什麼的,但如果你只是在做頻率(或別的東西,可在PROC做),你可以這樣做:

proc sql; 
create view blah as select ... (your join); 
quit; 

proc freq data=blah; 
tables id/out=summary(rename=count=total keep=id count); 
run; 

或任何數目的其他選項(PROC MEANS,PROC TABULATE等)。這可能比在SQL中執行總和更快(取決於某些細節,例如數據的組織方式,實際執行情況以及可用內存量)。如果您在數據庫中創建視圖,這可能會更快,它還具有SAS可能選擇在數據庫中執行此操作的額外好處。 (實際上,如果你只是從基表中運行freq,可能會更快,然後將結果加入到較小的表中)。