Índice
- Resumen
- Cláusula WHERE
- Uniones de varias tablas
- Tabla local adjunta a una tabla remota
- Insertar, Actualizar y Eliminar
- Actualizar
- Actualizar con parámetros
- Insertar un nuevo registro y obtener un error BLOB
- Obtención del ID de Salesforce para el último registro que insertó
- Actualización de datos de SQL Server cuando cambian los datos de Salesforce
- Validación de esquema perezoso
- Limitaciones de OLEDB de Microsoft para el proveedor ODBC
- ¿Cómo encuentro registros con un avance de línea (nueva línea) en la dirección de facturación?
- ¿Puedo ver qué mesas están disponibles a través del software Easysoft?
- ¿Puedo ver qué columnas están disponibles a través del software Easysoft?
- ¿Puedo crear mediante programación un servidor vinculado?
Resumen
Este documento brinda algunos consejos sobre el uso de SQL Server con Salesforce. Los componentes utilizados para conectar SQL Server a Salesforce son un servidor vinculado de SQL Server y el controlador ODBC de Easysoft Salesforce. En este artículo se describe cómo conectar SQL Server a Salesforce. Para los ejemplos de este documento, el nombre del servidor vinculado (al que hace referencia en sus comandos SQL) utilizado es SF8.
Todo el SQL de este documento se probó con SQL Server 2017 y las versiones 2.0.0 a 2.0.7 del controlador ODBC de Easysoft Salesforce.
Las funciones de SQL Server OPENQUERY
y EXEC
(EXECUTE
) se introdujeron en SQL Server 2008 y estas funciones son compatibles con todas las versiones de SQL Server posteriores a 2008.
Hemos escrito este documento en respuesta a una serie de consultas recibidas por nuestro equipo de soporte con respecto a la conexión de SQL Server a través de Easysoft a Salesforce. Sin embargo, los ejemplos de SQL también deberían ser útiles para las conexiones de servidores vinculados que usan un controlador ODBC y un back-end diferentes.
Si desea contribuir con este documento, envíe su envío por correo electrónico a .
Cláusula WHERE
Un problema común que se nos informa es "Una cláusula WHERE simple tarda mucho tiempo en devolver solo una fila". Por ejemplo:
select Id, FirstName, LastName from SF8.SF.DBO.Contact where Id='00346000002I95MAAS'
SQL Server convierte la consulta anterior y la envía al controlador ODBC de Salesforce:
select Id, FirstName, LastName from SF.DBO.Contact
La cláusula WHERE siempre se elimina, lo que obliga al controlador ODBC a devolver todas las filas de esa tabla. Luego, SQL Server los filtra localmente para brindarle las filas requeridas. No parece importar qué cláusula WHERE haya especificado, esto nunca se pasa al controlador ODBC.
La solución simple a esto es usar SQL Server OPENQUERY
función en su lugar. Por ejemplo:
select * from OPENQUERY(SF8,'select Id, FirstName, LastName from SF.DBO.Contact where Id=''00346000002I95MAAS'' ')
Todo el SQL que ejecuta dentro de OPENQUERY
La función se pasa directamente al controlador, incluido el WHERE
cláusula.
Uniones de varias tablas
Aquí hay una unión simple de dos tablas donde ambas tablas regresan del servidor vinculado.
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 envía las siguientes consultas al controlador ODBC.
select * from Account select * from Contact
SQL Server hace esto para obtener una lista de nombres de columnas y tipos de datos. Luego pasa a enviar estas consultas al controlador 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
Los datos de ambas consultas se devuelven a las tablas locales, luego la cláusula WHERE se coloca en la tabla Cuenta y los datos de ambas tablas se unen y se devuelven.
De nuevo el uso de OPENQUERY
garantiza que el SQL que escriba se pase directamente al controlador ODBC, por lo que, en su lugar, en SQL Server ejecutaría:
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%'' ')
Necesita una ligera modificación, porque SQL Server no puede manejar varias columnas con el mismo "Nombre", por lo que debe cambiar el nombre de una de esas columnas. Por ejemplo:
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%'' ')
Esto obliga al controlador ODBC a procesar todo el SQL de una sola vez y solo devolver los resultados requeridos.
Tabla local adjunta a una tabla remota
En este ejemplo, la tabla local se creó ejecutando.
select * into LocalAccount from SF8.SF.DBO.Account
Ahora se ve la unión de las dos tablas.
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%'
Esto hace que SQL Server envíe la siguiente consulta tres veces al controlador ODBC.
select * from Contact
En al menos una de esas consultas, SQL Server solicita todos los datos de la tabla. Luego, SQL Server continúa solicitando:
SELECT "Tbl1003"."Name" "Col1008" FROM "SF"."DBO"."Contact" "Tbl1003" WHERE ?="Tbl1003"."AccountId"
Luego, SQL Server pasa al controlador ODBC una lista de AccountIds de la tabla LocalAccount en lugar del "?" parámetro donde la columna LocalAccount.[Name] coincide con la cláusula LIKE.
Una forma más rápida en la que la tabla ODBC es la segunda tabla de la consulta es obtener solo las columnas que necesita de la tabla ODBC. Esto se puede hacer usando OPENQUERY
función. Por ejemplo:
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%'
Si bien esto todavía obtiene todas las filas de la tabla de contactos, solo obtiene las columnas necesarias y, por lo tanto, es más rápido que la consulta estándar.
Otra forma posible sería usar un cursor y una tabla temporal. Por ejemplo:
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
Este método puede ser varias veces más rápido que el OPENQUERY
método que se muestra en el ejemplo anterior, si la cláusula WHERE que se pasa al controlador ODBC de Easysoft utiliza un índice en Salesforce.
Insertar, Actualizar y Eliminar
Si está ejecutando una consulta que no es una consulta SELECT, la mejor manera de hacerlo es usar el EXEC
de SQL Server. función. Si su servidor vinculado no puede usar EXEC
, recibirá un mensaje similar a:
Server 'SF8' is not configured for RPC.
Para usar EXEC
, haga clic derecho en su servidor vinculado y elija propiedades. En la sección "Opciones del servidor", establezca "RPC Out" en "True". A continuación, puede utilizar el EXEC
función.
Actualizar
Digamos que tiene esta declaración en SQL Server:
UPDATE SF8.SF.DBO.Contact SET LastName='James' WHERE Id='00346000002I95MAAS'
SQL Server envía este SQL al controlador ODBC.
select * from "SF"."DBO"."Contact"
Todos los registros se recuperan y SQL Server luego envía esta declaración al controlador ODBC.
UPDATE "SF"."DBO"."Contact" SET "LastName"=? WHERE "Id"=? AND "LastName"=?
SQL Server lo está haciendo para garantizar que el registro no se modifique entre el momento en que ejecutó la consulta y el momento en que se ejecuta la ACTUALIZACIÓN. Un método más rápido es usar SQL Server EXEC
función. Por ejemplo:
exec ('update SF.DBO.Contact set LastName=''James'' where Id=''00346000002I95MAAS''' ) at SF8
SQL Server envía al controlador ODBC la cadena completa que ingresó, por lo que la consulta se ejecuta sin seleccionar toda la tabla.
Actualizar con parámetros
Di que tienes:
Begin declare @Id varchar(20)='00346000002I95MAAS' declare @LastName varchar(20)='James' update SF8.SF.DBO.Contact set LastName=@LastName where Id=@Id End
Esto funciona exactamente de la misma manera que se describe en las notas de actualización. Sin embargo, la sintaxis cuando se usa EXEC
cambios de función:
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
Donde tiene una columna como LastName=
pones un ?
en lugar de @LastName
para representar lo que va a pasar al parámetro. Luego, los parámetros se enumeran después de la instrucción UPDATE en el orden en que deben leerse.
Insertar un nuevo registro y obtener un error BLOB
Digamos que está intentando ejecutar:
insert into SF8.SF.DBO.Contact ( FirstName, LastName ) values ('Easysoft','Test')
SQL Server envía esto al controlador ODBC:
select * from "SF"."DBO"."Contact"
Esto se hace dos veces. La primera vez que se ejecuta, SQL Server comprueba si el conjunto de resultados se puede actualizar. La segunda vez que se envía esto, SQL Server se mueve a un registro vacío después del último registro devuelto e intenta hacer un INSERT posicional, lo que genera un error.
OLE DB provider "MSDASQL" for linked server "SF8" returned message "Query-based insertion or updating of BLOB values is not supported.".
Este mensaje se devuelve porque una inserción posicional intenta insertar todas las columnas con valores NULL excepto las que haya especificado en su instrucción INSERT, y en el caso de la tabla de contactos, hay un BLOB (Área de texto largo en Salesforce), que el proveedor OLE DB de Microsoft no admite. El controlador ODBC de Easysoft Salesforce admite la inserción de todos los campos dentro de Salesforce donde tiene permiso para insertar datos. Para evitar esto, todo lo que necesita hacer es usar EXEC.
exec ('insert into SF.DBO.Contact ( FirstName, LastName ) values (''Easysoft'',''Test'')') at SF8
Esto simplemente envía el INSERT directamente al controlador ODBC.
Obtención del ID de Salesforce para el último registro que insertó
Algunos de nuestros clientes nos han preguntado cuál es el método más fácil para obtener el Id. de la fila que se acaba de insertar. Este ejemplo muestra cómo puede obtener la identificación del último registro que insertó en la tabla "Contacto".
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
Cuando se crea un registro en Salesforce, la columna "CreatedDate" contiene una marca de tiempo que es la UTC (hora universal coordinada) en que se creó el registro y no necesariamente su fecha/hora actual. El @FindTs
la cadena se establece en el UTC antes de que se lleve a cabo INSERT, por lo que cuando se llama a SELECT para obtener el Id., solo está mirando las filas insertadas después de @FindTS
fue establecido.
Durante la SELECCIÓN, Easysoft CURRENT_USER
La función también se usa para limitar las filas devueltas por Salesforce solo al usuario que ha insertado los datos.
Actualización de datos de SQL Server cuando cambian los datos de Salesforce
Esta sección le muestra cómo crear una nueva tabla de SQL Server basada en la estructura de una tabla de Salesforce y actualizar esa tabla cuando haya cambios en esa tabla de 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
Ejecute el procedimiento para copiar la estructura de registros de la tabla de Salesforce a la tabla local y luego transfiera todos los datos de Salesforce. Este comando de ejemplo utiliza la tabla Cuenta. Este proceso puede llevar bastante tiempo dependiendo de la cantidad de datos que tenga en la tabla de Salesforce.
SFMakeLocal 'SF8','Account','LocalAccount', 0
Los argumentos son:
Argumento | Valor |
---|---|
SF8 | El nombre del servidor vinculado de SQL Server. |
Cuenta | El nombre de la tabla de Salesforce que desea usar para leer la estructura y los datos. |
Cuenta local | El nombre de su tabla en SQL Server. |
0 | Este valor predeterminado se puede cambiar a 1 si agrega más columnas personalizadas en Salesforce y desea eliminar la tabla local para crearla nuevamente con las nuevas columnas. |
El siguiente paso es crear dos procedimientos más que actualizarán la tabla local si se actualiza o inserta algún dato en la tabla de Salesforce:
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.
Para probar esto, ejecuta:
SFUpdateTable 'SF8','Account','LocalAccount'
Este ejemplo se puede utilizar con cualquier tabla de Salesforce a la que tenga acceso un usuario.
Validación de esquema perezoso
En las propiedades del servidor vinculado de SQL Server, en la sección "Opciones del servidor", hay una opción para "Validación de esquema diferido". De forma predeterminada, se establece en FALSO, lo que hace que SQL Server envíe declaraciones SELECT dos veces. La primera vez que se envía la consulta, SQL Server usa los detalles transmitidos para generar metadatos sobre su conjunto de resultados. A continuación, la consulta se envía de nuevo. Esta es una sobrecarga bastante costosa, por lo que Easysoft recomendaría que configure "Lazy Schema Validation" en TRUE, lo que significa que solo se envía una consulta, recuperando tanto los metadatos como el conjunto de resultados de una sola vez. Esto también ahorra en la cantidad de llamadas a la API de Salesforce que se realizan.
Limitaciones de OLEDB de Microsoft para el proveedor ODBC
Los detalles sobre las limitaciones de OLEDB para el proveedor ODBC se pueden encontrar en:
https://msdn.microsoft.com/en-us/library/ms719628(v=vs.85).aspx
¿Cómo encuentro registros con un avance de línea (nueva línea) en la dirección de facturación?
Mediante el uso de algunas de las funciones internas del controlador de Easysoft, puede encontrar fácilmente registros en los que la dirección de facturación tenga un avance de línea dentro del registro. Por ejemplo:
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)
Esta función busca la posición de x
dentro de la columna especificada.
CHAR(X)
Esta función devuelve el carácter con el valor ASCII de x
.
Puede encontrar más información sobre las funciones disponibles en nuestro controlador ODBC de Salesforce aquí
¿Puedo ver qué mesas están disponibles a través del software Easysoft?
Para obtener una lista de tablas a las que puede acceder, ejecute:
select * from openquery(SF8,'select TABLE_NAME from INFO_SCHEMA.TABLES')
¿Puedo ver qué columnas están disponibles a través del software Easysoft?
Puede obtener una lista de columnas que están en la tabla ejecutando:
select * from openquery(SF8,'select * from INFO_SCHEMA.COLUMNS where TABLE_NAME=''Cuenta'' ')
Con este método solo puede obtener una lista de las columnas que pertenecen a la tabla que especifica en la cláusula TABLE_NAME WHERE. Si desea ver una lista completa de columnas para todas las tablas, ejecute:
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
¿Puedo crear programáticamente un servidor vinculado?
Sí. Hay muchos ejemplos de esto en la web, por ejemplo:
http://www.sqlservercentral.com/articles/Linked+Servers/142270/?utm_source=SSC