Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Suggerimenti per l'utilizzo di SQL Server con Salesforce

Sommario

  1. Panoramica
  2. Clausola WHERE
  3. Più join di tabelle
  4. Tabella locale collegata a una tabella remota
  5. Inserisci, aggiorna ed elimina
  6. Aggiorna
  7. Aggiorna con i parametri
  8. Inserimento di un nuovo record e ricezione di un errore BLOB
  9. Ottenere l'ID Salesforce per l'ultimo record inserito
  10. Aggiornamento dei dati di SQL Server quando i dati di Salesforce cambiano
  11. Convalida dello schema pigro
  12. Limitazioni del provider OLEDB di Microsoft per ODBC
  13. Come faccio a trovare i record con un feed di riga (nuova riga) nell'indirizzo di fatturazione?
  14. Posso vedere quali tabelle sono disponibili tramite il software Easysoft?
  15. Posso vedere quali colonne sono disponibili tramite il software Easysoft?
  16. Posso creare a livello di codice un server collegato?

Panoramica

Questo documento fornisce alcuni suggerimenti sull'utilizzo di SQL Server con Salesforce. I componenti utilizzati per connettere SQL Server a Salesforce sono un SQL Server Linked Server e il driver Easysoft Salesforce ODBC. In questo articolo viene descritta la modalità di connessione di SQL Server a Salesforce. Per gli esempi in questo documento, il nome del server collegato (a cui fai riferimento nei comandi SQL) utilizzato è SF8.

Tutto l'SQL in questo documento è stato testato rispetto a SQL Server 2017 e alle versioni del driver Easysoft Salesforce ODBC da 2.0.0 a 2.0.7.

Le funzioni di SQL Server OPENQUERY e EXEC (EXECUTE ) sono stati introdotti in SQL Server 2008 e queste funzioni sono compatibili con tutte le versioni di SQL Server successive al 2008.

Abbiamo scritto questo documento in risposta a una serie di domande ricevute dal nostro team di supporto in merito alla connessione di SQL Server tramite Easysoft a Salesforce. Tuttavia, gli esempi SQL dovrebbero essere utili anche per le connessioni al server collegato che utilizzano un driver ODBC e un back-end diversi.

Se desideri contribuire a questo documento, invia la tua richiesta via email a .

Clausola WHERE

Un problema comune segnalato a noi è "Una semplice clausola WHERE impiega molto tempo per restituire solo una riga". Ad esempio:

select Id, FirstName, LastName from SF8.SF.DBO.Contact where Id='00346000002I95MAAS'

SQL Server converte la query precedente e la invia al driver ODBC Salesforce:

select Id, FirstName, LastName from SF.DBO.Contact

La clausola WHERE viene sempre rimossa che impone al driver ODBC di restituire tutte le righe per quella tabella. Quindi SQL Server li filtra localmente per fornire le righe richieste. Non sembra importare quale clausola WHERE hai specificato, questa non viene mai trasmessa al driver ODBC.

La soluzione semplice a questo è usare SQL Server OPENQUERY funzione invece. Ad esempio:

select * from OPENQUERY(SF8,'select Id, FirstName, LastName from SF.DBO.Contact where Id=''00346000002I95MAAS'' ')

Tutto l'SQL che esegui all'interno di OPENQUERY la funzione viene passata direttamente al driver, incluso il WHERE clausola.

Più join di tabelle

Ecco un semplice join di due tabelle in cui entrambe le tabelle stanno tornando dal server collegato.

select a.[Name], BillingStreet, c.[Name] from SF8.SF.DBO.Account a, SF8.SF.DBO.Contact c where a.Id=c.AccountID and a.[Name] like 'United%'

SQL Server invia le seguenti query al driver ODBC.

select * from Account
select * from Contact

SQL Server esegue questa operazione per ottenere un elenco di nomi di colonne e tipi di dati. Quindi invia queste query al driver ODBC.

SELECT "Tbl1001"."Id" "Col1042","Tbl1001"."Name" "Col1044","Tbl1001"."BillingStreet" "Col1046" FROM "SF"."DBO"."Account" "Tbl1001" ORDER BY "Col1042" ASC
SELECT "Tbl1003"."AccountId" "Col1057","Tbl1003"."Name" "Col1058" FROM "SF"."DBO"."Contact" "Tbl1003" ORDER BY "Col1057" ASC

I dati di entrambe le query vengono restituiti alle tabelle locali, quindi la clausola WHERE viene inserita nella tabella Account e i dati di entrambe le tabelle vengono uniti e restituiti.

Ancora l'uso di OPENQUERY assicura che l'SQL che scrivi venga passato direttamente al driver ODBC, quindi, invece, in SQL Server dovresti eseguire:

select * from OPENQUERY(SF8,'select a.[Name], BillingStreet, c.[Name] from SF.DBO.Account a, SF.DBO.Contact c where a.Id=c.AccountID and a.[Name] like ''United%'' ')

È necessaria una leggera modifica, poiché SQL Server non può gestire più colonne con lo stesso "Nome", quindi è necessario rinominare una di queste colonne. Ad esempio:

select * from OPENQUERY(SF8,'select a.[Name], BillingStreet, c.[Name] as FullName from SF.DBO.Account a, SF.DBO.Contact c where a.Id=c.AccountID and a.[Name] like ''United%'' ')

Ciò costringe il driver ODBC a elaborare l'intero SQL in una volta sola e restituire solo i risultati richiesti.

Tabella locale collegata a una tabella remota

In questo esempio, la tabella locale è stata creata eseguendo.

select * into LocalAccount from SF8.SF.DBO.Account

L'unione delle due tabelle ora è simile.

select a.[Name], BillingStreet, c.[Name] as FullName from LocalAccount a, SF8.SF.DBO.Contact c where a.Id=c.AccountID and a.[Name] like 'United%'

Ciò fa sì che SQL Server invii tre volte la query seguente al driver ODBC.

select * from Contact

In almeno una di queste query, SQL Server richiede tutti i dati nella tabella. Quindi SQL Server continua a chiedere:

SELECT "Tbl1003"."Name" "Col1008" FROM "SF"."DBO"."Contact" "Tbl1003" WHERE ?="Tbl1003"."AccountId"

SQL Server passa quindi al driver ODBC un elenco di AccountIds dalla tabella LocalAccount al posto del "?" parametro in cui la colonna LocalAccount.[Nome] corrisponde alla clausola LIKE.

Un modo più rapido in cui la tabella ODBC è la seconda tabella nella query consiste nell'ottenere solo le colonne necessarie dalla tabella ODBC. Questo può essere fatto usando il OPENQUERY funzione. Ad esempio:

select a.[Name], BillingStreet, c.[Name] as FullName from LocalAccount a, openquery(SF8,'select [Name], AccountId from SF.DBO.Contact') c where a.Id=c.AccountID and a.[Name] like 'United%'

Sebbene questo ottenga ancora tutte le righe dalla tabella Contact, ottiene solo le colonne necessarie ed è quindi più veloce della query standard.

Un altro modo possibile sarebbe utilizzare un cursore e una tabella temporanea. Ad esempio:

Begin
      declare @AccountId as varchar(20)
      declare @SQL as varchar(1024)

      -- Create a temporary table to store the Account information. The Id check ensures 0 rows of data are returned
      select * into #LocalContact from openquery(SF8,'select [Name], AccountId from SF.DBO.Contact where Id=''000000000000000000'' ')
      
      -- Set up the cursor      
      declare selcur cursor for
            select distinct Id from LocalAccount where [Name] like 'United%'
      
      	open selcur
      	fetch next from selcur into @AccountId
      	while @@FETCH_STATUS=0
      	Begin
      		select @SQL ='insert into #LocalContact select [Name], '''+@AccountId+''' from OPENQUERY(SF8,''select [Name] from Contact where AccountId=''''' + @AccountId + ''''' '')'
      		exec (@SQL)
      		
      		fetch next from selcur into @AccountId
      	End
      	close selcur
      	deallocate selcur
	
      	-- Next, join your tables and view the data      	
      	select a.[Name], BillingStreet, c.[Name] as FullName from LocalAccount a, #LocalContact c where a.Id=c.AccountID and a.[Name] like 'United%'
      
      	-- Don't forget to remove the temporary table
      	drop table #LocalContact
      
      End

Questo metodo può essere molte volte più veloce di OPENQUERY metodo mostrato nell'esempio precedente, se la clausola WHERE passata al driver ODBC Easysoft utilizza un indice in Salesforce.

Inserisci, aggiorna ed elimina

Se stai eseguendo una query che non è una query SELECT, il modo migliore per farlo è utilizzare SQL Server EXEC funzione. Se il tuo server collegato non può utilizzare EXEC , riceverai un messaggio simile a:

Server 'SF8' is not configured for RPC.

Per utilizzare EXEC , fai clic con il pulsante destro del mouse sul server collegato e scegli le proprietà. Nella sezione "Opzioni server", imposta "RPC Out" su "True". È quindi possibile utilizzare il EXEC funzione.

Aggiorna

Supponiamo che tu abbia questa istruzione in SQL Server:

UPDATE SF8.SF.DBO.Contact SET LastName='James' WHERE Id='00346000002I95MAAS'

SQL Server invia questo SQL al driver ODBC.

select * from "SF"."DBO"."Contact"

Tutti i record vengono recuperati e SQL Server invia quindi questa istruzione al driver ODBC.

UPDATE "SF"."DBO"."Contact" SET "LastName"=? WHERE "Id"=? AND "LastName"=?

SQL Server sta facendo ciò per garantire che il record non venga modificato tra il momento in cui è stata eseguita la query e il momento in cui viene eseguito UPDATE. Un metodo più veloce consiste nell'usare SQL Server EXEC funzione. Ad esempio:

exec ('update SF.DBO.Contact set LastName=''James'' where Id=''00346000002I95MAAS''' ) at SF8 

SQL Server invia al driver ODBC l'intera stringa immessa, quindi la query viene eseguita senza selezionare l'intera tabella.

Aggiorna con i parametri

Supponi di avere:

Begin
	declare @Id varchar(20)='00346000002I95MAAS'
	declare @LastName varchar(20)='James'
	update SF8.SF.DBO.Contact set LastName=@LastName where Id=@Id
End

Funziona esattamente allo stesso modo descritto nelle note di aggiornamento. Tuttavia, la sintassi quando si utilizza EXEC modifiche alla funzione:

Begin
      	declare @Id varchar(20)='00346000002I95MAAS'
      	declare @LastName varchar(20)='James'
	exec ('update SF.DBO.Contact set LastName=? where Id=?', @LastName, @Id)
      		at SF8
End

Dove hai una colonna come LastName= hai messo un ? al posto di @LastName per rappresentare ciò che stai per passare nel parametro. I parametri vengono quindi elencati dopo l'istruzione UPDATE nell'ordine in cui devono essere letti.

Inserimento di un nuovo record e ricezione di un errore BLOB

Supponiamo che tu stia tentando di eseguire:

insert into SF8.SF.DBO.Contact ( FirstName, LastName ) values ('Easysoft','Test')

SQL Server lo invia al driver ODBC:

select * from "SF"."DBO"."Contact"

Questo viene fatto due volte. La prima volta che viene eseguito, SQL Server verifica se il set di risultati è aggiornabile. La seconda volta che viene inviato, SQL Server si sposta su un record vuoto dopo l'ultimo record restituito e tenta di eseguire un INSERT posizionale, che restituisce un errore.

OLE DB provider "MSDASQL" for linked server "SF8" returned message "Query-based insertion or updating of BLOB values is not supported.".

Questo messaggio viene restituito perché un inserimento posizionale tenta di inserire tutte le colonne con valori NULL ad eccezione di quelle specificate nell'istruzione INSERT e, nel caso della tabella Contact, è presente un BLOB (Long Text Area in Salesforce ), che il provider OLE DB di Microsoft non supporta. Il driver Easysoft Salesforce ODBC supporta l'inserimento di tutti i campi all'interno di Salesforce in cui si dispone dell'autorizzazione per inserire i dati. Per aggirare questo problema, tutto ciò che devi fare è utilizzare EXEC.

exec ('insert into SF.DBO.Contact ( FirstName, LastName ) values (''Easysoft'',''Test'')') at SF8

Questo invia semplicemente l'INSERT direttamente al driver ODBC.

Come ottenere l'ID Salesforce per l'ultimo record inserito

Alcuni nostri clienti ci hanno chiesto qual è il metodo più semplice per ottenere l'Id della riga appena inserita. Questo esempio mostra come ottenere l'ID dell'ultimo record inserito nella tabella "Contatti".

Begin
	declare @Id varchar(20)='00346000002I95MAAS'
      	declare @FirstName varchar(20)='Easysoft'
      	declare @LastName varchar(20)='Test'
      	declare @FindTS varchar(22)=convert(varchar(22),GETUTCDATE(),120)
      	declare @SQL as varchar(1024)
      
      	exec ('insert into SF.DBO.Contact (FirstName, LastName ) values (?, ?)', @FirstName, @LastName ) at SF8

      	select @SQL='select Id from openquery(SF8, ''select top 1 c.Id from [User] u, Contact c where u.Username=CURRENT_USER and c.CreatedDate>={ts '''''+@FindTS+'''''} and c.CreatedById=u.Id order by c.CreatedDate desc'')'

      	exec (@SQL) 

End

Quando un record viene creato in Salesforce, la colonna "CreatedDate" contiene un timestamp che è l'UTC (Coordinated Universal Time) in cui è stato creato il record e non necessariamente la data/ora corrente. Il @FindTs string è impostato sull'UTC prima che avvenga INSERT, quindi quando viene chiamato SELECT per ottenere l'ID, sta solo guardando le righe inserite dopo il @FindTS era impostato.

Durante la SELECT, il CURRENT_USER di Easysoft viene utilizzata anche per limitare le righe restituite da Salesforce al solo utente che ha inserito i dati.

Aggiornamento dei dati di SQL Server quando i dati di Salesforce cambiano

Questa sezione mostra come creare una nuova tabella di SQL Server basata sulla struttura di una tabella Salesforce e aggiornare tale tabella quando vengono apportate modifiche alla tabella Salesforce.

create procedure SFMakeLocal( @Link varchar(50), @Remote varchar(50), @Local varchar(50), @DropLocal int) as
          declare @SQL as nvarchar(max)
          begin
              /* Imports the data into a local table */
              /* Set DropLocal to 1 to drop the local table if it exists */
      
              if OBJECT_ID(@Local, 'U') IS NOT NULL 
              begin
                  if (@DropLocal=1) 
                  begin
                      set @SQL='DROP TABLE dbo.'+@Local
                      exec ( @SQL)
                  end
              else
                  RAISERROR(15600,1,1, 'Local table already exists')
                  RETURN  
              end
      
              set @SQL='select * into dbo.'+@Local+' from OPENQUERY('+@Link+',''select * from '+@Remote+''')'
              
              exec(@SQL)
      		select 'Local Table :'+@Local+' created.'
          end
      
      -- @Link Your SQL Server linked server
      -- @Remote The name of the table within Salesforce
      -- @Local The local table you want the data to be stored in
      -- @DropLocal Set to 1 if the table exists and you want to drop it

Eseguire la procedura per copiare la struttura del record dalla tabella Salesforce nella tabella locale e quindi trasferire tutti i dati Salesforce. Questo comando di esempio utilizza la tabella Account. Questo processo può richiedere parecchio tempo a seconda della quantità di dati che hai nella tabella Salesforce.

SFMakeLocal 'SF8','Account','LocalAccount', 0

Gli argomenti sono:

Argomento Valore
SF8 Il nome del server collegato a SQL Server.
Account Il nome della tabella Salesforce da cui desideri leggere la struttura e i dati.
Account locale Il nome della tabella in SQL Server.
0 Questo valore predefinito può essere modificato in 1 se aggiungi più colonne personalizzate in Salesforce e desideri eliminare la tabella locale per crearla di nuovo con le nuove colonne.

Il passaggio successivo consiste nel creare altre due procedure che aggiorneranno la tabella locale se dei dati vengono aggiornati o inseriti nella tabella Salesforce:

create procedure SFUpdateTable ( @Link varchar(50), @Remote varchar(50),
      create procedure SFUpdateTable  
      @Link varchar(50), @Remote varchar(50), @LocalTable varchar(50) 
      as
          begin
              -- Updates the data into a local table based on changes in Salesforce.
      
      		declare @TempDef as varchar(50)='##EasyTMP_'
      		declare @TempName as varchar(50)
      		declare @TempNumber as decimal
      
      		declare @CTS as datetime=current_timestamp
      		declare @TTLimit int = 100
              declare @MaxCreated as datetime
              declare @MaxModified as datetime
              declare @SQL as nvarchar(max)
      		declare @RC as int
      
      		-- The first step is to create a global temporary table.
      
      		set @TempNumber=datepart(yyyy,@CTS)*10000000000+datepart(mm,@CTS)*100000000+datepart(dd,@CTS)*1000000+datepart(hh,@CTS)*10000+datepart(mi,@CTS)*100+datepart(ss,@CTS)
      		set @TempName=@TempDef+cast(@TempNumber as varchar(14))
      
      		while OBJECT_ID(@TempName, 'U') IS NOT NULL 
              begin
                  RAISERROR (15600,1,1, 'Temp name already in use.')
                  RETURN  
              end
      
      		set @SQL='select * into '+@TempName+' from '+@LocalTable+' where 1=0'
      
      		create table #LocalDates ( ColName varchar(20), DTS datetime)
              set @sql='insert into #LocalDates select ''Created'', max(CreatedDate) from '+@LocalTable
      		exec (@sql)
              set @sql='insert into #LocalDates select ''Modified'', max(LastModifiedDate) from '+@LocalTable
      		exec (@sql)
      
      		select @MaxCreated=DTS from #LocalDates where ColName='Created'
      		select @MaxModified=DTS from #LocalDates where ColName='Modified'
      
      		drop table #LocalDates
      
              set @SQL='select * into '+@TempName+' from openquery('+@Link+',''select * from '+@Remote+' where CreatedDate>{ts'''''+convert(varchar(22),@MaxCreated,120)+'''''}'')'
              exec(@SQL)
      		exec SFAppendFromTemp @LocalTable, @TempName
      
      		set @SQL='drop table '+@TempName
      		exec (@SQL)
      
              set @SQL='select * into '+@TempName+' from openquery('+@Link+',''select * from '+@Remote+' where LastModifiedDate>{ts'''''+convert(varchar(22),@MaxModified,120)+'''''} and CreatedDate<={ts'''''+convert(varchar(22),@MaxCreated,120)+'''''}'')'
      		exec (@SQL)
              exec SFAppendFromTemp @LocalTable, @TempName
      
      		set @SQL='drop table '+@TempName
      		exec (@SQL)
      
      
          end
      create procedure SFAppendFromTemp(@Local varchar(50), @TempName varchar(50)) as 
      begin
      
      
          /* Uses the temp table to import the data into the local table making sure any duplicates are removed first */
      
          declare @Columns nvarchar(max)
          declare @ColName varchar(50)
          declare @SQL nvarchar(max)
      
          set @sql='delete from '+@Local+' where Id in ( select Id from '+@TempName+')'
          exec (@SQL)
      
          set @Columns=''
      
          declare col_cursor cursor for 
              select syscolumns.name from sysobjects inner join syscolumns on sysobjects.id = syscolumns.id where sysobjects.xtype = 'u' and  sysobjects.name = @Local
      
          open col_cursor
          fetch next from col_cursor into @ColName
          while @@FETCH_STATUS=0
          Begin
              set @Columns=@Columns+'['+@ColName+']'
              fetch next from col_cursor into @ColName
              if (@@FETCH_STATUS=0)
                  set @Columns=@Columns+', '
          End
          close col_cursor
          deallocate col_cursor
      
          set @sql='insert into '+@Local+' (' +@Columns+') select '+@Columns+' from '+@TempName
          exec (@sql)
      
      end
      
      -- Two procedures are used to get the data from a remote table. 1) SFUpdateTable, which
      -- copies the data into a temporary table. 2) SFAppendFromTemp, which appends
      -- the data from the temporary table into the local table.
      
      -- @Link Your SQL Server linked server name
      -- @Remote The name of the table within Salesforce
      -- @Local The local table where you want the data to be stored in
      -- @TempName A name of a table that can be used to temporary store data. Do not
      -- use an actual temporary table name such as #temp, this will not work.

Per verificarlo, esegui:

SFUpdateTable 'SF8','Account','LocalAccount'

Questo esempio può essere utilizzato con qualsiasi tabella Salesforce a cui un utente ha accesso.

Convalida schema pigro

Nelle proprietà del server collegato di SQL Server, nella sezione "Opzioni server", è presente un'opzione per "Convalida schema pigro". Per impostazione predefinita, è impostato su FALSE, che fa sì che SQL Server invii due volte le istruzioni SELECT. La prima volta che viene inviata la query, SQL Server utilizza i dettagli restituiti per creare metadati sul set di risultati. Quindi la query viene inviata di nuovo. Questo è un sovraccarico piuttosto costoso, quindi Easysoft consiglia di impostare "Lazy Schema Validation" su TRUE, il che significa che viene inviata solo una query, recuperando sia i metadati che il set di risultati in una volta sola. Ciò consente di risparmiare anche sul numero di chiamate API Salesforce effettuate.

Limitazioni del provider OLEDB di Microsoft per ODBC

I dettagli sulle limitazioni del provider OLEDB per ODBC sono disponibili all'indirizzo:

https://msdn.microsoft.com/en-us/library/ms719628(v=vs.85).aspx

Come faccio a trovare i record con un avanzamento riga (nuova riga) nell'indirizzo di fatturazione?

Utilizzando alcune delle funzioni interne del driver Easysoft, puoi trovare facilmente record in cui l'indirizzo di fatturazione ha un avanzamento di riga all'interno del record. Ad esempio:

select * from openquery(sf8,'select Id, Name, {fn POSITION({fn CHAR(10)} IN BillingStreet)} LinePos from Account where {fn POSITION({fn CHAR(10)} IN BillingStreet)} >0')

POSITION(x) Questa funzione cerca la posizione di x all'interno della colonna specificata.

CHAR(X) Questa funzione restituisce il carattere con il valore ASCII di x .

Ulteriori informazioni sulle funzioni disponibili nel nostro driver ODBC Salesforce sono disponibili qui

Posso vedere quali tabelle sono disponibili tramite il software Easysoft?

Per ottenere un elenco di tabelle a cui puoi accedere, esegui:

select * from openquery(SF8,'select TABLE_NAME from INFO_SCHEMA.TABLES')

Posso vedere quali colonne sono disponibili tramite il software Easysoft?

Puoi ottenere un elenco di colonne presenti nella tabella eseguendo:

seleziona * da openquery(SF8,'select * da INFO_SCHEMA.COLUMNS where TABLE_NAME=''Account'' ')

Usando questo metodo puoi ottenere solo un elenco delle colonne che appartengono alla tabella specificata nella clausola TABLE_NAME WHERE. Se vuoi vedere un elenco completo di colonne per tutte le tabelle, esegui:

begin
    declare @Table nvarchar(max)
	
	declare table_cursor cursor for 
        select TABLE_NAME from openquery(SF8,'select TABLE_NAME from INFO_SCHEMA.TABLES')

    open table_cursor
    fetch next from table_cursor into @Table
    while @@FETCH_STATUS=0
    Begin
		exec ('select * from INFO_SCHEMA.COLUMNS where TABLE_NAME=?', @Table) at SF8
		fetch next from table_cursor into @Table
	End

    close table_cursor
    deallocate table_cursor

end

Posso creare a livello di codice un server collegato?

Sì. Ci sono molti esempi di questo sul web, per esempio:

http://www.sqlservercentral.com/articles/Linked+Servers/142270/?utm_source=SSC