Innehållsförteckning
- Översikt
- WHERE-klausul
- Flera tabellanslutningar
- Lokalt bord kopplat till ett fjärrbord
- Infoga, uppdatera och ta bort
- Uppdatera
- Uppdatera med parametrar
- Infoga en ny post och få ett BLOB-fel
- Hämta Salesforce-id för den senaste posten du infogade
- Uppdatera SQL Server-data när Salesforce-data ändras
- Lazy Schema Validering
- Begränsningar för Microsofts OLEDB för ODBC-leverantör
- Hur hittar jag poster med ett radflöde (ny rad) i faktureringsadressen?
- Kan jag se vilka tabeller som är tillgängliga via Easysoft-programvaran?
- Kan jag se vilka kolumner som är tillgängliga via Easysoft-programvaran?
- Kan jag skapa en länkad server programmatiskt?
Översikt
Det här dokumentet ger några tips om hur du använder SQL Server med Salesforce. Komponenterna som används för att ansluta SQL Server till Salesforce är en SQL Server Linked Server och Easysoft Salesforce ODBC Driver. Hur du ansluter SQL Server till Salesforce beskrivs i den här artikeln. För exemplen i det här dokumentet är det länkade servernamnet (som du refererar till i dina SQL-kommandon) som används SF8.
All SQL i det här dokumentet testades mot SQL Server 2017 och Easysoft Salesforce ODBC-drivrutin version 2.0.0 till 2.0.7.
SQL Server-funktionerna OPENQUERY
och EXEC
(EXECUTE
) introducerades i SQL Server 2008 och dessa funktioner är kompatibla med alla versioner av SQL Server efter 2008.
Vi har skrivit det här dokumentet som svar på ett antal frågor som mottagits av vårt supportteam angående anslutning av SQL Server via Easysoft till Salesforce. Men SQL-exemplen bör också vara användbara för länkade serveranslutningar som använder en annan ODBC-drivrutin och backend.
Om du vill bidra till det här dokumentet, skicka ett e-postmeddelande till .
WHERE-klausul
Ett vanligt problem som rapporterats till oss är "En enkel WHERE-klausul tar lång tid att returnera endast en rad". Till exempel:
select Id, FirstName, LastName from SF8.SF.DBO.Contact where Id='00346000002I95MAAS'
SQL Server konverterar ovanstående fråga och skickar denna till Salesforce ODBC-drivrutinen:
select Id, FirstName, LastName from SF.DBO.Contact
WHERE-satsen tas alltid bort, vilket tvingar ODBC-drivrutinen att returnera alla rader för den tabellen. Sedan filtrerar SQL Server dem lokalt för att ge dig den eller de rader som krävs. Det verkar inte spela någon roll vilken WHERE-klausul du har angett, detta skickas aldrig vidare till ODBC-drivrutinen.
Den enkla lösningen på detta är att använda SQL Server OPENQUERY
funktion istället. Till exempel:
select * from OPENQUERY(SF8,'select Id, FirstName, LastName from SF.DBO.Contact where Id=''00346000002I95MAAS'' ')
All SQL du kör i OPENQUERY
funktionen skickas direkt till föraren, inklusive WHERE
klausul.
Flera tabellanslutningar
Här är en enkel koppling med två tabeller där båda tabellerna kommer tillbaka från den länkade servern.
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 skickar följande frågor till ODBC-drivrutinen.
select * from Account select * from Contact
SQL Server gör detta för att få en lista över kolumnnamn och datatyper. Den fortsätter sedan med att skicka dessa frågor till ODBC-drivrutinen.
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
Data från båda frågorna returneras till lokala tabeller, sedan placeras WHERE-satsen på kontotabellen och data från båda tabellerna sammanfogas och returneras.
Återigen användningen av OPENQUERY
säkerställer att SQL du skriver skickas direkt till ODBC-drivrutinen, så istället, i SQL Server skulle du köra:
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%'' ')
Du behöver en liten modifiering, eftersom SQL Server inte kan hantera flera kolumner med samma "Namn", så du måste byta namn på en av dessa kolumner. Till exempel:
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%'' ')
Detta tvingar ODBC-drivrutinen att bearbeta hela SQL på en gång och bara returnera de resultat som krävs.
Lokalt bord kopplat till ett fjärrbord
I det här exemplet skapades den lokala tabellen genom att köra.
select * into LocalAccount from SF8.SF.DBO.Account
Sammanfogningen av de två tabellerna ser nu ut.
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%'
Detta gör att SQL Server skickar följande fråga tre gånger till ODBC-drivrutinen.
select * from Contact
I minst en av dessa frågor frågar SQL Server efter all data i tabellen. Sedan fortsätter SQL Server att fråga efter:
SELECT "Tbl1003"."Name" "Col1008" FROM "SF"."DBO"."Contact" "Tbl1003" WHERE ?="Tbl1003"."AccountId"
SQL Server skickar sedan till ODBC-drivrutinen en lista med konto-ID från tabellen LocalAccount i stället för "?" parameter där kolumnen LocalAccount.[Name] matchar LIKE-satsen.
Ett snabbare sätt där ODBC-tabellen är den andra tabellen i frågan, är att bara hämta de kolumner du behöver från ODBC-tabellen. Detta kan göras genom att använda OPENQUERY
fungera. Till exempel:
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%'
Även om detta fortfarande hämtar alla rader från kontakttabellen, får det bara de kolumner som behövs och är därför snabbare än standardfrågan.
Ett annat möjligt sätt skulle vara att använda en markör och en tillfällig tabell. Till exempel:
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
Denna metod kan vara flera gånger snabbare än OPENQUERY
metod som visas i föregående exempel, om WHERE-satsen som skickas till Easysoft ODBC-drivrutinen använder ett index i Salesforce.
Infoga, uppdatera och ta bort
Om du kör en fråga som inte är en SELECT-fråga är det bästa sättet att göra detta att använda SQL Server EXEC
fungera. Om din länkade server inte kan använda EXEC
, får du ett meddelande som liknar:
Server 'SF8' is not configured for RPC.
För att använda EXEC
, högerklicka på din länkade server och välj egenskaper. I avsnittet "Serveralternativ" ställer du in "RPC Out" till "True". Du kan sedan använda EXEC
funktion.
Uppdatera
Låt oss säga att du har detta uttalande i SQL Server:
UPDATE SF8.SF.DBO.Contact SET LastName='James' WHERE Id='00346000002I95MAAS'
SQL Server skickar denna SQL till ODBC-drivrutinen.
select * from "SF"."DBO"."Contact"
Alla poster hämtas och SQL Server skickar sedan denna sats till ODBC-drivrutinen.
UPDATE "SF"."DBO"."Contact" SET "LastName"=? WHERE "Id"=? AND "LastName"=?
SQL Server gör det för att säkerställa att posten inte ändras mellan det att du körde frågan och den tidpunkt då UPDATEN körs. En snabbare metod är att använda SQL Server EXEC
fungera. Till exempel:
exec ('update SF.DBO.Contact set LastName=''James'' where Id=''00346000002I95MAAS''' ) at SF8
SQL Server skickar ODBC-drivrutinen hela strängen du har angett, så frågan exekveras utan att hela tabellen markeras.
Uppdatera med parametrar
Säg att du har:
Begin declare @Id varchar(20)='00346000002I95MAAS' declare @LastName varchar(20)='James' update SF8.SF.DBO.Contact set LastName=@LastName where Id=@Id End
Detta fungerar på exakt samma sätt som beskrivs i uppdateringsanteckningarna. Men syntaxen när du använder EXEC
funktionsändringar:
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
Där du har en kolumn som LastName=
du sätter en ?
i stället för @LastName
för att representera vad du ska skicka in i parametern. Parametrarna listas sedan efter UPDATE-satsen i den ordning som de behöver läsas.
Infoga en ny post och få ett BLOB-fel
Säg att du försöker köra:
insert into SF8.SF.DBO.Contact ( FirstName, LastName ) values ('Easysoft','Test')
SQL Server skickar detta till ODBC-drivrutinen:
select * from "SF"."DBO"."Contact"
Detta görs två gånger. Första gången detta körs kontrollerar SQL Server för att se om resultatuppsättningen är uppdateringsbar. Andra gången detta skickas, flyttar SQL Server till en tom post efter att den sista posten returnerades och försöker göra en positionsINSERT, vilket ger ett fel.
OLE DB provider "MSDASQL" for linked server "SF8" returned message "Query-based insertion or updating of BLOB values is not supported.".
Det här meddelandet returneras eftersom en positionsinfogning försöker infoga alla kolumner med NULL-värden utom de du har angett i din INSERT-sats, och i fallet med kontakttabellen finns det en BLOB (Long Text Area i Salesforce ), som OLE DB-leverantören från Microsoft inte stöder. Easysoft Salesforce ODBC-drivrutinen stöder infogning av alla fält inom Salesforce där du har behörighet att infoga data. Allt du behöver göra för att komma runt detta är att använda EXEC.
exec ('insert into SF.DBO.Contact ( FirstName, LastName ) values (''Easysoft'',''Test'')') at SF8
Detta skickar bara INSERT direkt till ODBC-drivrutinen.
Hämta Salesforce-id för den senaste posten du infogade
Vi har blivit tillfrågade av några av våra kunder vad som är den enklaste metoden för att få ID:t för raden som precis infogades. Det här exemplet visar hur du kan få ID för den senaste posten du infogade i tabellen "Kontakt".
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
När en post skapas i Salesforce innehåller kolumnen "CreatedDate" en tidsstämpel som är UTC (Coordinated Universal Time) som posten skapades och inte nödvändigtvis ditt aktuella datum/tid. @FindTs
strängen sätts till UTC innan INSERT äger rum, så när SELECT för att få ID anropas, tittar den bara på raderna infogade efter @FindTS
var inställd.
Under SELECT, Easysoft CURRENT_USER
Funktionen används också för att begränsa raderna som returneras från Salesforce till endast den användare som har infogat data.
Uppdatera SQL Server-data när Salesforce-data ändras
Det här avsnittet visar hur du skapar en ny SQL Server-tabell baserat på strukturen för en Salesforce-tabell och uppdaterar den tabellen när det finns ändringar i den Salesforce-tabellen.
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
Kör proceduren för att kopiera poststrukturen från Salesforce-tabellen till den lokala tabellen och överför sedan all Salesforce-data. Det här exempelkommandot använder kontotabellen. Denna process kan ta ganska lång tid beroende på mängden data du har i Salesforce-tabellen.
SFMakeLocal 'SF8','Account','LocalAccount', 0
Argumenten är:
Argument | Värde |
---|---|
SF8 | SQL Server Linked Server-namnet. |
Konto | Det Salesforce-tabellnamn du vill använda för att läsa strukturen och data från. |
Lokalt konto | Namnet på din tabell i SQL Server. |
0 | Detta standardvärde kan ändras till 1 om du lägger till fler anpassade kolumner i Salesforce och du vill ta bort den lokala tabellen för att skapa den igen med de nya kolumnerna. |
Nästa steg är att skapa ytterligare två procedurer som kommer att uppdatera den lokala tabellen om någon data uppdateras eller infogas i Salesforce-tabellen:
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.
För att testa detta, kör:
SFUpdateTable 'SF8','Account','LocalAccount'
Det här exemplet kan användas med alla Salesforce-tabeller som en användare har tillgång till.
Lazy Schema Validering
I dina SQL Server-länkade serveregenskaper, under avsnittet "Serveralternativ", finns ett alternativ för "Lazy Schema Validation". Som standard är detta inställt på FALSE, vilket gör att SQL Server skickar SELECT-satser två gånger. Första gången frågan skickas använder SQL Server den information som skickas tillbaka för att bygga upp metadata om din resultatuppsättning. Sedan skickas frågan igen. Detta är en ganska dyr omkostnad, så Easysoft skulle rekommendera att du ställer in "Lazy Schema Validation" till TRUE, vilket innebär att endast en fråga skickas och hämtar både metadata och resultatuppsättning på en gång. Detta sparar också på antalet Salesforce API-anrop som görs.
Begränsningar för Microsofts OLEDB för ODBC-leverantör
Detaljer om begränsningarna för OLEDB för ODBC-leverantör finns på:
https://msdn.microsoft.com/en-us/library/ms719628(v=vs.85).aspx
Hur hittar jag poster med ett radflöde (ny rad) i faktureringsadressen?
Genom att använda några av Easysoft-förarens interna funktioner kan du enkelt hitta poster där faktureringsadressen har en radmatning i posten. Till exempel:
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)
Den här funktionen letar efter positionen för x
inom den angivna kolumnen.
CHAR(X)
Denna funktion returnerar tecknet med ASCII-värdet x
.
Mer information om de funktioner som är tillgängliga i vår Salesforce ODBC-drivrutin finns här
Kan jag se vilka tabeller som är tillgängliga via programvaran Easysoft?
För att få en lista över tabeller som du kan komma åt, kör:
select * from openquery(SF8,'select TABLE_NAME from INFO_SCHEMA.TABLES')
Kan jag se vilka kolumner som är tillgängliga via programvaran Easysoft?
Du kan få en lista över kolumner som finns i tabellen genom att köra:
välj * från openquery(SF8,'välj * från INFO_SCHEMA.COLUMNS där TABLE_NAME=''Konto'' ')
Med den här metoden kan du bara få en lista över de kolumner som hör till tabellen du anger i TABLE_NAME WHERE-satsen. Om du vill se en fullständig lista med kolumner för alla tabeller, kör:
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
Kan jag skapa en länkad server programmatiskt?
Ja. Det finns massor av exempel på detta på webben, till exempel:
http://www.sqlservercentral.com/articles/Linked+Servers/142270/?utm_source=SSC