Написание эффективных запросов в SAS с использованием proc sql с Teradata

EDIT: вот более полный набор кода, который показывает, что именно происходит в ответ ниже.

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;

цель состоит в том, чтобы просто запросить таблицу для некоторых идентификаторов на основе членства в категории. Затем я суммирую активность этих членов по всем категориям.

вышеуказанный подход намного медленнее, чем:

  1. запуск первого запроса для получения подмножества
  2. запуск второго запроса суммирует каждый ID
  3. запуск третьего запрос, который inner соединяет два результирующих набора.

Если я правильно понимаю, может быть более эффективно убедиться, что весь мой код полностью передан, а не перекрестная загрузка.


после публикации вопроса вчера один из членов предложил мне задать отдельный вопрос о производительности, который был бы более конкретным для моей ситуации.

Я использую SAS Enterprise Guide для написания некоторых программ / запросов данных. Я не имеют разрешения на изменение базовых данных, которые хранятся в'Teradata'.

моя основная проблема заключается в написании эффективных SQL-запросов в этой среде. Например, я запрашиваю большую таблицу (с десятками миллионов записей) для небольшого подмножества идентификаторов. Затем я использую это подмножество для запроса большей таблицы снова:

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

)

это работает в считанные секунды и возвращает 90K 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, поэтому я ищу любые советы о том, как это сделать больше эффективно или ускорить процесс. Я открыт для любых мыслей или предложений и, пожалуйста, дайте мне знать, если я могу предложить более подробную информацию. Наверное, я просто удивлен, что второй запрос занимает так много времени.

5 ответов


самое важное, что нужно понять при использовании SAS для доступа к данным в Teradata (или любой другой внешней базе данных), - это то, что программное обеспечение SAS готовит SQL и отправляет его в базу данных. Идея состоит в том, чтобы попытаться освободить вас (пользователя) от всех конкретных деталей базы данных. SAS делает это, используя концепцию под названием "implict pass-through", что просто означает, что SAS выполняет перевод из кода SAS в код СУБД. Среди многих вещей, которые происходят, это преобразование типа данных: SAS имеет только два (и только два) типа данных: числовой и символьный.

SAS занимается переводом вещей для вас, но это может быть запутанным. Например, я видел "ленивые" таблицы базы данных, определенные столбцами VARCHAR(400), имеющие значения, которые никогда не превышают некоторую меньшую длину (например, столбец для имени человека). В базе данных это не большая проблема, но поскольку SAS не имеет типа данных VARCHAR, он создает переменную шириной 400 символов для каждой строки. Даже с набором данных сжатие, это действительно может сделать результирующий набор данных SAS излишне большим.

альтернативный способ-использовать "явный сквозной", где вы пишете собственные запросы, используя фактический синтаксис рассматриваемой СУБД. Эти запросы выполняются полностью на СУБД и возвращают результаты обратно в 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. Чтобы лучше помочь, вам нужно показать реальную программу, включая любые ссылки на библиотеку. Например, предположим, что ваша реальная программа выглядит так это:

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
   ;

это указывает на ранее назначенный оператор LIBNAME, через который SAS подключался к Teradata. Синтаксис этого предложения WHERE будет очень важен, если SAS даже сможет передать полный запрос в Teradata. (В вашем примере не показано, что означают" a "и" b". Очень возможно, что единственный способ SAS выполнить соединение-перетащить обе таблицы обратно в локальный рабочий сеанс и выполнить соединение на сервере SAS.

одно Я настоятельно рекомендую вам попытаться убедить администраторов Teradata разрешить вам создавать таблицы "драйверов" в некоторой базе данных утилит. Идея заключается в том, что вы создадите относительно небольшую таблицу внутри Teradata, содержащую идентификаторы, которые вы хотите извлечь, а затем используйте эту таблицу для выполнения явных соединений. Я уверен, что для этого вам понадобится немного более формальная подготовка базы данных (например, как определить правильный индекс и как "собирать статистику"), но с этими знаниями и способностями ваша работа будет просто летать.

я мог бы продолжать и продолжать, но я остановлюсь здесь. Я использую SAS с Teradata экстенсивно каждый день против того, что мне говорят, является одной из крупнейших сред Teradata на планете. Мне нравится программирование в обоих.


вы подразумеваете предположение, что записи 90k в вашем первом запросе уникальны ids. Это точно?

я спрашиваю, потому что из вашего второго запроса следует, что они не уникальны.
- Один id может иметь несколько значений с течением времени, и разные somevalues

если ids не уникальны в первом наборе данных, вам нужно GROUP BY id или использовать DISTINCT, в первом запросе.

представьте себе, что 90k строк состоит из 30K уникальных ids, и поэтому в среднем 3 строки на id.

а затем представьте себе эти 30K уникальные ids на самом деле имеют 9 записей в вашем окне времени, включая строки, где somevalue <> x.

затем вы получите 3x9 записей обратно за id.

и по мере того, как эти два числа растут, количество записей во втором запросе растет геометрически.


альтернатива Запрос

если это не проблема, альтернативный запрос (который не идеален, но возможен) будет...

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

если ID уникален и имеет одно значение, вы можете попробовать создать формат.

создать набор данных, который выглядит так:

fmtname, start, label

где fmtname одинаково для всех записей, имя юридического формата (начинается и заканчивается буквой, содержит буквенно-цифровой или _); start-значение ID; и label-1. Затем добавьте одну строку с тем же значением для fmtname, пустой старт, метку 0 и другую переменную hlo='o' (для 'other'). Затем импортировать в формат proc с использованием CNTLIN опция, и теперь у вас есть преобразование 1/0 значения.

вот краткий пример использования SASHELP.КЛАСС. 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.)


если идентификатор уникален, вы можете добавить уникальный первичный индекс (id) в эту таблицу, иначе он по умолчанию будет не уникальным PI. Знание об uniquenes помогает оптимизатору создать лучший план.

без дополнительной информации, такой как объяснение (просто поместите объяснение перед выбором), трудно сказать, как это можно улучшить.


одним из альтернативных решений является использование процедур 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 означает, proc TABULATE и т. д.). Это может быть быстрее, чем делать сумму в SQL (в зависимости от некоторых деталей, таких как организация ваших данных, что вы на самом деле делаете и сколько памяти у вас есть). Он добавил преимущество, что SAS может сделать это в базе данных, если вы создадите представление в базе данных, что может быть быстрее. (На самом деле, если вы просто запустите freq из базовой таблицы, возможно, это будет еще быстрее, а затем присоедините результаты к меньшей таблице).