sql >> Databasteknik >  >> RDS >> Sqlserver

Tips för att använda SQL Server med Salesforce

Innehållsförteckning

  1. Översikt
  2. WHERE-klausul
  3. Flera tabellanslutningar
  4. Lokalt bord kopplat till ett fjärrbord
  5. Infoga, uppdatera och ta bort
  6. Uppdatera
  7. Uppdatera med parametrar
  8. Infoga en ny post och få ett BLOB-fel
  9. Hämta Salesforce-id för den senaste posten du infogade
  10. Uppdatera SQL Server-data när Salesforce-data ändras
  11. Lazy Schema Validering
  12. Begränsningar för Microsofts OLEDB för ODBC-leverantör
  13. Hur hittar jag poster med ett radflöde (ny rad) i faktureringsadressen?
  14. Kan jag se vilka tabeller som är tillgängliga via Easysoft-programvaran?
  15. Kan jag se vilka kolumner som är tillgängliga via Easysoft-programvaran?
  16. 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


  1. Hur man uppdaterar med inre join i Oracle

  2. Exekveringssekvens för Group By, Have and Where-klausul i SQL Server?

  3. Värdpaket på Chocolatey

  4. Postgres datatyp cast