2013-07-10 20 views
6

MODIFICA: ecco un set di codice più completo che mostra esattamente cosa sta succedendo per la risposta seguente.Scrittura di query efficienti in SAS utilizzando Proc sql con Teradata

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; 

L'obiettivo è semplicemente di interrogare la tabella per alcuni ID in base all'appartenenza alla categoria. Quindi riassumo l'attività di questi membri in tutte le categorie.

L'approccio di cui sopra è molto più lento di:

  1. Esecuzione della prima query per ottenere il sottoinsieme
  2. Esecuzione di una seconda query le somme ogni ID
  3. Esecuzione di una terza query che interno unisce le due risultato imposta.

Se sto capendo correttamente, potrebbe essere più efficiente assicurarsi che tutto il mio codice sia passato completamente attraverso il crossloading.


Dopo aver postato una domanda di ieri, un membro ha suggerito che potrei beneficiare di fare una domanda separata sulle prestazioni che è stato più specifico alla mia situazione.

Sto utilizzando SAS Enterprise Guide per scrivere alcuni programmi/query di dati. Non ho le autorizzazioni per modificare i dati sottostanti, che è memorizzato in 'Teradata'.

Il mio problema di base è scrivere query SQL efficienti in questo ambiente. Ad esempio, interrogo una tabella di grandi dimensioni (con decine di milioni di record) per un piccolo sottoinsieme di ID. Poi, io uso questo sottoinsieme di interrogare di nuovo il tavolo più grande:

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

) 

Questo funziona in una manciata di secondi e restituisce identità 90k di. Successivamente, voglio interrogare questo set di ID contro il grande tavolo, e ne conseguono problemi. Desidero sommare i valori nel tempo per gli 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 
) 

Per qualsiasi motivo, ciò richiede molto tempo. La differenza è che la prima query contrassegna "someValue". Il secondo riguarda tutte le attività, indipendentemente da ciò che è in 'someValue'. Ad esempio, potrei segnalare ogni cliente che ordina una pizza. Poi guarderei ogni acquisto per tutti i clienti che hanno ordinato la pizza.

Non ho molta familiarità con SAS, quindi sto cercando qualche consiglio su come farlo in modo più efficiente o accelerare le cose. Sono aperto a qualsiasi pensiero o suggerimento e per favore fatemi sapere se posso offrire maggiori dettagli. Immagino di essere sorpreso che la seconda query impieghi così tanto tempo per essere elaborata.

risposta

8

La cosa più importante da comprendere quando si utilizza SAS per accedere ai dati in Teradata (o qualsiasi altro database esterno per quella materia) è che il software SAS prepara SQL e lo invia al database. L'idea è di provare ad alleggerirti (l'utente) da tutti i dettagli specifici del database. SAS fa questo usando un concetto chiamato "pass-through implicito", che significa semplicemente che SAS esegue la traduzione dal codice SAS al codice DBMS. Tra le molte cose che si verificano è la conversione del tipo di dati: SAS ha solo due (e solo due) tipi di dati, numerici e caratteri.

SAS si occupa della traduzione di cose per te ma può essere fonte di confusione. Ad esempio, ho visto tabelle di database "lazy" definite con colonne VARCHAR (400) con valori che non superano mai una lunghezza inferiore (come la colonna per il nome di una persona). Nel database questo non è un grosso problema, ma poiché SAS non ha un tipo di dati VARCHAR, crea una variabile di 400 caratteri per ogni riga. Anche con la compressione del set di dati, questo può davvero rendere il set di dati SAS risultante inutilmente grande.

Il modo alternativo è utilizzare "pass-through esplicito", in cui si scrivono query native utilizzando la sintassi effettiva del DBMS in questione. Queste query vengono eseguite interamente sul DBMS e restituiscono i risultati a SAS (che esegue ancora la conversione del tipo di dati per l'utente. Ad esempio, ecco una query "pass-through" che esegue un join su due tabelle e crea un set di dati SAS come risultato:

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; 

si noti che tutto all'interno della coppia di parentesi è nativo Teradata SQL e che l'operazione di join in sé è in esecuzione all'interno del database

il codice di esempio che avete dimostrato in questione non è . un esempio completo e funzionante di un programma SAS/Teradata. Per una migliore assistenza, è necessario mostrare il programma reale, inclusi eventuali riferimenti bibliografici. ppose il programma vero e proprio si presenta così:

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 
    ; 

che possa indicare una dichiarazione libname precedentemente assegnato attraverso il quale SAS stava collegando a Teradata. La sintassi di tale clausola WHERE sarebbe molto rilevante se SAS fosse anche in grado di passare la query completa a Teradata. (L'esempio non mostra a cosa si riferiscono "a" e "b". È molto probabile che l'unico modo in cui SAS può eseguire il join sia quello di trascinare entrambe le tabelle in una sessione di lavoro locale ed eseguire il join sul server SAS

Una cosa che posso fortemente suggerire è che si cerchi di convincere gli amministratori di Teradata a consentire la creazione di tabelle "driver" in un database di utilità. L'idea è di creare una tabella relativamente piccola all'interno di Teradata contenente gli ID vuoi estrarre, quindi utilizzare quella tabella per eseguire join espliciti. Sono sicuro che avresti bisogno di un addestramento di database un po 'più formale per farlo (come come definire un indice corretto e come "raccogliere statistiche"), ma con quello Conoscenza e capacità, il tuo lavoro volerà.

Potrei andare avanti all'infinito Mi fermerò qui. Uso SAS con Teradata estesamente ogni giorno contro quello che mi viene detto è uno dei più grandi ambienti Teradata del pianeta. Mi piace programmare in entrambi.

+1

Ottima risposta. –

+0

Questo è esattamente quello che stavo cercando. Grazie mille per l'informazione. Sto ancora imparando su SAS e Teradata perché sono nuovo ad entrambi, avendo fatto quasi tutto in R per ottenere un formato di database proprietario nel mio vecchio lavoro. Pubblicherò un esempio più completo del codice in una modifica alla mia domanda originale. Sono curioso di vedere cosa ne pensi. Cercherò anche di capire lo stato delle tabelle dei driver. Sembra che sarebbe una buona idea, specialmente i modi in cui ho bisogno di interrogare questi dati. Ancora una volta, grazie mille per il consiglio, lo apprezzo davvero e ne imparo. –

+1

Jeff, la tua domanda aggiornata mostra che vuoi davvero utilizzare una query pass-through. Il modo in cui lo stai facendo ora garantisce che l'intera tabella deve essere scaricata da Teradata perché ti stai unendo a un set di dati SAS. Ti suggerisco di fare una nuova domanda poiché le risposte a questo sono diventate un po 'fuori dal campo di applicazione. Quando lo fai, vedi se riesci a trovare l'istruzione LIBNAME originale che definisce 'mydb'. – BellevueBob

1

Si presuppone che i record 90k nella prima query siano tutti unici id s. È definito?

Chiedo perché l'implicazione dalla seconda query è che non sono unici.
- Uno id può avere più valori nel corso del tempo, e hanno diversi somevalue s

Se i id s non sono univoci nel primo set di dati, è necessario GROUP BY id o utilizzare DISTINCT, nella prima query.

Immaginiamo che i 90k righe costituito da 30k unici id s, e quindi hanno una media di 3 righe per id.

E quindi immaginare quei 30k unici id in realtà hanno 9 record nella finestra del tempo, comprese le righe dove somevalue <> x.

Riceverete quindi 3x9 record per id.

E man mano che questi due numeri crescono, il numero di record nella seconda query aumenta in modo geometrico.


Query alternativa

Se questo non è il problema, una query alternativa (che non è l'ideale, ma possibile) sarebbe ...

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

Questo è un buon punto, grazie per averlo presentato. Gli ID sono decisamente unici. La query subset effettiva è più complicata, ma l'output finale è una lista 'DISTINCT'. Non ho usato una dichiarazione di "HAVING" prima, darò una prova e vedere se aiuta. –

+0

Solo per testare le cose, ho aggiunto un timestamp a un piccolo sottoinsieme dell'intervallo di date della query.Senza il join ci sono voluti 13,57 (0,41 CPU) secondi per restituire 250k record. Con il join, sono stati necessari 29.09 (1,12 CPU) secondi per recuperare 1800 righe. Che strano. –

+0

Inoltre, l'esecuzione della query principale prima e l'applicazione successiva del join (subquery) vengono eseguiti in 15.97 secondi. È in grado di calcolare TUTTE le somme, quindi ridurle più velocemente del paring e quindi di sommare. –

1

Se ID è unico e un singolo valore, quindi puoi provare a costruire un formato.

Creare un set di dati che assomiglia a questo:

fmtname, start, label

dove fmtname è lo stesso per tutti i record, un nome di forma giuridica (inizia e termina con una lettera, contiene alfanumerico o _); start è il valore ID; e label è un 1. Quindi aggiungi una riga con lo stesso valore per fmtname, un inizio vuoto, un'etichetta di 0 e un'altra variabile, hlo='o' (per "altro"). Quindi importa nel formato proc usando l'opzione CNTLIN e ora hai una conversione del valore 1/0.

Ecco un breve esempio utilizzando SASHELP.CLASS. L'ID qui è il nome, ma può essere numerico o di carattere, a seconda di quale sia giusto per l'utilizzo.

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; 

Ora, invece di fare un join, si può fare la query 'normalmente', ma con un ulteriore clausola where di and put(id,$IDF.)='1'. Questo non sarà ottimizzato con un indice o altro, ma potrebbe essere più veloce del join. (Potrebbe anche non essere più veloce - dipende da come funziona l'ottimizzatore SQL.)

+0

Grazie Joe, vedo dove stai andando con questo. L'idea di base è di applicare un'etichetta al set grande e quindi semplicemente filtrare fuori da quella etichetta, piuttosto che fare un join. Sfortunatamente non è un valore singolo, quindi non posso davvero applicarlo in questo modo. Tuttavia, è una cosa interessante da tenere a mente. –

1

Se l'ID è univoco, è possibile aggiungere un INDICE PRIMARIO UNICO (id) a tale tabella, altrimenti viene impostato automaticamente su un PI non univoco . La conoscenza degli uniqueni aiuta l'ottimizzatore a produrre un piano migliore.

Senza ulteriori informazioni come un Explain (basta mettere SPIEGARE di fronte a SELECT) è difficile dire come questo può essere migliorato.

0

Una soluzione alternativa consiste nell'utilizzare le procedure SAS. Non so che cosa il vostro SQL attuale sta facendo, ma se si sta solo facendo frequenze (o qualcos'altro che può essere fatto in un PROC), si potrebbe fare:

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; 

o un qualsiasi numero di altre opzioni (PROC MEANS, PROC TABULATE, ecc.). Potrebbe essere più veloce di fare la somma in SQL (a seconda di alcuni dettagli, come il modo in cui i dati sono organizzati, cosa stai facendo in realtà e quanta memoria hai a disposizione).Ha il vantaggio aggiuntivo che SAS potrebbe scegliere di fare questo in-database, se si crea la vista nel database, che potrebbe essere più veloce. (In effetti, se si esegue il freq fuori dalla tabella di base, è possibile che sia ancora più veloce, quindi unire i risultati alla tabella più piccola).