다음을 통해 공유


Null 값 처리

관계형 데이터베이스의 null 값은 열의 값을 알 수 없거나 누락된 경우에 사용됩니다. null은 빈 문자열(문자 또는 날짜/시간 데이터 형식의 경우)도 아니고 숫자 데이터 형식의 경우 0 값도 아닙니다. ANSI SQL-92 사양은 모든 null이 일관되게 처리되도록 모든 데이터 형식에 대해 null이 동일해야 한다고 명시하고 있습니다. System.Data.SqlTypes 네임스페이스는 INullable 인터페이스를 구현하여 null 의미 체계를 제공합니다. 각 데이터 형식 System.Data.SqlTypes 에는 자체 IsNull 속성과 Null 해당 데이터 형식의 인스턴스에 할당할 수 있는 값이 있습니다.

비고

.NET Framework 버전 2.0에서는 프로그래머가 기본 형식의 모든 값을 나타내도록 값 형식을 확장할 수 있는 nullable 값 형식에 대한 지원을 도입했습니다. 이러한 CLR nullable 값 형식은 Nullable 구조체의 인스턴스를 나타냅니다. 이 기능은 값 형식이 boxed 및 unboxed인 경우 특히 유용하므로 개체 형식과의 호환성이 향상됩니다. CLR nullable 값 형식은 ANSI SQL null이 null 참조(또는 Visual Basic에서는 Nothing)와 동일한 방식으로 동작하지 않으므로 데이터베이스 null을 저장하기 위한 용도가 아닙니다. 데이터베이스 ANSI SQL null 값을 사용할 때는 System.Data.SqlTypes 대신 Nullable null을 사용하세요. Visual Basic에서 CLR 값 nullable 형식을 사용하는 방법에 대한 자세한 내용은 Nullable 값 형식을 참조하고 C#의 경우 Nullable 값 형식을 참조하세요.

Null 및 Three-Valued 논리

열 정의에서 null 값을 허용하면 애플리케이션에 세 가지 값 논리가 도입됩니다. 비교는 다음 세 가지 조건 중 하나로 평가할 수 있습니다.

  • 진실

  • 거짓

  • 알 수 없음

null은 알 수 없는 것으로 간주되므로 서로 비교하여 두 null 값이 같은 것으로 간주되지 않습니다. 산술 연산자를 사용하는 식에서 피연산자가 null이면 결과도 null입니다.

Null 및 SqlBoolean

System.Data.SqlTypes의 비교 결과는 SqlBoolean을 반환합니다. IsNull의 각 함수는 SqlType 함수를 SqlBoolean 반환하며 null 값을 확인하는 데 사용할 수 있습니다. 다음 진리 표에서는 AND, OR 및 NOT 연산자가 null 값이 있는 상태에서 작동하는 방법을 보여 줍니다. (T=true, F=false 및 U=unknown 또는 null입니다.)

진실 테이블

ANSI_NULLS 옵션 이해

System.Data.SqlTypes 는 SQL Server에서 ANSI_NULLS 옵션이 설정된 경우와 동일한 의미 체계를 제공합니다. 모든 산술 연산자(+, -, *, /, %), 비트 연산자(~, &, |) 및 대부분의 함수는 속성을 IsNull제외하고 피연산자 또는 인수가 null이면 null을 반환합니다.

ANSI SQL-92 표준은 WHERE 절에서 columnName = NULL을 지원하지 않습니다. SQL Server에서 ANSI_NULLS 옵션은 데이터베이스의 기본 null 허용 여부와 null 값에 대한 비교 평가를 모두 제어합니다. ANSI_NULLS 켜져 있는 경우(기본값) Null 값을 테스트할 때 식에서 IS NULL 연산자를 사용해야 합니다. 예를 들어 다음 비교는 ANSI_NULLS가 켜져 있을 때 항상 '알 수 없음' 상태를 반환합니다.

colname > NULL  

null 값을 포함하는 변수와 비교하면 알 수 없는 값도 생성됩니다.

colname > @MyVariable  

IS NULL 또는 IS NOT NULL 조건자를 사용하여 null 값을 테스트합니다. 이렇게 하면 WHERE 절에 복잡성이 더해질 수 있습니다. 예를 들어 AdventureWorks Customer 테이블의 TerritoryID 열은 null 값을 허용합니다. SELECT 문이 다른 값 외에도 null 값을 테스트하려면 IS NULL 조건자를 포함해야 합니다.

SELECT CustomerID, AccountNumber, TerritoryID  
FROM AdventureWorks.Sales.Customer  
WHERE TerritoryID IN (1, 2, 3)  
   OR TerritoryID IS NULL  

SQL Server에서 ANSI_NULLS 설정하는 경우 같음 연산자를 사용하여 null과 비교하는 식을 만들 수 있습니다. 그러나 다른 연결이 해당 연결에 대해 null 옵션을 설정하는 것을 방지할 수는 없습니다. 연결에 대한 ANSI_NULLS 설정에 관계없이 IS NULL을 사용하여 null 값 테스트는 항상 작동합니다.

ANSI_NULLS를 해제하는 설정은 DataSet에서 지원되지 않으며, 이 System.Data.SqlTypes는 null 값을 처리하기 위해 항상 ANSI SQL-92 표준에 따릅니다.

Null 값을 할당하기

Null 값은 특별하며 스토리지 및 할당 의미 체계는 다양한 형식 시스템과 스토리지 시스템에 따라 다릅니다. A Dataset 는 다른 형식 및 스토리지 시스템에서 사용하도록 설계되었습니다.

이 섹션에서는 서로 다른 형식 시스템에서 DataColumn에 null 값을 DataRow에 할당하는 null 의미 체계에 대해 설명합니다.

DBNull.Value
이 할당은 모든 형식에 DataColumn 유효합니다. INullable가 구현되면, DBNull.Value가 적절한 강력한 형식의 Null 값으로 강제 변환됩니다.

SqlType.Null
모든 System.Data.SqlTypes 데이터 형식은 INullable을 구현합니다. 강력히 형식화된 null 값을 암시적 캐스트 연산자를 사용하여 열의 데이터 형식으로 변환할 수 있는 경우, 할당이 가능해야 합니다. 그렇지 않으면 잘못된 캐스트 예외가 발생합니다.

null
'null'이 지정된 DataColumn 데이터 형식에 대한 법적 값인 경우 해당 DbNull.Value 데이터 형식으로 강제 변환되거나 Null 형식(INullable)과 SqlType.Null 연결됩니다.

derivedUdt.Null
UDT 열에서는 DataColumn에 연결된 형식에 따라 null이 항상 저장됩니다. UDT가 DataColumn와 연결되어 있고 구현을 하지 않는 반면, 해당 하위 클래스가 INullable을 구현하는 경우를 고려해보세요. 파생 클래스와 연결된 강력한 형식의 null 값이 할당되면, 이 값은 형식이 없는 DbNull.Value로 저장됩니다. null 스토리지는 항상 DataColumn의 데이터 형식과 일치하기 때문입니다.

비고

Nullable<T> 또는 Nullable 구조체는 현재 DataSet에서 지원되지 않습니다.

모든 System.Data.SqlTypes 인스턴스의 기본값은 null입니다.

Null은 System.Data.SqlTypes 형식별로 지정되며 , 같은 DbNull단일 값으로 나타낼 수 없습니다. IsNull 속성을 사용하여 null을 확인합니다.

Null 값을 DataColumn에 할당할 수 있는 예가 다음 코드 예제에 나와 있습니다. 예외를 트리거하지 않고도 변수에 null 값을 직접 할당할 SqlTypes 수 있습니다.

예시

다음 코드 예제에서는 DataTableSqlInt32로 정의된 두 개의 열이 있는 SqlString을 만듭니다. 이 코드는 알려진 값의 한 행, null 값의 한 행을 추가한 다음 DataTable, 변수에 값을 할당하고 콘솔 창에 출력을 표시하여 반복합니다.

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

이 예제에서는 다음 결과를 표시합니다.

isColumnNull=False, ID=123, Description=Side Mirror  
isColumnNull=True, ID=Null, Description=Null  

여러 열(행) 할당

DataTable.Add, DataTable.LoadDataRow, 또는 행에 매핑되는 ItemArray를 받아들이는 다른 API의 경우, 'null'을 DataColumn의 기본값으로 매핑합니다. 배열의 객체에 DbNull.Value가 포함되거나 강력한 형식의 대응이 포함된 경우, 위에서 설명한 것과 동일한 규칙이 적용됩니다.

또한 다음 규칙은 null 할당 인스턴스 DataRow.["columnName"] 에 적용됩니다.

  1. 기본값DbNull.Value 강력한 형식의 null 열을 제외한 모든 열에 대한 것입니다. 이 열은 강력한 형식의 null 값입니다.

  2. XML 파일로 serialization하는 동안 Null 값은 기록되지 않습니다("xsi:nil"에서와 같이).

  3. 기본값을 포함하여 null이 아닌 모든 값은 XML로 serialize하는 동안 항상 기록됩니다. 이는 null 값(xsi:nil)이 명시적이고 기본값이 암시적(XML에 없는 경우 유효성 검사 파서가 연결된 XSD 스키마에서 가져올 수 있음)인 XSD/XML 의미 체계와는 다릅니다. DataTable의 경우는 반대입니다: null 값은 암시적이고 기본값은 명시적입니다.

  4. XML 입력에서 읽은 행의 누락된 열 값은 모두 NULL로 할당됩니다. 메서드를 사용 NewRow 하거나 유사한 방법으로 만든 행에는 DataColumn의 기본값이 할당됩니다.

  5. 메서드는 IsNulltrueDbNull.Value 모두에 대해 INullable.Null을 반환합니다.

SqlType 및 CLR 형식과 Null 값 비교

null 값을 비교할 때, Equals에서 메서드가 null 값을 평가하는 방식과 CLR 형식이 System.Data.SqlTypes에서 작동하는 방식의 차이를 이해하는 것이 중요합니다. 모든 메서드는 System.Data.SqlTypesEquals null 값을 평가하기 위해 데이터베이스 의미 체계를 사용합니다. 값 중 하나 또는 둘 다 null이면 비교에서 null을 생성합니다. 반면에 두 값이 null인 경우에 CLR Equals 메서드를 둘 다 System.Data.SqlTypes에 사용하면 true를 반환합니다. 이는 CLR String.Equals 메서드와 같은 인스턴스 메서드를 사용하는 것과 정적/공유 메서드 SqlString.Equals를 사용하는 것의 차이를 반영합니다.

다음 예제는 각각 null 값 쌍과 빈 문자열 쌍을 SqlString.Equals 메서드와 String.Equals 메서드에 전달할 때 나타나는 결과의 차이를 보여 줍니다.

    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={SqlStringEquals(a, b)}");

        // Compare nulls using instance method String.Equals.
        Console.WriteLine();
        Console.WriteLine("String.Equals instance method:");
        Console.WriteLine($"  Two nulls={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={SqlStringEquals(a, b)}");

        Console.WriteLine();
        Console.WriteLine("String.Equals instance method:");
        Console.WriteLine($"  Two empty strings={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

이 코드는 다음 출력을 생성합니다.

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

참고하십시오