Sommario
- Panoramica
- Clausola WHERE
- Più join di tabelle
- Tabella locale collegata a una tabella remota
- Inserisci, aggiorna ed elimina
- Aggiorna
- Aggiorna con i parametri
- Inserimento di un nuovo record e ricezione di un errore BLOB
- Ottenere l'ID Salesforce per l'ultimo record inserito
- Aggiornamento dei dati di SQL Server quando i dati di Salesforce cambiano
- Convalida dello schema pigro
- Limitazioni del provider OLEDB di Microsoft per ODBC
- Come faccio a trovare i record con un feed di riga (nuova riga) nell'indirizzo di fatturazione?
- Posso vedere quali tabelle sono disponibili tramite il software Easysoft?
- Posso vedere quali colonne sono disponibili tramite il software Easysoft?
- 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