Inhoudsopgave
- Overzicht
- WAAR-clausule
- Meerdere tabellen samen
- Lokale tafel gekoppeld aan een externe tafel
- Invoegen, bijwerken en verwijderen
- Bijwerken
- Bijwerken met parameters
- Een nieuw record invoegen en een BLOB-fout krijgen
- De Salesforce-ID ophalen voor de laatste record die u heeft ingevoegd
- SQL Server-gegevens bijwerken wanneer Salesforce-gegevens worden gewijzigd
- Luie schemavalidatie
- Beperkingen van de OLEDB voor ODBC-provider van Microsoft
- Hoe vind ik records met een regelinvoer (nieuwe regel) in het factuuradres?
- Kan ik zien welke tabellen beschikbaar zijn via de Easysoft-software?
- Kan ik zien welke kolommen beschikbaar zijn via de Easysoft-software?
- Kan ik programmatisch een gekoppelde server maken?
Overzicht
Dit document geeft enkele tips voor het gebruik van SQL Server met Salesforce. De componenten die worden gebruikt om SQL Server met Salesforce te verbinden, zijn een SQL Server Linked Server en het Easysoft Salesforce ODBC-stuurprogramma. In dit artikel wordt beschreven hoe u SQL Server verbindt met Salesforce. Voor de voorbeelden in dit document is de naam van de gekoppelde server (waar u naar verwijst in uw SQL-opdrachten) SF8.
Alle SQL in dit document is getest tegen SQL Server 2017 en de Easysoft Salesforce ODBC-stuurprogrammaversies 2.0.0 tot 2.0.7.
De SQL Server-functies OPENQUERY
en EXEC
(EXECUTE
) zijn geïntroduceerd in SQL Server 2008 en deze functies zijn compatibel met alle versies van SQL Server na 2008.
We hebben dit document geschreven naar aanleiding van een aantal vragen die ons ondersteuningsteam heeft ontvangen over het verbinden van SQL Server via Easysoft met Salesforce. De SQL-voorbeelden zouden echter ook nuttig moeten zijn voor Linked Server-verbindingen die een ander ODBC-stuurprogramma en backend gebruiken.
Als u wilt bijdragen aan dit document, kunt u uw inzending e-mailen naar .
WHERE-clausule
Een veelvoorkomend probleem dat aan ons is gemeld, is "Een eenvoudige WHERE-clausule duurt lang om slechts één rij te retourneren". Bijvoorbeeld:
select Id, FirstName, LastName from SF8.SF.DBO.Contact where Id='00346000002I95MAAS'
SQL Server converteert de bovenstaande query en stuurt deze naar het Salesforce ODBC-stuurprogramma:
select Id, FirstName, LastName from SF.DBO.Contact
De WHERE-component wordt altijd verwijderd, waardoor het ODBC-stuurprogramma alle rijen voor die tabel moet retourneren. Vervolgens filtert SQL Server ze lokaal om u de vereiste rij(en) te geven. Het lijkt niet uit te maken welke WHERE-clausule u hebt opgegeven, deze wordt nooit doorgegeven aan de ODBC-driver.
De eenvoudige oplossing hiervoor is het gebruik van de SQL Server OPENQUERY
in plaats daarvan functioneren. Bijvoorbeeld:
select * from OPENQUERY(SF8,'select Id, FirstName, LastName from SF.DBO.Contact where Id=''00346000002I95MAAS'' ')
Alle SQL die u uitvoert in de OPENQUERY
functie wordt rechtstreeks aan de bestuurder doorgegeven, inclusief de WHERE
clausule.
Meerdere tabellen samen
Hier is een eenvoudige join met twee tafels waarbij beide tafels terugkomen van de gekoppelde server.
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 stuurt de volgende query's naar het ODBC-stuurprogramma.
select * from Account select * from Contact
SQL Server doet dit om een lijst met kolomnamen en gegevenstypen te krijgen. Vervolgens stuurt het deze vragen naar het ODBC-stuurprogramma.
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
De gegevens van beide query's worden geretourneerd naar lokale tabellen, vervolgens wordt de WHERE-component in de Account-tabel geplaatst en worden de gegevens uit beide tabellen samengevoegd en geretourneerd.
Opnieuw het gebruik van OPENQUERY
zorgt ervoor dat de SQL die u schrijft direct wordt doorgegeven aan het ODBC-stuurprogramma, dus in plaats daarvan voert u in SQL Server het volgende uit:
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%'' ')
Je hebt een kleine aanpassing nodig, omdat SQL Server niet meerdere kolommen met dezelfde "Naam" aankan, dus je moet een van die kolommen hernoemen. Bijvoorbeeld:
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%'' ')
Dit dwingt het ODBC-stuurprogramma om de volledige SQL in één keer te verwerken en alleen de vereiste resultaten te retourneren.
Lokale tafel gekoppeld aan een externe tafel
In dit voorbeeld is de lokale tabel gemaakt door te rennen.
select * into LocalAccount from SF8.SF.DBO.Account
De samenvoeging van de twee tabellen ziet er nu als volgt uit.
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%'
Dit zorgt ervoor dat SQL Server de volgende query drie keer naar de ODBC-driver stuurt.
select * from Contact
In ten minste één van die query's vraagt SQL Server om alle gegevens in de tabel. Dan vraagt SQL Server verder naar:
SELECT "Tbl1003"."Name" "Col1008" FROM "SF"."DBO"."Contact" "Tbl1003" WHERE ?="Tbl1003"."AccountId"
SQL Server geeft vervolgens een lijst met AccountIds uit de LocalAccount-tabel aan het ODBC-stuurprogramma door in plaats van de "?" parameter waarbij de kolom LocalAccount.[Name] overeenkomt met de LIKE-component.
Een snellere manier waarbij de ODBC-tabel de tweede tabel in de query is, is om alleen de kolommen te halen die u nodig hebt uit de ODBC-tabel. Dit kan gedaan worden met behulp van de OPENQUERY
functie. Bijvoorbeeld:
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%'
Hoewel dit nog steeds alle rijen uit de tabel Contactpersoon haalt, krijgt het alleen de benodigde kolommen en is het daarom sneller dan de standaardquery.
Een andere mogelijke manier zou zijn om een cursor en een tijdelijke tabel te gebruiken. Bijvoorbeeld:
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
Deze methode kan meerdere keren sneller zijn dan de OPENQUERY
methode getoond in het vorige voorbeeld, als de WHERE-component die wordt doorgegeven aan het Easysoft ODBC-stuurprogramma een index in Salesforce gebruikt.
Invoegen, bijwerken en verwijderen
Als u een query uitvoert die geen SELECT-query is, kunt u dit het beste doen door de SQL Server EXEC
te gebruiken. functie. Als uw gekoppelde server EXEC
niet kan gebruiken , krijgt u een bericht dat lijkt op:
Server 'SF8' is not configured for RPC.
EXEC
gebruiken , klik met de rechtermuisknop op uw gekoppelde server en kies eigenschappen. Stel in het gedeelte "Serveropties" "RPC Out" in op "True". U kunt dan de EXEC
. gebruiken functie.
Bijwerken
Laten we zeggen dat je deze verklaring in SQL Server hebt:
UPDATE SF8.SF.DBO.Contact SET LastName='James' WHERE Id='00346000002I95MAAS'
SQL Server stuurt deze SQL naar het ODBC-stuurprogramma.
select * from "SF"."DBO"."Contact"
Alle records worden opgehaald en SQL Server stuurt deze instructie vervolgens naar het ODBC-stuurprogramma.
UPDATE "SF"."DBO"."Contact" SET "LastName"=? WHERE "Id"=? AND "LastName"=?
SQL Server doet dat om ervoor te zorgen dat het record niet wordt gewijzigd tussen het moment dat u de query hebt uitgevoerd en het moment waarop de UPDATE wordt uitgevoerd. Een snellere methode is om de SQL Server EXEC
. te gebruiken functie. Bijvoorbeeld:
exec ('update SF.DBO.Contact set LastName=''James'' where Id=''00346000002I95MAAS''' ) at SF8
SQL Server stuurt de ODBC-driver de hele string die je hebt ingevoerd, dus de query wordt uitgevoerd zonder de hele tabel te selecteren.
Update met parameters
Stel dat je het volgende hebt:
Begin declare @Id varchar(20)='00346000002I95MAAS' declare @LastName varchar(20)='James' update SF8.SF.DBO.Contact set LastName=@LastName where Id=@Id End
Dit werkt op precies dezelfde manier als beschreven in de Update-opmerkingen. De syntaxis bij gebruik van de EXEC
functiewijzigingen:
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
Waar je een kolom hebt zoals LastName=
plaats je een ?
in plaats van @LastName
om aan te geven wat u in de parameter gaat doorgeven. De parameters worden dan vermeld achter de UPDATE-instructie in de volgorde waarin ze moeten worden gelezen.
Een nieuw record invoegen en een BLOB-fout krijgen
Stel dat je probeert te rennen:
insert into SF8.SF.DBO.Contact ( FirstName, LastName ) values ('Easysoft','Test')
SQL Server stuurt dit naar het ODBC-stuurprogramma:
select * from "SF"."DBO"."Contact"
Dit wordt twee keer gedaan. De eerste keer dat dit wordt uitgevoerd, controleert SQL Server of de resultatenset kan worden bijgewerkt. De tweede keer dat dit wordt verzonden, gaat SQL Server naar een leeg record nadat het laatste record is geretourneerd en probeert een positionele INSERT uit te voeren, wat een fout geeft.
OLE DB provider "MSDASQL" for linked server "SF8" returned message "Query-based insertion or updating of BLOB values is not supported.".
Dit bericht wordt geretourneerd omdat een positionele invoeging probeert alle kolommen met NULL-waarden in te voegen, behalve de kolommen die u hebt opgegeven in uw INSERT-instructie, en in het geval van de tabel Contactpersoon is er een BLOB (Long Text Area in Salesforce), die de OLE DB-provider van Microsoft niet ondersteunt. Het Easysoft Salesforce ODBC-stuurprogramma ondersteunt het invoegen van alle velden binnen Salesforce waar u toestemming hebt om gegevens in te voegen. Om dit te omzeilen, hoef je alleen maar EXEC te gebruiken.
exec ('insert into SF.DBO.Contact ( FirstName, LastName ) values (''Easysoft'',''Test'')') at SF8
Dit stuurt de INSERT gewoon rechtstreeks naar de ODBC-driver.
De Salesforce-ID ophalen voor de laatste record die u heeft ingevoegd
Een aantal van onze klanten heeft ons gevraagd wat de gemakkelijkste methode is om de ID van de zojuist ingevoegde rij te krijgen. Dit voorbeeld laat zien hoe u de id kunt krijgen van het laatste record dat u in de tabel "Contact" hebt ingevoegd.
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
Wanneer een record wordt gemaakt in Salesforce, bevat de kolom "CreatedDate" een tijdstempel dat de UTC (Coordinated Universal Time) is waarop het record is gemaakt en niet noodzakelijk uw huidige datum/tijd. De @FindTs
string is ingesteld op de UTC voordat de INSERT plaatsvindt, dus wanneer de SELECT om de id te krijgen wordt aangeroepen, kijkt deze alleen naar de rijen die zijn ingevoegd na de @FindTS
was ingesteld.
Tijdens de SELECT, de Easysoft CURRENT_USER
functie wordt ook gebruikt om de rijen die worden geretourneerd door Salesforce te beperken tot alleen de gebruiker die de gegevens heeft ingevoegd.
SQL Server-gegevens bijwerken wanneer Salesforce-gegevens worden gewijzigd
In deze sectie wordt uitgelegd hoe u een nieuwe SQL Server-tabel maakt op basis van de structuur van een Salesforce-tabel en hoe u die tabel bijwerkt wanneer er wijzigingen zijn in die Salesforce-tabel.
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
Voer de procedure uit om de recordstructuur van de Salesforce-tabel naar de lokale tabel te kopiëren en breng vervolgens alle Salesforce-gegevens over. In deze voorbeeldopdracht wordt de tabel Account gebruikt. Dit proces kan behoorlijk wat tijd in beslag nemen, afhankelijk van de hoeveelheid gegevens die u in de Salesforce-tabel heeft.
SFMakeLocal 'SF8','Account','LocalAccount', 0
De argumenten zijn:
Argument | Waarde |
---|---|
SF8 | De naam van de SQL Server Linked Server. |
Account | De naam van de Salesforce-tabel die u wilt gebruiken om de structuur en de gegevens uit te lezen. |
LocalAccount | De naam van uw tabel in SQL Server. |
0 | Deze standaardwaarde kan worden gewijzigd in 1 als u meer aangepaste kolommen toevoegt aan Salesforce en u de lokale tabel wilt verwijderen om deze opnieuw te maken met de nieuwe kolommen. |
De volgende stap is het maken van nog twee procedures die de lokale tabel bijwerken als er gegevens worden bijgewerkt of ingevoegd in de Salesforce-tabel:
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.
Voer het volgende uit om dit te testen:
SFUpdateTable 'SF8','Account','LocalAccount'
Dit voorbeeld kan worden gebruikt met elke Salesforce-tabel waartoe een gebruiker toegang heeft.
Luie schemavalidatie
In de eigenschappen van uw SQL Server-gekoppelde server, onder de sectie "Serveropties", is een optie voor "Lazy Schema Validation". Standaard is dit ingesteld op FALSE, waardoor SQL Server twee keer SELECT-instructies verzendt. De eerste keer dat de query wordt verzonden, gebruikt SQL Server de doorgegeven details om metagegevens over uw resultatenset op te bouwen. Daarna wordt de vraag opnieuw verzonden. Dit is nogal een dure overhead, dus Easysoft raadt aan om "Lazy Schema Validation" in te stellen op TRUE, wat betekent dat er slechts één query wordt verzonden, waarbij zowel metadata als resultatenset in één keer worden opgehaald. Dit bespaart ook op het aantal Salesforce API-aanroepen dat wordt gedaan.
Beperkingen van Microsoft's OLEDB for ODBC Provider
Details over de beperkingen van de OLEDB voor ODBC Provider zijn te vinden op:
https://msdn.microsoft.com/en-us/library/ms719628(v=vs.85).aspx
Hoe vind ik records met een regelfeed (newline) in het factuuradres?
Door enkele van de interne functies van het Easysoft-stuurprogramma te gebruiken, kunt u gemakkelijk records vinden waarbij het factuuradres een regelinvoer in het record heeft. Bijvoorbeeld:
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)
Deze functie zoekt naar de positie van x
binnen de gespecificeerde kolom.
CHAR(X)
Deze functie retourneert het teken met de ASCII-waarde x
.
Meer informatie over de functies die beschikbaar zijn in ons Salesforce ODBC-stuurprogramma vindt u hier
Kan ik zien welke tabellen beschikbaar zijn via de Easysoft-software?
Voer het volgende uit om een lijst te krijgen met tabellen waartoe u toegang hebt:
select * from openquery(SF8,'select TABLE_NAME from INFO_SCHEMA.TABLES')
Kan ik zien welke kolommen beschikbaar zijn via de Easysoft-software?
U kunt een lijst met kolommen in de tabel krijgen door het volgende uit te voeren:
selecteer * uit openquery(SF8,'select * from INFO_SCHEMA.COLUMNS waar TABLE_NAME=''Account'' ')
Met deze methode kunt u alleen een lijst krijgen van de kolommen die behoren tot de tabel die u opgeeft in de TABLE_NAME WHERE-component. Als u een volledige lijst met kolommen voor alle tabellen wilt zien, voert u het volgende uit:
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 ik programmatisch een gekoppelde server maken?
Ja. Er zijn veel voorbeelden hiervan op internet, bijvoorbeeld:
http://www.sqlservercentral.com/articles/Linked+Servers/142270/?utm_source=SSC