Archivo

Hacienda somos todos (pero unos más que otros).

Este post tiene que ver poco con la vertiente técnica de programación, así que en principio sería un off-topic total, pero sí que tiene que ver mucho con el hecho de ser autónomo. Seré claro… Hacienda no nos mima en absoluto a los autónomos. Y no sólo no nos mima: nos aprieta las clavijas.

Estamos ya a 10 de diciembre y todavía no me han devuelto los 460 euros correspondientes a la declaración de IRPF que presenté el 7 de mayo. Pero entendamos el concepto de fondo… en la declaración lo que hacemos es mirar todo el dinero que en concepto de impuesto (de IRPF) hemos pagado de antemano durante el año 2007 y compararlo con el impuesto que realmente deberíamos haber pagado una vez ha terminado el año y conocemos los datos definitivos. De modo que en mi caso esos 460 euros es un dinero que Hacienda me ha tomado a cuenta incorrectamente durante el año 2007. Vamos a suponer que lo hizo el 10 de diciembre de 2007 (caso muy extremo, pues seguramente la fecha media sería bastante anterior, así que aún peor). En este caso tendríamos que Hacienda ha tenido en su poder durante un año natural 460 euros que no le corresponden, sino que me corresponden a mí. No sólo me ha privado de los intereses que me podrían haber generado durante este año natural (fácil 35 euros más) sino que en plena crisis, recesión económica, parón del consumo, etc-etc-etc, se ha dedicado a quitarme liquidez.

Pero como de todo se aprende, esto no me volverá a ocurrir. A partir de ahora la declaración anual de IRPF me saldrá a pagar. Así, el dinero en discordia no lo van a tener ellos (bueno, nosotros, todos) hasta que les (nos) parezca oportuno devolvérmelo, sino que lo tendré yo y lo pagaré tan tarde como pueda (es decir, me acogeré a ese 40% que se puede pagar en noviembre).

Uno está ya acostumbrado a que en general se trabaje mal. Triste, pero cierto. Portátiles devueltos porque no conseguían mantener la horizontal sin problemas en una mesa (en PcCity, pero culpa de HP), televisiones devueltas porque al sacarlas de la caja tenían una pieza metálica grande bailando por el interior de la TV (en MediaMarkt, pero culpa de Samsung), servicios de atención al cliente ineptos y con números 807, empresas que gestionan mal su propia página web de tienda online… en fin, hoy en día casi parece difícil hacer dos gestiones sin contratiempo alguno. Pero que incluso Hacienda actúe así…

Por no hablar de qué ocurre con el IVA si, dios no lo quiera, cobras a más de 90 días (nada raro). Imaginemos facturas mensuales de 10.000 euros más IVA:

A 31 de enero facturarías 11.600 euros pero no cobrarías nada.

A 28 de febrero facturarías otros 11.600 pero seguirías sin cobrar nada.

A 31 de marzo facturarías otra vez 11.600 euros pero aún no habrías cobrado nada.

A 20 de abril toca presentar IVA trimestral. Tendrías que ingresarle a Hacienda 4.800 euros cuando en realidad tú aún no habrías recibido cobro alguno.

Puede parecer un caso algo extremo, pero no hace falta cobrar a 90 días ya que lo mismo puede ocurrir con pagos a 30 días facturados a finales de marzo. Una factura importante ahí puede dejarte sin liquidez en el momento de hacer el pago del IVA. Y que esto les ocurra a las empresas… bueno, podemos discutirlo. ¿Pero a los autónomos? ¿Qué sentido tiene, por dios? ¿Dónde está la lógica? Y ya no digamos si trabajas para la Administración. En ese caso estarías dándole un servicio y prestándole un dinero al mismo tiempo…

Actualización.

¡Cielos, qué poder de intimidación tiene mi blog! Esta mañana me he despertado con la gratísima sorpresa de que hoy mismo, un día después de publicar este post, por fin he recibido el ingreso de los 460 euros en cuestión… Ya me lo imagino, alguien en Hacienda buscando todo el día en Google cosas que hayan podido escribir los contribuyentes insatisfechos… y de repente cae en mi blog y lee atónito… (”ho haveu vist??? ho haveu vist???”)… así da gusto…

Report in .NET using Crystal Reports and MySQL database.

This is just the first of some English posts that I’ll publish by translating most popular posts in this blog. Original version (in Spanish) is here.

First of all, I must asume that creating reports is one of that things I like worst in programming. But it’s quite obvious that few serious applications don’t need them, and the one I’m developing now isn’t an exception to this rule. So I’ve been creating some reports recently and I’ve discovered a new way to do it. And that’s what I explain in this post.

As I’ve said sometimes before in previous posts, I develop with VisualBasic.NET and MySQL database. And I use Crystal Reports to create reports, as this tool is integrated in VisualStudio.NET. Since now, I used to use an ODBC connection configured in each PC to connect to MySQL server. But I didn’t like this system much, because actually I’m not working just with one database but with some with different names. They have the same structure, tables and data, but just one is the good one, as the rest are just for developing purposes. It’s really easy to use one or other connection string to make the application connect with one or other database, but with reports it isn’t so easy as they take data using that ODBC connection (and it just can connect with one database).

But now I’ve discovered how to create reports with just a DataTable and an XML schema, needing nothing else. Actually it’s possible to use a DataSet instead of a DataTable as well. So I’m going to explain it with an easy example and some images.

I’ll work with two tables in my MySQL database where I’ll keep information about bills. First table is the one with information about headers and has this data:

+---------+------------+-------------------------+
| blh_num | blh_dat    | blh_cus                 |
+---------+------------+-------------------------+
|       1 | 2008-07-30 | CERAMICAS PEPE, S.A.    |
|       2 | 2008-07-30 | TALLERES GOMEZ, S.L.    |
|       3 | 2008-07-31 | DEPORTES DAMIAN, S.L.   |
|       4 | 2008-07-31 | SOFTWARE ALBERTMATA.NET |
+---------+------------+-------------------------+

Second table is the one with information about positions and has this rows:

+---------+---------+------------------------+---------+---------+
| blp_num | blp_pos | blp_art                | blp_pri | blp_qty |
+---------+---------+------------------------+---------+---------+
|       1 |       1 | RATON LOGITECH         |   15.95 |       1 |
|       2 |       1 | MONITOR LG 19 PULGADAS |   210.5 |       1 |
|       3 |       1 | ROUTER DLINK           |      56 |       1 |
|       4 |       1 | RATON LOGITECH         |   15.95 |       2 |
|       4 |       2 | TECLADO LOGITECH       |   12.95 |       1 |
|       4 |       3 | RECEPTOR GPS ZAPPA     |   59.95 |       1 |
|       4 |       4 | PAQUETE 500 FOLIOS     |     3.7 |       4 |
+---------+---------+------------------------+---------+---------+

It’s something really simple and not normalized, but will be enough for this example, as we’re going to create a report that will be the inovice for purchase number 4 (the one with customer SOFTWARE ALBERTMATA.NET). Obviously, we’ll need information about both tables but I just want to work with one DataTable, so first of all I’m going to create a MySQL view with this sentence:

CREATE VIEW zbl_bill2print AS 
(
SELECT
    blh_num AS BILL_NUMBER,
    blh_dat AS BILL_DATE,
    blh_cus AS BILL_CUSTOMER,
    blp_pos AS LINE_NUMBER,
    blp_art AS LINE_ARTICLE,
    blp_pri AS LINE_UNITPRICE,
    blp_qty AS LINE_UNITS,
    blp_pri * blp_qty AS LINE_TOTALPRICE
FROM
    blh_billheader LEFT JOIN blp_billposits ON blh_num = blp_num
WHERE
    blh_num = 4
);

So, from now on the report will be created using this zbl_bill2print view. Let’s go with the .NET part.

Step 1. Creating XML file containing table/view structure.

Along this post we’ll work with these three things:

1) a Windows form (frmMain) where we’ll have the report viewer object.
2) a class (clsReportCreator) we’re going to create right now.
3) a report (rptBill) that will be the invoice we want to print.

So let’s start creating clsReportCreator class. It’ll have only one attribute (the name of the table or view), one constructor method, one method to load DataTable object and one last method to generate the XML file. Here is the full code for this class:

'--------------------------------------------------------------------
' Author:      Albert Mata (www.albertmata.net)
' Date:        20080731
' Needs:       MySQL.Data reference.
' Description: Class to create a report using just an XML file. 
'--------------------------------------------------------------------
Imports MySql.Data.MySqlClient

Public Class clsReportCreator

    '----------------------------------------------------------------
    ' Attributes.
    '----------------------------------------------------------------
    Private TableOrView As String

    '----------------------------------------------------------------
    ' Constructor method.
    '----------------------------------------------------------------
    Public Sub New(ByVal TableOrView As String)
        Me.TableOrView = TableOrView
    End Sub

    '----------------------------------------------------------------
    ' Returns DataTable corresponding to TableOrView.
    '----------------------------------------------------------------
    Public Function GetDataTable() As DataTable
        Dim DA As MySqlDataAdapter
        Dim DS As New DataSet
        Dim DT As DataTable
        Dim ConnectionString As String
        Dim SQL As String

        'Setting connection string to connect to MySQL database.
        ConnectionString = "Database = blog; " _
                         & "Data Source = localhost; " _
                         & "User ID = root; " _
                         & "Password = mypassword"

        'Setting SQL string.
        SQL = "SELECT * FROM " & Me.TableOrView

        'Getting data and filling DataSet and DataTable.
        DA = New MySqlDataAdapter(SQL, ConnectionString)
        DA.Fill(DS, Me.TableOrView)
        DT = DS.Tables(Me.TableOrView)

        'Returning DataTable.
        Return DT
    End Function

    '----------------------------------------------------------------
    ' Creates XML file in desired path.
    '----------------------------------------------------------------
    Public Sub CreateXMLFile(ByVal FilePath As String)
        Dim DT As DataTable

        'Creating DataTable.
        DT = Me.GetDataTable()

        'Writting XML file in desired path.
        DT.WriteXmlSchema(FilePath & Me.TableOrView & ".xml")
    End Sub

End Class

And we also create frmMain form, which only code by the moment will be this:

'--------------------------------------------------------------------
' Author:      Albert Mata (www.albertmata.net)
' Date:        20080731
' Description: Form to show how to create a report using just an XML
'              file. 
'--------------------------------------------------------------------
Public Class frmMain

    '----------------------------------------------------------------
    ' As a first step, creates XML file.
    '----------------------------------------------------------------
    Private Sub frmMain_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
       'Creating XML file.
        Dim RC As New clsReportCreator("zbl_bill2print")
        RC.CreateXMLFile("C:\")
    End Sub

End Class

Right now we have a first application. If we execute it we’ll get C:\zbl_bill2print.xml file with the structure of zbl_bill2print view. So we run it and get that file.

Step 2. Creating report and loading data source.

First, we add a report to our project and give it a name like rptBill.rpt. We create it choosing empty report option, so desestimating any templates.

Now we go to Fields explorer menu and right-click the first option (Database fields). In new contextual menu we click on Database assistant option.

After this we get the Available data source menu, where we choose Create new connection and after that ADO.NET option.

Making this, we’ll see a new form where we’ll be asked about File’s path. In this point we have to find XML file we’ve created before (in my example C:\zbl_bill2print.xml) and then press Finish. We have NewDataSet option including our just added zbl_bill2print in Available data source menu now.

So we select it and press button to move it to Selected tables menu. Done this, it’s time to click on Accept.

With all this stuff we’ve gotten that zbl_bill2print structure available in Fields explorer menu with all its fields, as shown in image below:

Step 3. Designing report.

Nothing special to say here. Just adding fields from Fields explorer menu, inserting text objects where needed, sums, text formats, images and so on…

I’ve just created a very simple design like this:

Step 4. Last actions to get the invoice.

Finally we’re going to create the bill. To do that, we add a CrystalReportViewer object in frmMain form. I call it crvBill. After that it’s necessary to modify frmMain source code to make it look like this:

'--------------------------------------------------------------------
' Author:      Albert Mata (www.albertmata.net)
' Date:        20080731
' Description: Form to show how to create a report using just an XML
'              file. 
'--------------------------------------------------------------------
Imports CrystalDecisions.CrystalReports.Engine

Public Class frmMain

    '----------------------------------------------------------------
    ' Creates XML file (just once) and creates and loads a report.
    '----------------------------------------------------------------
    Private Sub frmMain_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
        'Creating XML file.
        Dim RC As New clsReportCreator("zbl_bill2print")
        'RC.CreateXMLFile("C:\")

        'Creating report.
        Dim RD As ReportDocument = New rptBill()

        'Setting data source for report.
        Dim DT As DataTable = RC.GetDataTable()
        RD.SetDataSource(DT)

        'Setting data source for possible subreports.
        For Each SR As ReportDocument In RD.Subreports
            If SR.Database.Tables.Count > 0 Then
                SR.SetDataSource(DT)
            End If
        Next

        'Setting recently created report must be shown in viewer.
        Me.crvBill.ReportSource = RD
    End Sub

End Class

It’s important to note that the line where the XML file is created is commented now, as we just need to create this file once to use it to create the source data, but from now on we don’t need to generate it every time.

What we’re mainly doing in this code is:

1) creating a report object same kind we’ve designed in step 3,
2) getting a DataTable with data we want to show (in this example and according to the way we’ve defined MySQL view, we want to show invoice number 4),
3) setting this DataTable as the report’s source data,
4) asking CrystalReportViewer to show this report.

We execute the application again and get desired invoice:

Of course there should be quite more information, images and legal texts in a real invoice, but this is just an easy example of how to do the report itself.

So we’ve seen how to create a report in VisualBasic.NET just using an XML file. Of course there are plenty of things to improve, as optimizing how database connection is done, or avoiding WHERE condition directly in MySQL view and so on… but what I was looking for with this example was just a very minimum guide to show the process.

PS. Some menu and option names can be different as I develope in VisualStudio Spanish version and I’ve just translated them as I’ve thought they could appear in English version. Sorry about that!

Update.

There is a second part for this post explaining how to pass parameters from form to report, but it’s still only in Spanish.

Estadística de visitas (200811).

Este pasado mes de noviembre ha sido un mes de parón, tanto en número de visitantes como en posts publicados. He tenido mucho -demasiado- trabajo, he estado de mudanza, he andado liado con la facultad (aunque al final he tenido que renunciar a un par de asignaturas matriculadas porque no llegaba)… y en general he tenido poco tiempo para postear. Espero este mes de diciembre coger de nuevo el ritmo y publicar algunas de las cosas que tengo por ahí pendientes.

De todos modos, y en tanto que utilizo mi propio blog como archivo donde consultar cosas más adelante, hay un par de posts de Pedro Cambra sobre MySQL que descubrí durante este mes y que no quisiera perder de vista y aprovecho para recomendar:

1. Equivalente del ROWNUM de Oracle en MySQL

Como él mismo comenta, no es posible aplicar este método en vistas, pero aún así nos puede ser muy útil en determinados momentos.

2. Mysql - Cómo averiguar el último registro insertado en una tabla

Muy interesante, al menos a mí me lo ha parecido, el hecho de que sea un valor que depende no de usuario ni de base de datos, sino de sesión. Si dependiera de base de datos y fuera una base de datos con muchos usuarios conectados a la vez, sería arriesgadísimo su uso. Si dependiera de usuario y tuviéramos muchas instancias de un mismo programa que se conecta a la base de datos utilizando un único usuario, tampoco podríamos fiarnos de utilizar este sistema. No obstante, al depender de sesión de usuario, en muchas ocasiones es utilísimo.

Y ahora ya paso a las estadísticas del mes.

Noviembre 2008.

Número de visitas totales:       3521     (+  5,45%)
Páginas individuales vistas:     4348     (+  3,16%)
Promedio de tiempo en el sitio:  00:01:00 (-  9,09%)
Porcentaje de rebote:            85,88%   (+  0,76%)
Porcentaje de visitas nuevas:    86,74%   (+  4,32%)

El día con más visitas del mes ha sido el 26 de noviembre con un total de 167.

Y los tres posts que durante el mes han sido más visitados son los mismos que el mes anterior:

1. Informe en .NET con Crystal Reports y base de datos MySQL. (9,31%)
2. Pasando parámetros al informe en .NET con Crystal Reports. (6,60%)
3. Crear librería .dll en .NET… ¡y utilizarla! (4,65%)

De paso, aprovecho para mencionar la nueva política de comentarios que aplico, ya que desde hace unas semanas vengo siendo algo más estricto en lo que a su publicación se refiere. Me he dado cuenta que en poco tiempo han comenzado a llegarme demasiadas preguntas técnicas sobre aspectos unas veces relacionados con el post en cuestión y otras no. Y, aunque lo siento, no dispongo ni del tiempo ni del conocimiento para responder la mayoría de esas preguntas. Es por ello que a partir de ahora (y lo he aplicado con efectos retroactivos borrando muchos comentarios, lo siento) aplico estos criterios:

1. Si tu comentario es realmente eso -un comentario- y no resulta ofensivo, se publicará.

2. Si tu comentario es en realidad una pregunta, lo más probable es que no se publique. Debes pensar que aquí solo la leeré y podré contestar yo, mientras que si expones tus dudas en algún foro o grupo podrás recibir respuestas de mucha más gente y mucho más cualificada que yo.

3. Si pese a todo, quieres enviarme una pregunta, pueden pasar cuatro cosas:

i) Que sepa la respuesta y sea rapidita de contestar. En ese caso publicaré pregunta y respuesta para aumentar el valor del post.

ii) Que sepa la respuesta pero sea largo de contestar. Sintiéndolo mucho pasará al buzón de tareas pendientes hasta que tenga tiempo para ponerme con ello, pero la experiencia me dice que eso en realidad será nunca, con lo cual la pregunta se quedará sin contestar. En realidad como ya sé que lo que ocurriría sería eso, lo que realmente pasará será que directamente eliminaré el comentario.

iii) Que no sepa la respuesta (escenario más probable). En este caso no publicaré el comentario porque como he dicho antes, foros y grupos son sitios más adecuados para formular preguntas sobre cualquier duda técnica.

iv) Que no sepa la respuesta pero sea un tema que me intrigue/motive lo suficiente como para indagarlo. En este caso lo más probable es que tu comentario termine generando un nuevo post, cosa que te agradeceré.

4. Como es evidente, HOYGANs y similares son implacablemente eliminados.

Con estas sencillas normas, confío invertir menos tiempo de ahora en adelante con el tema de los comentarios y sentirme menos culpable cuando borro o dejo de dar respuesta a alguno. No perdamos de vista que los comentarios son justamente eso, comentarios. Pequeños añadidos a un post, reflexiones, opiniones, cosas así.

Por último, han sido bastantes los que me han solicitado que publique el control Matchcode que en su día mencioné. Lo tengo en mente y lo haré tan pronto como pueda. Ocurre que la versión que utilizo en mi aplicación está demasiado personalizada para ella y quisiera poder publicar una versión algo más genérica. Lo haré, sin duda, solo que un poquito más adelante.

MySQL no admite TRANSFORM PIVOT (pero se pueden obtener resultados parecidos).

Estos últimas días he estado mirando un tema que me preocupaba porque iba a necesitarlo en la aplicación que ando desarrollando… y tras no haber encontrado nada, finalmente me he tenido que poner a fondo con ello hasta sacarlo de una u otra manera. El tema consiste en hacer una consulta de referencias cruzadas en MySQL y, como digo, tras bastante investigar he descubierto que en realidad MySQL no admite esta clase de consultas. Para explicar mejor lo que pretendía hacer voy a basarme en un ejemplo consistente en la siguiente tabla sfc_salefrcast:

+---------+---------+------------+---------+
| sfc_cus | sfc_mat | sfc_mth    | sfc_qty |
+---------+---------+------------+---------+
| 12345   |       1 | 2008-11-01 |    1200 |
| 12345   |       3 | 2008-11-01 |    1500 |
| 54321   |       2 | 2008-11-01 |    2500 |
| 54321   |       3 | 2008-12-01 |    3500 |
| 54321   |       3 | 2009-03-01 |    4500 |
| 54321   |       3 | 2009-07-01 |    4500 |
| 99999   |       4 | 2009-02-01 |    2000 |
| 99999   |       4 | 2009-04-01 |    4000 |
| 99999   |       4 | 2009-06-01 |    6000 |
+---------+---------+------------+---------+

Es una tabla de previsiones de ventas en donde sfc_cus es el código del cliente, sfc_mat el código del material, sfc_mth el mes representado por su primer día y sfc_qty la cantidad que prevemos que vamos a venderle de dicho material a dicho cliente dicho mes (y dichosos nosotros si acertamos).

Bien, pues a partir de esta tabla pretendía conseguir una tabla (o vista… un conjunto de registros, vamos) en la que en cada registro se me mostrara una combinación de cliente y material, y a partir de aquí una columna para cada mes con la cantidad correspondiente. Algo del estilo de lo siguiente…

+---------+---------+------+------+------+------+------+------+
| sfc_cus | sfc_mat | M0   | M1   | M2   | M3   | M4   | M5   |
+---------+---------+------+------+------+------+------+------+
| 12345   |       1 | 1200 |    0 |    0 |    0 |    0 |    0 |
| 12345   |       3 | 1500 |    0 |    0 |    0 |    0 |    0 |
| 54321   |       2 | 2500 |    0 |    0 |    0 |    0 |    0 |
| 54321   |       3 |    0 | 3500 |    0 |    0 | 4500 |    0 |
| 99999   |       4 |    0 |    0 |    0 | 2000 |    0 | 4000 |
+---------+---------+------+------+------+------+------+------+

…en donde M0 es el mes actual (2008-11-01), M1 es el mes próximo, M2 el siguiente, etc.

Pues bien, si estuviéramos trabajando en Microsoft Access podríamos hacer algo simple como…

TRANSFORM SUM(sfc_qty) 
    SELECT sfc_cus, sfc_mat
    FROM sfc_salefrcast
    GROUP BY sfc_cus, sfc_mat
PIVOT sfc_mth;

…y obtendríamos algo muy parecido al resultado deseado. Simplemente no nos mostraría las columnas donde no haya ningún valor que mostrar (como M2), pero sería un problema muy menor que solventaríamos sin dificultad. En Microsoft Excel también sería sencillísimo obtener esos resultados a través de una tabla dinámica.

No obstante, trabajo con MySQL. Y por desgracia MySQL no admite TRANSFORM-PIVOT. De hecho no he encontrado ninguna alternativa atractiva para resolver mi problema, de modo que he tenido que rascar un poquito y crear una a medida, que me ha quedado así:

SELECT
    sfc_cus,
    sfc_mat,
    SUM(IF(MID(sfc_mth, 1, 7) = MID(NOW(), 1, 7), sfc_qty, 0)) AS M0,
    SUM(IF(MID(sfc_mth, 1, 7) = 
           MID(DATE_ADD(NOW(),INTERVAL 1 MONTH), 1, 7), 
           sfc_qty, 0))                                        AS M1,
    SUM(IF(MID(sfc_mth, 1, 7) = 
           MID(DATE_ADD(NOW(),INTERVAL 2 MONTH), 1, 7), 
           sfc_qty, 0))                                        AS M2,
    SUM(IF(MID(sfc_mth, 1, 7) = 
           MID(DATE_ADD(NOW(),INTERVAL 3 MONTH), 1, 7), 
           sfc_qty, 0))                                        AS M3,
    SUM(IF(MID(sfc_mth, 1, 7) = 
           MID(DATE_ADD(NOW(),INTERVAL 4 MONTH), 1, 7), 
           sfc_qty, 0))                                        AS M4,
    SUM(IF(MID(sfc_mth, 1, 7) = 
           MID(DATE_ADD(NOW(),INTERVAL 5 MONTH), 1, 7), 
           sfc_qty, 0))                                        AS M5
FROM
    sfc_salefrcast
GROUP BY 
    sfc_cus, sfc_mat;

(Ver actualización 1.)

Al no utilizar directamente fechas sino estar jugando con NOW() y la función DATE_ADD, obtenemos siempre resultados para el mes corriente y los siguientes N meses, independientemente de cuando solicitemos esa información a la base de datos.

Ignoro si hay alguna manera más directa de hacerlo (y si la hay y la conoces, estaré encantadísimo de leerla), pero por lo pronto lo he solucionado así…

Actualización.

1. Siguiendo el consejo -uno de ellos- de Luís Medel en los comentarios, he cambiado la versión original que había publicado para dejar esta más simplificada al realizar en un solo paso el sumatorio y el filtrado. Además, Luís propone utilizar CASE WHEN THEN ELSE END en lugar de IF.

2. Pedro Cambra facilita en los comentarios un link a la página de Roland Bouman donde se expone un método para hacerlo de forma dinámica utilizando procedimientos almacenados.

3. Pedro Cambra comenta también que MySQL dispone de la función EXTRACT con el parámetro YEAR_MONTH que efectivamente se podría utilizar para mejorar el apartado del MID(fecha,1,7). Más información en el manual de referencia de MySQL.

ReDim Preserve para cambiar más de una dimensión en .NET.

Recientemente en grupos de .NET salió el tema de cómo se pueden redimensionar las dos dimensiones de una matriz de dos dimensiones sin perder los valores que ya se tienen almacenados en dicha matriz.

Si fuera una matriz de una dimensión no habría ningún problema, ya que la opción Preserve nos permite hacer precisamente eso:

Dim myArray(3) As Int32
myArray(0) = 2
myArray(1) = 4
myArray(2) = 6
myArray(3) = 8
ReDim Preserve myArray(5)
myArray(4) = 10
myArray(5) = 12

Así, este código no da ningún problema. Y tampoco lo da este otro:

Dim myArray(3, 0) As Int32
myArray(0, 0) = 2
myArray(1, 0) = 4
myArray(2, 0) = 6
myArray(3, 0) = 8
ReDim Preserve myArray(3, 1)
myArray(0, 1) = 10
myArray(1, 1) = 12
myArray(2, 1) = 14
myArray(3, 1) = 1

Ya que aunque es una matriz de dos dimensiones, sólo estamos redimensionando la dimensión situada más a la derecha. En cambio si intentamos hacer esto que sigue:

Dim myArray(3, 0) As Int32
myArray(0, 0) = 2
ReDim Preserve myArray(4, 1)
myArray(4, 1) = 10

Nos dará una excepción de tipo ArrayTypeMismatchException y nos dirá que…

‘ReDim’ sólo puede cambiar la dimensión situada más a la derecha

…porque un ReDim Preserve en una matriz de dos dimensiones sólo puede actuar sobre la última dimensión.

Para solventar esto podemos utilizar la siguiente función:

'--------------------------------------------------------------------
' Author:      Albert Mata (www.albertmata.net)
' Date:        20081118
' Description: Simulates a ReDim Preserve action on 2-dimensions
'              arrays, allowing to change not only the last dimension
'              but both.
'--------------------------------------------------------------------
Public Function ReDimPreserve(ByVal M As Array, _
ByVal NewLimit0 As Integer, ByVal NewLimit1 As Integer) As Array
    If NewLimit0 >= M.GetUpperBound(0) _
    And NewLimit1 >= M.GetUpperBound(1) Then
        Dim NewArray(NewLimit0, NewLimit1) As [Int32]
        For i As Integer = 0 To M.GetUpperBound(0)
            For j As Integer = 0 To M.GetUpperBound(1)
                NewArray.SetValue(M.GetValue(i, j), i, j)
            Next
        Next
        Return NewArray
    Else
        Return M
    End If
End Function

De tal manera que ahora ante el siguiente código, donde creamos una matriz inicialmente de dimensiones (2,3) para luego redimensionarla a (4,5) y por tanto cambiando las dos dimensiones de la matriz, sin perder los valores que ya teníamos almacenados…

Debug.Print("Creating array with dimensions [2,3]")
Dim myArray(2, 3) As [Int32]
Debug.Print("Storing value 12 in position [0,2]")
myArray.SetValue(12, 0, 2)
Debug.Print("Storing value 15 in position [2,3]")
myArray.SetValue(15, 2, 3)
Debug.Print("Upper bound for first dimension = " _
            & (myArray.GetUpperBound(0)))
Debug.Print("Upper bound for second dimension = " _
            & (myArray.GetUpperBound(1)))
Debug.Print("Value in position [0,2] = " _
            & myArray.GetValue(0, 2).ToString)
Debug.Print("Value in position [2,3] = " _
            & myArray.GetValue(2, 3).ToString)
Try
    myArray.SetValue(24, 3, 4)
    Debug.Print("I can store values in position [3,4]")
Catch ex As Exception
    Debug.Print("I can't store values in position [3,4]")
End Try

Debug.Print("Changing array dimensions to [4,5]")
myArray = DirectCast(Me.ReDimPreserve(myArray, 4, 5), Integer(,))
Debug.Print("Upper bound for first dimension = " _
            & (myArray.GetUpperBound(0)))
Debug.Print("Upper bound for second dimension = " _
            & (myArray.GetUpperBound(1)))
Debug.Print("Value in position [0,2] = " _
            & myArray.GetValue(0, 2).ToString)
Debug.Print("Value in position [2,3] = " _
            & myArray.GetValue(2, 3).ToString)
Try
    myArray.SetValue(24, 3, 4)
    Debug.Print("I can store values in position [3,4]")
Catch ex As Exception
    Debug.Print("I can't store values in position [3,4]")
End Try

…obtenemos esta salida en la Ventana Inmediato:

Creating array with dimensions [2,3]
Storing value 12 in position [0,2]
Storing value 15 in position [2,3]
Upper bound for first dimension = 2
Upper bound for second dimension = 3
Value in position [0,2] = 12
Value in position [2,3] = 15
*** 'System.IndexOutOfRangeException' EXCEPTION ***
I can't store values in position [3,4]
Changing array dimensions to [4,5]
Upper bound for first dimension = 4
Upper bound for second dimension = 5
Value in position [0,2] = 12
Value in position [2,3] = 15
I can store values in position [3,4]

La función propuesta no es ni mucho menos perfecta. Tendría que hacerse más genérica, permitir redimensionar no dos sino N dimensiones, controlar mejor posibles errores y demás. Pero puede ser una primera aproximación para resolver situaciones de este tipo…

PD. También es posible utilizar el método Array.Copy en lugar de iterar por las dos dimensiones de la matriz, aunque en este caso lo he hecho así para que resulte más evidente el proceso.

Estadística de visitas (200810).

Venga, una vez más, publico las estadísticas de visitas a este blog del mes recién cerrado. Como siempre, si hay alguien interesado en realizar el seguimiento, puede hacerlo a través del tema habitual visitas, que aglutina todos estos posts. Este mes los resultados han sido especialmente buenos (no me preguntéis por qué) y me da que marcarán un listón difícil de superar por meses venideros.

Octubre 2008.

Número de visitas totales:       3339     (+ 88,75%)
Páginas individuales vistas:     4215     (+ 83,82%)
Promedio de tiempo en el sitio:  00:01:06 (-  8,33%)
Porcentaje de rebote:            85,12%   (+  1,40%)
Porcentaje de visitas nuevas:    82,42%   (+  0,96%)

El día con más visitas del mes ha sido el 28 de octubre con un total de 173.

Y los tres posts que durante el mes han sido más visitados son los siguientes:

1. Informe en .NET con Crystal Reports y base de datos MySQL. (9,21%)
2. Pasando parámetros al informe en .NET con Crystal Reports. (6,81%)
3. Crear librería .dll en .NET… ¡y utilizarla! (6,50%)

Lo cual me gusta porque los posts más visitados venían siendo siempre los mismos y andaba yo ya con la sensación de que no lograba aportar nada que tuviera el mismo interés de nuevo, pero el que en octubre ha sido el segundo más visitado es un post del, precisamente, 1 de octubre.

Y por último… este mes recupero la sección Premio Google del Mes. No es que haya habido una búsqueda especialmente absurda que ha venido a parar a este blog, pero sí ha habido una que me ha resultado curiosa, así que proclamo ganador de esta nueva edición y por tanto merecedor del…

Premio Google del Mes de Octubre del 2008

…a quien hizo la búsqueda en Google:

5 horas para ejecutar un archivo bat

Sorprendente. No quiero ni pensar qué hará dicho archivo (ni cómo)… :-)

Correspondencia entre tipos de datos en MySQL, en VisualBasic.NET y en .NET Framework.

Estos últimos días he estado liado programando una clase que tira intensamente de reflexión (me refiero a System.Reflection, no a que haya estado reflexionando intensamente, que también) para relacionar un objeto de una clase determinada (la que sea) con una tabla en la base de datos. Así, utilizando esa clase auxiliar podemos decirle que cargue un objeto a partir de un registro de una tabla o que lo guarde en ella. Y sirve para cualquier clase que tenga una tabla relacionada en la base de datos. Quizá otro día cuelgo esa clase… Pero el caso es que mientras lo desarrollaba he tenido algunos problemillas por no encajarme exactamente los tipos de datos que me devolvía MySQL y los que esperaba .NET, así que tras haber estado buscando cuáles son las equivalencias exactas, paso a exponer la tabla de correspondencias entre tipos de datos tanto en MySQL como en VisualBasic.NET como en el .NET Framework.

+--------------------+----------+----------------+
|       MYSQL        |  VB.NET  | .NET Framework |
+--------------------+----------+----------------+
| TINYINT            | SByte    | SByte          |
| TINYINT UNSIGNED   | Byte     | Byte           |
| SMALLINT           | Short    | Int16          |
| SMALLINT UNSIGNED  | UShort   | UInt16         |
| MEDIUMINT          | Integer  | Int32          |
| MEDIUMINT UNSIGNED | UInteger | UInt32         |
| INT                | Integer  | Int32          |
| INT UNSIGNED       | UInteger | UInt32         |
| BIGINT             | Long     | Int64          |
| BIGINT UNSIGNED    | ULong    | UInt64         |
+--------------------+----------+----------------+

Teniendo en cuenta estas relaciones, todo funciona a las mil maravillas… :-)

Integer en VisualBasic.NET no es una clase.

Miniapunte que puede parecer absurdo pero que me ha hecho perder un ratito. El caso es que estaba intentando conseguir que un atributo de tipo Integer sin inicializar tuviera un valor nulo, para así poder insertarlo en un registro de una tabla de base de datos como NULL, ya que dada la naturaleza del atributo, un valor 0 (que es como un atributo de tipo Integer queda automáticamente inicializado) y un valor NULL significan cosas muy distintas. Pero resulta que no se puede hacer, ya que Integer no es una clase en VisualBasic.NET sino sólo un tipo de datos.

En el caso de un atributo de tipo String no hay problema, porque String sí puede ser una clase y por tanto sí acepta valores nulos, pero no así en el caso de Integer y Double, que son sólo tipos de datos y automáticamente quedan inicializados a 0. La solución -no óptima- puede pasar por utilizar la clase Object en su lugar o bien -algo más óptima en tanto que menos genérica- utilizar la clase Nullable y crear el atributo de tipo Nullable (Of Integer) o Nullable (Of Double).

Y la confusión me ha venido motivada porque en Java Integer sí es una clase…

En los grupos de discusión de Microsoft, Enrique Martínez ha dado una excelente explicación que copio literalmente:

«Tipos de datos» son todos: el tipo de dato Integer, el tipo de datos String, el tipo de datos Object, etc. Lo que ocurre es que mientras que los tipos de datos Object y String están definidos como «Class» (un tipo de datos por referencia), los tipos de datos como Short, Integer, Long, Single, Double, etc., se encuentran definidos como «Structure» (un tipo de datos por valor). Por este motivo, los valores de los tipos de datos «por referencia» (las clases), se inicializan a un valor «Nothing» en Visual Basic, mientras que los valores de los tipos de datos «por valor» (las estructuras), en principio se inicializan a 0.

Resumiendo, ante el código siguiente:

Dim n As Integer
Debug.Print(”El valor de n es: ” & n.ToString)
Debug.Print(”¿Es n nulo? ” & IsNothing(n))

Se obtiene la salida:

El valor de n es: 0
¿Es n nulo? False

Mientras que ante este otro código:

Dim n As Nullable(Of Integer)
Debug.Print(”El valor de n es: ” & n.ToString)
Debug.Print(”¿Es n nulo? ” & IsNothing(n))

Obtenemos esta otra salida:

El valor de n es:
¿Es n nulo? True

Ídem con Double, Long… ;-)

Pepecar o una mala opción para alquilar un coche.

pepecarComo cualquier autónomo, mis necesidades de desplazamiento son constantes y bastante ineludibles, así que cuando hace cosa de un par de meses mi coche dijo de golpe y sopetón hasta aquí hemos llegado, se me planteó un problema urgente que resolver. Me decidí a contratar un vehículo de renting a través de CaixaRenting (concretamente un Mazda2 que de momento va muy bien), pero como quiera que el plazo de entrega era de entre tres y cuatro semanas, opté también por alquilar un vehículo durante ese período para poder seguir acudiendo a las instalaciones del cliente principal para el cual trabajo.

Así que estuve sopesando varias alternativas para alquilar un coche para salir del apuro durante esas tres semanas. Y de entre todas las opciones y como soy gran defensor de las compañías de bajo coste en todos los sectores, me decidí por alquilar en Pepecar. Craso error. No le recomendaría a nadie hacerlo. Y paso a explicar mis motivos.

Alquilé un Smart Fortwo (el modelo más baratito que tenían disponible) para 22 días. En su página web mostraban un precio de lo más competitivo, algo inferior a las compañías de alquiler de vehículos tradicionales (Europcar, Hertz, Avis, etc). Como muestra, un simulacro que acabo de hacer ahora mismo para un alquiler de 22 días del mismo vehículo y en la misma sucursal:

O sea, unos 300 euros. Un buen precio, sí señor, unos 13 euros diarios, está bien. No obstante, a la que pasamos a la siguiente pantalla de contratación nos encontramos lo siguiente:

pepecar

No necesito silla de niño, no haré más de 500 kilómetros diarios y estoy seguro que no cancelaré. Puedo ahorrarme esos suplementos. La asistencia en carretera tiene un precio coherente y aunque es una práctica bastante triste que tengas que contratarla aparte, la marqué para no tener que andar empujando el Smart si se me quedaba tirado…

No obstante la cobertura extra de daños es exagerada. Prácticamente te dobla el precio del alquiler. O sea, que como reclamo muestran un precio muy interesante y después te dicen… paga el doble de lo que creías que ibas a pagar o hazte cargo de cualquier ralladita o golpecito que le des o te den al coche. No sé, esta clase de prácticas siempre me han parecido muy mediocres, la verdad. Pero el caso es que asumí el riego de tener que abonar los daños que pudiera causarle al coche y no marqué ese importe que casi me doblaba el importe reclamo con el que Pepecar me había llevado hasta ese punto.

Pues bien, durante esas tres semanas hubo un día que fui a recoger el coche donde lo había estacionado y me lo encontré con una rueda menos de las esperadas:

Vamos a ahorrarnos los calificativos que me merece quien se dedica a ir robando ruedecillas ajenas. El caso es que llamé a los Mossos d’Esquadra para presentar la correspondiente denuncia y me dijeron que enseguida venían para levantar acta de daños. Mientras tanto llamé a Pepecar. Aquí viene otra práctica a mi entender patética: su único teléfono de atención telefónica es un 807. O sea, que Pepephone vela por nuestras facturas de móvil pero después Pepecar nos penga un sablazo de padre y muy señor mío como les tengamos que preguntar cualquier cosita. Está bien, sí, sí, muy coherente.

Llegaron los Mossos y para mi sorpresa inicial me comentaron que no iban a levantar acta de daños alguna porque en realidad el coche no presentaba el más mínimo daño (lo cual de hecho era cierto) sino que simplemente había sufrido un robo. Pero que un robo y un daño son cosas distintas y que lo que sí tenía que hacer era ir a la comisaría más cercana a presentar denuncia por robo, pero que de acta de daños nanai.

A partir de aquí me salto las penurias de ese día (caminar arriba y abajo cargando los bártulos, acudir a la comisaría, acudir al taller, utilizar el transporte público -de un polígono industrial, o sea andar mucho-, no poder trabajar en toda la jornada, etc). Hasta que por la tarde me llaman desde Pepecar y me dicen que por la rueda me van a cargar 300 euros en mi tarjeta de crédito porque no contraté la cobertura extra de daños. Les explico que según mi entender (y el de los Mossos) lo que le ha pasado al coche no es un daño sino un robo, y que por tanto no aplica la cobertura de daños y que no soy yo sino la compañía con la que tengan contratado el seguro del coche la que debe hacer frente a la incidencia…

Como no nos ponemos de acuerdo contacto con la Agència Catalana de Consum en el 012 para consultarles el caso. Me hacen saber que Pepecar debe facilitarme una copia de la póliza de seguro que tiene contratada el Smart en cuestión porque así podremos ver si dicha póliza incluye el robo o no (prácticamente cualquier póliza a terceros suele incluir dicha contingencia), ya que si lo incluye es efectivamente la aseguradora y no yo quien debe hacerse cargo del importe de la rueda.

Informo de todo ello a Pepecar, pero me comentan que esa póliza está en la central de Madrid (cómo no) y que en la sucursal no disponen de copias (para qué). Quedo con ellos que para el día que tengo que devolver el coche me lo tengan por favor preparado… y hasta hoy.

Devolví el coche, no tenían la copia de la póliza, les di más días de margen para que por favor me la remitieran via correo electrónico, pero han pasado ya dos semanas desde entonces y no he recibido noticia alguna de Pepecar. Así que les acabo de enviar una hoja de reclamaciones (y archivos adjuntos varios) como primer paso para intentar recuperar esos 300 euros que sigo pensando no deberían haberme reclamado a mí sino a su compañía de seguros.

Pero en cualquier caso, mi recomendación personal para cualquiera que quiera alquilar un coche durante unos días es que no lo haga con Pepecar. Si contratas el seguro opcional te estarás moviendo en los mismos o mayores precios que otras compañías. Si no lo contratas te arriesgas a que cualquier mínimo incidente que tengas (ni hablemos de que tengas la mala fortuna de que te roben el coche) convierta el alquiler en una pequeña ruina. Entiendo que hay mejores alternativas, dadas las circunstancias. Y con suerte no tendrán un número 807 como único número de teléfono para atención telefónica.

PD1. Seguiré informando de cómo evoluciona este tema.

PD2. Todo el personal con el que traté de Pepecar, pese a nuestra manera dispar de entender lo que había pasado, educadísimo y muy amable en el trato, que una cosa no quita la otra.

Actualización.

12 de diciembre de 2008. Tal como comenté cuando escribí este post, tenía la intención de reclamar la devolución de los 300 euros y de seguir informando sobre como evolucionaba este caso. La segunda parte de este post ya esta disponible aquí.

UPDATE my_data SET my_age = 30;

Pues nada, ya de lleno en los treinta y con un montón de proyectos en mente, a ver qué sale de todo ello. De buenas a primeras con este blog llevo ya, sin darme cuenta, más de cinco meses compartiendo miniapuntes de programación (y de lo que va saliendo) y la experiencia está siendo de lo más positiva y enriquecedora. Pero andan también por ahí otros proyectos a los que habrá que ir dando forma cuando las horas no escaseen como estas últimas semanas… Lo que faltan no son ideas, es tiempo.