NULL
NULLとは
T-SQLにおけるNULLは、特定の列またはフィールドに値が存在しない、または不明であることを示す特殊なマーカーです。NULLは「ゼロ」や「空の文字列」とは異なります。
NULLの特性
等価性: NULLは他のNULLと等しいとは考えられません。つまり、NULL = NULLはFALSEと評価されます。
計算: 任意の算術計算にNULLを使用すると、結果は常にNULLになります。
NULLの基本的な取り扱い
IS NULL: 列の値がNULLかどうかをチェックするには、= NULLの代わりにIS NULLを使用します。
SELECT * FROM Employees WHERE LastName IS NULL;
IS NOT NULL: 列の値がNULLでないかどうかをチェックするには、IS NOT NULLを使用します。
SELECT * FROM Employees WHERE LastName IS NOT NULL;
COALESCE: 最初の非NULL値を返します。複数の列または値を調べる場合に便利です。
SELECT COALESCE(FirstName, LastName, 'N/A') AS DisplayName FROM Employees;
NULLIF: 2つの値が等しい場合にNULLを返し、それ以外の場合は最初の値を返します。
SELECT NULLIF(LastName, 'Smith') AS Result FROM Employees;
注意点と考慮事項
NULLを使用する場合、意図しない結果を避けるために、クエリの論理を慎重に考慮する必要があります。
NULLを適切に取り扱うことで、データの不整合や欠落を防ぐことができます。
SET ANSI_NULLS OFFを設定すると、NULLの比較動作が変わりますが、このオプションは非推奨とされています。
まとめ
NULLはT-SQLで特別な取り扱いが必要な値です。正確な結果を得るために、NULLの動作と関数の使用方法を理解することが重要です。
SET ANSI_NULLS の動作
SET ANSI_NULLS ON:
この設定の下では、NULL値は他のどの値とも等しくないと評価されます。これはANSI SQL規格に従った動作です。
例: NULL = NULL は FALSE と評価され、NULL <> NULL も FALSE と評価されます。
このモードは、新しく作成されるすべてのT-SQLオブジェクト(ストアドプロシージャ、トリガー、ビューなど)のデフォルト設定です。
SET ANSI_NULLS OFF:
この設定の下では、NULL値は他のNULL値と等しいと評価されます。
例: NULL = NULL は TRUE と評価されます。
しかし、この動作は非推奨とされています。将来のバージョンのSQL Serverでは、このオプションをOFFにすることがサポートされなくなる可能性があります。
注意点
SET ANSI_NULLSの設定は、オブジェクト(ストアドプロシージャやビューなど)の作成時にそのオブジェクトに固定されます。つまり、オブジェクトを作成した後にセッションレベルでSET ANSI_NULLSを変更しても、そのオブジェクトの動作は変わりません。
SET ANSI_NULLS OFFは古いアプリケーションのための後方互換性の設定として残されていますが、現代のアプリケーションではONにして、標準的な動作に従うことが推奨されます。
まとめ
SET ANSI_NULLSはT-SQLのNULLの取り扱いに関する重要な設定です。現代のアプリケーション開発においては、このオプションをONに設定して、標準的なNULLの動作を利用することが一般的に推奨されます。
ANTI JOIN
ANTI JOINとは
ANTI JOINは、SQLのJOIN操作の一種で、一方のテーブルには存在するが、もう一方のテーブルには存在しないレコードを取得するためのものです。T-SQLには明示的な"ANTI JOIN"のキーワードは存在しないので、通常、LEFT JOINとWHERE句を組み合わせて表現します。
基本的な構文
SELECT A.*
FROM TableA A
LEFT JOIN TableB B ON A.key = B.key
WHERE B.key IS NULL;
使用例
仮に、EmployeesテーブルとOrdersテーブルがあり、どの従業員がまだ注文を処理していないのかを知りたい場合のクエリは以下のようになります:
SELECT E.*
FROM Employees E
LEFT JOIN Orders O ON E.EmployeeID = O.EmployeeID
WHERE O.OrderID IS NULL;
このクエリは、Ordersテーブルに関連するレコードが存在しないEmployeesのレコードのみを取得します。
注意点と考慮事項
ANTI JOINは、特定の情報が存在しないことを確認する場面で非常に有効です。
LEFT JOINを使用してANTI JOINを模倣する際、WHERE句で右側のテーブルのキーがNULLであることを明示的に指定する必要があります。
他のJOIN操作と同様に、性能の観点から適切なインデックスを持つことが重要です。
まとめ
ANTI JOINは、一方のテーブルには存在するが、もう一方のテーブルには存在しないレコードを取得したい場合に非常に役立ちます。T-SQLでは、LEFT JOINとWHERE句を組み合わせることでこの操作を実現することができます。
フィルタリング
BETWEEN
T-SQLのBETWEEN句とは
BETWEEN句は、指定された範囲内の値をフィルタリングするために使用されます。主にSELECTステートメントのWHERE節内で使用されることが多いです。
基本的な構文
SELECT column1, column2, ...
FROM table_name
WHERE column_name BETWEEN value1 AND value2;
使用例
数値の範囲でのフィルタリング:
SELECT ProductName, Price
FROM Products
WHERE Price BETWEEN 10 AND 20;
上記のクエリは、価格が10から20の間の商品のみを取得します。
日付の範囲でのフィルタリング:
SELECT OrderID, OrderDate
FROM Orders
WHERE OrderDate BETWEEN '2021-01-01' AND '2021-12-31';
このクエリは、2021年の注文のみを取得します。
注意点
BETWEEN句は、指定した値の範囲両端の値も含みます。上記の価格の例でいうと、価格が10または20の商品も結果に含まれます。
文字列の範囲もサポートしていますが、辞書順に基づいた範囲になります。そのため、'apple' BETWEEN 'a' AND 'b'は真と評価されますが、'apple' BETWEEN 'b' AND 'z'は偽と評価されます。
BETWEEN句を使用する場合は、範囲の始点と終点の順序に注意する必要があります。value1はvalue2より小さくなければなりません。
代替手段
BETWEEN句は、次のような等価の手段で置き換えることができます:
WHERE column_name >= value1 AND column_name <= value2;
この方法を使用すると、BETWEENと同じ結果を得ることができます。
IN
T-SQLのIN句とは
IN句は、指定されたリスト内の任意の値と一致するデータをフィルタリングするために使用されます。主にSELECTステートメントのWHERE節内で使用されます。
基本的な構文
SELECT column1, column2, ...
FROM table_name
WHERE column_name IN (value1, value2, ...);
使用例
特定の値のリストに基づくフィルタリング:
SELECT ProductName, CategoryID
FROM Products
WHERE CategoryID IN (1, 3, 5);
上記のクエリは、CategoryIDが1, 3, または5である商品のみを取得します。
別のクエリの結果を使用したフィルタリング:
SELECT OrderID
FROM Orders
WHERE CustomerID IN (SELECT CustomerID FROM Customers WHERE Country = 'USA');
このクエリは、国が'USA'である顧客が注文した注文のみを取得します。
注意点
IN句は、指定したリストの中の任意の値と一致する行をフィルタリングします。リストの中に同じ値が複数回出現する場合、その値に基づいて行を複数回取得することはありません。
大きなリストを使用する場合、IN句のパフォーマンスが低下する可能性があります。この場合、JOINや他の方法を検討すると良いでしょう。
代替手段
IN句は、次のような等価の手段で置き換えることができます:
WHERE column_name = value1 OR column_name = value2 OR ...
ただし、IN句を使用する方が読みやすく、簡潔です。
いろいろなjoinの結合条件
基本的にWHEREで使うるものはJOINのONでも使えます。
等号
1つの条件で
複数の条件で
不等号
1つの不等号
範囲
SELECT R.[ReceiptId]
,R.[CheckTime]
,D.[DetailId]
,D.[ItemId]
,D.[Quantity]
,P.[Price]
,P.[FromDate]
,P.[ToDate]
FROM [sqlgeginner20231014].[dbo].[Receipt] AS R
INNER JOIN [sqlgeginner20231014].[dbo].[ReceiptDetail] AS D ON D.[ReceiptId] = R.[ReceiptId]
LEFT JOIN [sqlgeginner20231014].[dbo].[Price] AS P ON P.[ItemId] = D.[ItemId] AND [FromDate] <= [CheckTime] AND [CheckTime] < [ToDate]
特定日の直近とか
SELECT R.[ReceiptId]
,R.[CheckTime]
,D.[DetailId]
,D.[ItemId]
,D.[Quantity]
,P.[Price]
,P.[FromDate]
,P.[ToDate]
FROM [sqlgeginner20231014].[dbo].[Receipt] AS R
INNER JOIN [sqlgeginner20231014].[dbo].[ReceiptDetail] AS D ON D.[ReceiptId] = R.[ReceiptId]
LEFT JOIN [sqlgeginner20231014].[dbo].[Price] AS P ON D.[ItemId] = P.[ItemId] AND P.[FromDate] = (SELECT MAX([FromDate]) FROM [Price] WHERE D.[ItemId] = [ItemId] AND R.[CheckTime] >= [FromDate])
もっと頑張って
SELECT TOP (1000) M.[modelId]
,M.[makerId]
,M.[modelName]
,M.[price]
,M.[nickname]
,S.[name]
FROM [sqlgeginner20231014].[dbo].[model] AS M
LEFT JOIN [series] S ON [modelName] LIKE S.[header]