domingo, 18 de noviembre de 2018

► Ocultar objetos a usuarios


Es muy común que algunos objetos los queramos ocultar a algunos usuarios. Incluso se pueden ocultar hojas completas. Basta con poner esta sentencia en la propiedad Mostrar à Condicional de la pestaña de Diseño de las propiedades del objeto.


=upper(subfield(OSUser(),'\',2))='USUARIO1'  or upper(subfield(OSUser(),'\',1))='DOMINIO1'
OR upper(subfield(OSUser(),'\',2))=2' USUARIO' OR upper(subfield(OSUser(),'\',2))=' USUARIO3’
or upper(subfield(OSUser(),'\',2))='DOMINIO2'



La función OSUSER() devuelve el nombre del usuario en el formato Dominio\Usuario

Obviamente, si son muchos usuarios y dominios, la sentencia anterior puede ser complicada de entender. Una forma mucho más elegante de hacer lo mismo es usar la función MATCH():


=MATCH(upper(subfield(OSUser(),'\',2)),'USER1', 'USER2', 'USER3')
or MATCH(upper(subfield(OSUser(),'\',1)), 'DOMINIO1','DOMINIO2')




► Dimensiones dinámicas


Imaginemos que tenemos un gráfico en el que las expresiones son siempre las mismas pero queremos representar una dimensión u otra en función de una selección que haga el usuario.
Los ejemplos de uso más comunes son:

1.-  Las traducciones a otros idiomas
Supongamos que tenemos las categorías profesionales de los empleados en un campo llamado Categorías y los valores son: Presidente, Director,  Gerente, Operario, Administrador, Soporte y necesitamos que nuestra aplicación sea multiidioma (español e inglés), pero no sólo traduciendo los textos y las etiquetas de los objetos, si no traduciendo también el idioma de los valores de las dimensiones. 
Deberíamos cambiar el nombre del campo Categorías a Categoria_ES
RENAME FIELD Categorías TO Categoria_ES

Después deberíamos crear el campo Categoria_EN con los valores President, Director, Manager, Operator,  Administrator, Support
Este nuevo campo debería formar parte del modelo de datos al mismo nivel que Categoria_ES

El tercer paso es crear una isla de datos con los idiomas. El campo idioma debe coincidir exactamente con la terminación de las dimensiones anteriores. El usuario elegirá siempre un idioma, ES o EN:

Idiomas:
LOAD * INLINE [
Idioma
ES
EN ]
;

Por último, como dimensión de gráficos, crearemos una dimensión calculada con la expresión:

=$(='Categoria_'&ONLY(Idioma))

El argumento de ONLY() es el campo “Idioma” de la isla de datos “Idiomas” y según lo que seleccione el usuario se mostrarán los valores del campo Categoria_ES o Categoria_EN

2.- Y el ahorro de tamaño del CdM en memoria. Dos latermativas posibles serían usar un grupo cíclico o crear tantos gráficos idénticos como dimensiones tengamos (en cada uno sólo cambia la dimensión) y visualizar uno u otro según se cumpla una condición o meterlos todos en un contenedor de modo que el usuario elija cuál ver.

Imaginemos que tenemos las siguientes dimensiones: Continente, País y División y queremos analizar su facturación. No queremos un grupo cíclico porque el usuario no quiere ver el icono amarillo en el gráfico y quiere ver en todo momento las opciones disponibles.
Nota para Qlik Sense: En Qlik Sense tenemos también la opción de hacer grupos cíclicos de expresiones y dimensiones, pero esto puede llevar al usuario a confusión con lo que está viendo en pantalla en un momento dado. La solución sería escribir un texto en el título del gráfico con la dimensión y la expresión utilizando las funciones GetObjectDimension y GetObjectMeasure, pero desafortunadamente estas no funcionan en los títulos de los gtáficos. La única forma de hacerlo es siguiendo el método descrito aquí.

Primero creamos una isla de datos con los nombres de las dimensiones y su representación en el CdM. Es importantísimo que los nombres de las dimensiones coincidan (mayúsculas, signos de puntuación…. Etc) con los nombres de los campos de las tablas del modelo.

Selector_1:
LOAD * INLINE [
Dim_1,             Descr_1
NOMBRE_CONTINENTE, Continente
NOMBRE_PAIS,       País
NOMBRE_DIVISION,   División
]
;


Por último, como dimensión de gráficos, crearemos una dimensión calculada con la expresión:

                 =$(=ONLY(Dim_1)) o  =$(=Dim_1)

Y la etiqueta será

             =Descr_1


► Cómo evitar IF anidados


Cuando tenemos que aplicar expresiones distintas en función del valor que tome una dimensión, podemos hacerlo de varias formas.

Ej: If (dimension =1, expr1,
        If (dimension=2, exp2,
            If (dimension=3, exp3,
                If (dimension=x, exprx))))

      1) Usando la función PICK(). Esto sólo lo haremos cuando los valores de la dimensión sean números naturales.

            Pick(dimensión, expr1, expr2, expr3……exprx)

Una forma rápida de crear esta función pick es hacerlo dinámicamente usando CONCAT() en lugar de escribir una función larguísima que contenga todas las expresiones. Así mejoramos el mantenimiento y la legibilidad.
Para hacerlo, hay que meter todas las expresiones en un campo en una isla de datos. El origen puede ser un inline, una Excel o un fichero de texto. Lo único que hay que tener en cuenta es que deben estar ordenadas según el valor de la dimensión y el orden que ocuparán en la función PICK().
Ejemplo: Imaginemos que tenemos una dimensión “Dim_x” con los valores 1, 2 y 3 y que según el valor que tome se aplique una expresión u otra: Para el valor 1 hará la suma del campo1, para el valor 2 hará la media y para el valor 3 obtendrá la moda.
Tenemos este inline:

ISLA_DATOS:
LOAD * INLINE [
Orden,    Expresion
1,        SUM(campo1)
2,        AVG(campo1)
3,        MODE(campo1)
];

En las expresiones de gráficos u objetos sustituiremos los if anidados por esta única expresión:

PICK(Dim_x, $(=CONCAT(Expresion, ’,’ ,Orden)))

Observad que los argumentos de CONCAT son las columnas del INLINE
 

    2)   Con expansión del signo $. De esta forma los valores de la dimensión no tienen por qué ser numéricos.
A diferencia del ejemplo 1 en el que las diferentes expresiones se introducían en un campo de una isla de datos cuyo origen era un inline, un Excel o cualquier otra fuente, aquí crearemos una variable por cada expresión.
El nombre de cada variable debe tener un formato muy concreto: una raíz común + cada valor posible de la dimensión:

Ejemplo 1: Suponiendo que tenemos una dimensión “Dim_x” con los valores 1, 2 y 3
Creamos las variables:

       SET Var_1 = SUM(campo1);
      SET Var_2 = AVG(campo1);
      SET Var_3 = MODE(campo1);

Ejemplo 2: Suponiendo que la dimensión “Dim_x” tenga los valores Madrid, Málaga y Bilbao

Creamos las variables

SET Var_Madrid = SUM(campo1);
          SET Var_Málaga = SUM(Campo1*0,05);
          SET Var_Bilbao =  SUM(campo1*0,02);

En los objetos sustituimos los if anidados por esta expresión:

                             =$(='Var_'&ONLY(Dim_x))


3)  Usando la función ALT().
Esta opción es la menos recomendable porque se ejecuta casi igual que los IF anidados, pero al menos facilita la lectura y comprensión de la expresión. 
Se debe usar sólo cuando no se puedan evitar los IF anidados, como por ejemplo en el Script.
Sólo funciona cuando los valores devueltos son números. No funciona si devuelve cadenas de caracteres.
Usando el ejemplo 2, lo podemos transformar por:


 ALT(
      IF(Dim_x=’Madrid’, SUM(campo1)),
      IF(Dim_x=’Málaga’, SUM(Campo1*0,05)),       
      IF(Dim_x=’Bilbao’, SUM(campo1*0,02)),
      0
    );

► Recarga en local o en el servidor


Normalmente las rutas que utilizamos en nuestros Pc cuando desarrollamos son diferentes de las del servidor de producción, lo que nos obliga a comentar y descomentar continuamente código cuando recargamos en local o en el servidor.
Para evitarlo, se puede escribir una condición que averigüe dónde se está ejecutando la recarga y en función del resultado ejecute un trozo de código u otro.

La forma más rápida es usando la función COMPUTERNAME()

Ejemplo: 


IF UPPER(COMPUTERNAME())= 'QLIKSERVER01' THEN         
     ...........  //Sentencias
ELSE //Estamos en otro ordenador que no es el server  
     ...........  //Sentencias
END IF

Otra forma de hacerlo es verificando quién es el usuario que lo ejecuta. Normalmente en nuestro Pc entraremos con nuestro usuario de dominio o un usuario local. En el servidor normalmente ejecutará las recargas el usuario propietario de los servicios QlikView o algún usuario del grupo de administradores.

//Si el usuario NO es uno de esos 3 usuarios del servidor, estamos en el pc local,  (QLIKVIEWADMIN=Propietario de los servicios de QlikView, USERADMIN= Usuario Administrador, USER= Otro usuario del servidor)

IF Index('QLIKVIEWADMIN, USERADMIN, USER', UPPER(subfield(OSUser(),'\',2)))=0 THEN   
     ...........  //Sentencias
   
ELSE //Estamos en el Server  
     ...........  //Sentencias
   
END IF    

La sentencia

 IF Index('QLIKVIEWADMIN, USERADMIN, USER', UPPER(subfield(OSUser(),'\',2)))=0

 se puede sustituir por

IF(MATCH(UPPER(subfield(OSUser(),'\',2), 'QLIKVIEWADMIN, USERADMIN, USER')




► Problemas con la sintaxis de INLINE


El problema más habitual es cargar datos que contienen caracteres reservados como $ ó []
Para el caso de los corchetes, podemos sustituir los corchetes de la sintaxis de Inline por dobles comillas “ (observar que los campos de las expresiones que se cargan en Formula usan corchetes)

LOAD NumExpresion,
    
DUAL(Nombre, NumExpresion) as Nombre,
    
Formula;
LOAD * Inline "
NumExpresion,  Nombre, Formula
1, Coste Total   , Sum([Coste Total])
2, Importe Venta , Sum([Importe Venta])
3, Expediciones  , Sum(Expediciones)
4, Cobrado       , Sum(Cobrado)
"
;


Otro problema muy común es cuando los valores a cargar tienen una coma. En este caso, el funcionamiento por defecto de INLINE piensa que se trata de un separador de campos y lo trata como tal. La solución viene utilizando la opción DELIMITER.

En este ejemplo cargamos datos de usuarios: Su Id y su nombre completo en formato "apellidos, nombre"
Si no usamos la opción DELIMITER interpretaría la coma del nombre como delimitador de campos y daría un resultado incorrecto. Por eso establecemos que el delimitador entre campos es '|'.


                  LOAD * Inline [
            id   | nombre
            1015 | Martinez Mayoral, José Manuel
            2113 | Callejón López, María
         ] (delimiter is '|');

► Variables con fechas (YTD, MTD, AñoAnterior…. Etc)


Lo primero que debemos hacer es crear un campo llamado Periodo asociado a cada fecha. Este campo tiene el formato YYYYMM y se puede crear con la fórmula (Año*100 + Mes).

Para los ejemplos vamos a tomar como “fecha actual” la mayor de todas las seleccionadas (en caso de que se puedan seleccionar varios meses). 

Partimos de que tenemos un calendario maestro con los campos Año, Mes y Día

Las expresiones a utilizar son del tipo:

SUM ({<Año=,Mes=, Día=, Periodo={">=$(vL.InicioAño) <=$(vL.PeriodoActual)"}>} Campo_A_Sumar)  //esto calcula el acumulado YTD

=SUM({<Año={"<=$(vL.AñoActual) >=$(=$(vL.AñoActual)-1)"}, Mes={"$(=$(vL.MesActual))"} >} Campo_A_Sumar)  //Calcula el acumulado YTD también pero de este año y de los mismos meses del año anterior. Del año anterior sólo toma los mismos meses que hayan transcurrido del año actual.


Type
Variable Name
Value
Comment
SET
vL.AñoActual
=Max(Año)
Año Actual
SET
vL.AñoAnterior
=Max(Año)-1
Año Anterior
SET
vL.InicioAño
=num(MAX(Año)&'0101')
Día de Inicio Año Actual
SET
vL.InicioAñoAnterior
=num((MAX(Año)-1)&'0101')
Día de Inicio año Anterior
SET
vL.MesActual
=num(Month(MAX([fecha_referencia])))
Mes actual en número. No es Max(Mes) porque el año más grande puede pertenecer al año anterior.

SET
vL.PeriodoActual
=MAX(Periodo)
El periodo actual es el mayor de los seleccionados
SET
vL.PeriodoAnterior
=IF(MAX(Mes)=1 , num((MAX(Año)-1)&'12')  ,  num(MAX(Periodo)-1))
AñoMes anterior al PeriodoActual
SET
vL.PeriodoAñoAnterior
=((MAX(Año)-1)*100) + MAX(Mes)
Mismo periodo que el actual pero del año anterior
SET
vL.InicioMes
=num(MAX(Periodo)&'01')
Día de inicio del PeriodoActual
SET
vL.InicioMesAnterior
=IF(MAX(Mes)=1 , num((MAX(Año)-1)&'1201')  ,  num(MAX(Periodo)-1&'01'))
Día de inicio del periodo anterior a PeriodoActual
SET
vL.TrimAnterior
=if(MAX(Mes)>3, (MAX(Año)*100)+num((MAX(Mes)-3),'00'), (MAX(Año)-1)&pick(MAX(Mes),'10','11','12'))
3 periodos antes al PeriodoActual
SET
vL.DiaMesActual
=MAX(PeriodoDia)
Día mayor de los seleccionados. Formato YYYYMMDD
SET
vL.DiaMesAnterior
=If(MAX(PeriodoDia)=text(date(monthend(date#(MAX(PeriodoDia),'YYYYMMDD')),'YYYYMMDD')),
text(date(monthend(date#(IF(MAX(Mes)=1 , num((MAX(Año)-1)&'1201')  ,  num(MAX(Periodo)-1)&'01'),'YYYYMMDD')),'YYYYMMDD')),
IF(MAX(Mes)=1 , num((MAX(Año)-1)&'12'&num(MAX(Día),'00'))  ,  num(MAX(Periodo)-1)&num(MAX(Día),'00'))
 )
Mismo día del mes anterior. Si el día actual es el último del mes, se calcula el último día del mes anterior. Este mes puede tener 31 días y el anterior 28, 29 ó 30
SET
vL.DiaAñoAnterior
=((MAX(Año)-1)*10000) + (MAX(Mes)*100) + num(MAX(Día),'00')
Mismo día del Año anterior. Puede haber un problema con el 29 de Febrero.
SET
vL.MesesTranscurridos
floor((MonthStart("Fecha Reciente") - monthStart("Fecha Antigua"))/30)
Meses transcurridos entre dos fechas.

► Cambiar el orden predeterminado en un gráfico


Cuando queremos mostrar algo ordenado, pero queremos controlar nosotros en qué orden tienen que aparecer los valores, podemos hacerlo de 3 formas:

1.- A partir de una tabla INLINE que no desaparecerá del modelo de datos.

Map_Orden:
LOAD * Inline [
Ciudad,     Orden
París,      1
Madrid,     2
Londres,    3
Tokyo,      4
Roma,       5
Moscú,      6
]
;
 

Después pondremos como expresión de ordenación en Propiedades del grafico à Pestaña Ordenar (sort) à Ordenar Por à Expresion(expression) y ahí colocar el nombre del campo ‘Orden’ de la tabla Inline.

2.- Combinando una tabla de mapeo y DUAL. Esta tabla no formará parte del modelo de datos.
Map_Orden:
Mapping LOAD * Inline [
Ciudad,     orden
París,      1
Madrid,     2
Londres,    3
Tokyo,      4
Roma,       5
Moscú,      6
]
;
 
Datos:
LOAD
........... //Lista de campos
DUAL
(Ciudad, ApplyMap('Map_Orden', Ciudad)) as Ciudad,
........... //Lista de campos
FROM  ........... //Tabla, fichero… etc.

Después pondremos como expresión de ordenación en Propiedades del grafico à  Pestaña Ordenar (sort) à Ordenar Por à Valor numérico


3.-  Escribir directamente los valores en el orden deseado usando la función WildMatch() en Propiedades del grafico à  Pestaña Ordenar (sort) à Ordenar Por à Expresion(expression)

Wildmatch(Ciudad,’Paris',’Madrid’,’Londres’,’Tokyo’,’Roma’,’Moscú’)