Indholdsfortegnelse
- Oversigt
- WHERE-klausul
- Flere tabelforbindelser
- Lokalt bord knyttet til et eksternt bord
- Indsæt, opdater og slet
- Opdater
- Opdater med parametre
- Indsættelse af en ny post og få en BLOB-fejl
- Hent Salesforce-id'et for den sidste post, du indsatte
- Opdatering af SQL Server-data, når Salesforce-data ændres
- Dovne skemavalidering
- Begrænsninger for Microsofts OLEDB for ODBC-udbyder
- Hvordan finder jeg poster med et linjefeed (ny linje) i faktureringsadressen?
- Kan jeg se, hvilke tabeller der er tilgængelige via Easysoft-softwaren?
- Kan jeg se, hvilke kolonner der er tilgængelige via Easysoft-softwaren?
- Kan jeg oprette en linket server programmæssigt?
Oversigt
Dette dokument giver nogle tips om brug af SQL Server med Salesforce. Komponenterne, der bruges til at forbinde SQL Server til Salesforce, er en SQL Server Linked Server og Easysoft Salesforce ODBC Driver. Hvordan du forbinder SQL Server til Salesforce er beskrevet i denne artikel. For eksemplerne i dette dokument er det linkede servernavn (som du refererer til i dine SQL-kommandoer) SF8.
Al SQL i dette dokument blev testet mod SQL Server 2017 og Easysoft Salesforce ODBC-driverversionerne 2.0.0 til 2.0.7.
SQL Server-funktionerne OPENQUERY
og EXEC
(EXECUTE
) blev introduceret i SQL Server 2008, og disse funktioner er kompatible med alle versioner af SQL Server efter 2008.
Vi har skrevet dette dokument som svar på antallet af forespørgsler modtaget af vores supportteam vedrørende tilslutning af SQL Server via Easysoft til Salesforce. SQL-eksemplerne skulle dog også være nyttige for Linked Server-forbindelser, der bruger en anden ODBC-driver og backend.
Hvis du gerne vil bidrage til dette dokument, bedes du sende en e-mail til .
WHERE-klausul
Et almindeligt problem rapporteret til os er "En simpel WHERE-klausul tager lang tid at returnere kun én række". For eksempel:
select Id, FirstName, LastName from SF8.SF.DBO.Contact where Id='00346000002I95MAAS'
SQL Server konverterer ovenstående forespørgsel og sender denne til Salesforce ODBC-driveren:
select Id, FirstName, LastName from SF.DBO.Contact
WHERE-sætningen fjernes altid, hvilket tvinger ODBC-driveren til at returnere alle rækkerne for den tabel. Derefter filtrerer SQL Server dem lokalt for at give dig den eller de rækker, der kræves. Det ser ikke ud til at være lige meget, hvilken WHERE-klausul du har angivet, dette videregives aldrig til ODBC-driveren.
Den enkle løsning på dette er at bruge SQL Server OPENQUERY
funktion i stedet for. For eksempel:
select * from OPENQUERY(SF8,'select Id, FirstName, LastName from SF.DBO.Contact where Id=''00346000002I95MAAS'' ')
Al den SQL du kører inde i OPENQUERY
funktionen sendes direkte til driveren, inklusive WHERE
klausul.
Flere tabeltilslutninger
Her er en simpel joinforbindelse med to tabeller, hvor begge tabeller kommer tilbage fra den linkede 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 sender følgende forespørgsler til ODBC-driveren.
select * from Account select * from Contact
SQL Server gør dette for at få en liste over kolonnenavne og datatyper. Det fortsætter derefter med at sende disse forespørgsler til ODBC-driveren.
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
Dataene fra begge forespørgsler returneres til lokale tabeller, hvorefter WHERE-sætningen placeres på kontotabellen, og dataene fra begge tabeller sammenføjes og returneres.
Igen brugen af OPENQUERY
sikrer, at den SQL, du skriver, sendes direkte til ODBC-driveren, så du i stedet for i SQL Server ville køre:
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 har brug for en lille ændring, fordi SQL Server ikke kan håndtere flere kolonner med det samme "navn", så du skal omdøbe en af disse kolonner. For eksempel:
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%'' ')
Dette tvinger ODBC-driveren til at behandle hele SQL'en på én gang og kun returnere de nødvendige resultater.
Lokalt bord knyttet til et eksternt bord
I dette eksempel blev den lokale tabel oprettet ved at køre.
select * into LocalAccount from SF8.SF.DBO.Account
Sammenføjningen af de to tabeller ser nu ud.
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%'
Dette får SQL Server til at sende følgende forespørgsel tre gange til ODBC-driveren.
select * from Contact
I mindst én af disse forespørgsler beder SQL Server om alle dataene i tabellen. Så fortsætter SQL Server med at bede om:
SELECT "Tbl1003"."Name" "Col1008" FROM "SF"."DBO"."Contact" "Tbl1003" WHERE ?="Tbl1003"."AccountId"
SQL Server sender derefter en liste over AccountId'er fra LocalAccount-tabellen til ODBC-driveren i stedet for "?" parameter, hvor kolonnen LocalAccount.[Name] matcher LIKE-udtrykket.
En hurtigere måde, hvor ODBC-tabellen er den anden tabel i forespørgslen, er kun at hente de kolonner, du har brug for, fra ODBC-tabellen. Dette kan gøres ved at bruge OPENQUERY
fungere. For eksempel:
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%'
Selvom dette stadig henter alle rækkerne fra kontakttabellen, får det kun de nødvendige kolonner og er derfor hurtigere end standardforespørgslen.
En anden mulig måde ville være at bruge en markør og en midlertidig tabel. For eksempel:
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
Denne metode kan være flere gange hurtigere end OPENQUERY
metode vist i det foregående eksempel, hvis WHERE-sætningen, der overføres til Easysoft ODBC-driveren, bruger et indeks i Salesforce.
Indsæt, opdater og slet
Hvis du kører en forespørgsel, der ikke er en SELECT-forespørgsel, er den bedste måde at gøre dette på at bruge SQL Server EXEC
fungere. Hvis din sammenkædede server ikke kan bruge EXEC
, vil du få en besked, der ligner:
Server 'SF8' is not configured for RPC.
For at bruge EXEC
, højreklik på din linkede server og vælg egenskaber. I afsnittet "Serverindstillinger" skal du indstille "RPC Out" til "True". Du kan derefter bruge EXEC
funktion.
Opdatering
Lad os sige, at du har denne sætning i SQL Server:
UPDATE SF8.SF.DBO.Contact SET LastName='James' WHERE Id='00346000002I95MAAS'
SQL Server sender denne SQL til ODBC-driveren.
select * from "SF"."DBO"."Contact"
Alle posterne hentes, og SQL Server sender derefter denne erklæring til ODBC-driveren.
UPDATE "SF"."DBO"."Contact" SET "LastName"=? WHERE "Id"=? AND "LastName"=?
SQL Server gør det for at sikre, at posten ikke bliver ændret mellem det tidspunkt, hvor du kørte forespørgslen, og det tidspunkt, hvor OPDATERINGER udføres. En hurtigere metode er at bruge SQL Server EXEC
fungere. For eksempel:
exec ('update SF.DBO.Contact set LastName=''James'' where Id=''00346000002I95MAAS''' ) at SF8
SQL Server sender ODBC-driveren hele den streng, du har indtastet, så forespørgslen udføres uden at vælge hele tabellen.
Opdater med parametre
Sig, at 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
Dette fungerer på nøjagtig samme måde som beskrevet i opdateringsbemærkningerne. Syntaksen, når du bruger EXEC
funktionsændringer:
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
Hvor du har en kolonne såsom LastName=
du indsætter en ?
i stedet for @LastName
at repræsentere, hvad du vil overføre til parameteren. Parametrene vises derefter efter UPDATE-sætningen i den rækkefølge, de skal læses i.
Indsættelse af en ny post og få en BLOB-fejl
Lad os sige, at du prøver at køre:
insert into SF8.SF.DBO.Contact ( FirstName, LastName ) values ('Easysoft','Test')
SQL Server sender dette til ODBC-driveren:
select * from "SF"."DBO"."Contact"
Dette gøres to gange. Første gang dette køres, tjekker SQL Server for at se, om resultatsættet kan opdateres. Anden gang dette sendes, flytter SQL Server til en tom post efter den sidste post returnerede og forsøger at lave en positionel INSERT, hvilket giver en fejl.
OLE DB provider "MSDASQL" for linked server "SF8" returned message "Query-based insertion or updating of BLOB values is not supported.".
Denne meddelelse returneres, fordi en positionel indsættelse forsøger at indsætte alle kolonnerne med NULL-værdier undtagen dem, du har angivet i din INSERT-sætning, og i tilfælde af kontakttabellen er der en BLOB (Long Text Area i Salesforce ), som OLE DB-udbyderen fra Microsoft ikke understøtter. Easysoft Salesforce ODBC-driveren understøtter indsættelse af alle felter i Salesforce, hvor du har tilladelse til at indsætte data. For at komme uden om dette, skal du blot bruge EXEC.
exec ('insert into SF.DBO.Contact ( FirstName, LastName ) values (''Easysoft'',''Test'')') at SF8
Dette sender bare INSERT direkte til ODBC-driveren.
Få Salesforce-id'et for den sidste post, du indsatte
Vi er blevet spurgt af et par af vores kunder, hvad der er den nemmeste metode til at få id'et for den række, der lige blev indsat. Dette eksempel viser, hvordan du kan få id'et for den sidste post, du indsatte i "Kontakt"-tabellen.
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 oprettes i Salesforce, indeholder kolonnen "CreatedDate" et tidsstempel, der er den UTC (Coordinated Universal Time), som posten blev oprettet og ikke nødvendigvis din nuværende dato/tid. @FindTs
strengen er sat til UTC før INSERT finder sted, så når SELECT for at få Id kaldes, ser den kun på rækkerne indsat efter @FindTS
blev indstillet.
Under SELECT, Easysoft CURRENT_USER
Funktionen bruges også til at begrænse de rækker, der returneres fra Salesforce, til kun den bruger, der har indsat dataene.
Opdatering af SQL Server-data, når Salesforce-data ændres
Dette afsnit viser dig, hvordan du opretter en ny SQL Server-tabel baseret på strukturen af en Salesforce-tabel og opdaterer den tabel, når der er ændringer i den pågældende 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
Kør proceduren for at kopiere poststrukturen fra Salesforce-tabellen til den lokale tabel, og overfør derefter alle Salesforce-dataene. Denne eksempelkommando bruger kontotabellen. Denne proces kan tage et stykke tid afhængigt af mængden af data, du har i Salesforce-tabellen.
SFMakeLocal 'SF8','Account','LocalAccount', 0
Argumenterne er:
Argument | Værdi |
---|---|
SF8 | SQL Server Linked Server-navnet. |
Konto | Det Salesforce-tabelnavn, du ønsker at bruge til at læse strukturen og dataene fra. |
Lokal konto | Navnet på din tabel i SQL Server. |
0 | Denne standardværdi kan ændres til 1, hvis du tilføjer flere tilpassede kolonner i Salesforce, og du ønsker at droppe den lokale tabel for at oprette den igen med de nye kolonner. |
Det næste trin er at oprette yderligere to procedurer, der opdaterer den lokale tabel, hvis nogen data opdateres eller indsættes 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.
For at teste dette, kør:
SFUpdateTable 'SF8','Account','LocalAccount'
Dette eksempel kan bruges med enhver Salesforce-tabel, som en bruger har adgang til.
Dovne skemavalidering
I dine SQL Server-linkede serveregenskaber, under "Serverindstillinger" sektionen, er en mulighed for "Lazy Schema Validation". Som standard er dette sat til FALSE, hvilket får SQL Server til at sende SELECT-sætninger to gange. Første gang forespørgslen sendes, bruger SQL Server de detaljer, der sendes tilbage til at opbygge metadata om dit resultatsæt. Så sendes forespørgslen igen. Dette er ret dyrt overhead, så Easysoft vil anbefale, at du indstiller "Lazy Schema Validation" til TRUE, hvilket betyder, at der kun sendes én forespørgsel, der henter både metadata og resultatsæt på én gang. Dette sparer også på antallet af Salesforce API-kald, der foretages.
Begrænsninger for Microsofts OLEDB for ODBC-udbyder
Detaljer om begrænsningerne af OLEDB for ODBC-udbyderen kan findes på:
https://msdn.microsoft.com/en-us/library/ms719628(v=vs.85).aspx
Hvordan finder jeg poster med et linjefeed (ny linje) i faktureringsadressen?
Ved at bruge nogle af Easysoft-chaufførens interne funktioner, kan du nemt finde poster, hvor faktureringsadressen har et linjeskift i posten. For eksempel:
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)
Denne funktion leder efter positionen for x
i den angivne kolonne.
CHAR(X)
Denne funktion returnerer tegnet med ASCII-værdien x
.
Flere oplysninger om de tilgængelige funktioner i vores Salesforce ODBC-driver kan findes her
Kan jeg se, hvilke tabeller der er tilgængelige via Easysoft-softwaren?
For at få en liste over tabeller, som du kan få adgang til, skal du køre:
select * from openquery(SF8,'select TABLE_NAME from INFO_SCHEMA.TABLES')
Kan jeg se, hvilke kolonner der er tilgængelige via Easysoft-softwaren?
Du kan få en liste over kolonner, der er i tabellen, ved at køre:
vælg * fra openquery(SF8,'vælg * fra INFO_SCHEMA.COLUMNS hvor TABLE_NAME=''Konto'' ')
Ved at bruge denne metode kan du kun få en liste over de kolonner, der hører til den tabel, du angiver i TABLE_NAME WHERE-sætningen. Hvis du vil se en komplet liste over kolonner for alle tabeller, skal du køre:
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 jeg programmæssigt oprette en linket server?
Ja. Der er masser af eksempler på dette på nettet, for eksempel:
http://www.sqlservercentral.com/articles/Linked+Servers/142270/?utm_source=SSC