Archivo de la etiqueta: SQL Server

Posts sobre SQL Server, comandos de SQL, Management Studio, etc.

VarChar y NVarChar en SQL Server y MySQL el código para VB y C#

Pues eso… lo prometido es deuda y aquí tienes el código que te prometí del ejemplo correspondiente al post anterior (VarChar y NVarChar en SQL Server) para VB y C#.

Este código te permite comprobar que usando VARCHAR también puedes agregar caracteres especiales del idioma español: vocales acentuadas, eñes, etc.

En este ejemplo también uso dos bases de datos (en realidad 3), una de SQL Server y otra de MySQL en un servidor que está en la nube (en un hosting de Internet, concretamente en uno de Domitienda.com que es donde yo tengo mis sitios alojados). La tercera base es una de SLQ Server en mi servidor local (el de SQLEXPRESS).

El diseño del formulario y el código de VB y C#

Este es el formulario en modo de diseño (figura 1):

Figura 1. El formulario en modo de diseño.
Figura 1. El formulario en modo de diseño.

Nota:

En el código tienes la cadena de conexión para las bases de datos que están en «la web» y es posible que dejen de estar disponibles dentro de algún tiempo (no sé si una semana, un mes, un año o un día), así que… si quieres probarlo, usa cuanto antes el código del ZIP que te pondré al final o crea un proyecto a partir de lo que aquí te muestro.

El proyecto está creado con Visual Studio 2019 Preview 4.3 pero debería cargar también en Visual Studio 2017 y utiliza el .NET Framework 4.7.2.

El código para Visual Basic .NET

'------------------------------------------------------------------------------
' Prueba de los tipos de SQL VarChar y NVarChar                     (19/Mar/19)
' Usando base de SQL Server y MySQL
'
'
' (c) Guillermo (elGuille) Som, 2019
'------------------------------------------------------------------------------
Option Strict On
Option Infer On

Imports System.Data
Imports System.Data.SqlClient
' Versión de mysql.data.dll: Connector/Net 6.0.3.0
Imports MySql.Data.MySqlClient

Imports System.Linq


Public Class Form1

    Private inicializando As Boolean = True

    ''' <summary>
    ''' Para las primeras pruebas uso una base local
    ''' </summary>
    Private UsarBaseLocal As Boolean = True

    Private ServerName As String = ".\SQLEXPRESS"
    Private DatabaseName As String = "PruebaVarChar"
    Private UsuarioDB As String = "elGuille_info"
    Private PasswordDB As String = "VarChar_19"

    Private Tabla As String = "VarCharElGuille"
    ' Campos: Fecha, ConVarChar varchar(50), ConNVarChar nvarchar(50)
    ' en MySQL no hay NVARCHAR y uso TEXT(50)

    Private MaxFilas As Integer = 5

    ''' <summary>
    ''' La cadena de conexión a la base de datos
    ''' </summary>
    Private Function CadenaConexionSQL() As String
        ConectarSQL()

        Dim csb As New SqlConnectionStringBuilder

        csb.DataSource = ServerName
        csb.InitialCatalog = DatabaseName
        csb.UserID = UsuarioDB & "SQL"
        csb.Password = PasswordDB
        csb.IntegratedSecurity = False

        Return csb.ConnectionString
    End Function

    ''' <summary>
    ''' La cadena de conexión a la base de datos
    ''' </summary>
    Private Function CadenaConexionMySQL() As String
        If UsarBaseLocal Then Return "No hay base local de MySQL"

        ConectarMySQL()

        Dim csb As New MySqlConnectionStringBuilder

        csb.Server = ServerName
        csb.Database = DatabaseName
        csb.UserID = UsuarioDB & "My"
        csb.Password = PasswordDB

        Return csb.ConnectionString
    End Function


    ''' <summary>
    ''' Conectar a la base de datos de SQL Server
    ''' </summary>
    Private Sub ConectarSQL()
        If UsarBaseLocal Then
            ServerName = ".\SQLEXPRESS"
        Else
            ServerName = "sql3.servidoreswindows.net"
        End If
    End Sub

    ''' <summary>
    ''' Conectar a la base de datos de MySQL.
    ''' Siempre en remoto.
    ''' </summary>
    Private Sub ConectarMySQL()
        ServerName = "mysql1.servidoreswindows.net"
    End Sub


    ''' <summary>
    ''' Añadir el dato a la base de SQL Server
    ''' </summary>
    Private Function AñadirSQL() As String
        Dim sCon As String = CadenaConexionSQL()
        Dim sel = $"INSERT INTO {Tabla} (Fecha, ConVarChar, ConNVarChar) " &
                  $"VALUES('{DateTime.Now}', '{txtDato.Text}', '{txtDato.Text}')"

        Using con As New SqlConnection(sCon)
            Dim cmd As New SqlCommand(sel, con)

            Try
                con.Open()
                cmd.ExecuteNonQuery()
                con.Close()

                Return ""
            Catch ex As Exception
                Return $"ERROR: {ex.Message}"
            End Try
        End Using
    End Function

    ''' <summary>
    ''' Añadir el dato a la base de datos de MySQL
    ''' </summary>
    Private Function AñadirMySQL() As String
        If UsarBaseLocal Then Return "No hay base local de MySQL"

        Dim sCon = CadenaConexionMySQL()
        ' En MySQL he definido Fecha como varchar(20)
        Dim sel = $"INSERT INTO {Tabla} (Fecha, ConVarChar, ConNVarChar) " &
                  $"VALUES('{DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss")}', '{txtDato.Text}', '{txtDato.Text}')"

        Using con As New MySqlConnection(sCon)
            Dim cmd As New MySqlCommand(sel, con)

            Try
                con.Open()
                cmd.ExecuteNonQuery()
                con.Close()

                Return ""
            Catch ex As Exception
                Return $"ERROR: {ex.Message}"
            End Try
        End Using
    End Function

    ''' <summary>
    ''' Mostrar los datos de la base de SQLServer
    ''' </summary>
    Private Function MostrarDatosSQL(ByRef dt As DataTable) As String
        Dim orden = If(chkOrden.Checked, "DESC", "ASC")
        Dim sel = $"SELECT TOP ({MaxFilas}) * FROM {Tabla} ORDER BY Fecha {orden}"

        Dim sCon = CadenaConexionSQL()
        Dim da As SqlDataAdapter
        dt = New DataTable

        Try
            ' Crear un nuevo objeto del tipo DataAdapter
            da = New SqlDataAdapter(sel, sCon)

            ' Llenar la tabla con los datos indicados
            da.Fill(dt)

            Return $"Recuperadas {dt.Rows.Count} filas."
        Catch ex As Exception
            Return $"ERROR: {ex.Message}"
        End Try

    End Function

    ''' <summary>
    ''' Mostrar los datos de MySQL
    ''' </summary>
    Private Function MostrarDatosMySQL(ByRef dt As DataTable) As String
        If UsarBaseLocal Then Return "No hay base local de MySQL"

        Dim orden = If(chkOrden.Checked, "DESC", "ASC")
        ' MySQL no tiene TOP, usar en su lugar LIMIT
        Dim sel = $"SELECT * FROM {Tabla} ORDER BY Fecha {orden} LIMIT {MaxFilas}"

        Dim sCon = CadenaConexionMySQL()
        Dim da As MySqlDataAdapter
        dt = New DataTable

        Try
            ' Crear un nuevo objeto del tipo DataAdapter
            da = New MySqlDataAdapter(sel, sCon)

            ' Llenar la tabla con los datos indicados
            da.Fill(dt)

            Return $"Recuperadas {dt.Rows.Count} filas."
        Catch ex As Exception
            Return $"ERROR: {ex.Message}"
        End Try

    End Function

    Private Sub btnAñadir_Click(sender As Object,
                                e As EventArgs) Handles btnAñadir.Click
        Dim msg = ""

        msg = AñadirSQL() & vbCrLf
        msg &= AñadirMySQL()

        LabelError.Text = msg

        btnRefrescar.PerformClick()
    End Sub

    Private Sub btnRefrescar_Click(sender As Object,
                                   e As EventArgs) Handles btnRefrescar.Click
        Dim dt As DataTable = Nothing
        Dim msg = ""
        lvDatos.Items.Clear()

        Dim i = 5
        Integer.TryParse(txtFilas.Text, i)
        MaxFilas = i

        msg = MostrarDatosSQL(dt) & vbCrLf
        asignarDatos("SQL", dt)

        Dim lvi = lvDatos.Items.Add("---")
        lvi.SubItems.Add("---")
        lvi.SubItems.Add("---")
        lvi.SubItems.Add("---")

        dt = Nothing
        msg &= MostrarDatosMySQL(dt)
        asignarDatos("MySQL", dt)

        LabelError.Text = msg
    End Sub

    Private Sub asignarDatos(base As String, dt As DataTable)
        If dt Is Nothing Then Return

        For Each r As DataRow In dt.Rows
            Dim lvi = lvDatos.Items.Add(base)
            lvi.SubItems.Add(r("Fecha").ToString)
            lvi.SubItems.Add(r("ConVarChar").ToString)
            lvi.SubItems.Add(r("ConNVarChar").ToString)
        Next

    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        inicializando = False
    End Sub

    Private Sub ChkUsarBaseLocal_CheckedChanged(sender As Object, e As EventArgs) Handles chkUsarBaseLocal.CheckedChanged
        If inicializando Then Return

        UsarBaseLocal = chkUsarBaseLocal.Checked
    End Sub

End Class

El código para C#

//-----------------------------------------------------------------------------
// Prueba de los tipos de SQL VarChar y NVarChar                    (21/Mar/19)
// Usando base de SQL Server y MySQL
//
//
// (c) Guillermo (elGuille) Som, 2019
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Data.SqlClient;
// Versión de mysql.data.dll: Connector/Net 6.0.3.0
using MySql.Data.MySqlClient;

namespace VarChar_y_NVarChar_cs
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private bool inicializando = true;

        /// <summary>
        /// Para las primeras pruebas uso una base local
        /// </summary>
        private bool UsarBaseLocal = true;

        private string ServerName = @".\SQLEXPRESS";
        private string DatabaseName = "PruebaVarChar";
        private string UsuarioDB = "elGuille_info";
        private string PasswordDB = "VarChar_19";

        private string Tabla = "VarCharElGuille";
        // Campos: Fecha, ConVarChar varchar(50), ConNVarChar nvarchar(50)
        // en MySQL no hay NVARCHAR y uso TEXT(50)

        private int MaxFilas = 5;

        /// <summary>
        /// La cadena de conexión a la base de datos
        /// </summary>
        private string CadenaConexionSQL()
        {
            ConectarSQL();

            var csb = new SqlConnectionStringBuilder();

            csb.DataSource = ServerName;
            csb.InitialCatalog = DatabaseName;
            csb.UserID = UsuarioDB + "SQL";
            csb.Password = PasswordDB;
            csb.IntegratedSecurity = false;

            return csb.ConnectionString;
        }

        /// <summary>
        /// La cadena de conexión a la base de datos
        /// </summary>
        private string CadenaConexionMySQL()
        {
            if (UsarBaseLocal)
                return "No hay base local de MySQL";

            ConectarMySQL();

            var csb = new MySqlConnectionStringBuilder();

            csb.Server = ServerName;
            csb.Database = DatabaseName;
            csb.UserID = UsuarioDB + "My";
            csb.Password = PasswordDB;

            return csb.ConnectionString;
        }


        /// <summary>
        /// Conectar a la base de datos de SQL Server
        /// </summary>
        private void ConectarSQL()
        {
            if (UsarBaseLocal)
                ServerName = @".\SQLEXPRESS";
            else
                ServerName = "sql3.servidoreswindows.net";
        }

        /// <summary>
        /// Conectar a la base de datos de MySQL.
        /// Siempre en remoto.
        /// </summary>
        private void ConectarMySQL()
        {
            ServerName = "mysql1.servidoreswindows.net";
        }


        /// <summary>
        /// Añadir el dato a la base de SQL Server
        /// </summary>
        private string AñadirSQL()
        {
            string sCon = CadenaConexionSQL();
            var sel = $"INSERT INTO {Tabla} (Fecha, ConVarChar, ConNVarChar) " + 
                      $"VALUES('{DateTime.Now}', '{txtDato.Text}', '{txtDato.Text}')";

            using (var con = new SqlConnection(sCon))
            {
                var cmd = new SqlCommand(sel, con);

                try
                {
                    con.Open();
                    cmd.ExecuteNonQuery();
                    con.Close();

                    return "";
                }
                catch (Exception ex)
                {
                    return $"ERROR: {ex.Message}";
                }
            }
        }

        /// <summary>
        /// Añadir el dato a la base de datos de MySQL
        /// </summary>
        private string AñadirMySQL()
        {
            if (UsarBaseLocal)
                return "No hay base local de MySQL";

            var sCon = CadenaConexionMySQL();
            // En MySQL he definido Fecha como varchar(20)
            var sel = $"INSERT INTO {Tabla} (Fecha, ConVarChar, ConNVarChar) " + 
                      $"VALUES('{DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss")}', '{txtDato.Text}', '{txtDato.Text}')";

            using (var con = new MySqlConnection(sCon))
            {
                var cmd = new MySqlCommand(sel, con);

                try
                {
                    con.Open();
                    cmd.ExecuteNonQuery();
                    con.Close();

                    return "";
                }
                catch (Exception ex)
                {
                    return $"ERROR: {ex.Message}";
                }
            }
        }

        /// <summary>
        /// Mostrar los datos de la base de SQLServer
        /// </summary>
        private string MostrarDatosSQL(ref DataTable dt)
        {
            var orden = chkOrden.Checked ? "DESC" : "ASC";
            var sel = $"SELECT TOP ({MaxFilas}) * FROM {Tabla} ORDER BY Fecha {orden}";

            var sCon = CadenaConexionSQL();
            SqlDataAdapter da;
            dt = new DataTable();

            try
            {
                // Crear un nuevo objeto del tipo DataAdapter
                da = new SqlDataAdapter(sel, sCon);

                // Llenar la tabla con los datos indicados
                da.Fill(dt);

                return $"Recuperadas {dt.Rows.Count} filas.";
            }
            catch (Exception ex)
            {
                return $"ERROR: {ex.Message}";
            }
        }

        /// <summary>
        /// Mostrar los datos de MySQL
        /// </summary>
        private string MostrarDatosMySQL(ref DataTable dt)
        {
            if (UsarBaseLocal)
                return "No hay base local de MySQL";

            var orden = chkOrden.Checked ? "DESC" : "ASC";
            // MySQL no tiene TOP, usar en su lugar LIMIT
            var sel = $"SELECT * FROM {Tabla} ORDER BY Fecha {orden} LIMIT {MaxFilas}";

            var sCon = CadenaConexionMySQL();
            MySqlDataAdapter da;
            dt = new DataTable();

            try
            {
                // Crear un nuevo objeto del tipo DataAdapter
                da = new MySqlDataAdapter(sel, sCon);

                // Llenar la tabla con los datos indicados
                da.Fill(dt);

                return $"Recuperadas {dt.Rows.Count} filas.";
            }
            catch (Exception ex)
            {
                return $"ERROR: {ex.Message}";
            }
        }

        private void BtnAñadir_Click(object sender, EventArgs e)
        {
            var msg = "";

            msg = AñadirSQL() + "\n";
            msg += AñadirMySQL();

            LabelError.Text = msg;

            btnRefrescar.PerformClick();
        }

        private void BtnRefrescar_Click(object sender, EventArgs e)
        {
            DataTable dt = null;
            var msg = "";
            lvDatos.Items.Clear();

            var i = 5;
            int.TryParse(txtFilas.Text, out i);
            MaxFilas = i;

            msg = MostrarDatosSQL(ref dt) + "\n";
            asignarDatos("SQL", dt);

            var lvi = lvDatos.Items.Add("---");
            lvi.SubItems.Add("---");
            lvi.SubItems.Add("---");
            lvi.SubItems.Add("---");

            dt = null;
            msg += MostrarDatosMySQL(ref dt);
            asignarDatos("MySQL", dt);

            LabelError.Text = msg;
        }

        private void asignarDatos(string @base, DataTable dt)
        {
            if (dt == null) return;

            foreach (DataRow r in dt.Rows)
            {
                var lvi = lvDatos.Items.Add(@base);
                lvi.SubItems.Add(r["Fecha"].ToString());
                lvi.SubItems.Add(r["ConVarChar"].ToString());
                lvi.SubItems.Add(r["ConNVarChar"].ToString());
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            inicializando = false;
        }

        private void ChkUsarBaseLocal_CheckedChanged(object sender, EventArgs e)
        {
            if (inicializando) return;

            UsarBaseLocal = chkUsarBaseLocal.Checked;
        }
    }
}

Y esto es todo… espero que te sea de utilidad.

Nos vemos.
Guillermo

El código completo del ejemplo
(solución para Visual Studio usando .NET 4.7.2)

El ZIP con el código completo (una solución de Visual Studio 2019 Preview con los proyectos de Visual Basic y C# además de la DLL de MySQL con la referencia a la copia dentro del directorio de la solución.

El zip: VarChar_y_NVarChar_20190321.zip (147 KB)

MD5 Checksum: 888346C11622183F6A39FE5FBA4339D3

Nota:

Aquí tienes el enlace para la descarga del ZIP que me descargué con el conector de MySQL para .NET versión 6.0.3 (mysql-connector-net-6.0.3-noinstall.zip) el tamaño es 1.64MB y el MD5 Checksum es: 57BB7F42645665D5A616A3484041F0C0.

VarChar y NVarChar en SQL Server

Actualizado el 21/Mar/2019 con los pasos para resolver el fallo que tenía de acceso a las bases de datos remotas y también con el enlace al código para VB y C#.

Pues eso… el otro día estaba creando una base de datos de MySQL y me di cuenta que MySQL no tiene el tipo NVARCHAR o al menos a mí no me daba la opción de crear un campo con ese tipo, al menos desde el panel de control del hosting que utilizo (Domitienda.com), así que… utilicé VARCHAR, el problema que me temía que iba a tener era no poder usar las vocales acentuadas (con tilde), la eñe, etc. Pero resultó que sí… que lo aceptaba sin problemas.

Así que, me puse a buscar en Internet a ver cuál era la diferencia entre VARCHAR y NVARCHAR, ya que yo siempre usaba NVARCHAR en mis bases de SQL Server (que es el tipo de base que uso, salvo excepciones muy, muy contadas) porque era el tipo que me permitía usar los caracteres especiales del idioma español (o al menos así lo entendí yo, que puede ser que lo entendiese mal… ).

Pero resulta que si utilizo VARCHAR puedo indicar esos caracteres especiales sin mayor problema.

Por supuesto la información que encontré hablando de esa diferencia entre esos dos tipos de datos era… bueno… no aclaratoria del todo… pero en realidad me daba igual lo que me quisieran decir, ya que yo he comprobado por mí mismo que se puede guardar algo así: Raúl Rodríguez López es de Valdepeñas en un campo del tipo VARCHAR.

Un ejemplo para acceder a SQL Server y MySQL

Aquí tienes una captura de un ejemplo que he hecho para mostrar que sí funciona eso de guardar los caracteres especiales, al menos los usados en español.

El código lo publicaré otro día, ya que es tarde y además no me funciona el código para acceder a las bases de datos remotas (en el hosting de Domitienda), seguramente habré configurado algo mal, ya que a otras que tengo en el mismo hosting si que puedo acceder…

Nota del 21 de Marzo:

Según parece resulta que en el «plesk» del hosting no permite usar el mismo nombre de usuario para varias bases de datos o bien la longitud de la contraseña debe ser menor de la que usé… en cualquier caso lo he solucionado haciendo lo siguiente:

He creado dos usuarios diferentes, el de la base de datos de SQL Server se llama elGuille_infoSQL y el de MySQL se llama elGuille_infoMy.

He usado una clave más corta.

Y ahora todo funciona bien… o casi… ya que añadí un campo DATETIME a la base de MySQL y da error al convertir el contenido de la base de datos a un tipo DateTime de .NET, así que… lo he creado como VARCHAR(20).

Otra cosa con la que me he encontrado con la consulta de MySQL es que no acepta TOP para indicar el número de filas a devolver, en su lugar he usado LIMIT y ya va bien.

En la captura de la Figura 2 puedes ver la aplicación totalmente operativa y accediendo a las bases de datos remotas.

Si te fijas en los valores mostrados al final con todo a cero, eso era lo que había añadido usando el tipo de datos DATETIME en MySQL, después al añadirlo como VARCHAR ya se muestra bien.

El código de VB y C# lo puedes ver en la entrada publicada el 21 de marzo: VarChar y NVarChar en SQL Server y MySQL el código para VB y C#.

Figura 2. La aplicación operativa con acceso remoto después de los cambios hechos en el hosting.
Figura 2. La aplicación operativa con acceso remoto después de los cambios hechos en el hosting.

Figura 1. Formulario de prueba
Figura 1. El formulario de prueba

Los datos mostrados en la figura 1 son de una base de datos de SQL Server usada localmente, el mensaje de error que se muestra es el devuelto por el servidor de MySQL.

Lo importante es que puedes comprobar que tanto el campo VARCHAR como NVARCHAR muestran las vocales con tilde y la eñe.

Seguramente si uso caracteres de otros idiomas como el CHINO o RUSO no se guardarían bien en el VARCHAR… no lo sé porque no lo he probado… 😉

Pues esto es todo por hoy… en los enlaces puedes ver más información sobre esos tipos de datos (en la documentación de SQL Server) y lo que cuentan en algunos sitios sobre la diferencia entre ellos.

Este enlace es sobre VARCHAR en la documentación de MySQL.

A ver si el jueves próximo (día 21, luna llena, por más señas) te pongo el código de ejemplo de la aplicación que guarda esos caracteres en los dos tipos de datos y como de costumbre tanto para Visual Basic como para C#.

Nos vemos.
Guillermo

Nota:
Según el panel de control la versión de MySQL es:

mysql1.servidoreswindows.net
Versión del servidor: 5.1.73-log

Error al guardar datos decimales: El valor del parámetro ‘xxx’ está fuera del intervalo

Pues eso… aunque ya te expliqué cómo definir los decimales en un campo de una tabla de SQL Server (El número de decimales del tipo decimal de SQL Server) aquí te amplio ese mismo tema (o casi), ya que estuve trasteando con esto de los decimales, y… bueno, no presté mucha atención a la explicación de los argumentos indicados al declarar un tipo decimal o numeric de Transact-SQL (si la hubiese prestado no estaría escribiendo esto Winking smile).
Te copio/pego (buen invento este del Copy&Paste) lo que dice la página de la documentación de Transact-SQL sobre decimal y numeric:

Argumentos

decimal[ (p[ ,s] )] y numeric[ (p[ ,s] )]
Números de precisión y escala fijas. Cuando se utiliza la precisión máxima, los valores válidos se sitúan entre – 10^38 +1 y 10^38 – 1. Los sinónimos ISO para decimal son dec y dec(p, s). numeric equivale desde el punto de vista funcional a decimal.

p (precisión)
El número total máximo de dígitos decimales que almacenará, tanto a la izquierda como a la derecha del separador decimal. La precisión debe ser un valor comprendido entre 1 y la precisión máxima de 38. La precisión predeterminada es 18.

s (escala)
El número de dígitos decimales que se almacenará a la derecha del separador decimal. Este número se extrae de p para determinar el número máximo de dígitos a la izquierda del separador decimal. La escala debe ser un valor comprendido entre 0 y p. Solo es posible especificar la escala si se ha especificado la precisión. La escala predeterminada es 0; por tanto, 0 <= s <= p. Los tamaños de almacenamiento máximo varían según la precisión.

Si lees eso y lo asimila pues… no hace falta que sigas leyendo… Eye rolling smile pero ya que estás aquí… anda… sigue leyendo… Be right back

Pues yo… no me lo leí bien… y como iba a trabajar con campos para guardar el porcentaje de IVA, actualmente en España son: 4% (0,5% de RE), 10% (1,40% de RE) y 21% (5,20% de RE); así que pensé que declarando el tipo como decimal (4,4) me iba bien… ya que pensé Angel que el primer 4 sería para las cifras a la izquierda del separador decimal y los otros 4 para lo que haya después del separador decimal), ¡craso error! (pero en este caso sería: ¡Guille error! Rolling on the floor laughing

Bueno, el error fue el que me dio el SQL Server al querer guardar el valor del IVA del 4%, es decir, guardar un simple valor «cuatro» (con 4 decimales) .
El error fue:
El valor de parámetro ‘4,0000’ está fuera del intervalo.

Nota:
El error «El valor de parámetro ‘4,0000’ está fuera del intervalo.» me lo dio usando la inserción de datos usando Update en un DataAdapter.
Al usar ExecuteNonQuery o ExecuteScalar el error que da es: «Error de desbordamiento aritmético al convertir numeric al tipo de datos numeric.«
Estos dos casos de forma «programativa».
Si se añade desde Mangement Studio de SQL Server, el error que da es: «El nombre de columna o los valores especificados no corresponden a la definición de la tabla.«

Sí, estaba usando el objeto DataAdapter Smile para crear o actualizar los datos.

El error y la solución

El tema está en que si declaras un valor decimal de esta forma: decimal(4,4) esto lo que hace es definir el tipo decimal para 1ue acepte un máximo de 4 cifras (el primer argumento), incluidas las que van después del separador, y como está definido con 4 cifras decimales (el segundo valor) pues… ¡solo aceptará las 4 cifras decimales!
Por tanto… ¡error al canto! al menos si se asigna algún valor a la izquierda del separador decimal. Es decir, si le asignamos 4,0 dará error, pero si le asignas hasta un valor máximo de 0,9999 no lo dará. Otra cosa es que le asignes algo como: 0,9999876 en este caso si lo dará, ya que al redondear las cifras decimales a 4 se creará un valor mayor de cero, en este caso el valor que intentará guardar es: 1,0000.

Una solución sería definir el tipo para que acepte más cifras, por ejemplo:
decimal(6,4) permitirá 6 cifras en total, 4 de ellas para los decimales y 2 para la parte entera, de esta forma podremos guardar sin problemas valores hasta 99,9999.

Y esto sería todo, pero… te voy a mostrar código, que si no… no tienen gracia Winking smile

Código de ejemplo

Empezaré con un código Transact-SQL para crear dos tablas en una base de datos que previamente habrás creado con Management Studio de SQL Server llamada ErrorDecimal.

En la primera tabla (MiTabla1) definimos un tipo decimal(4,4):

use ErrorDecimal

CREATE TABLE dbo.MiTabla1 
(  
  Decimal_4_4 decimal(4,4)
 
);  

GO 

-- 21.5 dará error al asignar a decimal(4,4) ya que ese número tendría 6 cifras: el 21 y los 4 decimales
-- incluso 4.0 también daría error, pero no 0.9999

INSERT INTO dbo.MiTabla1 VALUES (21,5);  
GO 
SELECT Decimal_4_4 
FROM dbo.MiTabla1;

En la segunda tabla (MiTabla2) definimos dos columnas como decimal(6,4) y decimal(18,6):

use ErrorDecimal

CREATE TABLE dbo.MiTabla2 
(  
  Decimal_6_4 decimal(6,4)  
 ,Decimal_18_6 decimal(18,6)

);  

GO 

-- 99.9991912 no dará error al asignar a decimal(6,4) ya que solo aceptará los 4 primeros dígitos decimales
-- el valor que tendrá será: 99.9992
-- decimal(18,6) aceptará 18 cifras en total: 12 a la izquierda y 6 a la derecha
-- 999,999,999,999.9999199 mostrará 999999999999.999920
INSERT INTO dbo.MiTabla2 VALUES (99.9991912, 999999999999.9999199);  
GO 
SELECT Decimal_6_4, Decimal_18_6  
FROM dbo.MiTabla2;

Puedes ejecutar ese código y verás que te dará error al asignar el valor de MiTabla1.
En el código de MiTabla2 funcionará perfectamente (salvo que uses valores fuera de rango).

Una aplicación de ejemplo (Windows Forms)

Lo siguiente que te voy a mostrar es el código para usar una aplicación de Windows Forms (al final del artículo te he puesto los enlaces para el código de Visual Basic así como para C#).

Para poder usar este código debes definir en la base de datos un usuario llamado UsuarioErrDec con la clave 123456.

Nota:
Si no sabes cómo crear el usuario y la contraseña… ¡jum!
Bueno, tendrás que esperar a otro artículo que tengo preparado sobre este tema… o casi… Winking smile

Antes de poner el «tocho» de código (y las capturas del formulario en modo diseño) te diré las cosas que vas a encontrarte en el código, a ver si así te animas a seguir leyendo Smile

Base de datos:
1- Crear la cadena de conexión usando SqlConnectionStringBuilder.
2- Añadir nuevos datos a las tablas usando SqlConnection y ExecuteNonQuery.
2a- Los métodos de añadir utilizan una tupla (Tuple) para devolver dos valores: un valor Boolean para saber si hubo o no error y un valor String con el error o un mensaje de que se han añadido correctamente los datos.
3- Leer los datos de las tablas usando ExecuteReader y devolviendo una cadena con los nombres de las columnas y los valores.

Código general: Detectar las pulsaciones en los TextBox numéricos usando una función (AceptarTeclas)
4- En las cajas de texto numéricas permitir solo números y el signo menos y el separador decimal que definamos (en este ejemplo se permite la coma como separador y no se acepta el punto.
5- Permitir en las teclas numéricas la pulsación de la tecla INTRO y BORRAR.
indicar cual será el separador decimal y solo permitir números (y el signo menos)
6- Te explico el «fallillo» de que el punto siempre se acepta, salvo que se intercepte la pulsación.
7- Si se pulsa la tecla INTRO pasar al siguiente control.

Código general: Interceptar a nivel de formulario la pulsación de las teclas de edición (Ctrl+C, Ctrl+V, Ctrl+X y Ctrl+Z) en los controles de texto y trabajar con el portapapeles ¡sin necesidad de usar el objeto ClipBoard!
8- Las pulsaciones de las teclas se comprueban en el evento KeyDown del formulario.
9- Se utiliza ActiveControl y se comprueba si es una caja de texto.

El formulario en tiempo de diseño.

error_decimal_01

Comentarte que para que funcione lo de las «tuplas» en Visual Basic debes usar el .NET 4.7.2, por tanto yo he usado esa versión del compilador con el Visual Studio 2017 versión 15.9.3.
He usado la versión Professional, pero también funciona con la Community (gratuita). Por curiosidad, también lo he probado con el Visual Studio Enterprise 2019 (versión 16.0.0 Preview 1.0) y todo funciona perfectamente.

Y esto es todo amigos. Espero que te sea de utilidad Smile

Nos vemos.
Guillermo

P.S.
Este artículo está publicado originalmente en mi blog elguillemola.com pero también lo encontrarás en mi sitio elguille.info (realmente accede al post publicado en el blog).

El código de Visual Basic .NET y C#

Lo he puesto aparte para que sea más cómodo leerlo.

Estos son los enlaces en el blog, pero te recomiendo que lo veas en los publicados en elguille.info ya que el código se ve mejor Winking smile:

El código de Visual Basic .NET (blog)
El código de C# (blog)

El código de Visual Basic (elguille.info)
El código de C# (elguille.info)

Nota:
Convertido con mi utilidad gsConvertirCodigo y modificado manualmente ya que aún no «entiende» bien todo esto de la inferencia de tipos y las tuplas Smile

Por favor, cualquier comentario sobre el código por favor coméntalo en los posts correspondientes; aquí también puedes comentar, pero mejor en cada post para que el que vea ese post concreto sepa que se puede mejorar o lo que quieras que comentes, gracias.



Código de ejemplo (comprimido):

El zip con el código completo para Visual Basic y C#

El zip: SQL_Error_decimal.zip (en downloads.elguille.info)

Contiene una solución de Visual Studio 2017 con los proyectos para VB y C#

( MD5 Checksum: 2232781E047C1ED1A48DD00C7CA8E6F5 )

Error al guardar datos decimales – El código para C#

Aquí tienes el código para C# de la aplicación de ejemplo del artículo Error al guardar datos decimales: El valor del parámetro ‘xxx’ está fuera del intervalo.

Nota:
Si buscabas el código para Visual Basic está en este otro enlace:
Error al guardar datos decimales – El código para Visual Basic.

Cuando lo tenga publicado, te pondré el enlace para descargar la solución de Visual Studio 2017 tanto para VB como para C#.

El código de C#

//-----------------------------------------------------------------------------
//  Ejemplo para el error de asignar a decimal(4,4)                 (07/Dic/18)
//
// (c) Guillermo (elGuille) Som, 2018
//-----------------------------------------------------------------------------

using System;
using System.Data.SqlClient;
using System.Text;
using System.Windows.Forms;

namespace SQL_Error_decimal_cs
{
    public partial class Form1 : Form
    {


        //--------------------------------------------------------------------------
        // Los campos para acceder a la base de datos
        //--------------------------------------------------------------------------

        /// <summary>
        /// El usuario para acceder a la base de datos de SQL Server.<br />
        /// Si es una cadena vacía se usará la seguridad integrada de Windows.
        /// </summary>
        private string userDb = "UsuarioErrDec";

        /// <summary>
        /// El password del usuario que accede a la base de datos de SQL Server
        /// </summary>
        private string passwordDB = "123456";

        /// <summary>
        /// El servidor donde está la base de datos.<br />
        /// Normalmente será .\SQLEXPRESS o (local)
        /// </summary>
        private string serverName = @".\SQLEXPRESS";
        /// <summary>
        /// El nombre de la base de datos de SQL Server
        /// </summary>
        private string databaseName = "ErrorDecimal";

        /// <summary>
        /// Devuelve la cadena de conexión a la base de datos de SQL Server<br />
        /// Si el usuario es una cadena vacía, se usará la seguridad integrada de Windows
        /// </summary>
        private string ConnectionString {
            get {
                var sb = new SqlConnectionStringBuilder();
                sb.DataSource = serverName;
                sb.InitialCatalog = databaseName;
                if (String.IsNullOrWhiteSpace(userDb))
                {
                    sb.IntegratedSecurity = true;
                } else
                {
                    sb.UserID = userDb;
                    sb.Password = passwordDB;
                }
                return sb.ConnectionString;
            }
        }



        //--------------------------------------------------------------------------
        // Añadir un valor a las tablas
        //--------------------------------------------------------------------------
        private (bool hayError, string msg) AñadirMiTabla1(decimal valor)
        {
            var sel = "INSERT INTO MiTabla1 (Decimal_4_4) " +
                      "VALUES (@Decimal_4_4)";

            var retVal = (hayError: false, msg: "");

            var sCon = ConnectionString;

            using (SqlConnection con = new SqlConnection(sCon))
            {
                var cmd = new SqlCommand(sel, con);
                cmd.Parameters.AddWithValue("@Decimal_4_4", valor);

                con.Open();

                try
                {
                    var ret = Convert.ToInt32(cmd.ExecuteNonQuery());

                    retVal.hayError = (ret < 1);
                    retVal.msg = "Todo OK. cmd.ExecuteNonQuery() = " + ret.ToString();
                }
                catch (Exception ex)
                {
                    retVal.msg = ex.Message;
                    retVal.hayError = true;
                }
                con.Close();
            }
            return retVal;
        }

        private (bool hayError, string msg) AñadirMiTabla2(decimal[] valores)
        {
            var sel = "INSERT INTO MiTabla2 (Decimal_6_4, Decimal_18_6) " +
                      "VALUES (@Decimal_6_4, @Decimal_18_6)";

            var retVal = (hayError:false, msg:"");
            
            var sCon = ConnectionString;

            using (SqlConnection con = new SqlConnection(sCon))
            {
                SqlCommand cmd = new SqlCommand(sel, con);
                cmd.Parameters.AddWithValue("@Decimal_6_4", valores[0]);
                cmd.Parameters.AddWithValue("@Decimal_18_6", valores[1]);

                con.Open();

                try
                {
                    var ret = Convert.ToInt32(cmd.ExecuteNonQuery());

                    retVal.hayError = (ret < 1);
                    retVal.msg = "Todo OK. cmd.ExecuteNonQuery() = " + ret.ToString();
                }
                catch (Exception ex)
                {
                    retVal.msg = ex.Message;
                    retVal.hayError = true;
                }
                con.Close();
            }
            return retVal;
        }



        private string leerMiTabla(string tabla)
        {
            var sel = "SELECT * FROM " + tabla;

            var retVal = "";

            var sCon = ConnectionString;

            using (SqlConnection con = new SqlConnection(sCon))
            {
                var cmd = new SqlCommand(sel, con);

                con.Open();

                try
                {
                    var ret = cmd.ExecuteReader();

                    StringBuilder sb = new StringBuilder();

                    while (ret.Read())
                    {
                        sb.AppendLine(String.Format("{0} = {1}", ret.GetName(0), ret[0]));

                        if (ret.FieldCount > 1)
                            sb.AppendLine(String.Format("{0} = {1}", ret.GetName(1), ret[1]));
                    };
                    retVal = sb.ToString();
                }
                catch (Exception ex)
                {
                    retVal = "ERROR: " + ex.Message;
                }
                con.Close();
            };
            return retVal;
        }

        //--------------------------------------------------------------------------
        // Para aceptar la coma como decimal en las cajas numéricas
        //--------------------------------------------------------------------------
        /// <summary>
        /// El separador de decimales para los campos numéricos
        /// </summary>
        const string SeparadorDecimal = ",";

        /// <summary>
        /// Para indicar qué tecla "decimal" no se debe admitir
        /// </summary>
        const string NoSeparadorDecimal = ".";

        /// <summary>
        /// Comprobar si se aceptan las teclas en una caja de texto.
        /// En la pulsación de los controles numéricos
        /// aceptar solo los caracteres numéricos,
        /// el valor negativo, el separador de decimales
        /// y las teclas Intro, Delete, Back (borrar hacia atrás)
        ///
        /// Es raro, si teclasAceptadas es: ",-1234567890" también acepta el punto
        /// </summary>
        private char AceptarTeclas(KeyPressEventArgs e, string teclasAceptadas)
        {
            char c = e.KeyChar;

            if (c == Convert.ToChar(Keys.Return))
            {
                // con esto hacemos que se ignore la pulsación
                e.Handled = true;
                // se manda al siguiente control
                SendKeys.Send("{TAB}");
            }
            else if (c == Convert.ToChar(NoSeparadorDecimal))
            {
                e.KeyChar = Convert.ToChar(SeparadorDecimal);
            }
            else if (teclasAceptadas.Contains(c.ToString()))
            {
                // no hacer nada, se aceptan
            }
            else if (c == Convert.ToChar(Keys.Delete) || c == Convert.ToChar(Keys.Back))
            {
                // no hacer nada, se aceptan
            }
            else
            {
                e.Handled = true;
            }
            return c;
        }



        //--------------------------------------------------------------------------
        // Los métodos de evento del formulario
        //--------------------------------------------------------------------------

        private void btnCerrar_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void btnAsignarTabla1_Click(object sender, EventArgs e)
        {
            var d = 0M;
            Decimal.TryParse(txtTabla1_campo1.Text, out d);
            var ret = AñadirMiTabla1(d);

            txtMensaje1.Text = "";
            if (ret.hayError)
                txtMensaje1.Text = "ERROR\r\n";
            txtMensaje1.Text += ret.msg;
        }

        private void btnAsignarTabla2_Click(object sender, EventArgs e)
        {
            var valores = new decimal[12];
            var d = 0M;

            Decimal.TryParse(txtTabla2_campo1.Text, out d);
            valores[0] = d;

            d = 0M;
            Decimal.TryParse(txtTabla2_campo2.Text, out d);
            valores[1] = d;

            var ret = AñadirMiTabla2(valores);

            txtMensaje2.Text = "";
            if (ret.hayError)
                txtMensaje2.Text = "ERROR\r\n";
            txtMensaje2.Text += ret.msg;
        }

        private void btnMostrar1_Click(object sender, EventArgs e)
        {
            // mostrar los datos de MiTabla1
            txtMensaje1.Text = leerMiTabla("MiTabla1");
        }

        private void btnMostrar2_Click(object sender, EventArgs e)
        {
            // mostrar los datos de MiTabla2
            txtMensaje2.Text = leerMiTabla("MiTabla2");
        }

        private void txt_KeyPress(object sender, KeyPressEventArgs e)
        {
            AceptarTeclas(e, SeparadorDecimal + "-1234567890");
        }

        public Form1()
        {
            InitializeComponent();
        }

<

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            // detecta la pulsación de las teclas en el formulario
            // antes de mandarla a los controles
            // En el diseñador de formularios tienes que
            // asignar un valor True a la propiedad KeyPreview

            if (e.Modifiers == Keys.Control)
            {


                if (e.KeyCode == Keys.C)
                {
                    // copiar el texto
                    if (ActiveControl is TextBox)
                    {
                        //Dim texto = ActiveControl.Text
                        //Clipboard.SetText(texto)
                        var txt = ActiveControl as TextBox;
                        if (txt == null) return;
                        txt.Copy();

                        e.Handled = true;
                    }
                }
                else if (e.KeyCode == Keys.V)
                {
                    // pegar el texto
                    if (ActiveControl is TextBox)
                    {
                        var txt = ActiveControl as TextBox;
                        if (txt == null) return;
                        txt.Paste();

                        e.Handled = true;
                    }
                }
                else if (e.KeyCode == Keys.X)
                {
                    // cortar el texto
                    if (ActiveControl is TextBox)
                    {
                        var txt = ActiveControl as TextBox;
                        if (txt == null) return;
                        txt.Cut();

                        e.Handled = true;
                    }
                }
                else if (e.KeyCode == Keys.Z)
                {
                    //deshacer
                    if (ActiveControl is TextBox)
                    {
                        var txt = ActiveControl as TextBox;
                        if (txt == null) return;
                        if (txt.CanUndo)
                        {
                            txt.Undo();
                        }
                        e.Handled = true;
                    }
                }
            }
        }
    }
}

Espero que te sea de utilidad.

Nos vemos
Guillermo

Error al guardar datos decimales – El código para Visual Basic

Aquí tienes el código para Visual Basic de la aplicación de ejemplo del artículo Error al guardar datos decimales: El valor del parámetro ‘xxx’ está fuera del intervalo.

Nota:
Si buscabas el código para C# está en este otro enlace:
Error al guardar datos decimales – El código para C#.

Cuando lo tenga publicado, te pondré el enlace para descargar la solución de Visual Studio 2017 tanto para VB como para C#.

El código de Visual Basic .NET

'------------------------------------------------------------------------------
'  Ejemplo para el error de asignar a decimal(4,4)                  (07/Dic/18)
'
' (c) Guillermo (elGuille) Som, 2018
'------------------------------------------------------------------------------
Option Strict On
Option Infer On

Imports System
'Imports System.Data
Imports System.Data.SqlClient
Imports System.Text

Public Class Form1
    '--------------------------------------------------------------------------
    ' Los campos para acceder a la base de datos
    '--------------------------------------------------------------------------

    ''' <summary>
    ''' El usuario para acceder a la base de datos de SQL Server.<br />
    ''' Si es una cadena vacía se usará la seguridad integrada de Windows.
    ''' </summary>
    Private userDb As String = "UsuarioErrDec"

    ''' <summary>
    ''' El password del usuario que accede a la base de datos de SQL Server
    ''' </summary>
    Private passwordDB As String = "123456"

    ''' <summary>
    ''' El servidor donde está la base de datos.<br />
    ''' Normalmente será .\SQLEXPRESS o (local)
    ''' </summary>
    Private serverName As String = ".\SQLEXPRESS" ' "(local)"
    ''' <summary>
    ''' El nombre de la base de datos de SQL Server
    ''' </summary>
    Private databaseName As String = "ErrorDecimal"

    ''' <summary>
    ''' Devuelve la cadena de conexión a la base de datos de SQL Server<br />
    ''' Si el usuario es una cadena vacía, se usará la seguridad integrada de Windows
    ''' </summary>
    Private ReadOnly Property ConnectionString As String
        Get
            With New SqlConnectionStringBuilder
                .DataSource = serverName
                .InitialCatalog = databaseName
                If String.IsNullOrWhiteSpace(userDb) Then
                    .IntegratedSecurity = True
                Else
                    .UserID = userDb
                    .Password = passwordDB
                End If

                Return .ConnectionString
            End With
        End Get
    End Property

    '--------------------------------------------------------------------------
    ' Añadir un valor a las tablas
    '--------------------------------------------------------------------------
    Private Function AñadirMiTabla1(valor As Decimal) As (hayError As Boolean,
                                                         msg As String)
        Dim sel = "INSERT INTO MiTabla1 (Decimal_4_4) 
                   VALUES (@Decimal_4_4)"

        Dim retVal = (hayError:=False, msg:="")

        Dim sCon = ConnectionString

        Using con As New SqlConnection(sCon)
            Dim cmd As New SqlCommand(sel, con)

            cmd.Parameters.AddWithValue("@Decimal_4_4", valor)

            con.Open()

            Try
                Dim ret = CInt(cmd.ExecuteNonQuery())
                'Dim ret = CInt(cmd.ExecuteScalar())

                retVal.hayError = (ret < 1)
                retVal.msg = "Todo OK. cmd.ExecuteNonQuery() = " & ret.ToString

            Catch ex As Exception
                retVal.msg = ex.Message
                retVal.hayError = True
            End Try

            con.Close()
        End Using

        Return retVal
    End Function

    Private Function AñadirMiTabla2(valores() As Decimal) As (hayError As Boolean,
                                                         msg As String)
        Dim sel = "INSERT INTO MiTabla2 (Decimal_6_4, Decimal_18_6) 
                   VALUES (@Decimal_6_4, @Decimal_18_6)"

        Dim retVal = (hayError:=False, msg:="")

        Dim sCon = ConnectionString

        Using con As New SqlConnection(sCon)
            Dim cmd As New SqlCommand(sel, con)

            cmd.Parameters.AddWithValue("@Decimal_6_4", valores(0))
            cmd.Parameters.AddWithValue("@Decimal_18_6", valores(1))

            con.Open()

            Try
                Dim ret = CInt(cmd.ExecuteNonQuery())

                retVal.hayError = (ret < 1)
                retVal.msg = "Todo OK. cmd.ExecuteNonQuery() = " & ret.ToString

            Catch ex As Exception
                retVal.msg = ex.Message
                retVal.hayError = True
            End Try

            con.Close()
        End Using

        Return retVal
    End Function

    Private Function leerMiTabla(tabla As String) As String
        Dim sel = "SELECT * FROM " & tabla

        Dim retVal = ""

        Dim sCon = ConnectionString

        Using con As New SqlConnection(sCon)
            Dim cmd As New SqlCommand(sel, con)

            con.Open()

            Try
                Dim ret = cmd.ExecuteReader

                Dim sb As New StringBuilder

                While ret.Read()
                    sb.AppendLine(String.Format("{0} = {1}", ret.GetName(0), ret(0)))

                    If ret.FieldCount > 1 Then
                        sb.AppendLine(String.Format("{0} = {1}", ret.GetName(1), ret(1)))
                    End If
                End While

                retVal = sb.ToString

            Catch ex As Exception
                retVal = "ERROR: " & ex.Message
            End Try

            con.Close()
        End Using

        Return retVal
    End Function


    '--------------------------------------------------------------------------
    ' Para aceptar la coma como decimal en las cajas numéricas
    '--------------------------------------------------------------------------

    ''' <summary>
    ''' El separador de decimales para los campos numéricos
    ''' </summary>
    Private Const SeparadorDecimal As String = ","

    ''' <summary>
    ''' Para indicar qué tecla "decimal" no se debe admitir
    ''' </summary>
    Private Const NoSeparadorDecimal As String = "."

    ''' <summary>
    ''' Comprobar si se aceptan las teclas en una caja de texto.
    ''' En la pulsación de los controles numéricos
    ''' aceptar solo los caracteres numéricos, 
    ''' el valor negativo, el separador de decimales
    ''' y las teclas Intro, Delete, Back (borrar hacia atrás)
    ''' 
    ''' Es raro, si teclasAceptadas es: ",-1234567890" también acepta el punto
    ''' </summary>
    Private Function AceptarTeclas(e As KeyPressEventArgs, teclasAceptadas As String) As Char
        Dim c = e.KeyChar
        If c = Convert.ToChar(Keys.Return) Then
            ' con esto hacemos que se ignore la pulsación
            e.Handled = True
            ' se manda al siguiente control
            SendKeys.Send("{TAB}")
        ElseIf c = Convert.ToChar(NoSeparadorDecimal) Then
            e.KeyChar = Convert.ToChar(SeparadorDecimal)
        ElseIf teclasAceptadas.Contains(c) Then
            ' no hacer nada, se aceptan
        ElseIf c = Convert.ToChar(Keys.Delete) OrElse
               c = Convert.ToChar(Keys.Back) Then
            ' no hacer nada, se aceptan
        Else
            e.Handled = True
        End If

        Return c
    End Function

    '--------------------------------------------------------------------------
    ' Los métodos de evento del formulario
    '--------------------------------------------------------------------------

    Private Sub btnCerrar_Click(sender As Object, e As EventArgs) Handles btnCerrar.Click
        Me.Close()
    End Sub

    Private Sub btnAsignarTabla1_Click(sender As Object, e As EventArgs) Handles btnAsignarTabla1.Click
        Dim d = 0@
        Decimal.TryParse(txtTabla1_campo1.Text, d)
        Dim ret = AñadirMiTabla1(d)

        txtMensaje1.Text = ""
        If ret.hayError Then
            txtMensaje1.Text = "ERROR" & vbCrLf
        End If
        txtMensaje1.Text &= ret.msg
    End Sub

    Private Sub btnAsignarTabla2_Click(sender As Object, e As EventArgs) Handles btnAsignarTabla2.Click
        Dim valores(1) As Decimal
        Dim d = 0@
        Decimal.TryParse(txtTabla2_campo1.Text, d)
        valores(0) = d
        d = 0@
        Decimal.TryParse(txtTabla2_campo2.Text, d)
        valores(1) = d

        Dim ret = AñadirMiTabla2(valores)

        txtMensaje2.Text = ""
        If ret.hayError Then
            txtMensaje2.Text = "ERROR" & vbCrLf
        End If
        txtMensaje2.Text &= ret.msg
    End Sub

    Private Sub txt_KeyPress(sender As Object, e As KeyPressEventArgs) Handles _
                                        txtTabla2_campo1.KeyPress, txtTabla1_campo1.KeyPress,
txtTabla2_campo2.KeyPress AceptarTeclas(e, SeparadorDecimal &
"-1234567890") End Sub Private Sub btnMostrar1_Click(sender As Object, e As EventArgs) Handles btnMostrar1.Click ' mostrar los datos de MiTabla1 txtMensaje1.Text = leerMiTabla("MiTabla1") End Sub Private Sub btnMostrar2_Click(sender As Object, e As EventArgs) Handles btnMostrar2.Click ' mostrar los datos de MiTabla2 txtMensaje2.Text = leerMiTabla("MiTabla2") End Sub Private Sub Form1_KeyDown(sender As Object, e As KeyEventArgs) Handles MyBase.KeyDown ' detecta la pulsación de las teclas en el formulario ' antes de mandarla a los controles ' En el diseñador de formularios tienes que ' asignar un valor True a la propiedad KeyPreview If e.Modifiers = Keys.Control Then If e.KeyCode = Keys.C Then ' copiar el texto If TypeOf ActiveControl Is TextBox Then 'Dim texto = ActiveControl.Text 'Clipboard.SetText(texto) Dim txt = TryCast(ActiveControl, TextBox) If txt Is Nothing Then Return txt.Copy() e.Handled = True End If ElseIf e.KeyCode = Keys.V Then ' pegar el texto If TypeOf ActiveControl Is TextBox Then Dim txt = TryCast(ActiveControl, TextBox) If txt Is Nothing Then Return txt.Paste() e.Handled = True End If ElseIf e.KeyCode = Keys.X Then ' cortar el texto If TypeOf ActiveControl Is TextBox Then Dim txt = TryCast(ActiveControl, TextBox) If txt Is Nothing Then Return txt.Cut() e.Handled = True End If ElseIf e.KeyCode = Keys.Z Then 'deshacer If TypeOf ActiveControl Is TextBox Then Dim txt = TryCast(ActiveControl, TextBox) If txt Is Nothing Then Return If txt.CanUndo Then txt.Undo() End If e.Handled = True End If End If End If End Sub End Class

Espero que te sea de utilidad.

Nos vemos
Guillermo

Evitar el aviso Saving changes is not permitted al modificar una tabla con Microsoft SQL Server Management Studio

Pues eso… ya me ha ocurrido varias veces que al modificar una tabla de una base de datos de SQL Server me daba un aviso indicando que no podía guardar los datos al modificar la estructura de la tabla: cambiar alguna columna, añadir alguna nueva o eliminar una existente. El aviso en inglés es el siguiente:

Saving changes is not permitted. The changes that you have made require the following tables to be dropped and re-created. You have either made changes to a table that can’t be re-created or enabled the option Prevent saving changes that require the table to be re-created.

La última vez que me pasó (hace unos días) me puse a buscar en la web el porqué de este mensaje (que después de saber la solución me resulta más evidente que en el propio mensaje te dice –o casi- el porqué Smile).

La solución para evitarlo es cambiar la configuración en el propio Management Studio de SQL Server (en mi caso estoy usando el de la versión 17.9 instalada con SQL Server Express 2017), ahora te lo explico.

La solución ahora está «destacada» al buscar con Google o con Bing esa frase (la indicada en el título) y muestra la dada en «stackoverflow.com«, pero esa no fue la que usé… – en realidad ya no me acuerdo I don't know smile, pero creo que fue un artículo de Confluence Support del que te dejo también el enlace), en cualquier caso lo que debes hacer es lo siguiente:

En el Management Studio ve al menú Tools (Herramientas) selecciona Options… (Opciones) y en el cuadro de diálogo mostrado selecciona en el panel izquierdo Designers (Diseñadores) y quita la marca en la opción Prevent saving changes that require table re-creation (no sé qué texto pone en el Management Studio en español, pero como verás en la siguiente captura de ducho cuadro de diálogo es la última de las casillas seleccionables –checkboxes-).

SQLMS_Tools_Designers_ed
Figura 1 – Opción a quitar para evitar el aviso

Y eso es todo… espero que te sirva Winking smile

Nos vemos.
Guillermo

Cuidado con las horas de las fechas en las consultas de SQL

Pues eso… que esto de las fechas en las consultas es como el cuento de nunca acabar… al menos para mí, jeje.

Lo que me ha pasado esta vez es que he estado usando fechas en una consulta y, como ya comenté hace años, la mejor forma de pasarlas a la consulta de SQL es usando parámetros, hasta aquí todo bien.

' Crear un nuevo objeto del tipo DataAdapter
da = New SqlDataAdapter(sel, sCon)
da.SelectCommand.Parameters.AddWithValue("@fecha1", fecha1)
da.SelectCommand.Parameters.AddWithValue("@fecha2", fecha2)

En esta ocasión, las fechas las he pasado como argumento a un método normal con el tipo de datos de las fechas (DateTime o Date en VB). Aclaro esto porque cuando se convierte un objeto del tipo fecha a una cadena (que es lo que supongo que hace el SQL cuando va a trabajar con la fecha) ese objeto del tipo fecha se convierte en una fecha y una hora (ver la figura 1).

fechas_sql_02jun2016_peq
Figura 1. Los objetos de tipo Date (DateTime) contienen la fecha y la hora.

En este ejemplo, el valor pasado en el primer argumento tiene la fecha y la hora a las 12 de la madrugada, es decir a las 00:00:00 que es lo que ocurre cuando conviertes una fecha de cadena a tipo DateTime.

Pero si la fecha la obtienes, por ejemplo, de un DateTimePicker o simplemente usando DateTime.Now que obtiene la fecha (y hora) actual. En el caso de que ese valor se obtenga a la hora en que escribo esto, mostrará un valor de fecha 2 de junio de 2016 y la hora 13 (01 PM) y 24 minutos con nosecuantos segundos (ver la figura 2).

fechas_sql_02jun2016_2
Figura 2. Now devuelve la fecha y hora actual completa.

En este caso la propiedad Now del objeto fecha devuelve la fecha y la hora actual, y lo que hace la función esa que muestro en la figura 2 (NowDate) es devolver solo la fecha, sin hora… aunque la hora siempre está, pero si no se indica será 00:00:00 (media noche).

Bueno, espero que te pueda servir de ayuda en casos como el que me pasó a mí, que aún habiendo un dato (de la misma fecha en la que hacía la comprobación) no me lo mostraba porque la hora era (por ejemplo las 9 de la noche) y esa fecha no era igual (o menor) que la que estaba guardada en la base de datos (que era el mismo día, pero a las cero horas), en fin…

Nos vemos.
Guillermo

Cuidado con las horas de las fechas en las consultas de SQL

Pues eso… que esto de las fechas en las consultas es como el cuento de nunca acabar… al menos para mí, jeje.

Lo que me ha pasado esta vez es que he estado usando fechas en una consulta y, como ya comenté hace años, la mejor forma de pasarlas a la consulta de SQL es usando parámetros, hasta aquí todo bien.

' Crear un nuevo objeto del tipo DataAdapter
da = New SqlDataAdapter(sel, sCon)
da.SelectCommand.Parameters.AddWithValue("@fecha1", fecha1)
da.SelectCommand.Parameters.AddWithValue("@fecha2", fecha2)

En esta ocasión, las fechas las he pasado como argumento a un método normal con el tipo de datos de las fechas (DateTime o Date en VB). Aclaro esto porque cuando se convierte un objeto del tipo fecha a una cadena (que es lo que supongo que hace el SQL cuando va a trabajar con la fecha) ese objeto del tipo fecha se convierte en una fecha y una hora (ver la figura 1).

fechas_sql_02jun2016_peq
Figura 1. Los objetos de tipo Date (DateTime) contienen la fecha y la hora.

En este ejemplo, el valor pasado en el primer argumento tiene la fecha y la hora a las 12 de la madrugada, es decir a las 00:00:00 que es lo que ocurre cuando conviertes una fecha de cadena a tipo DateTime.

Pero si la fecha la obtienes, por ejemplo, de un DateTimePicker o simplemente usando DateTime.Now que obtiene la fecha (y hora) actual. En el caso de que ese valor se obtenga a la hora en que escribo esto, mostrará un valor de fecha 2 de junio de 2016 y la hora 13 (01 PM) y 24 minutos con nosecuantos segundos (ver la figura 2).

fechas_sql_02jun2016_2
Figura 2. Now devuelve la fecha y hora actual completa.

En este caso la propiedad Now del objeto fecha devuelve la fecha y la hora actual, y lo que hace la función esa que muestro en la figura 2 (NowDate) es devolver solo la fecha, sin hora… aunque la hora siempre está, pero si no se indica será 00:00:00 (media noche).

Bueno, espero que te pueda servir de ayuda en casos como el que me pasó a mí, que aún habiendo un dato (de la misma fecha en la que hacía la comprobación) no me lo mostraba porque la hora era (por ejemplo las 9 de la noche) y esa fecha no era igual (o menor) que la que estaba guardada en la base de datos (que era el mismo día, pero a las cero horas), en fin…

Nos vemos.
Guillermo

Cuidado con las horas de las fechas en las consultas de SQL

 

Pues eso… que esto de las fechas en las consultas es como el cuento de nunca acabar… al menos para mí, jeje.

Lo que me ha pasado esta vez es que he estado usando fechas en una consulta y, como ya comenté hace años, la mejor forma de pasarlas a la consulta de SQL es usando parámetros, hasta aquí todo bien.

 

' Crear un nuevo objeto del tipo DataAdapter
da = New SqlDataAdapter(sel, sCon)
da.SelectCommand.Parameters.AddWithValue("@fecha1", fecha1)
da.SelectCommand.Parameters.AddWithValue("@fecha2", fecha2)

 

En esta ocasión, las fechas las he pasado como argumento a un método normal con el tipo de datos de las fechas (DateTime o Date en VB). Aclaro esto porque cuando se convierte un objeto del tipo fecha a una cadena (que es lo que supongo que hace el SQL cuando va a trabajar con la fecha) ese objeto del tipo fecha se convierte en una fecha y una hora (ver la figura 1).

fechas_sql_02jun2016_peq
Figura 1. Los objetos de tipo Date (DateTime) contienen la fecha y la hora.

En este ejemplo, el valor pasado en el primer argumento tiene la fecha y la hora a las 12 de la madrugada, es decir a las 00:00:00 que es lo que ocurre cuando conviertes una fecha de cadena a tipo DateTime.

Pero si la fecha la obtienes, por ejemplo, de un DateTimePicker o simplemente usando DateTime.Now que obtiene la fecha (y hora) actual. En el caso de que ese valor se obtenga a la hora en que escribo esto, mostrará un valor de fecha 2 de junio de 2016 y la hora 13 (01 PM) y 24 minutos con nosecuantos segundos (ver la figura 2).

fechas_sql_02jun2016_2
Figura 2. Now devuelve la fecha y hora actual completa.

En este caso la propiedad Now del objeto fecha devuelve la fecha y la hora actual, y lo que hace la función esa que muestro en la figura 2 (NowDate) es devolver solo la fecha, sin hora… aunque la hora siempre está, pero si no se indica será 00:00:00 (media noche).

 

Bueno, espero que te pueda servir de ayuda en casos como el que me pasó a mí, que aún habiendo un dato (de la misma fecha en la que hacía la comprobación) no me lo mostraba porque la hora era (por ejemplo las 9 de la noche) y esa fecha no era igual (o menor) que la que estaba guardada en la base de datos (que era el mismo día, pero a las cero horas), en fin…

Nos vemos.
Guillermo

ejemplo de ExecuteScalar para leer una columna de una tabla

 

Pues eso… que esto seguro que lo tengo mil veces publicado en mi sitio, pero hoy lo he ido a buscar, y no lo he encontrado de forma concreta, así que… aquí está para que "yo" lo encuentre… jejeje

¿Por qué buscaba esto?

El caso es el siguiente: necesitaba saber el ID (u otro campo) de una tabla de una base de datos de SQL Server Express 2014 (el tipo de base de datos es indiferente siempre que tenga el método que te comento) haciendo una consulta, concretamente usando LIKE en la propia cadena de selección o consulta.

La consulta es la siguiente:

String.Format( "SELECT ID FROM Clientes WHERE Nombre LIKE '%{0}%' AND Apellidos LIKE '%{1}%'",

nombre, apellidos)

No indico la forma de asignar esa cadena para que uses VB o C#, en el primer caso tendrías que indicar Dim sel = y en el segundo var sel =

A continuación creamos un objeto del tipo Connection y otro del tipo Command y hacemos una llamada al método ExecuteScalar y el valor que devuelva será (en este caso concreto) el ID que estamos buscando o CERO si no lo ha encontrado.

Simplificando (sin tratamiento de error y esas cosas), si el acceso a la base de datos es de SQL Server (en este ejemplo en una instancia de SQLEXPRESS) y usamos las clases definidas en System.Data.SqlClient, el código sería algo así:

Visual Basic:

Public Shared Function BuscarCliente2(nombre As String, apellidos As String) As Integer
    Dim elID As Integer = 0

    Dim sel =
        String.Format(
            "SELECT ID FROM Clientes WHERE Nombre LIKE '%{0}%' AND Apellidos LIKE '%{1}%'",
            nombre, apellidos)

    Dim sCon = ".\sqlexpress;Initial Catalog=LaBaseDeDatos;Integrated Security=True"

    Using con As New SqlConnection(sCon)
        Dim cmd As New SqlCommand(sel, con)

        con.Open()

        elID = CInt(cmd.ExecuteScalar())

        con.Close()
    End Using

    Return elID
End Function

 

C#:

public static int BuscarCliente2(string nombre, string apellidos)
{
    int elID = 0;

    var sel =
        String.Format(
            "SELECT ID FROM Clientes WHERE Nombre LIKE '%{0}%' AND Apellidos LIKE '%{1}%'",
            nombre, apellidos);

    var sCon = @".\sqlexpress;Initial Catalog=LaBaseDeDatos;Integrated Security=True";

    using (SqlConnection con = new SqlConnection(sCon))
    {
        SqlCommand cmd = new SqlCommand(sel, con);

        con.Open();

        elID = Convert.ToInt32(cmd.ExecuteScalar());

        con.Close();
    }

    return elID;
}

 

Y esto es todo amigos… 🙂

 

Nos vemos.

Guillermo