Controlar valores Null
Se utiliza un valor NULL en una base de datos relacional cuando el valor de una columna es desconocido o falta. Un valor NULL no es una cadena vacía (para tipos de datos de caracteres o de fecha y hora) ni un valor cero (para tipos de datos numéricos). La especificación ANSI SQL-92 indica que un valor NULL debe ser el mismo para todos los tipos de datos, de modo que todos los valores NULL se traten de manera uniforme. El espacio de nombres System.Data.SqlTypes proporciona la semántica de NULL implementando la interfaz INullable. Cada uno de los tipos de datos de System.Data.SqlTypes tiene su propia propiedad IsNull
y un valor Null
que se puede asignar a una instancia de ese tipo de datos.
Nota:
En la versión 2.0 de .NET Framework se introduce la compatibilidad con tipos de valor que aceptan valores NULL, lo que permite a los programadores ampliar un tipo de valor para representar todos los valores del tipo subyacente. Estos tipos de valor CLR que aceptan valores NULL representan una instancia de la estructura Nullable. Esta funcionalidad es especialmente útil cuando se aplica la conversión boxing y la conversión unboxing de tipos de valor, lo que proporciona compatibilidad mejorada con los tipos de objeto. Los tipos de valor CLR que aceptan valores NULL no están pensados para el almacenamiento de valores NULL de base de datos, porque un valor NULL ANSI SQL no se comporta del mismo modo que una referencia null
(o Nothing
, en Visual Basic). Para trabajar con valores NULL de ANSI SQL de la base de datos, utilice valores NULL de tipo System.Data.SqlTypes en lugar de Nullable. Para más información sobre el trabajo con tipos de valor CLR que aceptan valores NULL en Visual Basic, consulte Tipos de valor que aceptan valores NULL y, para C#, consulte Tipos de valor que aceptan valores NULL.
Valores NULL y la lógica de tres valores
Al permitir valores NULL en definiciones de columna, se introduce la lógica de tres valores en la aplicación. Una comparación puede evaluarse en una de estas tres condiciones:
True
False
Desconocido
Dado que se considera que NULL es desconocido, dos valores NULL comparados entre sí no se consideran iguales. En las expresiones que usan operadores aritméticos, si alguno de los operandos es NULL, el resultado es NULL también.
Valores NULL y SqlBoolean
La comparación entre cualquier System.Data.SqlTypes devolverá un SqlBoolean. La función IsNull
para cada SqlType
devuelve un valor SqlBoolean y se puede usar para comprobar si hay valores NULL. Las siguientes tablas truth muestran cómo funcionan los operadores AND, OR y NOT en presencia de un valor NULL. (T = true, F = false y U = Unknown, o NULL).
Descripción de la opción ANSI_NULLS
System.Data.SqlTypes proporciona la misma semántica que cuando se establece la opción ANSI_NULLS en SQL Server. Todos los operadores aritméticos (+, -, *, /, %), operadores a nivel de bit (~, &, |) y la mayoría de las funciones devuelven NULL si alguno de los operandos o argumentos es nulo, excepto en el caso de la propiedad IsNull
.
El estándar ANSI SQL-92 no admite columnName = NULL en una cláusula WHERE. En SQL Server, la opción ANSI_NULLS controla la nulabilidad predeterminada en la base de datos y la evaluación de las comparaciones con respecto a los valores NULL. Si ANSI_NULLS está activado (valor predeterminado), se debe usar el operador IS NULL en expresiones al comprobar si hay valores NULL. Por ejemplo, la siguiente comparación genera siempre UNKNOWN cuando ANSI_NULLS está activado:
colname > NULL
La comparación con una variable que contenga un valor NULL también produce Unknown:
colname > @MyVariable
Use el predicado IS NULL o IS NOT NULL para probar un valor NULL. Esto puede hacer más compleja la cláusula WHERE. Por ejemplo, la columna TerritoryID de la tabla AdventureWorks Customer permite valores NULL. Si una instrucción SELECT debe comprobar la existencia de valores NULL además de otros, debe incluir un predicado IS NULL:
SELECT CustomerID, AccountNumber, TerritoryID
FROM AdventureWorks.Sales.Customer
WHERE TerritoryID IN (1, 2, 3)
OR TerritoryID IS NULL
Si desactiva ANSI_NULLS en SQL Server, puede crear expresiones que usen el operador de igualdad para comparar con NULL. Sin embargo, no puede evitar que diferentes conexiones establezcan opciones NULL para esa conexión. El uso de IS NULL para probar los valores NULL funciona siempre, independientemente de la configuración de ANSI_NULLS para una conexión.
No se admite la desactivación de ANSI_NULLS en un DataSet
, que siempre sigue el estándar ANSI SQL-92 para administrar valores NULL en System.Data.SqlTypes.
Asignación de valores NULL
Los valores NULL son especiales, y su semántica de asignación y almacenamiento difiere en diferentes sistemas de tipos y sistemas de almacenamiento. Un valor Dataset
está diseñado para usarse con diferentes sistemas de almacenamiento y tipos.
En esta sección se describe la semántica de valores NULL para asignar valores NULL a un parámetro DataColumn en un parámetro DataRow en los distintos sistemas de tipos.
DBNull.Value
Esta asignación es válida para un parámetro DataColumn
de cualquier tipo. Si el tipo implementa INullable
, DBNull.Value
se convierte en el valor NULL fuertemente tipado adecuado.
SqlType.Null
Todos los tipos de datos System.Data.SqlTypes implementan INullable
. Si el valor NULL fuertemente tipado se puede convertir en el tipo de datos de la columna mediante operadores de conversión implícitos, la asignación debería avanzar. En caso contrario, se produce una excepción de conversión no válida.
null
Si "null" es un valor válido para el tipo de datos DataColumn
especificado, se convierte en el parámetro DbNull.Value
adecuado o el parámetro Null
asociado al tipo de INullable
(SqlType.Null
)
derivedUdt.Null
En el caso de las columnas de UDT, los valores NULL se almacenan siempre en función del tipo asociado a DataColumn
. Considere el caso de un UDT asociado a un DataColumn
que no implementa INullable
, mientras su subclase sí lo hace. En este caso, si se asigna un valor NULL fuertemente tipado asociado a la clase derivada, se almacena como un DbNull.Value
sin tipo, ya que el almacenamiento NULL siempre es coherente con el tipo de datos de DataColumn.
Nota:
La estructura Nullable<T>
o Nullable no se admite actualmente en DataSet
.
El valor predeterminado de cualquier instancia System.Data.SqlTypes es NULL.
Los valores NULL de System.Data.SqlTypes son específicos del tipo y no se pueden representar con un valor único, como DbNull
. Use la propiedad IsNull
para comprobar si hay valores NULL.
Los valores NULL se pueden asignar a un parámetro DataColumn como se muestra en el ejemplo de código siguiente. Puede asignar directamente valores NULL a variables SqlTypes
sin desencadenar una excepción.
Ejemplo
En el ejemplo de código siguiente se crea un parámetro DataTable con dos columnas definidas como SqlInt32 y SqlString. El código agrega una fila de valores conocidos y una fila de valores NULL y, a continuación, recorre en iteración DataTable, asignando los valores a las variables y mostrando el resultado en la ventana de la consola.
static void WorkWithSqlNulls()
{
DataTable table = new();
// Specify the SqlType for each column.
DataColumn idColumn =
table.Columns.Add("ID", typeof(SqlInt32));
DataColumn descColumn =
table.Columns.Add("Description", typeof(SqlString));
// Add some data.
DataRow nRow = table.NewRow();
nRow["ID"] = 123;
nRow["Description"] = "Side Mirror";
table.Rows.Add(nRow);
// Add null values.
nRow = table.NewRow();
nRow["ID"] = SqlInt32.Null;
nRow["Description"] = SqlString.Null;
table.Rows.Add(nRow);
// Initialize variables to use when
// extracting the data.
SqlBoolean isColumnNull = false;
SqlInt32 idValue = SqlInt32.Zero;
SqlString descriptionValue = SqlString.Null;
// Iterate through the DataTable and display the values.
foreach (DataRow row in table.Rows)
{
// Assign values to variables. Note that you
// do not have to test for null values.
idValue = (SqlInt32)row["ID"];
descriptionValue = (SqlString)row["Description"];
// Test for null value in ID column.
isColumnNull = idValue.IsNull;
// Display variable values in console window.
Console.Write("isColumnNull={0}, ID={1}, Description={2}",
isColumnNull, idValue, descriptionValue);
Console.WriteLine();
}
Private Sub WorkWithSqlNulls()
Dim table As New DataTable()
' Specify the SqlType for each column.
Dim idColumn As DataColumn = _
table.Columns.Add("ID", GetType(SqlInt32))
Dim descColumn As DataColumn = _
table.Columns.Add("Description", GetType(SqlString))
' Add some data.
Dim row As DataRow = table.NewRow()
row("ID") = 123
row("Description") = "Side Mirror"
table.Rows.Add(row)
' Add null values.
row = table.NewRow()
row("ID") = SqlInt32.Null
row("Description") = SqlString.Null
table.Rows.Add(row)
' Initialize variables to use when
' extracting the data.
Dim isColumnNull As SqlBoolean = False
Dim idValue As SqlInt32 = SqlInt32.Zero
Dim descriptionValue As SqlString = SqlString.Null
' Iterate through the DataTable and display the values.
For Each row In table.Rows
' Assign values to variables. Note that you
' do not have to test for null values.
idValue = CType(row("ID"), SqlInt32)
descriptionValue = CType(row("Description"), SqlString)
' Test for null value with ID column
isColumnNull = idValue.IsNull
' Display variable values in console window.
Console.Write("isColumnNull={0}, ID={1}, Description={2}", _
isColumnNull, idValue, descriptionValue)
Console.WriteLine()
Next row
End Sub
Este ejemplo genera los siguientes resultados:
isColumnNull=False, ID=123, Description=Side Mirror
isColumnNull=True, ID=Null, Description=Null
Asignación de varias columnas (filas)
DataTable.Add
, DataTable.LoadDataRow
u otras API que aceptan un parámetro ItemArray que se asigna a una fila, asignan "null" al valor predeterminado de DataColumn. Si un objeto de la matriz contiene DbNull.Value
o su homólogo fuertemente tipado, se aplican las mismas reglas que se han descrito anteriormente.
Además, las siguientes reglas se aplican a una instancia de asignaciones NULL DataRow.["columnName"]
:
El valor predeterminado es
DbNull.Value
para todas las columnas excepto aquellas NULL fuertemente tipadas en las que se encuentra el valor NULL fuertemente tipado.Los valores NULL nunca se escriben durante la serialización en archivos XML (como en "xsi: nil").
Todos los valores no NULL, incluidos los valores predeterminados, siempre se escriben durante la serialización a XML. Esto se diferencia de la semántica de XSD/XML, en la que un valor NULL (xsi:nil) es explícito y el valor predeterminado es implícito (si no está presente en XML, un analizador de validación puede obtenerlo de un esquema XSD asociado). Lo contrario es true para un parámetro
DataTable
: un valor NULL es implícito y el valor predeterminado es explícito.Todos los valores de columna que faltan para las filas leídas de la entrada XML se asignan como NULL. A las filas creadas con NewRow o métodos similares se les asigna el valor predeterminado de DataColumn.
El método IsNull devuelve
true
paraDbNull.Value
yINullable.Null
.
Comparación de valores NULL con SqlTypes y tipos CLR
Al comparar valores NULL, es importante comprender la diferencia entre la forma en que el método Equals
evalúa los valores NULL en System.Data.SqlTypes en comparación con el modo en que funciona con tipos CLR. Todos los métodos System.Data.SqlTypes de Equals
utilizan la semántica de base de datos para evaluar valores NULL: si uno o ambos de los valores son NULL, la comparación da como resultado NULL. Por otro lado, el uso del método Equals
de CLR en dos System.Data.SqlTypes dará como resultado true si ambos son NULL. Esto refleja la diferencia entre el uso de un método de instancia como el método String.Equals
de CLR y el uso del método estático o compartido, SqlString.Equals
.
En el ejemplo siguiente se muestra la diferencia en los resultados entre el método SqlString.Equals
y el método String.Equals
cuando cada uno de ellos pasa un par de valores NULL y, a continuación, un par de cadenas vacías.
static void CompareNulls()
{
// Create two new null strings.
SqlString a = new();
SqlString b = new();
// Compare nulls using static/shared SqlString.Equals.
Console.WriteLine("SqlString.Equals shared/static method:");
Console.WriteLine(" Two nulls={0}", SqlStringEquals(a, b));
// Compare nulls using instance method String.Equals.
Console.WriteLine();
Console.WriteLine("String.Equals instance method:");
Console.WriteLine(" Two nulls={0}", StringEquals(a, b));
// Make them empty strings.
a = "";
b = "";
// When comparing two empty strings (""), both the shared/static and
// the instance Equals methods evaluate to true.
Console.WriteLine();
Console.WriteLine("SqlString.Equals shared/static method:");
Console.WriteLine(" Two empty strings={0}", SqlStringEquals(a, b));
Console.WriteLine();
Console.WriteLine("String.Equals instance method:");
Console.WriteLine(" Two empty strings={0}", StringEquals(a, b));
}
static string SqlStringEquals(SqlString string1, SqlString string2)
{
// SqlString.Equals uses database semantics for evaluating nulls.
var returnValue = SqlString.Equals(string1, string2).ToString();
return returnValue;
}
static string StringEquals(SqlString string1, SqlString string2)
{
// String.Equals uses CLR type semantics for evaluating nulls.
var returnValue = string1.Equals(string2).ToString();
return returnValue;
}
}
Private Sub CompareNulls()
' Create two new null strings.
Dim a As New SqlString
Dim b As New SqlString
' Compare nulls using static/shared SqlString.Equals.
Console.WriteLine("SqlString.Equals shared/static method:")
Console.WriteLine(" Two nulls={0}", SqlStringEquals(a, b))
' Compare nulls using instance method String.Equals.
Console.WriteLine()
Console.WriteLine("String.Equals instance method:")
Console.WriteLine(" Two nulls={0}", StringEquals(a, b))
' Make them empty strings.
a = ""
b = ""
' When comparing two empty strings (""), both the shared/static and
' the instance Equals methods evaluate to true.
Console.WriteLine()
Console.WriteLine("SqlString.Equals shared/static method:")
Console.WriteLine(" Two empty strings={0}", SqlStringEquals(a, b))
Console.WriteLine()
Console.WriteLine("String.Equals instance method:")
Console.WriteLine(" Two empty strings={0}", StringEquals(a, b))
End Sub
Private Function SqlStringEquals(ByVal string1 As SqlString, _
ByVal string2 As SqlString) As String
' SqlString.Equals uses database semantics for evaluating nulls.
Dim returnValue As String = SqlString.Equals(string1, string2).ToString()
Return returnValue
End Function
Private Function StringEquals(ByVal string1 As SqlString, _
ByVal string2 As SqlString) As String
' String.Equals uses CLR type semantics for evaluating nulls.
Dim returnValue As String = string1.Equals(string2).ToString()
Return returnValue
End Function
El código genera el siguiente resultado:
SqlString.Equals shared/static method:
Two nulls=Null
String.Equals instance method:
Two nulls=True
SqlString.Equals shared/static method:
Two empty strings=True
String.Equals instance method:
Two empty strings=True