sql >> Base de Datos >  >> RDS >> Sqlserver

Sugerencias para usar SQL Server con Salesforce

Índice

  1. Resumen
  2. Cláusula WHERE
  3. Uniones de varias tablas
  4. Tabla local adjunta a una tabla remota
  5. Insertar, Actualizar y Eliminar
  6. Actualizar
  7. Actualizar con parámetros
  8. Insertar un nuevo registro y obtener un error BLOB
  9. Obtención del ID de Salesforce para el último registro que insertó
  10. Actualización de datos de SQL Server cuando cambian los datos de Salesforce
  11. Validación de esquema perezoso
  12. Limitaciones de OLEDB de Microsoft para el proveedor ODBC
  13. ¿Cómo encuentro registros con un avance de línea (nueva línea) en la dirección de facturación?
  14. ¿Puedo ver qué mesas están disponibles a través del software Easysoft?
  15. ¿Puedo ver qué columnas están disponibles a través del software Easysoft?
  16. ¿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