目次
- 概要
- WHERE条項
- 複数のテーブル結合
- リモートテーブルに接続されたローカルテーブル
- 挿入、更新、削除
- 更新
- パラメータで更新
- 新しいレコードの挿入とBLOBエラーの取得
- 最後に挿入したレコードのSalesforceIDを取得する
- Salesforceデータの変更時にSQLServerデータを更新する
- レイジースキーマ検証
- MicrosoftのOLEDBforODBCプロバイダーの制限
- 請求先住所に改行(改行)が含まれるレコードを見つけるにはどうすればよいですか?
- Easysoftソフトウェアで利用できるテーブルを確認できますか?
- Easysoftソフトウェアで利用できる列を確認できますか?
- プログラムでリンクサーバーを作成できますか?
このドキュメントでは、SalesforceでSQLServerを使用するためのヒントをいくつか紹介します。 SQL ServerをSalesforceに接続するために使用されるコンポーネントは、SQLServerリンクサーバーとEasysoftSalesforceODBCドライバーです。この記事では、SQLServerをSalesforceに接続する方法について説明します。このドキュメントの例では、使用されているリンクサーバー名(SQLコマンドで参照)はSF8です。
このドキュメントのすべてのSQLは、SQLServer2017およびEasysoftSalesforceODBCドライバーバージョン2.0.0から2.0.7に対してテストされています。
SQLServerの機能OPENQUERY
およびEXEC
(EXECUTE
)はSQL Server 2008に導入され、これらの関数は2008以降のすべてのバージョンのSQLServerと互換性があります。
このドキュメントは、Easysoftを介したSQLServerのSalesforceへの接続に関してサポートチームが受け取った多数の問い合わせに応えて作成されました。ただし、SQLの例は、別のODBCドライバーとバックエンドを使用するリンクサーバー接続にも役立つはずです。
このドキュメントに貢献したい場合は、提出物をに電子メールで送信してください。
私たちに報告される一般的な問題は、「単純なWHERE句が1行だけを返すのに長い時間がかかる」です。例:
select Id, FirstName, LastName from SF8.SF.DBO.Contact where Id='00346000002I95MAAS'
SQL Serverは上記のクエリを変換し、これをSalesforceODBCドライバーに送信します。
select Id, FirstName, LastName from SF.DBO.Contact
WHERE句は常に削除されます。これにより、ODBCドライバーはそのテーブルのすべての行を返すように強制されます。次に、SQL Serverはそれらをローカルでフィルタリングして、必要な行を提供します。指定したWHERE句は重要ではないようです。これは、ODBCドライバーに渡されることはありません。
これに対する簡単な解決策は、SQLServerのOPENQUERY
を使用することです。 代わりに機能します。例:
select * from OPENQUERY(SF8,'select Id, FirstName, LastName from SF.DBO.Contact where Id=''00346000002I95MAAS'' ')
OPENQUERY
内で実行するすべてのSQL WHERE
を含む関数はドライバーに直接渡されます 条項。
これは、両方のテーブルがリンクサーバーから戻ってくる単純な2つのテーブル結合です。
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は、次のクエリをODBCドライバーに送信します。
select * from Account select * from Contact
SQL Serverはこれを実行して、列名とデータ型のリストを取得します。次に、これらのクエリを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
両方のクエリからのデータがローカルテーブルに返され、WHERE句がAccountテーブルに配置され、両方のテーブルからのデータが結合されて返されます。
ここでもOPENQUERY
を使用します 作成したSQLがODBCドライバーに直接渡されるようにするため、代わりにSQLServerで次のコマンドを実行します。
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%'' ')
SQL Serverは同じ「名前」を持つ複数の列を処理できないため、わずかな変更が必要です。そのため、これらの列の1つを名前変更する必要があります。例:
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%'' ')
これにより、ODBCドライバーはSQL全体を一度に処理し、必要な結果のみを返します。
この例では、ローカルテーブルはを実行して作成されました。
select * into LocalAccount from SF8.SF.DBO.Account
2つのテーブルの結合は次のようになります。
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%'
これにより、SQLServerは次のクエリをODBCドライバーに3回送信します。
select * from Contact
これらのクエリの少なくとも1つで、SQLServerはテーブル内のすべてのデータを要求します。次に、SQLServerは次のことを要求します。
SELECT "Tbl1003"."Name" "Col1008" FROM "SF"."DBO"."Contact" "Tbl1003" WHERE ?="Tbl1003"."AccountId"
次に、SQL Serverは、「?」の代わりにLocalAccountテーブルのAccountIdのリストをODBCドライバーに渡します。 LocalAccount。[Name]列がLIKE句と一致するパラメータ。
ODBCテーブルがクエリの2番目のテーブルである場合のより高速な方法は、ODBCテーブルから必要な列のみを取得することです。これは、OPENQUERY
を使用して実行できます 働き。例:
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%'
これでもContactテーブルからすべての行を取得しますが、必要な列のみを取得するため、標準のクエリよりも高速です。
別の可能な方法は、カーソルと一時テーブルを使用することです。例:
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
このメソッドは、OPENQUERY
よりも数倍高速になる可能性があります 前の例で示したメソッド。EasysoftODBCドライバーに渡されるWHERE句がSalesforceのインデックスを使用する場合。
挿入、更新、削除
SELECTクエリではないクエリを実行している場合、これを行うための最良の方法は、SQL Server EXEC
を使用することです。 働き。リンクサーバーがEXEC
を使用できない場合 、次のようなメッセージが表示されます:
Server 'SF8' is not configured for RPC.
EXEC
を使用するには 、リンクサーバーを右クリックして、プロパティを選択します。 [サーバーオプション]セクションで、[RPC出力]を[True]に設定します。その後、EXEC
を使用できます 機能。
SQLServerに次のステートメントがあるとします。
UPDATE SF8.SF.DBO.Contact SET LastName='James' WHERE Id='00346000002I95MAAS'
SQLServerはこのSQLをODBCドライバーに送信します。
select * from "SF"."DBO"."Contact"
すべてのレコードが取得され、SQLServerはこのステートメントをODBCドライバーに送信します。
UPDATE "SF"."DBO"."Contact" SET "LastName"=? WHERE "Id"=? AND "LastName"=?
SQL Serverは、クエリを実行してからUPDATEを実行するまでの間にレコードが変更されないようにするためにこれを実行しています。より高速な方法は、SQLServerのEXEC
を使用することです。 働き。例:
exec ('update SF.DBO.Contact set LastName=''James'' where Id=''00346000002I95MAAS''' ) at SF8
SQL Serverは入力した文字列全体をODBCドライバーに送信するため、テーブル全体を選択せずにクエリが実行されます。
あなたが持っていると言う:
Begin declare @Id varchar(20)='00346000002I95MAAS' declare @LastName varchar(20)='James' update SF8.SF.DBO.Contact set LastName=@LastName where Id=@Id End
これは、アップデートノートで説明されているのとまったく同じように機能します。ただし、EXEC
を使用する場合の構文 機能の変更:
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
LastName=
などの列がある場合 ?
を入れます @LastName
の代わりに パラメータに渡すものを表します。次に、パラメーターは、UPDATEステートメントの後に、読み取る必要のある順序でリストされます。
実行しようとしているとしましょう:
insert into SF8.SF.DBO.Contact ( FirstName, LastName ) values ('Easysoft','Test')
SQL ServerはこれをODBCドライバーに送信します:
select * from "SF"."DBO"."Contact"
これは2回行われます。これを初めて実行するとき、SQLServerは結果セットが更新可能かどうかを確認しています。これが2回送信されると、SQL Serverは最後のレコードが返された後、空のレコードに移動し、位置INSERTを実行しようとします。これにより、エラーが発生します。
OLE DB provider "MSDASQL" for linked server "SF8" returned message "Query-based insertion or updating of BLOB values is not supported.".
このメッセージが返されるのは、位置挿入がINSERTステートメントで指定した列を除くすべての列をNULL値で挿入しようとし、Contactテーブルの場合はBLOB(Salesforceの長いテキスト領域)があるためです。 MicrosoftのOLEDBプロバイダーはサポートしていません。 Easysoft Salesforce ODBCドライバーは、データを挿入する権限があるSalesforce内のすべてのフィールドの挿入をサポートします。これを回避するには、EXECを使用するだけです。
exec ('insert into SF.DBO.Contact ( FirstName, LastName ) values (''Easysoft'',''Test'')') at SF8
これにより、INSERTがODBCドライバーに直接送信されます。
数人のお客様から、挿入されたばかりの行のIDを取得する最も簡単な方法を尋ねられました。この例は、「連絡先」テーブルに挿入した最後のレコードのIDを取得する方法を示しています。
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
Salesforceでレコードが作成されると、[CreatedDate]列には、レコードが作成されたUTC(協定世界時)であるタイムスタンプが含まれますが、必ずしも現在の日付/時刻である必要はありません。 @FindTs
文字列はINSERTが実行される前にUTCに設定されるため、IDを取得するためのSELECTが呼び出されると、@FindTS
の後に挿入された行のみが参照されます。 設定されました。
SELECT中に、Easysoft CURRENT_USER
この関数は、Salesforceから返される行をデータを挿入したユーザーのみに制限するためにも使用されます。
このセクションでは、Salesforceテーブルの構造に基づいて新しいSQL Serverテーブルを作成し、その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
手順を実行して、Salesforceテーブルからローカルテーブルにレコード構造をコピーしてから、すべてのSalesforceデータを転送します。このコマンド例では、Accountテーブルを使用しています。 Salesforceテーブルにあるデータの量によっては、このプロセスにかなりの時間がかかる場合があります。
SFMakeLocal 'SF8','Account','LocalAccount', 0
引数は次のとおりです。
引数 | 値 |
---|---|
SF8 | SQLServerリンクサーバー名。 |
アカウント | 構造とデータの読み取りに使用するSalesforceテーブル名。 |
LocalAccount | SQLServerのテーブルの名前。 |
0 | Salesforceにカスタム列を追加し、ローカルテーブルを削除して新しい列で再度作成する場合は、このデフォルト値を1に変更できます。 |
次のステップは、データが更新された場合、またはSalesforceテーブルに挿入された場合に、ローカルテーブルを更新する2つのプロシージャを作成することです。
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.
これをテストするには、次を実行します:
SFUpdateTable 'SF8','Account','LocalAccount'
この例は、ユーザーがアクセスできるすべてのSalesforceテーブルで使用できます。
SQL Serverにリンクされたサーバーのプロパティの[サーバーオプション]セクションには、[遅延スキーマ検証]のオプションがあります。デフォルトでは、これはFALSEに設定されています。これにより、SQLServerはSELECTステートメントを2回送信します。クエリが初めて送信されるとき、SQL Serverは、返された詳細を使用して、結果セットに関するメタデータを構築します。その後、クエリが再度送信されます。これは非常にコストのかかるオーバーヘッドであるため、Easysoftは「LazySchema Validation」をTRUEに設定することをお勧めします。これは、1つのクエリのみが送信され、メタデータと結果セットの両方を一度に取得することを意味します。これにより、SalesforceAPI呼び出しの数も節約できます。
OLEDB for ODBCプロバイダーの制限の詳細については、次のURLを参照してください。
https://msdn.microsoft.com/en-us/library/ms719628(v=vs.85).aspx
請求先住所に改行(改行)が含まれるレコードを見つけるにはどうすればよいですか?
Easysoftドライバーの内部機能のいくつかを使用することにより、請求先住所にレコード内のラインフィードがあるレコードを簡単に見つけることができます。例:
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)
この関数は、x
の位置を検索します 指定された列内。
CHAR(X)
この関数は、ASCII値がx
の文字を返します。 。
Salesforce ODBCドライバーで使用可能な関数の詳細については、こちらをご覧ください
Easysoftソフトウェアで利用できるテーブルを確認できますか?
アクセスできるテーブルのリストを取得するには、次のコマンドを実行します。
select * from openquery(SF8,'select TABLE_NAME from INFO_SCHEMA.TABLES')
Easysoftソフトウェアで利用できる列を確認できますか?
次のコマンドを実行すると、テーブルにある列のリストを取得できます。
select * from openquery(SF8、'select * from INFO_SCHEMA.COLUMNS where TABLE_NAME =''Account''')
このメソッドを使用すると、TABLE_NAMEWHERE句で指定したテーブルに属する列のリストのみを取得できます。すべてのテーブルの列の完全なリストを表示するには、次のコマンドを実行します。
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
はい。この例はウェブ上にたくさんあります。例:
http://www.sqlservercentral.com/articles/Linked+Servers/142270/?utm_source=SSC