27 de septiembre de 2007

Inconsistencia de SELECT - SQL

Encontré la siguiente inconsistencia de SELECT-SQL que quiero compartir aquí. Prueben el siguiente código:

*-- primer ejemplo --
CREATE CURSOR prueba (texto c(15), numero I)
INSERT INTO prueba VALUES ("algo",9999)
SELECT SUM(numero) FROM prueba ;
  WHERE texto = "noexiste" ;
  INTO CURSOR primero
? sum_numero
BROWSE
*-----------------------------

Verán que el SELECT produce un resultado de un registro con un valor nulo en la columna sum_numero.

Sin embargo, si el SELECT se expresa de la siguiente forma:

*-- segundo ejemplo (con GROUP BY) --
SELECT texto, SUM(numero) FROM prueba ;
  WHERE texto = "noexiste" ;
  GROUP BY 1 ;
  INTO CURSOR segundo
? sum_numero
BROWSE
*-----------------------------

En este segundo ejemplo la consulta produce un resultado con cero registros y "? sum_numero" devuelve Cero (0) en vez de .NULL. como el primer ejemplo. Nótese entonces, que ambas consultas, siendo conceptualmente iguales, devuelven resultados diferentes.

Sin embargo, en VFP8, ambas formas producen el mismo resultado: sum_numero contiene cero, como en el segundo ejemplo.

La primera vez que noté que en VFP9, los casos como el primer ejemplo, me devolvían valores nulos si ningún registro satisface la condición WHERE, pensé que tenía sentido ese comportamiento. Es decir, traté de encontrarle un sentido que lo justifique. Me dije: “que la suma de todos los valores existentes sea igual a cero no es lo mismo a que no haya valores que sumar, en cuyo caso, devolver nulo aporta una información válida”. Lo acepté sin pensar mucho. Sin embargo, luego noté que no en todos los casos la ausencia de valores a sumar para satisfacer una condición WHERE devuelve un valor nulo. Entonces, esto ya debe ser clasificado como una inconsistencia, creo yo.

Además, y eso ya es un comentario aparte, en verdad que el valor nulo devuelto por el primer ejemplo no me sirve en ninguno de los casos prácticos en los que yo los utilizo. Así es que tuve que tomar el cuidado de utilizar SELECT siempre de la forma del segundo ejemplo.

Expongo este caso para que lo tomen en cuenta si todavía no lo han notado.

Saludos

Mario Esquivel Bado

25 de septiembre de 2007

Exportar a OpenOffice.org Calc

Rutina de Hector Urrutia para exportar un cursor a OpenOffice Calc.
*-------------------------------------------------------------*
*!*- FUNCTION ExporToCalc([cCursor], [cDestino], [cFileSave])
*!*- cCursor:  Alias del cursor que se va a exportar.
*!*- cDestino:  Nombre de la carpeta donde se va a grabar.
*!*- cFileName:  Nombre del archivo con el que se va a grabar.
*-------------------------------------------------------------*
FUNCTION ExporToCalc(cCursor, cDestino, cFileSave)
  LOCAL oManager, oDesktop, oDoc, oSheet, oCell, oRow, FileURL
  LOCAL ARRAY laPropertyValue[1]

  cWarning = "Exportar a OpenOffice.org Calc"

  IF EMPTY(cCursor)
    cCursor = ALIAS()
  ENDIF

  IF TYPE('cCursor') # 'C' OR !USED(cCursor)
    MESSAGEBOX("Parametros Invalidos",16,cWarning)
    RETURN .F.
  ENDIF

  lColNum = AFIELDS(lColName,cCursor)

  EXPORT TO (cDestino + cFileSave + [.ods]) TYPE XL5

  oManager = CREATEOBJECT("com.sun.star.ServiceManager.1")

  IF VARTYPE(oManager, .T.) # "O"
    MESSAGEBOX("OpenOffice.org Calc no esta instalado en su computador.",64,cWarning)
    RETURN .F.
  ENDIF

  oDesktop = oManager.createInstance("com.sun.star.frame.Desktop")

  COMARRAY(oDesktop, 10)

  oReflection = oManager.createInstance("com.sun.star.reflection.CoreReflection")

  COMARRAY(oReflection, 10)

  laPropertyValue[1] = createStruct(@oReflection, "com.sun.star.beans.PropertyValue")
  laPropertyValue[1].NAME = "ReadOnly"
  laPropertyValue[1].VALUE= .F.

  FileURL = ConvertToURL(cDestino + cFileSave + [.ods])

  oDoc = oDesktop.loadComponentFromURL(FileURL , "_blank", 0, @laPropertyValue)

  oSheet = oDoc.getSheets.getByIndex(0)

  FOR i = 1 TO lColNum
    oColumn = oSheet.getColumns.getByIndex(i)
    oColumn.setPropertyValue("OptimalWidth", .T.)

    oCell = oSheet.getCellByPosition( i-1, 0 )
    oDoc.CurrentController.SELECT(oCell)

    WITH oDoc.CurrentSelection
      .CellBackColor = RGB(200,200,200)
      .Cell
      .CharColor = RGB(255,0,0)
      .CharHeight = 10
      .CharPosture = 0
      .CharShadowed = .F.
      .FormulaLocal = lColName[i,1]
      .HoriJustify = 2
      .ParaAdjust = 3
      .ParaLastLineAdjust = 3
    ENDWITH
  ENDFOR

  oCell = oSheet.getCellByPosition( 0, 0 )
  oDoc.CurrentController.SELECT(oCell)

  laPropertyValue[1] = createStruct(@oReflection, "com.sun.star.beans.PropertyValue")
  laPropertyValue[1].NAME = "Overwrite"
  laPropertyValue[1].VALUE = .T.

  oDoc.STORE()
ENDFUNC

FUNCTION createStruct(toReflection, tcTypeName)
  LOCAL loPropertyValue, loTemp
  loPropertyValue = CREATEOBJECT("relation")
  toReflection.forName(tcTypeName).CREATEOBJECT(@loPropertyValue)
  RETURN (loPropertyValue)
ENDFUNC

FUNCTION ConvertToURL(tcFile AS STRING)
  IF(TYPE( "tcFile" ) == "C") AND (!EMPTY( tcFile ))
    tcFile = [file:///] + CHRTRAN(tcFile, "\", "/" )
  ELSE
    tcFile = [file:///C:/] + ALIAS() + [.ods]
  ENDIF
  RETURN tcFile
ENDFUNC

AUTOR: Hector Urrutia

Saludos desde EL Salvador

7 de septiembre de 2007

Traducir la función NVL2 de Oracle a Microsoft SQL Server

Artículo original: Translating Oracle’s NVL2 function to Microsoft SQL Server
http://www.foxpert.com/knowlbits_200703_2.htm
Autor: Christof Wollenhaupt
Traducido por: Ana María Bisbé York

En esta dirección de Technet, Microsoft expuso que la traducción adecuada para la función NVL2 de Oracle es:
NVL2 (Salary, Salary*2, 0)
Debería ser:
CASE SALARY
WHEN null THEN 0
ELSE SALARY*2
END
Ellos acertaron en la parte de Oracle, pero fallaron en la sintaxis de su propio servidor. La forma correcta de utilizar WHEN con valores NULL es:
CASE
WHEN SALARY IS NULL THEN 0
ELSE SALARY*2
END
En Microsoft SQL Server usted utiliza IS NULL o IS NOT NULL para verificar si existe NULL. La expresión original debería devolver siempre el valor desde a parte ELSE ya que la condición SALARY = NULL nunca será verdadera.