jueves, 29 de octubre de 2009

Delphi. Pulsar una tecla desde programación.

Para ejecutar el "pulsado" de intro, por ejemplo:

keybd_event(VK_RETURN, 0, 0, 0);


Y un bonito listado de Virtual Keys:


VK_LBUTTON = $1, //Dec 1; The left mouse button
VK_RBUTTON = $2, //Dec 2; The right mouse button
VK_CANCEL = $3, //Dec 3; The Cancel virtual key, used for control-break processing
VK_MBUTTON = $4, //Dec 4; The middle mouse button
VK_BACK = $8, //Dec 8; Backspace
VK_TAB = $9, //Dec 9; Tab
VK_CLEAR = $C, //Dec 12; 5 (keypad without Num Lock)
VK_RETURN = $D, //Dec 13; Enter
VK_SHIFT = $10, //Dec 16; Shift (either one)
VK_CONTROL = $11, //Dec 17; Ctrl (either one)
VK_MENU = $12, //Dec 18; Alt (either one)
VK_PAUSE = $13, //Dec 19; Pause Break
VK_CAPITAL = $14, //Dec 20; Caps Lock
VK_ESCAPE = $1B, //Dec 27; Esc
VK_SPACE = $20, //Dec 32; Spacebar
VK_PRIOR = $21, //Dec 33; Page Up
VK_NEXT = $22, //Dec 34; Page Down
VK_END = $23, //Dec 35; End
VK_HOME = $24, //Dec 36; Home
VK_LEFT = $25, //Dec 37; Left Arrow
VK_UP = $26, //Dec 38; Up Arrow
VK_RIGHT = $27, //Dec 39; Right Arrow
VK_DOWN = $28, //Dec 40; Down Arrow
VK_SELECT = $29, //Dec 41; Select
VK_PRINT = $2A, //Dec 42; Print (only used by Nokia keyboards)
VK_EXECUTE = $2B, //Dec 43; Execute (not used)
VK_SNAPSHOT = $2C, //Dec 44; Print Screen
VK_INSERT = $2D, //Dec 45; Insert
VK_DELETE = $2E, //Dec 46; Delete
VK_HELP = $2F, //Dec 47; Help
VK_0 = $30, //Dec 48; 0
VK_1 = $31, //Dec 49; 1
VK_2 = $32, //Dec 50; 2
VK_3 = $33, //Dec 51; 3
VK_4 = $34, //Dec 52; 4
VK_5 = $35, //Dec 53; 5
VK_6 = $36, //Dec 54; 6
VK_7 = $37, //Dec 55; 7
VK_8 = $38, //Dec 56; 8
VK_9 = $39, //Dec 57; 9
VK_A = $41, //Dec 65; A
VK_B = $42, //Dec 66; B
VK_C = $43, //Dec 67; C
VK_D = $44, //Dec 68; D
VK_E = $45, //Dec 69; E
VK_F = $46, //Dec 70; F
VK_G = $47, //Dec 71; G
VK_H = $48, //Dec 72; H
VK_I = $49, //Dec 73; I
VK_J = $4A, //Dec 74; J
VK_K = $4B, //Dec 75; K
VK_L = $4C, //Dec 76; L
VK_M = $4D, //Dec 77; M
VK_N = $4E, //Dec 78; N
VK_O = $4F, //Dec 79; O
VK_P = $50, //Dec 80; P
VK_Q = $51, //Dec 81; Q
VK_R = $52, //Dec 82; R
VK_S = $53, //Dec 83; S
VK_T = $54, //Dec 84; T
VK_U = $55, //Dec 85; U
VK_V = $56, //Dec 86; V
VK_W = $57, //Dec 87; W
VK_X = $58, //Dec 88; X
VK_Y = $59, //Dec 89; Y
VK_Z = $5A, //Dec 90; Z
VK_STARTKEY = $5B, //Dec 91; Start Menu key
VK_CONTEXTKEY = $5D, //Dec 93; Context Menu key
VK_NUMPAD0 = $60, //Dec 96; 0 (keypad with Num Lock)
VK_NUMPAD1 = $61, //Dec 97; 1 (keypad with Num Lock)
VK_NUMPAD2 = $62, //Dec 98; 2 (keypad with Num Lock)
VK_NUMPAD3 = $63, //Dec 99; 3 (keypad with Num Lock)
VK_NUMPAD4 = $64, //Dec 100; 4 (keypad with Num Lock)
VK_NUMPAD5 = $65, //Dec 101; 5 (keypad with Num Lock)
VK_NUMPAD6 = $66, //Dec 102; 6 (keypad with Num Lock)
VK_NUMPAD7 = $67, //Dec 103; 7 (keypad with Num Lock)
VK_NUMPAD8 = $68, //Dec 104; 8 (keypad with Num Lock)
VK_NUMPAD9 = $69, //Dec 105; 9 (keypad with Num Lock)
VK_MULTIPLY = $6A, //Dec 106; * (keypad)
VK_ADD = $6B, //Dec 107; + (keypad)
VK_SEPARATOR = $6C, //Dec 108; Separator (never generated by the keyboard)
VK_SUBTRACT = $6D, //Dec 109; - (keypad)
VK_DECIMAL = $6E, //Dec 110; . (keypad with Num Lock)
VK_DIVIDE = $6F, //Dec 111; / (keypad)
VK_F1 = $70, //Dec 112; F1
VK_F2 = $71, //Dec 113; F2
VK_F3 = $72, //Dec 114; F3
VK_F4 = $73, //Dec 115; F4
VK_F5 = $74, //Dec 116; F5
VK_F6 = $75, //Dec 117; F6
VK_F7 = $76, //Dec 118; F7
VK_F8 = $77, //Dec 119; F8
VK_F9 = $78, //Dec 120; F9
VK_F10 = $79, //Dec 121; F10
VK_F11 = $7A, //Dec 122; F11
VK_F12 = $7B, //Dec 123; F12
VK_F13 = $7C, //Dec 124; F13
VK_F14 = $7D, //Dec 125; F14
VK_F15 = $7E, //Dec 126; F15
VK_F16 = $7F, //Dec 127; F16
VK_F17 = $80, //Dec 128; F17
VK_F18 = $81, //Dec 129; F18
VK_F19 = $82, //Dec 130; F19
VK_F20 = $83, //Dec 131; F20
VK_F21 = $84, //Dec 132; F21
VK_F22 = $85, //Dec 133; F22
VK_F23 = $86, //Dec 134; F23
VK_F24 = $87, //Dec 135; F24
VK_NUMLOCK = $90, //Dec 144; Num Lock
VK_OEM_SCROLL = $91, //Dec 145; Scroll Lock
VK_OEM_1 = $BA, //Dec 186; ;
VK_OEM_PLUS = $BB, //Dec 187; =
VK_OEM_COMMA = $BC, //Dec 188;,
VK_OEM_MINUS = $BD, //Dec 189; -
VK_OEM_PERIOD = $BE, //Dec 190; .
VK_OEM_2 = $BF, //Dec 191; /
VK_OEM_3 = $C0, //Dec 192; `
VK_OEM_4 = $DB, //Dec 219; [
VK_OEM_5 = $DC, //Dec 220; \
VK_OEM_6 = $DD, //Dec 221; ]
VK_OEM_7 = $DE, //Dec 222; '
VK_OEM_8 = $DF, //Dec 223; (unknown)
VK_ICO_F17 = $E0, //Dec 224; F17 on Olivetti extended keyboard (internal use only)
VK_ICO_F18 = $E1, //Dec 225; F18 on Olivetti extended keyboard (internal use only)
VK_OEM_102 = $E2, //Dec 226; < or | on IBM-compatible 102 enhanced non-U.S. keyboard
VK_ICO_HELP = $E3, //Dec 227; Help on Olivetti extended keyboard (internal use only)
VK_ICO_00 = $E4, //Dec 228; 00 on Olivetti extended keyboard (internal use only)
VK_ICO_CLEAR = $E6, //Dec 230; Clear on Olivette extended keyboard (internal use only)
VK_OEM_RESET = $E9, //Dec 233; Reset (Nokia keyboards only)
VK_OEM_JUMP = $EA, //Dec 234; Jump (Nokia keyboards only)
VK_OEM_PA1 = $EB, //Dec 235; PA1 (Nokia keyboards only)
VK_OEM_PA2 = $EC, //Dec 236; PA2 (Nokia keyboards only)
VK_OEM_PA3 = $ED, //Dec 237; PA3 (Nokia keyboards only)
VK_OEM_WSCTRL = $EE, //Dec 238; WSCTRL (Nokia keyboards only)
VK_OEM_CUSEL = $EF, //Dec 239; CUSEL (Nokia keyboards only)
VK_OEM_ATTN = $F0, //Dec 240; ATTN (Nokia keyboards only)
VK_OEM_FINNISH = $F1, //Dec 241; FINNISH (Nokia keyboards only)
VK_OEM_COPY = $F2, //Dec 242; COPY (Nokia keyboards only)
VK_OEM_AUTO = $F3, //Dec 243; AUTO (Nokia keyboards only)
VK_OEM_ENLW = $F4, //Dec 244; ENLW (Nokia keyboards only)
VK_OEM_BACKTAB = $F5, //Dec 245; BACKTAB (Nokia keyboards only)
VK_ATTN = $F6, //Dec 246; ATTN
VK_CRSEL = $F7, //Dec 247; CRSEL
VK_EXSEL = $F8, //Dec 248; EXSEL
VK_EREOF = $F9, //Dec 249; EREOF
VK_PLAY = $FA, //Dec 250; PLAY
VK_ZOOM = $FB, //Dec 251; ZOOM
VK_NONAME = $FC, //Dec 252; NONAME
VK_PA1 = $FD, //Dec 253; PA1
VK_OEM_CLEAR = $FE //Dec 254; CLEAR

miércoles, 16 de septiembre de 2009

Access. Invalid field definition al vincular tabla ODBC

Llegas a tu Access'97 (siempre estamos a la punta de la tecnología, amigos) y vinculas vía ODBC una tabla Oracle, como has hecho cienes de veces. Pero esta vez la cosa se resiste, y te tira un bonito error:

"Invalid field definition 'SYS_NC000XX$' in definition of index or relationship"


Jarl.

¿Qué tiene esta tabla de extraño?... Fácil. Tiene un índice basado en una función (SUBSTR), que indexa por las primeras 8 posiciones de un campo. La solución más inmediata: si tienes posibilidad de crear vistas en la bd Oracle, pues creas una vista sobre la tabla para vincularla desde Access. Todo vuelve a funcionar. Si no existe esa posibilidad, he leído algo de que trasteando las propiedades del origen de datos ODBC puedes llegar a hacer algo. Pero yo no pondría la mano en el fuego.

martes, 24 de marzo de 2009

Oracle. Restar n años a una fecha.

Restarle cinco años a la fecha contenida en el campo FECHAFIN

select FECHAFIN - INTERVAL '5' YEAR
from X

Hale!

martes, 16 de diciembre de 2008

SQLServer. Columna(s) que forman la clave principal

Hola.

Bonita y elegante SQL que, tirando de las vistas de sistema, obtiene el nombre de las columnas que componen la clave principal (primary key para los amigos) de una tabla (en este caso la tabla 'PERSONA'):

SELECT i.name AS nombre, ic.column_id as numCampo, c.name as nombreCampo
FROM (sys.indexes AS i inner join sys.index_columns AS ic on i.object_id=ic.object_id
and i.index_id=ic.index_id) inner join sys.columns c on i.object_id=c.object_id
and ic.column_id=c.column_id
where i.object_id = OBJECT_ID('dbo.PERSONA') and i.is_primary_key=1


Hale!

jueves, 27 de noviembre de 2008

WinForms. Controlar fin de carga de CrystalReportViewer

Tenemos un rpt que vamos a visualizar en un crystalReportViewer (crv) de nuestro WinForm. Y queremos controlar que mientras se monta el listado el usuario tenga un bonito reloj de arena dando vueltas en su pantalla. Parece sencillo:

Dim rpt As New CrystalDecisions.CrystalReports.Engine.ReportDocument

Me.Cursor = Cursors.WaitCursor
rpt.Load(rutaRpt, CrystalDecisions.Shared.OpenReportMethod.OpenReportByTempCopy)
rpt.SetDataSource(ds)
crv.ReportSource = rpt
Me.Cursor = Cursors.default

Al ejecutarlo nos damos cuenta de que en realidad el reloj de arena desaparece mucho antes de que el listado esté montado en la pantalla (obviamente esto sólo lo podemos apreciar si el listado es "pesado" de carga). Putada. Buscamos un evento que se nos dispare al terminar de armar el crystalReportViewer. Vaya, hay uno (AfterRender), pero sólo para el viewer de web. PUTADA de las gordas...

Pues si no hay solución elegante habrá que buscar una solución rastrera. ¿Y si intentamos paginar entre la asignación del ReportSource y la desaparición del reloj de arena?

Dim rpt As New CrystalDecisions.CrystalReports.Engine.ReportDocument

Me.Cursor = Cursors.WaitCursor
rpt.Load(rutaRpt, CrystalDecisions.Shared.OpenReportMethod.OpenReportByTempCopy)
rpt.SetDataSource(ds)
crv.ReportSource = rpt
crv.ShowFirstPage()
Me.Cursor = Cursors.default

Pues funciona. Menuda chorrada. Vamos a tomarnos unas cañas.

martes, 7 de octubre de 2008

Invocar webservice con HTTP GET/POST

La manera habitual de consumir un webservice .NET es mediante el protocolo SOAP. Pero hay casos en los que implementar las llamadas en dicho protocolo puede resultar bastante costoso, como por ejemplo si queremos hacer la llamada directamente desde el cliente o desde una página ASP clásica (no .NET).

La solución es invocar el webservice usando HTTP GET y POST. En el framework 1.0 esta posibilidad estaba activa por defecto, pero a partir del 1.1 está deshabilitada por cuestiones de seguridad. Así que lo primero que hay que hacer es habilitar explícitamente esta posibilidad. En el webconfig del sitio donde resida el webservice hay que añadir lo siguiente dentro del nodo <system.web>:

<webServices>
    <protocols>
        <add name="HttpGet"/>
        <add name="HttpPost"/>
    </protocols>
</webServices>

Una vez realizado esto ya podemos comprobar que ya podemos invocar a nuestro webservice desde un navegador en cualquier cliente, tecleando la siguiente url:
http://servidor/nombreWebService.asmx/nombreMetodo?parametro1=valor1¶metro2=valor2(...)

Lo que nos aparecerá en el navegador es el chorro XML que nos devuelve el webservice.

Pues ya somos capaces de invocar a nuestro webservice usando HTTP GET y HTTP POST, pero eso lo explica mucho mejor que yo Juan Peláez en su blog.

(Gracias a Juan Carlos por el enlace)

lunes, 6 de octubre de 2008

ASP.NET. Añadir meta "refresh" desde código

Una manera de redireccionar a otra página pasados n segundos, y poder establecer la url de manera dinámica:

En el page_load de la página inicial ponemos:

Dim urlTmp as string="nuevaPagina.aspx?id=" & _
Request.QueryString("id")

Dim mt As New HtmlMeta
mt.Attributes.Add("http-equiv", "refresh")
mt.Attributes.Add("content", "5;url=" & urlTmp)

Me.Header.Controls.Add(mt)

De esta manera, a los 5 segundos seremos redireccionados directamente a nuevaPagina.aspx, pasándole como id el mismo que tuviésemos en la página inicial.

Si nuestra página tiene masterpage, hay que usar
Me.Master.Page.Header.Controls.Add(mt)

Hale!

miércoles, 17 de septiembre de 2008

ASP.NET. Conservar SelectCommand de SqlDatasource entre postbacks

Todos hemos flipado la primera vez que en dos patadas hemos enganchado un SqlDataSource a un GridView y automáticamente hemos obtenido contenido de la b.d. en nuestro grid, siendo capaces de paginarlo, ordenarlo... todo funciona de maravilla con el SelectCommand del SqlDataSource que hemos establecido en tiempo de diseño.

El siguiente paso es modificar ese SelectCommand en función de los criterios aportados por el usuario. Ponemos el típico botón buscar y en su evento "click" construimos nuestro SelectCommand. La primera página del grid se carga correctamente, pero si intentamos paginar ya la cosa se fastidia...

Esto es debido a que, por razones obvias de seguridad, el control SqlDataSource no guarda por defecto ninguno de sus comandos (select, update, etc...) en el ViewState. Como consecuencia, su contenido no se conserva entre postbacks, y es necesario almacenarlo de alguna manera y asignarlo en cada load si queremos que perdure.

Una manera de hacerlo es usar el propio viewstate, pero teniendo la precaución indispensable de encriptarlo. Para ello añadimos en la directiva page de nuestro aspx:

ViewStateEncryptionMode="Always"


En el click del botón construimos el selectCommand y lo almacenamos en el viewstate:

Protected Sub btBuscar_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
ViewState("selectGrid") = "select * from tabla"
SqlDataSource1.SelectCommand = ViewState("selectGrid")
End Sub


Para recuperarlo en el load de la página:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not ViewState("selectGrid") Is Nothing Then
SqlDataSource1.SelectCommand = ViewState("selectGrid")
End If
End Sub

miércoles, 21 de mayo de 2008

WinForms. Ocultar TabPage de un TabControl

Con lo fácil que sería que cada TabPage tuviese una propiedad "visible". Pues no.

Para ocultar un TabPage:

TabPage.Parent = nothing


Para volverlo a mostrar:
TabPage.Parent = TabControl


Hale!

miércoles, 9 de abril de 2008

CrystalReportViewer. Establecer zoom desde código.

Poner el zoom en ancho de página:

CrystalReportViewerX.Zoom(1)


Poner el zoom en página completa:
CrystalReportViewerX.Zoom(2)


Fácil, ¿verdad?

domingo, 24 de febrero de 2008

Winforms. Poner un ComboBox en modo "sólo lectura"

Una de winforms.

Ante la inexistencia de una propiedad ReadOnly en el ComboBox, análoga al del TextBox, la única solución para bloquear la entrada de un usuario en un combo es usar el enabled=false, lo que provoca efectivamente que el control no admita entradas pero que tampoco admita ser leído con claridad. Que al ponerse gris no se ve una mierda, vaya.

Pues nada, a montarse una pajilla mental de cómo simular el ReadOnly del TextBox en un ComboBox:

Private Sub proteger()
  ComboBox1.DropDownStyle = ComboBoxStyle.Simple
  AddHandler ComboBox1.KeyDown, AddressOf Me.DesactivarTeclas
End Sub

Private Sub desproteger()
    ComboBox1.DropDownStyle = ComboBoxStyle.DropDownList
    RemoveHandler ComboBox1.KeyDown, AddressOf Me.DesactivarTeclas
End Sub

Private Sub DesactivarTeclas(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs)
    e.SuppressKeyPress = True
End Sub


Cuidado si en el formulario hay eventos asociados a la pulsación de teclas, caso en el que habría que personalizar la aplicación del suppressKeyPress en el procedimiento DesactivarTeclas.

miércoles, 13 de febrero de 2008

Crystal Report. Ocultar cabecera de página cuando no hay detalle.

Típico listado de campos en columna, con una cabecera para cada columna y una línea de detalle por cada registro. También tenemos un ReportFooter con una firma. Queremos que si el detalle ocupa varias páginas la cabecera se repita en todas ellas. Sin problemas: creamos un PageHeader con nuestra cabecera de columnas.

El problema con este diseño viene cuando el reportFooter salta de página pero las líneas de detalle se quedan en la página anterior: tendremos una última página con el ReportFooter coronado con una bonita cabecera de columnas (el contenido del PageHeader) pero sin lineas de detalle.

Para solucionarlo hay que montarse una buena pajilla mental:

En el reportHeader poner esta fórmula oculta:

NumberVar x := 9999; //poner un número tan grande como el máx número de páginas previsto


En la sección de detalle, poner esta fórmula, oculta también:

NumberVar x;
If OnLastRecord and PageNumber < TotalPageCount Then x := PageNumber


Y al PageHeader poner en la fórmula asociada a su supresión:

NumberVar x;
PageNumber > x


Básicamente lo que hacemos es guardarnos el número de página en el que se acaba el detalle y usarlo para no imprimir la cabecera.(Solución copiada de aquí)

Si existe una solución más fácil ya saben, a comentarla.

jueves, 7 de febrero de 2008

Campo de GridView con enlace a valor del campo. 2ªparte (imagen con enlace)

Si queremos tener una imagen con enlace en una columna de un gridview y que el enlace dependa de un dato de nuestra base de datos, tenemos que añadir una columna TemplateField al gridview. Este TemplateField deberá contener un objeto HyperLink en el que definimos el enlace mediante una cadena de formato y en el que referenciamos el campo de la tabla (mediante {0}).

En la vista código veríamos algo parecido a lo siguiente :

<Columns>

...

<asp:TemplateField HeaderText="ImagenConEnlace">

<ItemTemplate>

<asp:HyperLink ID="HL_ImagenConEnlace" runat="server"

ImageUrl="~/carpeta1/imagenMostradaEnGridview.gif"

Text='texto_mostrado_cuando_el_raton_esta_sobre_la_imagen'

Target="_blank"

NavigateUrl='<%# Eval("nombre_campo_tabla", "~/carpeta2/{0}") %>'>

</asp:HyperLink>

</ItemTemplate>

</asp:TemplateField>

...

</Columns>


(Análogamente, podemos hacer que la imágen mostrada dependa de un campo de la tabla empleando también una cadena de formato)

jueves, 31 de enero de 2008

Numerar las filas de una tabla (se puede utilizar para implementar la paginación de un datalist)

Para ello se utiliza una claúsula nueva de SQL SERVER 2005 para numerar las filas : ROW_NUMBER() OVER (ORDER BY campo)

Si queremos filtrar por el número de fila es necesario crear una tabla temporal mediante la instrucción : WITH tabla_temporal AS (sentencia SQL)

En el ejemplo siguiente creamos un procedimiento almacenado que tiene dos parámetros para indicar el rango de filas que queremos devolver.

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
CREATE PROCEDURE [dbo].[prueba]

@inicio INT,
@fin INT
AS
BEGIN

SET NOCOUNT ON;

WITH consultaTMP AS
(SELECT pro_id, ROW_NUMBER() OVER (ORDER BY pro_id) AS posicion
FROM dbo.provincias)

SELECT pro_id, posicion
FROM consultaTMP
WHERE posicion BETWEEN @inicio AND @fin

END

jueves, 24 de enero de 2008

Averiguar último identificador insertado en una tabla

Cuando en una base de datos SQL Server se inserta un campo en un tabla de tipo int y se le especifica la propiedad indentidad a Sí, en ocasiones es necesario averiguar cuál fue el último valor.

Para ello, un ejemplo vale más que mil palabras.


Dim CadenaConexion As String
Dim cmd As SqlCommand
Dim x As Integer
Dim
cnn As SqlConnection = New SqlConnection()
Dim sqlStr As
String
CadenaConexion =
ConfigurationManager.ConnectionStrings("micadenilladeconexion").ConnectionString
cnn.ConnectionString
= CadenaConexion
sqlStr = "INSERT INTO PERSONA (Nombre) VALUES (@nombre);"
& _
"SELECT @UltimoId=SCOPE_IDENTITY() FROM PERSONA"
cmd = New
SqlCommand(sqlStr, cnn)
cmd.Parameters.Add("@nombre",
SqlDbType.NVarChar).Value = "Diego"
cmd.Parameters.Add("@UltimoId",
SqlDbType.Int).Direction =
ParameterDirection.Output
cnn.Open()
Try
cmd.ExecuteScalar()
x =
CType(cmd.Parameters("@UltimoId").Value, Integer)
Response.Write("VALOR
ULTIMO ID: " & x & " <==") Catch Excepcion As Exception Throw New Exception("Error al insertar datos", Excepcion) Finally cnn.Close() End Try

miércoles, 2 de enero de 2008

Oracle to_number() y error ORA-01722: invalid number

El escenario es el siguiente: tenemos una tabla t con un campo codOpe que es un código de operación y un campo infoOpe que es información sobre la operación. Ambos son varchar, pero sabemos que para un determinado valor de codOpe ('PR') el contenido de infoOpe siempre es numérico, sin excepciones, aunque para otros valores de codOpe no tiene porqué serlo.

Creamos una vista para explotar las operaciones de tipo 'PR':

create view opePR as
select to_number(infoOpe) as infoOpeNum from t
where codOpe='PR'

Perfecto. Oracle es la hostia y funciona al pelo. La vista me devuelve tantas filas como operaciones PR's tengo en mi tabla. Ahora quiero saber cuantos PR's tengo con infoOpe mayor que 100. Vamos a usar nuestra vista:

select * from opePR where infoOpeNum>100

Cuando lanzamos la sentencia Oracle (9.2) nos devuelve un bonito error:

ORA-01722: invalid number

Jarl. Oracle ya no es la hostia. Si la consulta nos devuelve sólo valores numéricos... ¿por qué nos falla cuando escribimos el where?...

El origen del problema es el optimizador de consultas de Oracle. El tener la vista almacenada no significa que primero se ejecute su where (el que nos asegura que el campo es numérico) y luego el where externo, sino que el optimizador combina ambos where para que el plan de ejecución de la consulta sea lo más eficiente posible, y conseguir así una consulta eficiente pero con un pequeño problema: que no funciona.

Solución: indicarle al optimizador que no mezcle los "where":

select /*+ NO_MERGE(t) */
from opePR t where infoOpeNum>100

La consulta será ineficiente, pero funciona, coño. Oracle vuelve a ser la hostia.

jueves, 20 de diciembre de 2007

Evolución .net versus Crystal Report

Clarificador diagrama:

Visual Studio 2005 y Delphi 2005. Efectos colaterales.

Si se te ocurre instalarte el VS2005 teniendo el Delphi 2005 instalado, la próxima vez que arranques tu Delphi te dará un bonito error diciendo que no puede cargar htmlhelp290.bpl. Pues bueno, que se quede sin cargar. Luego comprobarás que la consecuencia de este mensajito es que te deja de funcionar la ayuda. Vaya, pues había que cargarla.

La solución está en meterse en el registro de windows y:

En HKEY_CLASSES_ROOT\AppID\DExplore.exe, machacar el valor de la clave "AppId" poniendo

{4A79114D-19E4-11d3-B86B-00C04F79F802}

Si en el arranque de Delphi, cuando te da el error en la carga de htmlhelp290.bpl, le has dicho que no te la vuelva a cargar también tienes que tocar
HKEY_CURRENT_USER\Software\Borland\BDS\3.0\Disabled IDE Packages y borrar la clave del htmlhelp290

Solución extraída de aquí, también documentada por los chicos de Borland.

martes, 18 de diciembre de 2007

Dar formato a los datos usando una clase

Lo que se pretende es poder llegar a dar formato a información usando una clase, que no instanciaremos.
Para ello se necesita que nos creemos en la carpeta App_Code de nuestro proyecto un archivo de clase que llamaremos Funciones.vb o lo que nos parezca.
El código de dicha clase:

Imports Microsoft.VisualBasic

Public Class Funciones
Public Shared Function FormatoCadena(ByVal Valor
As String, ByVal Formato As String) As String
Dim ValorFormato As
Double
Dim Cadena As String
ValorFormato = Valor
Cadena =
ValorFormato.ToString(Formato)
Return Cadena
End Function
End
Class

Esta clase podrá ser llamada sin ser instanciada desde cualquier parte del proyecto. Así si deseamos que se puede llamar desde un FormView, cómo fue el caso que a mí me ocupó, accederemos a través de la etiqueta inteligente a la plantilla donde esté el campo al que aplicaremos formato, y accederemos a través de su etiqueta inteligente a "Editar DataBinding ...".
Activaremos "Personalizar enlace" y en la expresión de código pondremos:
Funciones.FormatoCadena(Eval("Telefono1"), "### ## ## ##")

También se podría evitar poner Funciones haciendo un Imports o agregandolo convenientemente en web.config.

Páginas de ayuda con información de formatos:

http://authors.aspalliance.com/aspxtreme/sys/globalization/demos/DateTimeFormatInfoGetAllDateTimePatterns.aspx
http://msdn2.microsoft.com/es-es/library/az4se3k1(VS.80).aspx
http://msdn2.microsoft.com/es-es/library/dwhawy9k(VS.80).aspx
http://msdn2.microsoft.com/es-es/library/0c899ak8(VS.80).aspx

Nada más.

lunes, 17 de diciembre de 2007

LLamar a un procedimiento almacenado que devuelve valor a través de RETURN

Como sabemos una de las maneras de que un procedimiento almacenado de SqlServer nos devuelva un valor (entero) es usando el RETURN:

CREATE PROCEDURE nombreProc
@Parametro nvarchar(50)
AS
BEGIN
  IF EXISTS (SELECT Id from Tabla where campo=@Parametro)
    begin
      RETURN 1
    end
    else begin
      RETURN 0
    end
END

Para llamar a este procedimiento desde el .net y obtener el valor devuelto hay que crearle un parámetro específico del tipo ReturnValue, aparte de los definidos en el procedimiento:

cmd.CommandType = Data.CommandType.StoredProcedure
cmd.CommandText = "nombreProc"
cmd.Parameters.Clear()
cmd.Parameters.Add(New SqlParameter("@Parametro", "valor"))
Dim retValue As New SqlParameter("@RETURN_VALUE", Data.SqlDbType.Int)
retValue.Direction = Data.ParameterDirection.ReturnValue
cmd.Parameters.Add(retValue)
cmd.ExecuteScalar()
Dim resultado As Integer = Convert.ToInt32(retValue.Value)
If resultado = 1 Then
  ...
  ...
Else
  ...
  ...
End If