6

編集:これは、以下の回答に従って何が起こっているかを正確に示す、より完全なコードのセットです。

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. 2 番目のクエリを実行してすべての ID を合計する
  3. 2 つの結果セットを内部結合する 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」にフラグを立てることです。2 つ目は、「someValue」の内容に関係なく、すべてのアクティビティを調べます。たとえば、ピザを注文するすべての顧客にフラグを立てることができます。次に、ピザを注文したすべての顧客のすべての購入を調べます。

私は SAS にあまり詳しくないので、これをより効率的に行う方法やスピードアップする方法についてのアドバイスを探しています。ご意見やご提案をお待ちしております。詳細をお知らせできる場合はお知らせください。2 番目のクエリの処理に非常に時間がかかることに驚いたと思います。

4

5 に答える 5

8

SAS を使用して Teradata (またはその他の外部データベース) のデータにアクセスする際に理解しておくべき最も重要なことは、SAS ソフトウェアが SQL を準備してデータベースに送信することです。アイデアは、データベース固有のすべての詳細からあなた (ユーザー) を解放しようとすることです。SAS は、「暗黙のパススルー」と呼ばれる概念を使用してこれを行います。これは、SAS が SAS コードから DBMS コードへの変換を行うことを意味します。発生する多くの事柄の中には、データ型の変換があります。SAS には、数値と文字の 2 つ (そして 2 つだけ) のデータ型しかありません。

SAS が翻訳を担当しますが、混乱を招く可能性があります。たとえば、VARCHAR(400) 列で定義された "怠惰な" データベース テーブルの値が、それより小さい長さ (人の名前の列など) を超えることのないものを見てきました。データベースでは、これは大きな問題ではありませんが、SAS には VARCHAR データ型がないため、行ごとに 400 文字幅の変数が作成されます。データセットを圧縮したとしても、結果の SAS データセットが不必要に大きくなる可能性があります。

別の方法は、問題の DBMS の実際の構文を使用してネイティブ クエリを記述する「明示的なパススルー」を使用することです。これらのクエリは完全に DBMS で実行され、結果が SAS に返されます (SAS は引き続きデータ型の変換を行います。たとえば、2 つのテーブルへの結合を実行し、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 プログラムの完全な動作例ではありません。より適切に支援するには、ライブラリ参照を含む実際のプログラムを表示する必要があります。たとえば、実際のプログラムが次のようになっているとします。

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 ステートメントを示しています。その WHERE 句の構文は、SAS が完全なクエリを Teradata に渡すことさえできる場合に非常に重要です。(あなたの例は、「a」と「b」が何を参照しているかを示していません。SAS が結合を実行できる唯一の方法は、両方のテーブルをローカル作業セッションにドラッグして戻し、SAS サーバーで結合を実行することである可能性が非常に高くなります。 .

私が強くお勧めできることの 1 つは、Teradata 管理者を説得して、一部のユーティリティ データベースに「ドライバー」テーブルを作成できるようにすることです。アイデアは、抽出する ID を含む比較的小さなテーブルを Teradata 内に作成し、そのテーブルを使用して明示的な結合を実行するというものです。そのためには、もう少し正式なデータベース トレーニング (適切なインデックスを定義する方法や「統計を収集する」方法など) が必要になると思いますが、その知識と能力があれば、作業は簡単に飛べます。

私は何度も続けることができますが、ここでやめます。地球上で最大の Teradata 環境の 1 つであると言われているにもかかわらず、私は Teradata で SAS を毎日広範囲に使用しています。私は両方でプログラミングを楽しんでいます。

于 2013-07-10T23:14:44.640 に答える
1

ID が一意の場合、そのテーブルに UNIQUE PRIMARY INDEX(id) を追加できます。そうでない場合は、デフォルトで非一意の PI になります。一意性について知ることは、オプティマイザーがより良い計画を作成するのに役立ちます。

Explain のような詳細情報がなければ (EXPLAIN を SELECT の前に置くだけです)、これをどのように改善できるかを判断するのは困難です。

于 2013-07-10T18:33:25.537 に答える
1

ID が一意で単一の値である場合は、フォーマットの構築を試すことができます。

次のようなデータセットを作成します。

fmtname, start, label

ここで、fmtname はすべてのレコードで同じであり、有効な形式名 (先頭と末尾が文字で、英数字または _ を含む) です。start は ID 値です。そして、ラベルは 1 です。次に、fmtname に同じ値、空白の開始、0 のラベル、および別の変数hlo='o'('other' の場合) を持つ 1 つの行を追加します。次に、オプションを使用して proc 形式にインポートするCNTLINと、値が 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;

結合を実行する代わりに、クエリを「通常どおり」実行できますが、 の where 句を追加しand put(id,$IDF.)='1'ます。これはインデックスなどでは最適化されませんが、結合よりも高速になる場合があります。(さらに高速ではない場合もあります - SQL オプティマイザの動作方法によって異なります。)

于 2013-07-10T16:46:47.557 に答える
1

最初のクエリの 90,000 レコードがすべて一意の であるという仮定を暗示していますid。それは確定ですか?

2番目のクエリからの含意は、それらが一意ではないということです.
-id時間の経過とともに複数の値を持つことができ、異なるsomevaluesを持つことができます

idが最初のデータセットで一意でない場合は、最初のクエリで , をGROUP BY id使用する必要があります。DISTINCT

90,000 行が 30,000 個の一意idの で構成されているため、 あたり平均 3 行であると想像してくださいid

そして、これらの 30,000 個の一意idの s が実際にタイム ウィンドウに 9 つのレコードを持っていると想像してくださいsomevalue <> x

その後、 ごとに 3x9 レコードが返されidます。

そして、これら 2 つの数値が大きくなるにつれて、2 番目のクエリのレコード数は幾何学的に大きくなります。


代替クエリ

それが問題でない場合、代替クエリ(理想的ではありませんが可能です)は...

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
于 2013-07-10T16:03:13.640 に答える