SQL Server リテラル

リテラル

SQL Serverにおいてリテラルとは、識別子によって表現する必要がない明示的な数値、テキスト、日時などのことです。
たとえば、数値リテラルは、’100’や’1000’などの数を示す値です。
テキスト(文字列)リテラル ‘SQL Server’ など、日時リテラルは’2017/12/15’などです。

数値リテラル

SQL Server では取り扱える型によって少しづつ指定が違います。
引用符では囲みません。

binary 型定数は 16 進数の文字列であり、0x というプレフィックスが付きます。
bit 型定数は数値の 0 または 1 で表します。
integer 型定数は数値文字列で表し、小数点を含まない整数である必要があります。
decimal 型定数は、小数点を含む数値文字列で表せます。
float 型定数と real 型定数は科学的表記法で表します。(101.5E5, 0.5E-2)
money 型定数は数値文字列で表し、オプションで小数点および通貨記号をプレフィックスとして付加することができます。

また、数値が正であるか負であるかを示すには、数値型定数に + または – 単項演算子を付加します。

テキスト(文字列)リテラル

英数字 (a ~ z、A ~ Z、および 0 ~ 9)、感嘆符 (!)、アット マーク (@)、および番号記号 (#) などの特殊文字を単一引用符で囲みます。
単一引用符で囲まれた文字列に単一引用符を埋め込む場合は、単一引用符を 2 つ続けて並べることで 1 つの単一引用符を表します。 文字列が二重引用符で囲まれている場合は該当しません。

文字列リテラルに N プレフィックスを付けると Unicode 文字列になります。

実はこのテキストリテラル、とても奥深いです。
私も深く掘り下げると理解できないところが出てきます。
UNICODEとか、照合順位とかいろいろありますので、興味がでたら調べてみてください。

日時リテラル

datetime 型を利用します
単一引用符で囲みます。

‘December 5, 1985’
‘5 December, 1985’
‘851205’
’12/5/98′
など、さまざま表記法があります。

以上、リテラルについての簡単な説明でした。

SQL Server INTERSECT

INTERSECT

INTERSECT は、ユニオンクエリと呼ばれ、1つのクエリ結果と別のクリエ結果の同じ行のみの結果を返します。
ユニオンクエリには、UNION, EXCEPT 句もあります。

ちなみに、SQL Server には、INTERSECT ALL はありません。

INTERSECT の使い方

1つの目のクエリ結果と、次のクリエ結果を比較し、同じ行(レコード)のみを結果として返します。

以下に簡単な例を書きます。
tble1の結果からtbl2と同じ結果(行)があるもののみ結果を返しています。

declare @tbl1 table(a_cd char(9), b_kbn char(1), c_kbn int)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000001','1',0)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000010','1',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000020','2',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000030','3',1)

declare @tbl2 table(a_cd char(9), b_kbn char(1), c_kbn int)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000001','1',0)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000010','1',1)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000020','2',1)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000030','3',2)

select * from @tbl1
INTERSECT
select * from @tbl2

left join 句を使用して同じこともできますが、INTERSECT 句を使った方がシンプルですね。

select t1.* from @tbl1 t1
left join @tbl2 t2
on t1.a_cd = t2.a_cd
and t1.b_kbn = t2.b_kbn
and t1.c_kbn = t2.c_kbn
where t2.a_cd is not null

以上、INTERSECT の簡単な使用方法でした。

SQL Server EXCEPT

EXCEPT

EXCEPT は、ユニオンクエリと呼ばれ、1つのクエリ結果から別のクリエ結果を差し引いて重複を取り除き結果を返します。
ユニオンクエリには、UNION, INTERSECT 句もあります。

ちなみに、SQL Server には、EXCEPT ALL はありません。

EXCEPT の使い方

1つの目のクエリ結果から、次のクリエ結果を差し引いて、重複を取り除き、結果を返します。

以下に簡単な例を書きます。
tble1の結果からtbl2と同じ結果(行)があるものを取り除き結果を返しています。

declare @tbl1 table(a_cd char(9), b_kbn char(1), c_kbn int)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000001','1',0)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000010','1',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000020','2',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000030','3',1)

declare @tbl2 table(a_cd char(9), b_kbn char(1), c_kbn int)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000001','1',0)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000010','1',1)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000020','2',1)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000030','3',2)

select * from @tbl1
except
select * from @tbl2

left join 句を使用して同じこともできますが、EXCEPT 句を使った方がシンプルですね。

select t1.* from @tbl1 t1
left join @tbl2 t2
on t1.a_cd = t2.a_cd
and t1.b_kbn = t2.b_kbn
and t1.c_kbn = t2.c_kbn
where t2.a_cd is null

以上、EXCEPT の簡単な使用方法でした。

SQL Server UNION と UNION ALL

UNION と UNION ALL

UNION と UNION ALL は、ユニオンクエリと呼ばれ、2つ以上のクエリ結果を結合して返します。
ユニオンクエリには、EXCEPT, INTERSECT 句もあります。

UNION と UNION ALL の違い

2つ以上のクエリ結果を結合して返します。
違いは、すべての結果を返すか、重複した行(レコード)は一行にして返すかの違いです。
使用するデータによっては、重複データを取り除きたい場合もあるし、すべてのレコードが必要な場合もあるので、選択して使用します。

以下に簡単な例を書きます。

declare @tbl1 table(a_cd char(9), b_kbn char(1), c_kbn int)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000001','1',0)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000010','1',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000020','2',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000030','2',1)

declare @tbl2 table(a_cd char(9), b_kbn char(1), c_kbn int)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000001','1',0)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000010','1',1)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000020','2',1)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000030','2',1)

select * from @tbl1
union
select * from @tbl2
order by a_cd

select * from @tbl1
union all
select * from @tbl2
order by a_cd

UNION , UNION ALL はクエリを結合するので、ネストなどもできます。
下の例は、tbl1とtbl2を先にUNION結合し、後からtbl3をUNION ALL結合しています。
()で順序をつけることができます。

declare @tbl1 table(a_cd char(9), b_kbn char(1), c_kbn int)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000001','1',0)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000010','1',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000020','2',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000030','2',1)

declare @tbl2 table(a_cd char(9), b_kbn char(1), c_kbn int)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000001','1',0)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000010','1',1)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000020','2',1)
insert into @tbl2 (a_cd,b_kbn,c_kbn) values ('000000030','2',1)

declare @tbl3 table(a_cd char(9), b_kbn char(1), c_kbn int)
insert into @tbl3 (a_cd,b_kbn,c_kbn) values ('000000001','1',0)
insert into @tbl3 (a_cd,b_kbn,c_kbn) values ('000000010','1',1)
insert into @tbl3 (a_cd,b_kbn,c_kbn) values ('000000020','2',2)
insert into @tbl3 (a_cd,b_kbn,c_kbn) values ('000000030','2',2)

select * from @tbl3
union all
(
select * from @tbl1
union
select * from @tbl2
)
order by a_cd

以上、UNION と UNION ALL の簡単な使用方法でした。

SQL Server NULL

NULL

SQL文を書くとき、データがnullを取りうるか否かを常に意識する必要があります。
nullを検索するためには、明示的に is null や is not null を使用する必要があります。

また、SQL Server で NULL 値を別の値にチェック後に変換するには、ISNULL 関数を使用します。

NULL を扱う

null 値を許可している場合、かつ、その値が検索対象になっている場合は先の is null や is not null を使用します。
簡単な例を下に示しておきます。

declare @tbl1 table(a_cd char(9), b_kbn char(1), c_kbn int)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000001','1',0)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000010','1',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000020','2',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000030','2',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000040','3',null)
 
select * from @tbl1

select * from @tbl1 where c_kbn = 0 or c_kbn <> 0 

select * from @tbl1 where c_kbn is null

select * from @tbl1 where c_kbn is not null

select a_cd, b_kbn, isnull(c_kbn, -1) from @tbl1

select a_cd, b_kbn,
    case
    when c_kbn is not null then c_kbn
    when c_kbn is null then '-1'
	end
from @tbl1

更にANSI/ISOのCOALESCE関数を使うこともできます。

declare @tbl1 table(a_cd char(9), b_kbn char(1), c_kbn int)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000001','1',0)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000010','1',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000020','2',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000030','2',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000040','3',null)

select a_cd, b_kbn, coalesce(c_kbn, '-1') from @tbl1

この他、SQL Server では、ANSI_NULLS の設定で = , <> を使用できるようになる。

declare @tbl1 table(a_cd char(9), b_kbn char(1), c_kbn int)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000001','1',0)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000010','1',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000020','2',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000030','2',1)
insert into @tbl1 (a_cd,b_kbn,c_kbn) values ('000000040','3',null)

set ansi_nulls on

select * from @tbl1 where c_kbn = null
select * from @tbl1 where c_kbn <> null

set ansi_nulls off

select * from @tbl1 where c_kbn = null
select * from @tbl1 where c_kbn <> null

ISNULLでNullを置換する

ISNULLでNULLを他の文字に置き換える方法もありますので紹介しておきます。

構文:ISNULL(チェック対象, 変換したい文字列)
ISNULLの第1パラメータの値がNULLであれば、第2パラメータの文字列を返します(変換します)。
NULLでなければ、第1パラメータの値をそのまま返します。

declare @val integer

set @val = null
select isnull(@val, 1)

set @val = 0
select isnull(@val, 1)

以上、null の扱いかたでした。

WindowsのDNSキャッシュをクリアする方法

WindowsのDNSキャッシュをクリアする方法

レンタルサーバーを変えていて、DNSの変更がきちんとされたかを調べたいときにDNSキャッシュが邪魔な時があります。
DNSキャッシュの削除方法を備忘録しておきます。

WindowsのDNSキャッシュをクリア・削除

コマンド一発です。
コマンドプロンプトで実行してください。

ipconfig /flushdns

DNSキャッシュがクリア・削除されたか確認

これもコマンド一発です。
コマンドプロンプトで実行してください。

ipconfig /displaydns

だらだらリストが出てこなければクリアされています。
クリアする前にipconfig /displaydnsを実施して状態を確認するとわかりやすいかも。

以上、WindowsのDNSキャッシュをクリアする方法でした。

楽天カードから不審なメールが来ました~

楽天カードから不審なメールが来ました~

本日2017/10/18 11:11 に楽天カードから不審なメールが来ました~。

打合せ中だったので、メールのタイトルを見て「おや??」と思ったのですが、スルーしました。
「おや??」の理由は、私、楽天カード持っていません。
なのに、「【楽天カード】ご請求予定金額のご案内」なるタイトルでメールがきてたので、不思議に思って。。。

打合せが終わって、お昼を食べながら、不審メールを見ていたのですが、いかにも怪しそうな感じがしないんですね。

楽天カード 不審メール

楽天カード 不審メール


普通の感じですよね。

でも、楽天カード持ってないですから、あきらかに不審メールです。
で、さっそく「楽天カード 不審メール」でググってみました。
出てきました。。。

楽天カードを装った不審なメールにご注意ください

楽天カードを装った不審なメールにご注意ください


URLは『楽天カードを装った不審なメールにご注意ください』です。

で、どう言った被害にあうか私自身は試してみるのも怖いので試していませんが、どうやらメール内のURLをタップorクリックすると、ウイルスに感染するなどの被害に遭う(可能性がございます)のそうです。
なので、メール内のURLは絶対にタップorクリックしてはいけません

楽天カードのNewsでの警告でも、
差出人が「楽天カード株式会社」となっていても、身に覚えのない内容のメールに記載されているURLは絶対にクリックせず、送られてきたメールそのものを削除していただきますよう、何卒宜しくお願いいたします。
となっています。
って、身に覚えにない内容ならまだしも、楽天カード使ってたら不審に思わないからタップorクリックすることもあるよね?って思いますが。。。

差出人メールアドレス(info@mail.rakuten-card.co.jp)はどうやら正規のメールアドレスのようです。成りすまして送っているのかと。
正規のメールアドレス(info@mail.rakuten-card.co.jp)なら余計にタップorクリックしちゃう人いるよね。

みなさん、不審なメールには注意しましょうね。
あっ、そういえば今日、葉書でも架空請求の案内来てました。
すごく重要なお知らせを装ってるのに、配達証明で送ってこない事態がすでに怪しいってバレバレですけどね。。。

詐欺や迷惑行為が多いので気をつけましょうね^^
以上、ちょっと気になったので報告でした。

robots.txt の使い方

robots.txt の使い方。。。の前に

先日、『IX Web Hosting ハッキングされた!』に書いた通りハッキングされたのですが、その後何も起こらず平穏な日々に戻っています。
ハッキングされたのは「趣味のブログ」サイトで被害自体はどうってことのないものでした。
勝手に時計を売るページを引っ張ってくるページを作られたのですが、参照先もどこかの企業のホームページを使っていました。

で、問題は、Googleの「Search Console」のクロールエラーが一向に減らないってことです。
404エラーなので、「Googlebot はこの URL をクロールできませんでした。この URL が存在しないページを指していることが原因です。一般に、404 が発生しても検索結果でのサイトの掲載順位が低下することはありませんが、このエラー情報を使用してユーザーの利便性を向上させることができます。」とある通り、影響はないのですが、気持ち悪くて。。。

Search Consoleのクロールエラー

Search Consoleのクロールエラー

robots.txt の使い方

そこで、robots.txt の出番です。
robots.txt とは、Google や Bing などのロボット型検索エンジンに対する命令(依頼?)を記述するためのファイルです。
例えば自分のページの一部を、検索エンジンのデータベースに登録されないように命令(依頼?)をします。

命令には、
 ・対象:User-agent
 ・許可:Allow
 ・拒否:Disallow
 ・通知:Sitemap
の記述があります。

User-agent

GooglebotやBingbotなど命令(依頼?)したいクローラーを指定します。
基本的には、「*」で全てのクローラーを対象にします。

Allow

クローラーの訪問をブロックしないディレクトリやページを指定します。
基本的には、ブロックしないディレクトリやページを許可する方法はとらないので、使わないケースが多いです。
ディレクトリを指定すると配下のページにも影響を及ぼします。

Disallow

クローラーの訪問をブロックするディレクトリやページを指定します。
基本的には、検索対象にしたくなページを指定することになりますが、写真などのディレクトリをインデックスされたくない場合などに使います。
「/」でサイト全体を、「/dir/」でdirディレクトリ以下全てのページを指定したことになります。
「/*.jpg$」で全てのjpgファイルに対した指定も可能です。

Sitemap

sitemap.xmlの場所(URL)をクローラーに通知します。
URLは絶対パスで記述します。

今回の robots.txt

User-Agent: *
Disallow: /asd.php
Disallow: /zxc.php
Disallow: /qwe.php

全てのbotに対して、
asd.php、zxc.php、qwe.phpへのインデックスを拒否するように依頼をしました。

「Search Console」のrobots.txt テスター

robots.txt テスターツールを使用すると、robots.txt ファイルでサイト内の特定の URL に対して Google のウェブクローラがブロックされているかどうかを確認できます。
一応、『robots.txt を使用して URL をブロックする robots.txt テスターで robots.txt をテストする』を貼っておきます。
具体的な使い方がわからない時は、robots.txt テスターでググると出てきます。

これでしばらくすると、エラー数も落ち着いてくると思うのですが。。。
減ってきたらまたここに追記します。
あっ、効果なくても報告します。

CSS id と class の違いと使い分け

id と class

CSS(スタイルシート)を書く際に、「特定のタグ・要素すべて」を指定して一括装飾することもありますが、「特定のタグ・要素」を装飾したい場合がほとんどです。
HTMLで指定できる属性は2種類あり、ご存知のid属性とclass属性です。
この id と class の 違い と どのように使い分ける か、迷うことがあります。

この id と class は、適当に使うのではなく、ちゃんとした設計思想があります(あるそうです。。。あると思います)。
そこで今回は、 id と class の違いと、その設計思想についてお話します。

id と class の違い

id と class の違いは、簡単に理解できます。

id は identification の略で、本来は身分証明とか身分証明書のことです。
身分証明とか身分証明書なので同じページ内に、同じ id を持つものは居てはなりません。
居た場合、エラーになるか、プログラム挙動がおかしくなります。

class は共通の性質を有する部類、種類、(学校の)クラス、学級、組などの意味がありますね。
共通の性質を有する組を表しているので、同じ Class に属するものが居てもおかしくないですね。

id と class の違いは判りましたか?
他と区別するために付けるものは id
他と共通するために付けるものは class
です。

id と class の使い分け

では、id と class の使い分けはどのように考えましょう?
id と class は両方付与しても構いません。
id の方が優先されるため、同じ指定なら id が優先されるのでそこは注意が必要ですし、使い方によっては良いケースでもあります。

で、 id と class の違いが理解できていれば、使い分けも知識としては理解できます。
他と区別するために付けるものは id
他と共通するために付けるものは class
です。

ただ、現実問題プログラムを組んで行くと適当になってきてしまい、class を id として使用したりします。
相当大きなページでも最近のPCやスマホは高機能なので、下手な作りでも高速に動いてくれるので問題もないのですが。

で、解決策として、ルールを作って使い方を変えましょうとか、決め打ち(命名ルールに従い)で主要なタグに id を付与しclassは後から追加しましょうとか、書いてある書籍をよく見かけます。
間違いではないですし、有効に機能もする場合もあると思うのですが、それでは本末転倒というか、私は違和感を感じてしまいます。

操作はユニークか否かが、鍵

id か class かはオブジェクト指向の考えたを導入すると明確になります。
要は設計方針で id か class かも決められるます。
でも、オブジェクト指向なんて言われても困りますよね。。。

で、まずは操作が「共通して行われるもの」か、ユニークなもので「共通化されないもの」かを判定します。

操作が共通して行われるもの、背景色や文字種類・大きさ、その他何でも一度に指定して一度に更新したい操作には class を使います。
たとえ今は一つのタグに対してであっても、拡張したり、他のページにコピペで使用したりもありますので、操作を共通で行うものと判断出来たら class にします。

逆にユニークなもので共通化されないものの場合は、間違いを防ぐために id を使用します。
例えばいくつも必須入力テキストがあり、各々の入力判定の結果OKなら緑、NGなら赤にバックグランドを変える場合は、共通部分は class で、バックグラウンド色は id で指定するようにします。

設計思想によって、class を追加、削除するプログラムを書くと上の説明とはことなる考えたが必要になります。
設計思想によって、コロコロ変わってしまうので、「ルールを作って使い方を変えましょうとか、決め打ち(命名ルールに従い)で主要なタグに id を付与しclassは後から追加しましょう」とか結果曖昧な表現になってしまうのですね。

今回は、「操作はユニークか否か」で使い分ける方法を紹介しましたが、これが絶対ではなく、あくまでも一例です。
大きくないWebページなら、変な使い方でも問題ないです。
大きなWebページでは、ごちゃごちゃして、class を触ったら、意図しないタグで意図しない変化が出てしまったってことが良くあります。
id や class を管理をしてすっきりしたCSSを書きましょう。

以上、CSS id と class の違いと使い分けでした。

SQLSERVER EXISTS、NOT EXISTS

SQLSERVER EXISTS、NOT EXISTS

EXISTS と NOT EXISTS

サブクエリの導入にキーワード EXISTS を使用した場合、そのサブクエリは「存在検査」として機能します。
NOT キーワードを付けた場合、そのサブクエリは「非存在検査」として機能します。

サブクエリの WHERE 句では、
・ データ抽出されるテーブル と 存在を試されるテーブル との リンク条件を指定します。
・ 存在を試されるテーブル の検索条件を指定します。
このことにより、このサブクエリから返される行が存在するかどうかがテストされます。
サブクエリは実際にはデータを生成せず、TRUE または FALSE の値を返します。

WHERE [NOT] EXISTS (subquery)

EXISTS演算子とは

EXISTS 演算子は、副問い合せの結果が存在するかを調べるときに使用します。
このとき、副問い合せの結果が存在するとき真になります。

NOT EXISTS演算子とは

NOT EXISTS 演算子は、副問い合せの結果が存在しないかを調べるときに使用します。
このとき、副問い合せの結果が存在しないとき真になります。

EXISTS、NOT EXISTS のテストサンプル

以下にテストSQLを載せておきます。
@tbl1には、id,kbn,pointの三項目
@tbl2には、id,kbn,priceの三項目
point,priceはランダムで数値が入ります。

@tbl1 と @tbl2は、同じid(kbnも)が入ります。
@tbl2のpriceの条件に合わせて、存在する存在しないをテストし、@tbl1のレコードを取得しています。

declare @tbl1 table(
    id int,
    kbn nchar(10) ,
    point int
)

declare @tbl2 table(
    id int,
    kbn nchar(10) ,
    price int
)

declare @start_id as int
set @start_id=0
 
while @start_id < 10
    begin
        print @start_id
        insert into @tbl1 values(@start_id, @start_id % 3, @start_id * Rand() * 10)
        insert into @tbl2 values(@start_id, @start_id % 3, @start_id * Rand() * 100)
        set @start_id = @start_id +1
    end

select * from @tbl1
select * from @tbl2

/* EXISTS */
/* テーブル@tbl2のpriceが200より大きなレコードを取得し、@tbl1の該当するレコードを表示する*/
select * from @tbl1 t1
  where EXISTS (
   select * from @tbl2 t2
     where t1.id= t2.id     --レコードのリンク
     and t2.price >= 200  --条件
 );


/* NOT EXISTS */
/* テーブル@tbl2のpriceが200より大きなレコードを取得し、@tbl1の該当しないレコードを表示する*/
select * from @tbl1 t1
  where NOT EXISTS (
    select * from @tbl2 t2
    where t1.id= t2.id     --レコードのリンク
    and t2.price >= 200  --条件
 );

IN 句と INNER JOIN 句 と比較

ちなみに、EXISTはINよりも速いとか、EXISTSは INNER JOIN より遅いだとか、良く耳にしますが、実際どうなんでしょうね?
個人的には、条件や状況によって違うのではないかと考えています。
Indexの有無、テーブルの大きさ、暗黙的な変換の有無、直前に使用テーブルにアクセスしてた等々条件や状況によって変わってくると。
とりあえず簡単なパフォーマンステストをしてみたいと思います。
サンプルSQLは以下の通りです。
自前の環境で、条件を変えてテストしてみてください。

create table #tbl1 (
    id int,
    kbn nchar(10) ,
    point int
)

create table #tbl2(
    id int,
    kbn nchar(10) ,
    price int
)
--create index #idx2 on #tbl2(price);

declare @start_id as int
set @start_id=0
 
while @start_id < 100000
    begin
        print @start_id
        insert into #tbl1 values(@start_id, @start_id % 3, @start_id * Rand() * 10)
        insert into #tbl2 values(@start_id, @start_id % 3, Rand() * 1000)
        insert into #tbl2 values(@start_id, @start_id % 3, Rand() * 1000)
        insert into #tbl2 values(@start_id, @start_id % 3, Rand() * 1000)
        set @start_id = @start_id +1
    end

--select * from #tbl1
--select * from #tbl2 where price >= 900
--ここからテスト

/* EXISTS 句でパフォーマンステスト */
print CONVERT(VARCHAR, GETDATE(), 114) 
select * from #tbl1 t1
 where EXISTS (
  select * from #tbl2 t2
	where t1.id= t2.id     --レコードのリンク
	and t2.price >= 900
 )
print CONVERT(VARCHAR, GETDATE(), 114) 


/* IN 句でパフォーマンステスト */
print CONVERT(VARCHAR, GETDATE(), 114) 

select * from #tbl1 t1
 where t1.id IN (
  select t2.id from #tbl2 t2
   where t2.price >= 900
 )

print CONVERT(VARCHAR, GETDATE(), 114) 


/* join 句でパフォーマンステスト */
print CONVERT(VARCHAR, GETDATE(), 114) 

select distinct t1.* from #tbl1 t1
inner join #tbl2 t2
on t1.id=t2.id
where t2.price >= 900

print CONVERT(VARCHAR, GETDATE(), 114) 

/* drop table */
/*
drop table #tbl1
drop table #tbl2
*/

ここでまず、INDEX無のEXISTS,IN,INNER JOINのそれぞれの実行プランを確認してみます。
EXISTS句

EXISTS句-パターン1INDEX無の実行プラン

EXISTS句-パターン1INDEX無の実行プラン


クエリコストがバッチ相対で9%になっています。

IN句

IN句-パターン1INDEX無の実行プラン

IN句-パターン1INDEX無の実行プラン


EXISTS句と同じくクエリコストがバッチ相対で9%になっています。

INNER JOIN句

INNER JOIN句-パターン1INDEX無の実行プラン

INNER JOIN句-パターン1INDEX無の実行プラン


クエリコストがバッチ相対で25%になっています。

実行して試してみます。
ただし一度メモリに読み込まれてしまうと、最適化された状態で動くことになるので、実際のパフォーマンスとはかなり異なります。
もし、少しでも正確に行いたいのなら、テーブルを毎回作り直して、初回で比較する方が良いと思われます。
今回はメモリに展開したうえでのパフォーマンス比較を行っています。各々2回図ります。

参考:ど初回、EXISTS句
開始:07:52:12:613
終了:07:52:14:300
結果:1687ms

—–ここから

初回:EXISTS句
開始:08:01:44:583
終了:08:01:46:287
結果:1704ms

初回:IN句
開始:07:58:20:097
終了:07:58:21:690
結果:1687ms

初回:INNER JOIN句
開始:08:07:10:097
終了:08:07:12:363
結果:2266ms

2回:EXISTS句
開始:08:08:32:600
終了:08:08:33:927
結果:1327ms

2回:IN句
開始:08:05:31:440
終了:08:05:33:160
結果:1720ms

2回:INNER JOIN句
開始:08:03:56:490
終了:08:03:57:973
結果:1483ms

なんとも締まらない結果になってしまいました。
10万件では少なかったかな。。。
とりあえず、次はpriceにIndexを貼ってやってみます。
でも実行プランも変わらなかったので、priceにIndexを貼っても効果ないようです。
どこにどう貼れば効果あるのかな・・・
ちなみに、Indexを貼るのに10秒かかりました。

初回:EXISTS句
開始:08:13:15:660
終了:08:13:17:647
結果:1987ms

初回:IN句
開始:08:15:48:677
終了:08:15:50:520
結果:1843ms

初回:INNER JOIN句
開始:08:14:47:300
終了:08:14:49:130
結果:1830ms

2回:EXISTS句
開始:08:16:37:147
終了:08:16:38:270
結果:1123ms

2回:IN句
開始:08:18:28:660
終了:08:18:29:833
結果:1173ms

2回:INNER JOIN句
開始:08:17:32:973
終了:08:17:34:647
結果:1674ms

Indexを貼っても有効に機能しなかったので結果は同じでしたね。
簡単なテーブルではパフォーマンスに差も出ないようですね。
じっくり検討すれば早くなる可能性もありますが、とりあえず簡易テストの結果、それほどの差は出ないということでした。

以上、簡単なサンプルでの説明でしたが、SQL SERVER EXISTS、NOT EXISTS についてでした。