情報システムをご利用になるお客様の立場に立ち、企画・提案から運用・保守まで一貫したソリューションをご提供する。
これまでに育ってきた技術力を一層高め、常に情報リテラシーを磨き、顧客満足度を重視しながら、より良いシステム開発とソリューションの提供に全力を尽くして、可能な限りの社会貢献を果たして参ります。
よろしくお願いします。
株式会社ノアテック
www.noah-tec.com
2012年12月24日月曜日
柠檬的错觉
1.爱是世界上最甜蜜的痛,痛是为了得到世界上最甜蜜的爱.
2.其实不想写,其实不想回忆,其实我希望永远记不起. 但痛苦一天天积累,希望一天天枯萎. 脑海里浮现着但丁的神曲, 总以为那是很久远的过去,没想到那竟是熟悉的回忆. 总以为那是很漂渺的唏嘘,想不到也有现实的异域.
3.在爱的路上潇洒的我,遇到了比我更潇洒的你. 我像潇洒的风,你像潇洒的雨. 风雨之中,我发现自己潇洒地爱上了你.
4.我把你放在一滴泪里,幻想千年以后化成琥珀. 我不敢低头,怕那滴眼泪滴落,碎了千年的梦.
5.梦醒之后,注定要启程,单薄的身影,沉重的脚步,都取代来时那轻盈的步伐. 让我离开这虚假的天空,深深的脚印刻印下你给的伤痕累累. 红肿的双眼,颤抖的双肩,都证实着伤寂的泪水. 但愿他能酿一杯涩酒,在每一个想你的深夜品尝.
6.孤独,这是为自由付出的代价. 潇洒,这是孤独的外衣. 7.人生若只如初见,春风扑面桃花艳. 人生若只如初见,何事秋风悲画扇.
2.其实不想写,其实不想回忆,其实我希望永远记不起. 但痛苦一天天积累,希望一天天枯萎. 脑海里浮现着但丁的神曲, 总以为那是很久远的过去,没想到那竟是熟悉的回忆. 总以为那是很漂渺的唏嘘,想不到也有现实的异域.
3.在爱的路上潇洒的我,遇到了比我更潇洒的你. 我像潇洒的风,你像潇洒的雨. 风雨之中,我发现自己潇洒地爱上了你.
4.我把你放在一滴泪里,幻想千年以后化成琥珀. 我不敢低头,怕那滴眼泪滴落,碎了千年的梦.
5.梦醒之后,注定要启程,单薄的身影,沉重的脚步,都取代来时那轻盈的步伐. 让我离开这虚假的天空,深深的脚印刻印下你给的伤痕累累. 红肿的双眼,颤抖的双肩,都证实着伤寂的泪水. 但愿他能酿一杯涩酒,在每一个想你的深夜品尝.
6.孤独,这是为自由付出的代价. 潇洒,这是孤独的外衣. 7.人生若只如初见,春风扑面桃花艳. 人生若只如初见,何事秋风悲画扇.
PL/SQL 勉強ノート
1.
declare
type empno_array is table of varchar2(3);
tep empno_array := empno_array('E01','E02','E03','E04','E05');
begin
-- 配列の最後の要素を削除
tep.trim;
for ep_cnt in 1..tep.count loop
dbms_output.put_line('ep1:' || tep(ep_cnt));
end loop;
-- 配列の最後の要素を2つ削除
tep.trim(2);
for ep_cnt in 1..tep.count loop
dbms_output.put_line('ep2:' || tep(ep_cnt));
end loop;
end;
declare
type empno_array is table of varchar2(3);
tep empno_array := empno_array('E01','E02','E03','E04','E05');
begin
-- 配列の最後の要素を削除
tep.trim;
for ep_cnt in 1..tep.count loop
dbms_output.put_line('ep1:' || tep(ep_cnt));
end loop;
-- 配列の最後の要素を2つ削除
tep.trim(2);
for ep_cnt in 1..tep.count loop
dbms_output.put_line('ep2:' || tep(ep_cnt));
end loop;
end;
Oracle PL/SQL
目次
はじめに
対象者
コメント
構造
コーディング規約
ハローワールド
データ形式
演算子
制御構造
データ処理
エラー処理
関数
API
デバック
sqlplusを使って、Helloworldから始まり、データ形式、制御構造、関数の書き方などをサラっと学習します。
今後、オラクル社のリファレンスで学習できるようにするのが目的です。
参考資料には、オラクル社へのマニュアル・リンクを用意しました。
入門書に書いてある例えなどはありません。
言語習得に必要な部分のみしか書きませんので、sqlplusでサンプルを実行しながら学習してください。
PL/SQLと他の言語を比較してポイントとなる部分は赤 マークしています。
対象者
Oracleをこれから始める方。
コメント
コメントの書き方は二つあります。
-- 1行のコメントはハイフン(-)2つです。
/*
複数行のコメントはC言語などのように
スラッシュ、アスタリスクです。
*/
構造
下記の構造をPL/SQLブロックという
Begin End;のみ必須
入れ子で書くこと(=ネストブロック)も可能
サンプル
Declare
-- 宣言
Begin
-- 処理
Exception
-- エラー処理
End;
ネストブロック
Declare
Begin
Declare
-- 宣言
Begin
-- 処理
Exception
-- エラー処理
End;
Exception
End;
declare
tmp_data number := 0;
begin
/* nest block */
declare
tmp_data number;
begin
select 1/0 into tmp_data from dual;
exception
when ZERO_DIVIDE then
tmp_data :=SQLCODE;
insert into test_tbl values (tmp_data, 'testok');
commit;
end;
exception
when others then
null;
end;
/
コーディング規約
コーディング規約はない。
ハローワールド
サンプルテーブル
SQL> desc test_tbl
名前 NULL? 型
---- ----- ----------------------------
COL1 NUMBER
COL2 VARCHAR2(8)
hello.sql作成
hello.sqlで以下のPL/SQLプログラムを保存。
declare
test_var date;
begin
select sysdate into test_var from dual;
end;
/
解説
文はセミコロン(;)で終了
ブロックの最後にスラッシュ(/)が必要
大文字、小文字は関係ない
実行
SQL> @hello.sql
PL/SQLプロシージャが正常に完了しました。
データ形式
データ型(スカラー型・プリミティブ型)
NUMBER 符号付整数、固定小数点数、浮動小数点数
BINARY_INTEGER NUMBER型よりメモリ領域が少ない
PLS_INTEGER NUMBER型より早い
CHAR
VARCHAR2
LONG 互換性のためにあるLOB型を推奨
LONGROW 互換性のためにあるLOB型を推奨
NCHAR
NVARCHAR2
RAW 互換性のためにあるLOB型を推奨
UROWID ROWID
BOOLEAN TRUE | FALSE | NULL
DATE
BFILE
BLOB
CLOB
NCLOB
データ定義
変数名、データ型の順番で定義
文はセミコロン(;)で終了
初期値は:=で 指定
NOT NULL制約が可能
定数の定義はCONST
変数定義(=データ定義)
変数名 型
test_number number(10);
test_string varchar2(100);
jpn_tax number(5,2) := 1.05;
test_number2 number(3) := 10 not null;
test_date date := sysdate;
MAX_VALUE const number := 1.05;
属性定義
PL/SQLでは、テーブル定義からデータ型を参照することが出来る。
サンプルのテーブル
create table emp_tbl (
empid number,
emp_name varchar2(20)
)
/
属性定義の仕方
declare
test_no emp_tbl.empid%type;
begin
end;
/
解説
emp_tblテーブルのempidカラムのデータ型を%typeで 取得できるのでテーブル定義が変更してもPL/SQLの実装を修正する必要がなくなる。
索引付き表(スカラー型の配列)
type 索 引付き表型名 is table of スカラー型 index by binary_integer;
declare
type test_array is table of hoge_table.fuga_txt%type index by binary_integer;
test_variable test_array;
cursor c_hoge is select hoge_id,fuga_txt from hoge_table;
begin
for wk_cursor in c_hoge loop
test_variable(wk_cursor.hoge_id) := wk_cursor.fuga_txt;
end loop;
end;
/
show errors;
レコード
type レコード型名 is record (列名 データ型, 列名 データ型, ...);
declare
-- CAUTION!!
-- A user execute this program on sqlplus who must input command that 'set
serveroutput on'.
--
-- definition
type t_rec is record (
id_record test_record_tbl.id_record%type,
record_name test_record_tbl.record_name%type
);
-- declaretion
wk_val t_rec;
cursor c_sample is select id_record, record_name from test_record_tbl;
begin
open c_sample;
loop
fetch c_sample into wk_val;
dbms_output.put_line(wk_val.record_name);
exit when c_sample%notfound;
end loop;
dbms_output.put_line('end loop');
close c_sample;
end;
/
show errors;
レコードの索引付き表(二次元配列)
type レコード型名 is record (列名 データ型, 列名 データ型, ...);
type レコードの索引付き表型名 is table of レコード型名 index by binary_integer;
declare
-- the generic program language say multi-dimensional array.
-- existed samle function is 'varray' object.
-- 1. define record
type r_sample is record (
id_record test_record_tbl.id_record%type,
record_name test_record_tbl.record_name%type
);
-- 2. define arrray
type idx_ is table of r_sample index by binary_integer;
-- 3. declaration
dimen_ary idx_;
cursor c_tmp is select id_record, record_name from test_record_tbl;
begin
for wk_emp in c_tmp loop
dimen_ary(wk_emp.id_record) := wk_emp;
end loop;
end;
/
show errors;
演算子
= ==ではないので注意
!=
<> != と同意
>
<
<=
>=
IN
NOT IN
BETWEEN x AND y
NOT BETWEEN x AND y
LIKE
NOT LIKE
IS NULL
IS NOT NULL
PL/SQLは、NULL は何と比較してもNULLである。
制御構造
分岐
if文
if 条件1 then
-- 処理
elsif 条件2 then
-- 処理
end if;
declare
test_var number := 1;
begin
if test_var = 1 then
null; -- null は何もしない時に記述
elsif test_var = 2 then
null;
end if;
end;
/
反復
for文
for カウンタ in min...max [reverse] loop
end loop;
以下の例は、グローバルなtest_var変数とfor文中のtest_var変数2つを定義している。
for文中のカウンタであるtest_var変数は暗黙で定義しているので型を書いたりしていない。
サンプルのテーブル定義
create table test_tbl (col1 number);
declare
test_var number := 100;
begin
for test_var in 1..10 loop -- ローカルとしてtest_var変数を暗黙定義.
insert into test_tbl values (test_var);
end loop;
insert into test_tbl values (test_var);
commit;
end;
/
前判定反復
while文
while 条件 loop
end loop;
declare
test_var number := 100;
begin
while test_var <= 110 loop
insert into test_tbl values (test_var);
test_var := test_var + 1; -- インクリメント演算は出来ない
end loop;
commit;
end;
/
後判定反復
loop文
loop
end loop;
PL/SQLではC言語のように、while文を反復の最後に設定できないのでif文とloop文で行う。
declare
test_var number := 100;
begin
loop
-- 処理を書く.
-- 後判定
if test_var = 100 then
exit; -- loopを抜ける
end if;
end loop;
end;
/
if文を利用してexitするのは、exit when文を使えば1 行に出来る。
declare
test_var number := 100;
begin
loop
exit when test_var = 100;
end loop;
end;
/
データ処理
select into文
select into文は、1レコードの取得しか出来ません。
複数のレコードの場合はカーソルを使います。
select カラム名 into 変数 from テーブル [where 検索条件など]
declare
test_var date;
begin
-- dual表はテストなどダミーで使う表.
select sysdate into test_var from dual;
end;
/
カーソル
select文の結果セット(データ集合)に対して、1レコードづつ処理していく時に利用する。
PL/SQLのカーソルは戻る事が出来ない。(Javaのjava.sql.ResultSetなどは戻る事が可能)
カーソル作成手順
カーソル型の定義
カーソル用の変数定義
オープン
フェッチ(fetch)(定義した変数にデータを格納する事)
データ存在チェック
処理
クローズ
SQL> desc test_tbl
名前 NULL? 型
---- ----- --------------
COL1 NUMBER
COL2 VARCHAR2(8)
declare
/* カーソル型の定義 */
cursor c_test is select col1, col2 from test_tbl;
/* カーソル用の変数定義 */
c_test_val c_test%rowtype;
begin
open c_test;
loop
fetch c_test into c_test_val;
exit when c_test%notfound;
end loop;
close c_test;
end;
/
解説
%rowtypeでcol1の型(NUMBER)とcol2の型 (VARCHAR2(8))を取得している。%notfoundで存在チェック
カーソルforループ
for レコード型変数名 in カーソル loop
end loop;
カーソルfor loop でレコード型変数名をforスコープ内に出来る。
declare
/* cursor definition */
cursor c_test is select col1, col2 from test_tbl;
begin
for c_test_val in c_test loop
exit when c_test%notfound;
end loop;
end;
/
エラー処理
例外処理と通知
exceptionブロックに例外処理を書く
バインド変数で外部に通知する
PL/SQLではexceptionからbeginには復旧できない
例外をハンドリングしてexceptionブロックが正常に終了するとエラーがなかったようになる。
このサンプルは、sqlplusで実行するとエラーが出ます。各自、情報収集して解決してください。
declare
cursor c_tmp is select col1, col2 from test_tbl;
tmp_val c_tmp%rowtype;
begin
open c_tmp;
loop
fetch c_tmp into tmp_val;
exit when c_tmp%notfound;
end loop;
close c_tmp
:status := '0'; /* bind variable */
exception
when others then
if c_tmp%isopen then
close c_tmp;
end if;
:status := '1';
end;
/
解説
when others thenは、C言語ではswitch文の defalt句、Javaではjava.lang.Exception
サンプル2
declare
tmp_date date;
begin
insert into test_tbl values (111, 'test');
select sysdate into tmp_date from dual where 1=2;
commit;
exception
when others then
commit;
end;
/
実行結果
SQL> @test9.sql
PL/SQLプロシージャが正常に完了しました。
解説
例外処理(exception)を書いているので正常終了になる
例外ハンドラ
例外一覧
例外名 ORA-XXX SQLCODE 条件
ACCESS_INTO_NULL 6530 -6530 初期化していないオブジェクト(アトミックNULL)の属性に代入した時
COLLECTION_IS_NULL 6531 -6531
CURSOR_ALREADY_OPEN 6511 -6511
DUP_VAL_ON_INDEX 1 -1 UNIQUE索引に重複(Duplicate)データを格納した時
INVALID_CURSOR 1001 -1001 不正カーソル処理時
INVALID_NUMBER 1722 -1722 文字列から数値の置換に失敗した時
LOGIN_DENIED 1017 -1017 ログイン失敗時
NO_DATA_FOUND 1403 +100
NOT_LOGGED_ON 1012 -1012 Oracleに接続していないプログラムがデータベースコールを発行した時
PROGRAM_ERROR 6501 -6501 PL/SQL内部に問題点がある時
ROWTYPE_MISMATCH 6504 -6504
SELF_IS_NULL 30625 -30625
STORAGE_ERROR 6500 -6500 メモリ関連エラー時
SUBSCRIPT_BEYOND_COUNT 6533 -6533 コレクションの要素数より大きいインデックスでアクセスした時
SUBSCRIPT_OUTSIDE_LIMIT 6532 -6532 有効範囲外で表、varrayなどにアクセスした時
SYS_INVALID_ROWID 1410 -1410
TIMEOUT_ON_RESOURCE 51 -51
TOO_MANY_ROWS 1422 -1422 select into文が複数行を返した時
VALUE_ERROR 6502 -6502 算術、変換、切り捨て、サイズ制約などのエラー時
ZERO_DIVIDE 1476 -1476
when 例外名 then
declare
tmp_data number;
begin
select 1/0 into tmp_data from dual;
exception
when ZERO_DIVIDE then
tmp_data := 10;
end;
/
declare
tmp_data number;
begin
select col1 into tmp_data from test_tbl;
exception
when no_data_found or too_many_rows then
null;
end;
/
declare
tmp_data number := 0;
begin
select 1/0 into tmp_data from dual;
exception
when no_data_found then
null;
when too_many_rows then
null;
when zero_divide then
null;
when others then
null;
end;
/
エラーメッセージ取得
SQLCODE関数とSQLERRM(ERRor-Message)関数を利用
SQLCODEとSQLERRMは必ず変数に代入し なければならない。
declare
tmp_id number;
tmp_data test_tbl.col2%type := '';
error_msg varchar2(2000);
begin
select 1/0 into tmp_data from dual;
exception
when others then
if SQLCODE = -1476 then
tmp_id := SQLCODE;
insert into test_tbl values(tmp_id, 'test ok');
commit;
else
tmp_id := SQLCODE;
error_msg := SQLERRM;
insert into test_tbl values(tmp_id, tmp_data);
commit;
end if;
end;
/
関数
関数の種類
Function
戻り値が1つ
Procedure
戻り値が複数
構文
Function構文
Function 名前 [ ([引数名 [IN | OUT | INOUT]? データ型 [default デフォルト値]? ]* ) ]
Return データ型 is [宣言部]
Begin
[実行部]
[Exception]
End;
Procedure構文
Procedure 名前[ ([引数名 [IN | OUT | INOUT]? データ型 [default デフォルト値]? ]* ) ] is [宣言部]
Begin
[実行部]
[Exception]
End;
プロシージャのサンプル
declare
procedure test_pcd( tmp_num IN number, tmp_str IN varchar2 ) is
begin
insert into test_tbl values (tmp_num, tmp_str);
end;
begin
test_pcd(99,'99');
commit;
end;
/
function, procedureでは、引数でデータサイズなどの制約をつけられない。
declare
procedure sample1(var in varchar2(20)) is
begin
end;
begin
end;
/
show errors;
SQL> @test04.sql
procedure sample1(var in varchar2(20)) is
*
行2でエラーが発生しました。:
ORA-06550: 行2、列36:
PLS-00103: 記号"("が見つかりました。 次のうちの1つが入るとき:
:= . ) , @ % default character
記号":=" は続行のために"("に代わりました。
ORA-06550: 行4、列3:
PLS-00103: 記号"END"が見つかりました。 次のうちの1つが入るとき:
begin case declare exit for goto if loop mod null pragma
raise return select update while with
制約をつけたい場合は、subtypeを使う
subtype データ名 is データ型
declare
subtype myVChar is varchar2(20);
procedure sample1(var in myVChar) is
begin
null;
end;
begin
null;
end;
/
show errors;
共通ライブラリ化の方法
PL/SQLでは、共通ライブラリ化することを、ストアドプロ グラムと呼ぶ
他の言語では、一般的にファイルとしてライブラリとするが、PL/SQLはデータベース内に格納する
データベーススキーマのオブジェクトであるので実行権限(Execute権限)が必要
PL/SQLをコンパイルしたコードをPコードと 呼ぶ
作成時にコンパイルしているため、解析フェーズが省略される分、パフォーマンスが良い
ユーザに関係なく、システム表領域に作成される
種類 概要
ストアドプロシージャ 1つのプロシージャをライブラリ化
ストアドファンクション 1つのファンクションをライブラリ化
データベーストリガ イベントハンドラ
パッケージ 上記をまとめる。
ストアド化
create or replace
procedure test_pcd(
tmp_num IN number,
tmp_str IN varchar2
) is
begin
insert into test_tbl values (tmp_num, tmp_str);
commit;
end;
/
show errors;
パッケージ作成
仕様部と本体部を作成する。
仕様部が外部インタフェースになる。
仕様部と本体部は1つのファイルに纏めても、分割しても良い
仕様部を最初にコンパイルしなければならない。
仕様部
create or replace package パッケージ名 is
...
end パッケージ名;
create or replace package sample_package is
procedure sample1(wk_col1 in number);
function sample2(wk_col2 in varchar2) return varchar2;
end sample_package;
/
show errors;
本体部
create or replace package body パッ ケージ名 is
...
end パッケージ名;
create or replace package body sample_package is
procedure sample1(wk_col1 in number) is
begin
insert into test_tbl values (wk_col1, 'package');
commit;
end;
function sample2(wk_col2 in varchar2) return varchar2 is
begin
insert into test_tbl values ('11', wk_col2);
commit;
return '0';
end;
end sample_package;
/
show errors;
作ったものをテストするプログラム
declare
wk_str varchar2(100);
function test_fnc(wk_col1 in number) return number is
begin
return 0;
end;
begin
sample_package.sample1(1);
wk_str := sample_package.sample2('1');
end;
/
show errors;
解説
パッケージもオブジェクトなので、ピリオドを利用して呼び出す。 適当なファイル名をつけて実行してみてください。
トリガー
ユーザはトリガーが起動した事は分からない
DMLの前後などに設定する。
プログラムでよくあるイベントハンドラである。
トリガーは引数を持たない。
注意点
トリガーは、自動起動するため、開発者はその存在を忘れる可能性がある。 運用後の保守、デバック等が複雑になる傾向があるので、基本的にはトリガー機能を使わず、代案がない場合に利用するように心がける事
トリガーの管理
alter triger トリガー名 [enable | disable]
user_triggers
create or replace trigger トリガー名 before
[insert | update | delete] or on テーブル名
declare
begin
end;
/
create or replace trigger sample_trigger before
insert or update or delete on test_tbl
declare
begin
insert into trigger_log values (99,'trger go');
end;
/
実行
SQL> insert into test_tbl values (1, '111');
1行が作成されました。
結果
SQL> select * from trigger_log;
COL1 COL2
---------- ----------------
99 trger go
API(Application Program Interface)
Oracle9i PLSQL パッケージ・プロシージャおよびタイプ・リファレンス リリース2(9.2)がAPIのドキュメントになる。 sysユーザ権限でdesc standardを実行すれば、 standardと呼ばれる、標準APIが分かる。 以下でソースコードが読める。
select text from user_source where name= 'STANDARD';
デバック
プログラムに空白行を含めない。
PL/SQLでは、空白行は実行時に1行として含めない。つまり空白行は実行時に削除 する。 そのため、エ ラー時の行番がエディタ上の行番号とづれてしまう。 エディタでの開発の場合は、プログラムに空白行を含めない方が効率がよい。
user_errors表をつかう。
PL/SQLでは、ストアドプログラムは、プログラムにエラーが存在しても、取り合え ずデータベースに登録する。どこにエラーがあるか判断するには、user_errors表を確認する。
user_source表をつかう。
コンパイルした結果とソースコードをこの表に記録しているので、デバック時に役立つ
show errorsコマンド
user_errors表を確認するのは、手間が掛かるのでソースコードの最終行に show errorsコマンドを書いておく事でデバックが簡単になる。
他の言語よりコンパイルのサイクルを短くする。
PL/SQLをエディタで開発する場合は、ソースコードを全て記述してからコンパイル するのではなく少しコーディングしたら直ぐにコンパイルするように心がける事。PL/SQLの開発環境は粗悪なので非常に有効である。
はじめに
対象者
コメント
構造
コーディング規約
ハローワールド
データ形式
演算子
制御構造
データ処理
エラー処理
関数
API
デバック
sqlplusを使って、Helloworldから始まり、データ形式、制御構造、関数の書き方などをサラっと学習します。
今後、オラクル社のリファレンスで学習できるようにするのが目的です。
参考資料には、オラクル社へのマニュアル・リンクを用意しました。
入門書に書いてある例えなどはありません。
言語習得に必要な部分のみしか書きませんので、sqlplusでサンプルを実行しながら学習してください。
PL/SQLと他の言語を比較してポイントとなる部分は赤 マークしています。
対象者
Oracleをこれから始める方。
コメント
コメントの書き方は二つあります。
-- 1行のコメントはハイフン(-)2つです。
/*
複数行のコメントはC言語などのように
スラッシュ、アスタリスクです。
*/
構造
下記の構造をPL/SQLブロックという
Begin End;のみ必須
入れ子で書くこと(=ネストブロック)も可能
サンプル
Declare
-- 宣言
Begin
-- 処理
Exception
-- エラー処理
End;
ネストブロック
Declare
Begin
Declare
-- 宣言
Begin
-- 処理
Exception
-- エラー処理
End;
Exception
End;
declare
tmp_data number := 0;
begin
/* nest block */
declare
tmp_data number;
begin
select 1/0 into tmp_data from dual;
exception
when ZERO_DIVIDE then
tmp_data :=SQLCODE;
insert into test_tbl values (tmp_data, 'testok');
commit;
end;
exception
when others then
null;
end;
/
コーディング規約
コーディング規約はない。
ハローワールド
サンプルテーブル
SQL> desc test_tbl
名前 NULL? 型
---- ----- ----------------------------
COL1 NUMBER
COL2 VARCHAR2(8)
hello.sql作成
hello.sqlで以下のPL/SQLプログラムを保存。
declare
test_var date;
begin
select sysdate into test_var from dual;
end;
/
解説
文はセミコロン(;)で終了
ブロックの最後にスラッシュ(/)が必要
大文字、小文字は関係ない
実行
SQL> @hello.sql
PL/SQLプロシージャが正常に完了しました。
データ形式
データ型(スカラー型・プリミティブ型)
NUMBER 符号付整数、固定小数点数、浮動小数点数
BINARY_INTEGER NUMBER型よりメモリ領域が少ない
PLS_INTEGER NUMBER型より早い
CHAR
VARCHAR2
LONG 互換性のためにあるLOB型を推奨
LONGROW 互換性のためにあるLOB型を推奨
NCHAR
NVARCHAR2
RAW 互換性のためにあるLOB型を推奨
UROWID ROWID
BOOLEAN TRUE | FALSE | NULL
DATE
BFILE
BLOB
CLOB
NCLOB
データ定義
変数名、データ型の順番で定義
文はセミコロン(;)で終了
初期値は:=で 指定
NOT NULL制約が可能
定数の定義はCONST
変数定義(=データ定義)
変数名 型
test_number number(10);
test_string varchar2(100);
jpn_tax number(5,2) := 1.05;
test_number2 number(3) := 10 not null;
test_date date := sysdate;
MAX_VALUE const number := 1.05;
属性定義
PL/SQLでは、テーブル定義からデータ型を参照することが出来る。
サンプルのテーブル
create table emp_tbl (
empid number,
emp_name varchar2(20)
)
/
属性定義の仕方
declare
test_no emp_tbl.empid%type;
begin
end;
/
解説
emp_tblテーブルのempidカラムのデータ型を%typeで 取得できるのでテーブル定義が変更してもPL/SQLの実装を修正する必要がなくなる。
索引付き表(スカラー型の配列)
type 索 引付き表型名 is table of スカラー型 index by binary_integer;
declare
type test_array is table of hoge_table.fuga_txt%type index by binary_integer;
test_variable test_array;
cursor c_hoge is select hoge_id,fuga_txt from hoge_table;
begin
for wk_cursor in c_hoge loop
test_variable(wk_cursor.hoge_id) := wk_cursor.fuga_txt;
end loop;
end;
/
show errors;
レコード
type レコード型名 is record (列名 データ型, 列名 データ型, ...);
declare
-- CAUTION!!
-- A user execute this program on sqlplus who must input command that 'set
serveroutput on'.
--
-- definition
type t_rec is record (
id_record test_record_tbl.id_record%type,
record_name test_record_tbl.record_name%type
);
-- declaretion
wk_val t_rec;
cursor c_sample is select id_record, record_name from test_record_tbl;
begin
open c_sample;
loop
fetch c_sample into wk_val;
dbms_output.put_line(wk_val.record_name);
exit when c_sample%notfound;
end loop;
dbms_output.put_line('end loop');
close c_sample;
end;
/
show errors;
レコードの索引付き表(二次元配列)
type レコード型名 is record (列名 データ型, 列名 データ型, ...);
type レコードの索引付き表型名 is table of レコード型名 index by binary_integer;
declare
-- the generic program language say multi-dimensional array.
-- existed samle function is 'varray' object.
-- 1. define record
type r_sample is record (
id_record test_record_tbl.id_record%type,
record_name test_record_tbl.record_name%type
);
-- 2. define arrray
type idx_ is table of r_sample index by binary_integer;
-- 3. declaration
dimen_ary idx_;
cursor c_tmp is select id_record, record_name from test_record_tbl;
begin
for wk_emp in c_tmp loop
dimen_ary(wk_emp.id_record) := wk_emp;
end loop;
end;
/
show errors;
演算子
= ==ではないので注意
!=
<> != と同意
>
<
<=
>=
IN
NOT IN
BETWEEN x AND y
NOT BETWEEN x AND y
LIKE
NOT LIKE
IS NULL
IS NOT NULL
PL/SQLは、NULL は何と比較してもNULLである。
制御構造
分岐
if文
if 条件1 then
-- 処理
elsif 条件2 then
-- 処理
end if;
declare
test_var number := 1;
begin
if test_var = 1 then
null; -- null は何もしない時に記述
elsif test_var = 2 then
null;
end if;
end;
/
反復
for文
for カウンタ in min...max [reverse] loop
end loop;
以下の例は、グローバルなtest_var変数とfor文中のtest_var変数2つを定義している。
for文中のカウンタであるtest_var変数は暗黙で定義しているので型を書いたりしていない。
サンプルのテーブル定義
create table test_tbl (col1 number);
declare
test_var number := 100;
begin
for test_var in 1..10 loop -- ローカルとしてtest_var変数を暗黙定義.
insert into test_tbl values (test_var);
end loop;
insert into test_tbl values (test_var);
commit;
end;
/
前判定反復
while文
while 条件 loop
end loop;
declare
test_var number := 100;
begin
while test_var <= 110 loop
insert into test_tbl values (test_var);
test_var := test_var + 1; -- インクリメント演算は出来ない
end loop;
commit;
end;
/
後判定反復
loop文
loop
end loop;
PL/SQLではC言語のように、while文を反復の最後に設定できないのでif文とloop文で行う。
declare
test_var number := 100;
begin
loop
-- 処理を書く.
-- 後判定
if test_var = 100 then
exit; -- loopを抜ける
end if;
end loop;
end;
/
if文を利用してexitするのは、exit when文を使えば1 行に出来る。
declare
test_var number := 100;
begin
loop
exit when test_var = 100;
end loop;
end;
/
データ処理
select into文
select into文は、1レコードの取得しか出来ません。
複数のレコードの場合はカーソルを使います。
select カラム名 into 変数 from テーブル [where 検索条件など]
declare
test_var date;
begin
-- dual表はテストなどダミーで使う表.
select sysdate into test_var from dual;
end;
/
カーソル
select文の結果セット(データ集合)に対して、1レコードづつ処理していく時に利用する。
PL/SQLのカーソルは戻る事が出来ない。(Javaのjava.sql.ResultSetなどは戻る事が可能)
カーソル作成手順
カーソル型の定義
カーソル用の変数定義
オープン
フェッチ(fetch)(定義した変数にデータを格納する事)
データ存在チェック
処理
クローズ
SQL> desc test_tbl
名前 NULL? 型
---- ----- --------------
COL1 NUMBER
COL2 VARCHAR2(8)
declare
/* カーソル型の定義 */
cursor c_test is select col1, col2 from test_tbl;
/* カーソル用の変数定義 */
c_test_val c_test%rowtype;
begin
open c_test;
loop
fetch c_test into c_test_val;
exit when c_test%notfound;
end loop;
close c_test;
end;
/
解説
%rowtypeでcol1の型(NUMBER)とcol2の型 (VARCHAR2(8))を取得している。%notfoundで存在チェック
カーソルforループ
for レコード型変数名 in カーソル loop
end loop;
カーソルfor loop でレコード型変数名をforスコープ内に出来る。
declare
/* cursor definition */
cursor c_test is select col1, col2 from test_tbl;
begin
for c_test_val in c_test loop
exit when c_test%notfound;
end loop;
end;
/
エラー処理
例外処理と通知
exceptionブロックに例外処理を書く
バインド変数で外部に通知する
PL/SQLではexceptionからbeginには復旧できない
例外をハンドリングしてexceptionブロックが正常に終了するとエラーがなかったようになる。
このサンプルは、sqlplusで実行するとエラーが出ます。各自、情報収集して解決してください。
declare
cursor c_tmp is select col1, col2 from test_tbl;
tmp_val c_tmp%rowtype;
begin
open c_tmp;
loop
fetch c_tmp into tmp_val;
exit when c_tmp%notfound;
end loop;
close c_tmp
:status := '0'; /* bind variable */
exception
when others then
if c_tmp%isopen then
close c_tmp;
end if;
:status := '1';
end;
/
解説
when others thenは、C言語ではswitch文の defalt句、Javaではjava.lang.Exception
サンプル2
declare
tmp_date date;
begin
insert into test_tbl values (111, 'test');
select sysdate into tmp_date from dual where 1=2;
commit;
exception
when others then
commit;
end;
/
実行結果
SQL> @test9.sql
PL/SQLプロシージャが正常に完了しました。
解説
例外処理(exception)を書いているので正常終了になる
例外ハンドラ
例外一覧
例外名 ORA-XXX SQLCODE 条件
ACCESS_INTO_NULL 6530 -6530 初期化していないオブジェクト(アトミックNULL)の属性に代入した時
COLLECTION_IS_NULL 6531 -6531
CURSOR_ALREADY_OPEN 6511 -6511
DUP_VAL_ON_INDEX 1 -1 UNIQUE索引に重複(Duplicate)データを格納した時
INVALID_CURSOR 1001 -1001 不正カーソル処理時
INVALID_NUMBER 1722 -1722 文字列から数値の置換に失敗した時
LOGIN_DENIED 1017 -1017 ログイン失敗時
NO_DATA_FOUND 1403 +100
NOT_LOGGED_ON 1012 -1012 Oracleに接続していないプログラムがデータベースコールを発行した時
PROGRAM_ERROR 6501 -6501 PL/SQL内部に問題点がある時
ROWTYPE_MISMATCH 6504 -6504
SELF_IS_NULL 30625 -30625
STORAGE_ERROR 6500 -6500 メモリ関連エラー時
SUBSCRIPT_BEYOND_COUNT 6533 -6533 コレクションの要素数より大きいインデックスでアクセスした時
SUBSCRIPT_OUTSIDE_LIMIT 6532 -6532 有効範囲外で表、varrayなどにアクセスした時
SYS_INVALID_ROWID 1410 -1410
TIMEOUT_ON_RESOURCE 51 -51
TOO_MANY_ROWS 1422 -1422 select into文が複数行を返した時
VALUE_ERROR 6502 -6502 算術、変換、切り捨て、サイズ制約などのエラー時
ZERO_DIVIDE 1476 -1476
when 例外名 then
declare
tmp_data number;
begin
select 1/0 into tmp_data from dual;
exception
when ZERO_DIVIDE then
tmp_data := 10;
end;
/
declare
tmp_data number;
begin
select col1 into tmp_data from test_tbl;
exception
when no_data_found or too_many_rows then
null;
end;
/
declare
tmp_data number := 0;
begin
select 1/0 into tmp_data from dual;
exception
when no_data_found then
null;
when too_many_rows then
null;
when zero_divide then
null;
when others then
null;
end;
/
エラーメッセージ取得
SQLCODE関数とSQLERRM(ERRor-Message)関数を利用
SQLCODEとSQLERRMは必ず変数に代入し なければならない。
declare
tmp_id number;
tmp_data test_tbl.col2%type := '';
error_msg varchar2(2000);
begin
select 1/0 into tmp_data from dual;
exception
when others then
if SQLCODE = -1476 then
tmp_id := SQLCODE;
insert into test_tbl values(tmp_id, 'test ok');
commit;
else
tmp_id := SQLCODE;
error_msg := SQLERRM;
insert into test_tbl values(tmp_id, tmp_data);
commit;
end if;
end;
/
関数
関数の種類
Function
戻り値が1つ
Procedure
戻り値が複数
構文
Function構文
Function 名前 [ ([引数名 [IN | OUT | INOUT]? データ型 [default デフォルト値]? ]* ) ]
Return データ型 is [宣言部]
Begin
[実行部]
[Exception]
End;
Procedure構文
Procedure 名前[ ([引数名 [IN | OUT | INOUT]? データ型 [default デフォルト値]? ]* ) ] is [宣言部]
Begin
[実行部]
[Exception]
End;
プロシージャのサンプル
declare
procedure test_pcd( tmp_num IN number, tmp_str IN varchar2 ) is
begin
insert into test_tbl values (tmp_num, tmp_str);
end;
begin
test_pcd(99,'99');
commit;
end;
/
function, procedureでは、引数でデータサイズなどの制約をつけられない。
declare
procedure sample1(var in varchar2(20)) is
begin
end;
begin
end;
/
show errors;
SQL> @test04.sql
procedure sample1(var in varchar2(20)) is
*
行2でエラーが発生しました。:
ORA-06550: 行2、列36:
PLS-00103: 記号"("が見つかりました。 次のうちの1つが入るとき:
:= . ) , @ % default character
記号":=" は続行のために"("に代わりました。
ORA-06550: 行4、列3:
PLS-00103: 記号"END"が見つかりました。 次のうちの1つが入るとき:
begin case declare exit for goto if loop mod null pragma
raise return select update while with
制約をつけたい場合は、subtypeを使う
subtype データ名 is データ型
declare
subtype myVChar is varchar2(20);
procedure sample1(var in myVChar) is
begin
null;
end;
begin
null;
end;
/
show errors;
共通ライブラリ化の方法
PL/SQLでは、共通ライブラリ化することを、ストアドプロ グラムと呼ぶ
他の言語では、一般的にファイルとしてライブラリとするが、PL/SQLはデータベース内に格納する
データベーススキーマのオブジェクトであるので実行権限(Execute権限)が必要
PL/SQLをコンパイルしたコードをPコードと 呼ぶ
作成時にコンパイルしているため、解析フェーズが省略される分、パフォーマンスが良い
ユーザに関係なく、システム表領域に作成される
種類 概要
ストアドプロシージャ 1つのプロシージャをライブラリ化
ストアドファンクション 1つのファンクションをライブラリ化
データベーストリガ イベントハンドラ
パッケージ 上記をまとめる。
ストアド化
create or replace
procedure test_pcd(
tmp_num IN number,
tmp_str IN varchar2
) is
begin
insert into test_tbl values (tmp_num, tmp_str);
commit;
end;
/
show errors;
パッケージ作成
仕様部と本体部を作成する。
仕様部が外部インタフェースになる。
仕様部と本体部は1つのファイルに纏めても、分割しても良い
仕様部を最初にコンパイルしなければならない。
仕様部
create or replace package パッケージ名 is
...
end パッケージ名;
create or replace package sample_package is
procedure sample1(wk_col1 in number);
function sample2(wk_col2 in varchar2) return varchar2;
end sample_package;
/
show errors;
本体部
create or replace package body パッ ケージ名 is
...
end パッケージ名;
create or replace package body sample_package is
procedure sample1(wk_col1 in number) is
begin
insert into test_tbl values (wk_col1, 'package');
commit;
end;
function sample2(wk_col2 in varchar2) return varchar2 is
begin
insert into test_tbl values ('11', wk_col2);
commit;
return '0';
end;
end sample_package;
/
show errors;
作ったものをテストするプログラム
declare
wk_str varchar2(100);
function test_fnc(wk_col1 in number) return number is
begin
return 0;
end;
begin
sample_package.sample1(1);
wk_str := sample_package.sample2('1');
end;
/
show errors;
解説
パッケージもオブジェクトなので、ピリオドを利用して呼び出す。 適当なファイル名をつけて実行してみてください。
トリガー
ユーザはトリガーが起動した事は分からない
DMLの前後などに設定する。
プログラムでよくあるイベントハンドラである。
トリガーは引数を持たない。
注意点
トリガーは、自動起動するため、開発者はその存在を忘れる可能性がある。 運用後の保守、デバック等が複雑になる傾向があるので、基本的にはトリガー機能を使わず、代案がない場合に利用するように心がける事
トリガーの管理
alter triger トリガー名 [enable | disable]
user_triggers
create or replace trigger トリガー名 before
[insert | update | delete] or on テーブル名
declare
begin
end;
/
create or replace trigger sample_trigger before
insert or update or delete on test_tbl
declare
begin
insert into trigger_log values (99,'trger go');
end;
/
実行
SQL> insert into test_tbl values (1, '111');
1行が作成されました。
結果
SQL> select * from trigger_log;
COL1 COL2
---------- ----------------
99 trger go
API(Application Program Interface)
Oracle9i PLSQL パッケージ・プロシージャおよびタイプ・リファレンス リリース2(9.2)がAPIのドキュメントになる。 sysユーザ権限でdesc standardを実行すれば、 standardと呼ばれる、標準APIが分かる。 以下でソースコードが読める。
select text from user_source where name= 'STANDARD';
デバック
プログラムに空白行を含めない。
PL/SQLでは、空白行は実行時に1行として含めない。つまり空白行は実行時に削除 する。 そのため、エ ラー時の行番がエディタ上の行番号とづれてしまう。 エディタでの開発の場合は、プログラムに空白行を含めない方が効率がよい。
user_errors表をつかう。
PL/SQLでは、ストアドプログラムは、プログラムにエラーが存在しても、取り合え ずデータベースに登録する。どこにエラーがあるか判断するには、user_errors表を確認する。
user_source表をつかう。
コンパイルした結果とソースコードをこの表に記録しているので、デバック時に役立つ
show errorsコマンド
user_errors表を確認するのは、手間が掛かるのでソースコードの最終行に show errorsコマンドを書いておく事でデバックが簡単になる。
他の言語よりコンパイルのサイクルを短くする。
PL/SQLをエディタで開発する場合は、ソースコードを全て記述してからコンパイル するのではなく少しコーディングしたら直ぐにコンパイルするように心がける事。PL/SQLの開発環境は粗悪なので非常に有効である。
2012年7月7日土曜日
ブランドアイ
ブランドアイ
http://www.branday.com
欢迎大家光临BRANDay宝爱伊(株式会社)ブランドアイ
我们将为大家带来最新的国际名牌二手名牌包包,手表和首饰等的销售。
我们的公司已经在日本开业9年了,在日本大阪有较高知名度,
希望更多中国华人的顾客也能用低廉的价格购买到真正的名牌商品。
★★2012年7月夏季大折扣活动!!!\(⌒∇⌒)/★★
★★难波店全商品★★ ❤❤20%OFF❤❤
2012-6-28 17:53 上传下载附件 (70.77 KB)
大家抓紧时间和机会,
用最低廉的价格买到喜欢的奢侈品,
我们帮你完成这样的心愿(●>v<●)
恭候您的光临喔!!
↓↓↓
宝爱伊 难波店(大阪市中央区)
大阪府大阪市中央区千日前1-8-13 位于大阪中心最繁华的商业街的难波店,是我们的人气商店呢!
销售和收购同时进行中,敬请光临!
【营业时间】 AM11:00 ~ PM8:00 【定休日】 年中无休
【咨询电话】 06-6212-3133(中文对应可)
宝爱伊的网址:http://www.branday.com/cn/ (中文)
❤★❤★❤★❤★❤★❤★❤★❤★❤★❤★❤★❤★❤★❤★❤★❤★❤★❤★❤★❤★❤
主要品牌:
ルイ・ヴィトン 路易·威登
エルメス 爱马仕
シャネル 香奈儿
グッチ 古琦
コーチ 蔻驰
ディオール 迪奥
プラダ 普拉达
カルティエ 卡地亚
ブルガリ 宝格丽
バレンシアガ 巴黎世家
クロエ 克洛伊
ミュウミュウ 缪缪
バリー 巴利
ボッテガ・ヴェネタ 宝嘉
フェンディ 芬迪
セリーヌ 赛琳
タグ・ホイヤー 豪雅
ロレックス 劳力士
オメガ 欧米茄
销售&收购店地址:
宝爱伊 难波店(大阪市中央区)2011年10月开业
大阪府大阪市中央区千日前1-8-13 位于大阪中心最繁华的商业街的难波店,是我们的人气商店呢!
销售和收购同时进行中,敬请光临!
【营业时间】 AM11:00 ~ PM8:00 【定休日】 年中无休
【咨询电话】 06-6212-3133(中文对应可)
宝爱伊的网址:http://www.branday.com/cn/ (中文)
其他收购店地址:
难波店收购中心(大阪市中央区)2011年7月开业
大阪市中央区難波1-4-6 ミフネ難波ビル9F JR/地铁各线なんば駅步行即到。
【营业时间】 AM10:30 ~ PM7:00
【定休日】 星期日・日本国定假日
【咨询电话】 06-6214-2277(中文对应可)
梅田本店&收购专门店 2007年4月重新装修开业
大阪市北区中崎西2-2-1東梅田八千代ビル1階
【营业时间】 AM10:30 ~ PM7:00
【定休日】 星期日・日本国定假日
【咨询电话】 06-6362-0002
西九条駅前店(大阪市此花区) 2009年10月开业
大阪府大阪市此花区西九条1-32-16 サローネ西九条1F JR・阪神『西九条駅』徒步即到。
【营业时间】 AM10:30 ~ PM7:00
【定休日】 星期日・日本国定假日
【咨询电话】 06-6147-0007
2009年12月29日火曜日
主成分分析法图像处理
1.主成分的一般定义
设有随机变量X1,X2,…,Xp, 其样本均数记为 ,,…, ,样本标准差记为S1,S2,…,Sp。首先作标准化变换
我们有如下的定义:
(1) 若C1=a11x1+a12x2+ … +a1pxp, ,且使 Var(C1)最大,则称C1为第一主成分;
(2) 若C2=a21x1+a22x2+…+a2pxp, ,(a21,a22,…,a2p)垂直于(a11,a12,…,a1p),且使Var(C2)最大,则称C2为第二主成分;
(3) 类似地,可有第三、四、五…主成分,至多有p个。
2.主成分的性质
主成分C1,C2,…,Cp具有如下几个性质:
(1) 主成分间互不相关,即对任意i和j,Ci 和Cj的相关系数
Corr(Ci,Cj)=0 i ¹ j
(2) 组合系数(ai1,ai2,…,aip)构成的向量为单位向量,
(3) 各主成分的方差是依次递减的, 即
Var(C1)≥Var(C2)≥…≥Var(Cp)
(4) 总方差不增不减, 即
Var(C1)+Var(C2)+ … +Var(Cp)
=Var(x1)+Var(x2)+ … +Var(xp)
=p
这一性质说明,主成分是原变量的线性组合,是对原变量信息的一种改组,主成分不增加总信息量,也不减少总信息量。
(5) 主成分和原变量的相关系数 Corr(Ci,xj)=aij =aij
(6) 令X1,X2,…,Xp的相关矩阵为R, (ai1,ai2,…,aip)则是相关矩阵R的第i个特征向量(eigenvector)。而且,特征值li就是第i主成分的方差, 即
Var(Ci)= li
其中li为相关矩阵R的第i个特征值(eigenvalue)
l1≥l2≥…≥lp≥0
3.主成分的数目的选取
前已指出,设有p个随机变量,便有p个主成分。由于总方差不增不减,C1,C2等前几个综合变量的方差较大,而Cp,Cp-1等后几个综合变量的方差较小, 严格说来,只有前几个综合变量才称得上主(要)成份,后几个综合变量实为“次”(要)成份。实践中总是保留前几个,忽略后几个。
保留多少个主成分取决于保留部分的累积方差在方差总和中所占百分比(即累计贡献率),它标志着前几个主成分概括信息之多寡。实践中,粗略规定一个百分比便可决定保留几个主成分;如果多留一个主成分,累积方差增加无几,便不再多留。
4.主成分回归
主成分分析本身往往并不是目的,而是达到目的的一种手段。因此,它多用在大型研究项目的某个中间环节。例如,把它用在多重回归中,便产生了主成分回归。另外,它还可以用于聚类、判别分析等。
在多重回归曾指出,当自变量间高度相关时,某些回归参数的估计值极不稳定,甚至出现有悖常理、难以解释的情形。这时,可先采用主成分分析产生若干主成分,它们必定会将相关性较强的变量综合在同一个主成分中,而不同的主成分又是互相独立的。只要多保留几个主成分,原变量的信息不致过多损失。然后,以这些主成分为自变量进行多重回归就不会再出现共线性的困扰。如果原有p个自变量X1,X2,…,Xp,那么,采用全部p个主成分所作回归完全等价于直接对原变量的回归;采用一部分主成分所作回归虽不完全等价于对原变量的回归,但往往能摆脱某些虚假信息,而出现较合理的结果。
5.主成分分析与图像处理
在研究主成分分析方法基本原理的基础上,用MATLAB软件编写了的主成分分析代码用于神经元结构信息的MOST图片处理的程序,并 MATLB软件对图像进行分析,并对处理结果作比较分析。最后证明图像作主成分分析取得了较好的效果,具有一定的可靠性和实用性。
关于MOST图片的一些信息:MOST图片不适合用主成分分析除噪,去除非神经元的冗余信息,因为主成分分析的精髓在于变换和降维,需要有一个特征向量,变换提取出主成分,而MOST数据为灰度图,一个像素用一个0~255的数字表示,且一个位置只有一幅图像,特征向量为一维,无所谓提取,降维。但当把小块图片区域取出,所有像素点灰度构成一个特征向量,则这个向量维度很高(100*100的图片就是10'000维),但可以考虑用PCA提取主成分,降维,再用SVM,ANN等算法识别小块区域中是否有神经元,实现自动分割。
这里是采用记录CSD过程的图片集为例,应用Salwa Saleh编写的matlab代码,使用PCA方法处理后,得到了表现CSD扩散过程的图片。
6.使用PCA算法处理CSD图片集
(1)、材料:在小鼠发生CSD现象的时候,使用内源信号光学成像(OISI)技术,记录到的时间序列图片集
(2)、使用Salwa Saleh的matlab代码,运行程序,得到如下结果:
图一、PCA方法处理得到的一些成分。
图二、PCA方法处理后,与原始图片相减,得到的CSD扩散过程。
由处理结果可见,PCA方法提取出的主成份,清晰地表现出了CSD扩散过程。
附:Salwa Saleh的matlab代码源码
function [psi,a,ev]=pca(f,N)
% [psi,a,ev]=pca(f,N)
%
% Principal Component Analysis using by the Snapshot Method. (Sirovich, 1987)
% Use SVD instead if (# of images) > (# of pixels/image).
%
% MODEL: f = a * diag(sqrt(ev)) * psi'
%
% INPUT:
% f = matrix of images. (individual images are row vectors)
% N = the number of eigenpairs to compute. [default ALL]
%
% OUTPUT:
% psi = column vectors of spatial principal components.
% a = comumn vectors of temporal principal components.
% ev = PCA eigenvalues in descending order.
if size(f,1) > size(f,2)
disp('ERROR: The number of images exceeds the number of pixels/iamge.')
disp(' Consider using SVD instead.')
warning off; return;
end
disp('Forming the correlation matrix ...');
cor = f*f'; % the pixel-correlation matrix.
if nargin<2 % If N is not specified, compute all eigenpairs.
N=size(f,1);
end;
disp('Diagonalizing ...')
[a,ev]=eig(cor); % compute the temporal eigenvectors/values.
[ev,ind]=sort(diag(ev)); % sort in ascending magnitude.
ev=flipud(ev); % switch from ascending to descending.
a=a(:,flipud(ind)); % order the temporal eigenvectors accordingly.
disp('Calculating PCA eigenvectors ...')
a=a(:,1:N);
psi=f'*a*diag(ev(1:N).^-0.5); % compute the spatial eigenvectors.
参考文献:
[1]Salwa Saleh,Shangbin Chen,Dong Chen,Qingming Luo,Using Principal Component Analysis to Detect the
Wavefront of Cortical Spreading Depression
[2]Lu Chen(2007)“主元分析(PCA)理论分析及应用”
http://www.cad.zju.edu.cn/home/chenlu/pca.htm
[3]Jonathon Shlens.(2005)“A Tutorial on Principal Component Analysis”
http://www.snl.salk.edu/~shlens/pub/notes/pca.pdf
[4]aizaixiyuanqian1985提供(2008)“pca方法实现详细步骤”
http://www.pudn.com/downloads102/sourcecode/math/detail417142.html
[4]“[人脸识别Step2]对人脸图片作PCA降维”
http://www.sw-china.org/forum/simple/index.php?t40.html
设有随机变量X1,X2,…,Xp, 其样本均数记为 ,,…, ,样本标准差记为S1,S2,…,Sp。首先作标准化变换
我们有如下的定义:
(1) 若C1=a11x1+a12x2+ … +a1pxp, ,且使 Var(C1)最大,则称C1为第一主成分;
(2) 若C2=a21x1+a22x2+…+a2pxp, ,(a21,a22,…,a2p)垂直于(a11,a12,…,a1p),且使Var(C2)最大,则称C2为第二主成分;
(3) 类似地,可有第三、四、五…主成分,至多有p个。
2.主成分的性质
主成分C1,C2,…,Cp具有如下几个性质:
(1) 主成分间互不相关,即对任意i和j,Ci 和Cj的相关系数
Corr(Ci,Cj)=0 i ¹ j
(2) 组合系数(ai1,ai2,…,aip)构成的向量为单位向量,
(3) 各主成分的方差是依次递减的, 即
Var(C1)≥Var(C2)≥…≥Var(Cp)
(4) 总方差不增不减, 即
Var(C1)+Var(C2)+ … +Var(Cp)
=Var(x1)+Var(x2)+ … +Var(xp)
=p
这一性质说明,主成分是原变量的线性组合,是对原变量信息的一种改组,主成分不增加总信息量,也不减少总信息量。
(5) 主成分和原变量的相关系数 Corr(Ci,xj)=aij =aij
(6) 令X1,X2,…,Xp的相关矩阵为R, (ai1,ai2,…,aip)则是相关矩阵R的第i个特征向量(eigenvector)。而且,特征值li就是第i主成分的方差, 即
Var(Ci)= li
其中li为相关矩阵R的第i个特征值(eigenvalue)
l1≥l2≥…≥lp≥0
3.主成分的数目的选取
前已指出,设有p个随机变量,便有p个主成分。由于总方差不增不减,C1,C2等前几个综合变量的方差较大,而Cp,Cp-1等后几个综合变量的方差较小, 严格说来,只有前几个综合变量才称得上主(要)成份,后几个综合变量实为“次”(要)成份。实践中总是保留前几个,忽略后几个。
保留多少个主成分取决于保留部分的累积方差在方差总和中所占百分比(即累计贡献率),它标志着前几个主成分概括信息之多寡。实践中,粗略规定一个百分比便可决定保留几个主成分;如果多留一个主成分,累积方差增加无几,便不再多留。
4.主成分回归
主成分分析本身往往并不是目的,而是达到目的的一种手段。因此,它多用在大型研究项目的某个中间环节。例如,把它用在多重回归中,便产生了主成分回归。另外,它还可以用于聚类、判别分析等。
在多重回归曾指出,当自变量间高度相关时,某些回归参数的估计值极不稳定,甚至出现有悖常理、难以解释的情形。这时,可先采用主成分分析产生若干主成分,它们必定会将相关性较强的变量综合在同一个主成分中,而不同的主成分又是互相独立的。只要多保留几个主成分,原变量的信息不致过多损失。然后,以这些主成分为自变量进行多重回归就不会再出现共线性的困扰。如果原有p个自变量X1,X2,…,Xp,那么,采用全部p个主成分所作回归完全等价于直接对原变量的回归;采用一部分主成分所作回归虽不完全等价于对原变量的回归,但往往能摆脱某些虚假信息,而出现较合理的结果。
5.主成分分析与图像处理
在研究主成分分析方法基本原理的基础上,用MATLAB软件编写了的主成分分析代码用于神经元结构信息的MOST图片处理的程序,并 MATLB软件对图像进行分析,并对处理结果作比较分析。最后证明图像作主成分分析取得了较好的效果,具有一定的可靠性和实用性。
关于MOST图片的一些信息:MOST图片不适合用主成分分析除噪,去除非神经元的冗余信息,因为主成分分析的精髓在于变换和降维,需要有一个特征向量,变换提取出主成分,而MOST数据为灰度图,一个像素用一个0~255的数字表示,且一个位置只有一幅图像,特征向量为一维,无所谓提取,降维。但当把小块图片区域取出,所有像素点灰度构成一个特征向量,则这个向量维度很高(100*100的图片就是10'000维),但可以考虑用PCA提取主成分,降维,再用SVM,ANN等算法识别小块区域中是否有神经元,实现自动分割。
这里是采用记录CSD过程的图片集为例,应用Salwa Saleh编写的matlab代码,使用PCA方法处理后,得到了表现CSD扩散过程的图片。
6.使用PCA算法处理CSD图片集
(1)、材料:在小鼠发生CSD现象的时候,使用内源信号光学成像(OISI)技术,记录到的时间序列图片集
(2)、使用Salwa Saleh的matlab代码,运行程序,得到如下结果:
图一、PCA方法处理得到的一些成分。
图二、PCA方法处理后,与原始图片相减,得到的CSD扩散过程。
由处理结果可见,PCA方法提取出的主成份,清晰地表现出了CSD扩散过程。
附:Salwa Saleh的matlab代码源码
function [psi,a,ev]=pca(f,N)
% [psi,a,ev]=pca(f,N)
%
% Principal Component Analysis using by the Snapshot Method. (Sirovich, 1987)
% Use SVD instead if (# of images) > (# of pixels/image).
%
% MODEL: f = a * diag(sqrt(ev)) * psi'
%
% INPUT:
% f = matrix of images. (individual images are row vectors)
% N = the number of eigenpairs to compute. [default ALL]
%
% OUTPUT:
% psi = column vectors of spatial principal components.
% a = comumn vectors of temporal principal components.
% ev = PCA eigenvalues in descending order.
if size(f,1) > size(f,2)
disp('ERROR: The number of images exceeds the number of pixels/iamge.')
disp(' Consider using SVD instead.')
warning off; return;
end
disp('Forming the correlation matrix ...');
cor = f*f'; % the pixel-correlation matrix.
if nargin<2 % If N is not specified, compute all eigenpairs.
N=size(f,1);
end;
disp('Diagonalizing ...')
[a,ev]=eig(cor); % compute the temporal eigenvectors/values.
[ev,ind]=sort(diag(ev)); % sort in ascending magnitude.
ev=flipud(ev); % switch from ascending to descending.
a=a(:,flipud(ind)); % order the temporal eigenvectors accordingly.
disp('Calculating PCA eigenvectors ...')
a=a(:,1:N);
psi=f'*a*diag(ev(1:N).^-0.5); % compute the spatial eigenvectors.
参考文献:
[1]Salwa Saleh,Shangbin Chen,Dong Chen,Qingming Luo,Using Principal Component Analysis to Detect the
Wavefront of Cortical Spreading Depression
[2]Lu Chen(2007)“主元分析(PCA)理论分析及应用”
http://www.cad.zju.edu.cn/home/chenlu/pca.htm
[3]Jonathon Shlens.(2005)“A Tutorial on Principal Component Analysis”
http://www.snl.salk.edu/~shlens/pub/notes/pca.pdf
[4]aizaixiyuanqian1985提供(2008)“pca方法实现详细步骤”
http://www.pudn.com/downloads102/sourcecode/math/detail417142.html
[4]“[人脸识别Step2]对人脸图片作PCA降维”
http://www.sw-china.org/forum/simple/index.php?t40.html
2008年12月27日土曜日
袁剑:三十年未见之变局
什么词汇能够最准确地概括2008年的世界?答案可能是:崩溃。
在这个即将完结的年份中,人们目不暇接地见证了一场又一场几近完美的崩溃,从资产市场到宏观经济,仿佛永不落幕的盛宴一瞬间戛然而止。
此刻,惊魂未定的大众仍然处于危机令人窒息的追击之中,但已经有足够多的理由让我们相信:这些接踵而至的崩塌,虽然伴随着诸多偶然,但也绝不是祸从天降。
房地产负重死扛
一场不期而至的雪灾,蹊跷地发生在很少下雪的中国南方,为2008年的中国做出了巫术般的预言。它似乎在暗示:2008年将注定是诡异而又充满转折意味的一年。接下来的汶川地震、瓮安事件、股市崩溃、三鹿奶粉,以及惊涛拍岸的金融海啸,无不暗合了这个预言。显然,对所有这些事件加以解读并理解其中的神秘联系,已经超出了我们的智力范围,因此,我们仅仅能够对那些我们可能理解的一部分予以追索和评论。
2008年,在经过长达接近十年的牛市之后,中国炽热的房地产市场,终于冷却下来,价格下滑,成交锐减。如果说在奥运会之前,人们还对房地产市场抱有某种不切实际的预期的话,那么奥运会之后,绝大多数人都应该承认,中国的房地产市场已经出现了趋势性的逆转。
起始于1998年的这一轮房地产的超级牛市,大致有如下几个动力。一是所谓住房制度改革,即由以前主要由单位分配住房的制度变为个人购买住房。这个改革所释放的需求形成了中国房地产市场第一级动力。在住房制度改革动力释放的同时,中国的大规模城市化成为中国房地产市场的第二级动力,城市化所引发的大量外来人口的进入进一步刺激了房地产的市场需求。中国房地产市场的第三个动力来自于投资需求。其中,国际资本以及中国中产阶层的投资和投机性介入是这一动力的主要成分。
不过,在中国的房地产市场上,还有一个更加隐蔽和更加强劲的动力,那就是政府。自1990年代中后期以降,中国各级地方政府出于对经济增长的渴求,开始强力介入房地产市场。而到了后期,房地产更成为政府推动GDP以及增加地方财政收入的法宝。数据表明,在中国的大城市中,卖地收入以及房地产的相关税收普遍占到地方政府收入的三分之一以上,而房地产对相关产业的拉动则更是为其他行业所望尘莫及。以上诸多因素的不断接力,相互叠加,共同推动了中国房地产市场的这一超级周期。
尤其需要指出的是,由于中国土地市场以及政治的特殊性,中国房地产市场充满了剥夺、腐败并因此充满了暴利,这使得各类资本对这一市场趋之若鹜,并极大地加剧了这一市场的狂热和泡沫程度。容易看出,在推动中国房地产市场的主要因素中,很多是其他经济体所不具备的所谓独特的中国因素。因是之故,中国房地产市场才能够不断让预言家们名声扫地,不管他是外来的和尚,还是本土的专家。
然而,再特殊的因素也无法阻止市场本身荣衰交替、牛熊循环的基本规则。在2008年,中国“特殊”的房地产市场终于暴露了它早应该暴露的疲态。面对这个迟到但显然是再正常不过的调整,赚惯了快钱、过惯了好日子的房地产商们开始集体跳脚。对于绝大部分没有经历过完整市场周期因而也显得特别幼稚的中国的房地产商来说,这种反应并不奇怪。
真正让人奇怪的是,部分地方政府在这个调整面前所表现出的轻率和疯狂。就数据和我们的观察而言,中国房地产市场的调整才刚刚开始。如果用一个更为人们所熟悉的指数来标示的话,今天的中国房地产价格仍然相当于上证指数的5000点,继续调整的空间依然巨大。
更有甚者,在东南沿海的一些主要城市中,地方政府竟然采取各种行政措施,禁止房地产商降价。这种行为非但不能起到阻止房价下跌的作用,反而更彰显了中国房地产市场内在的下跌趋势。同时,它也强化了消费者对房地产市场的熊市预期,从而推动房地产市场的加速下跌。显然,无论从房价收入比、月供收入比、租售比等最终决定房地产价格的基本面因素来看,中国的房地产市场依然泡沫飞溅。尤其需要提醒的是,随着一个从未见过的经济下滑周期的到来,潜在购房人群的未来收入预期将会恶化,这可能是压垮仍然在“负重死扛”的中国房地产市场并将其最终推入深渊的最后一根稻草。
毫无疑问,特殊的中国因素仍然会在未来的中国房地产市场中发挥作用。但同样毫无疑问的是,所有这些特殊因素都已是强弩之末。所以,假如在2009年,中国房地产市场发生一场真正意义上的崩溃并将国有银行以及经济整个拖下水的情况,我们丝毫也不应该感到惊讶。其中理由其实十分简单:以前透支了多少,以后就要还回去多少。从长期看,任何人都无法挑战这一铁律。事实上,我们已经看到高盛在2009年的投资策略中,将中国内地的房地产基本假设从降幅5%~10%扩大至35%~40%。
股市不坚强
如果2008年的中国房地产还显示了某种“猪坚强”的韧性的话,那么,2008年的中国股票市场就是一场标准的崩溃。在这一年中,中国股市一再打破人们的预期,从6000点一路狂泻到最低1600多点,最高跌幅达到将近75%。在前几年全球最牛的中国股市,2008年变成了全球最熊的股市。
与房地产市场相比,中国股市的许多投资者都已经经历过好几轮牛熊循环,对牛市之后的熊市已经有所准备,但如此跌法,仍然超出绝大多数人的预期。这除了显示了那些在集体癫狂时还在高喊“黄金十年”的分析师们的轻佻之外,也给我们留下了一个巨大的疑问:这里面究竟发生了什么?或者换一个问法:究竟有哪些因素远远超出了我们曾经坚信不疑的投资假设?
中国的消费率在2007年已经下降到35%的超低水平,而其他国家的同一指标大多在70%以上。居民在国民收入的所占比例不仅没有随着经济发展而增加,反而不断缩小。在1994年,政府、企业和居民三者的分配比例为18∶16∶66,而到了2005年,这个比例变化为23.8∶16.2∶60.0,居民收入占比不断下滑。
无疑,中国股票市场中的绝大部分投资者都深信这样一个假设:中国是全球最具有成长性的经济体,其高速成长的周期至少还可以再延续三十年。中国经济即便出现调整,也只是我们以前见过的那种周而复始的调整。这个信念被中国的主流媒体反复灌输和强化,成为中国大众思维框架中无需检验的假设。也正是因为这个原因,大多数人都相信,中国股票市场即便发生下跌,也只是正常的调整,最多也只是一个短暂的熊市周期。不幸的是,正是这样一个我们曾经笃信不疑的基础假设正在接受严峻的考验。就我们的智慧可以洞悉的,中国经济基本面的超预期下滑可能是解释中国股票市场超常下跌的最重要的因素。如果中国投资者一直坚信的基本假设——中国经济仍然可以高速成长三十年,果真受到侵蚀的话,2009年的中国股票市场就可能比2008年更为严酷。
在上一轮牛市过后,中国资本市场发生了一个重要的嬗变,那就是,大型垄断企业和国有银行已经成为中国资本市场的主体,占据了绝对份额。从相当程度上,为大型国企和银行上市铺路,正是政府方面推动上一轮牛市的重要动力之一。虽然在上一个经济扩张周期中,这些巨无霸企业的表现似乎差强人意,但这些主要依靠垄断地位以及国家财政挹注的大型企业自1990年代末期逐渐成形之后,毕竟没有经历过真正意义上的下行周期,更不用说像2008年这样突如其来且破坏性极强的下行周期。
所以,这些大型垄断企业在即将到来的惨淡日子中的抗击打能力将对股票市场的表现产生重要影响。对这一点,我们一点也不敢乐观。最新的证据是,三家最重要的大型航空公司已经陆续发生大面积亏损,而这种亏损竟然出现在GDP增速仍然达到10%的条件下。我们很难想像如果GDP增速跌落到8%或者更低的情况下,这些企业将如何应对。人们的确有理由担心,在几年之后,中国的纳税人是不是还需要对这些大型企业及银行再来一次巨额的输血?在资本市场上,唯一确定的就是不确定性。对于2009年及其之后的中国资本市场,则尤其如此。
中国股票市场的巨幅崩跌,虽然称得上是2008年中国的重要事件之一,但就其影响层面以及让人意外的程度,无疑都远远不敌中国经济在2008年三季度所开始呈现出的突然失速。
危机的气味
2008年下半年之后,在上半年还一路狂飙的中国的经济开始出现增长乏力的趋向,而到了九十月份,这种减缓开始演变成高空坠落的硬着陆态势。各种公开数据的转向之快,跌幅之巨,完全可以用急速崩落形容。在10月份,中国规模以上工业企业增加值同比增长仅为8.2%,比去年同期狂跌9.7个百分点,仅仅为过去5年平均增幅的一半。而到了11月份,这个数据更是跌落到5.4%,创2001年末以来的新低。
在财政收入方面,中国2008年11月的财政收入出现多年未见的同比负增长,比去年同月下降3.1%。而在被认为最能够真实反映经济增长的发电量方面,中国11月份统调电厂发电量同比急剧下降7%,是1989年以来的最大跌幅。与此同时,11月份发布的中国11月份采购经理人指数(PMI)也从10月份44.6大幅跌至38.8。与这些宏观数据相印证的则是,微观经济方面所呈现的萧条景象。按照目前最流行的说法就是,需求几乎在一夜之间就消失了。
一位为某家全球新能源巨头供货的朋友告知,这家曾经风光无限的厂商突然之间几乎冻结了所有原材料进货。而另外一位远在南方的朋友则透露,那边的港口出现了1989年以来最惨淡的景象。如果足够细心,我们就会发现,中国诸多宏观数据的下滑幅度,都是20年甚至30年以来所仅见。令人迷惑的是,中国宏观经济的这种急速转向几乎是在短短两三个月之间发生的。这一切,此前虽然多少有所察觉,但下滑之快仍然让人措手不及。仅仅在几个月之前,中央政府还在不断调高银行准备金及利率以抑制经济过热,但在几个月之后,同样是中央政府,却以罕见的频率和幅度大幅降低准备金及利率,并旋即推出了三十年来规模最大的四万亿财政刺激计划。如果你看到一个司机在猛踩刹车之后又突然猛踩油门,那他一定是遇到了某种紧急情况。
这可能正是隐藏在政策急转弯背后的东西。然而,无论是从决策层所表现出的“惊讶”中,还是从中国经济学家们所表现出的令人尴尬的集体失语中,抑或是中国企业界的惊慌失措中,都透露出了这样一个信息:我们对危机以及危机的形式几乎毫无准备。这不啻于证明,在蒙受了三十年难以置信的好运之后,在我们的主流意识中存在着一种危险的智力缺陷——近乎蒙昧的自大和乐观。有趣的是,最早嗅到危机气味的竟然是中国的几位最高领导人。
留意倾听他们含蓄的发言,我们或许可以更早地察觉目下这场危机。其中最直言不讳的是温家宝总理,早在春天,温家宝就直率地发出了预警,他说,2008年将是中国经济最困难的一年。为此,曾有人向媒体力荐温为2008年的最佳宏观分析师。显然,这不仅仅是一个相当有创意的幽默。不过,最困难的可能不是2008年,而是2009年或者再下一年。
相比于奥运会、“神七”上天、雪灾、地震、股市崩溃、房地产低迷,中国经济的突然失速无疑都是2008年头号事件,也必将成为2009年中国最重要的看点。在多年以GDP为中心,甚至以GDP为压倒一切的目标之后,没有什么比经济莫名其妙的失速更令人恐惧的了。因为它已经成为这个社会唯一的凝聚力和黏合剂。值得补充的是,就个人的观察而言,中国所谓精英阶层中的悲观气氛也为二十年来所仅见。
到现在为止,我们仍然无法判断,中国经济在2008年的怪异表现,究竟是一次短暂的意外事故,还是持续了三十年的中国发展模式的最后终结,但毫无疑问的是,我们已经越来越逼近一个危险的转捩点。然而,2008年并不是突然来到的,它不过是一段复杂而晦涩的历史最终显示了它惊人的力量而已。如果我们能够更好地理解过去的三十年,那么,2008年的变化可能就不再会显得如此诡异了,而中国刻下的处境也会被更清晰地呈现出来。
中国模式后遗症
从1978年到2008年,中国改革开放于今已经走过了三十个年头。三十年之后,在人们对市场化的成果已经习以为常的时候,中国独特发展模式所遗留下来的一些后果正在以一种尖利的方式为中国社会所感知。
二十年来,经济的高速增长,更加准确地说,GDP的高速增长成为缓解某种社会焦虑的唯一药方。如此,是不是能够迅速增加GDP,便成为衡量改革和真理的唯一标准。在这样一种思维的推动下,并基于当时的政治及自然禀赋,中国经济被迅疾推上了一条高投资率、高外向型、高污染、低内需、低福利、低保障的发展路径。在1990年代初期冷战结束之后,这种发展模式更被澎湃而至的全球化浪潮推向了极致水平。在经过十数年的酝酿并在新世纪(尤其是2001年加入WTO之后)发力冲刺之后,中国虽然取得了奇迹般的GDP增长速度,但也同样存在结构的扭曲以及内部问题的积累等弊端。
在内需方面,中国的消费率在2007年已经下降到35%的超低水平,而其他国家的同一指标大多在70%以上。隐藏在这种不不寻常的消费率背后的,实际上是财富分配结构的严重扭曲。资料表明,居民在国民收入的所占比例不仅没有随着经济发展而增加,反而不断缩小。在1994年,政府、企业和居民三者的分配比例为18∶16∶66,而到了2005年,这个比例变化为23.8∶16.2∶60.0,居民收入占比不断下滑。有评论者指出:
中国当今消费率比1950年代末期开始的“三年困难”时期还要低。这实际上就是中国内需长期低迷的主要因素。需要指出的是,中国内需低迷并不是一个市场博弈的后果,而是一个政治博弈的后果、其对应的是政府和企业在改革过程中权力的扩张,以及公民权利在同一时期的极度抑制。所谓低保障成本指的即是这样一种情形。
事实上,中国经济中的另外一个畸形指标,也源于同样的政治禀赋。从建国初期起,高投资率就一直是经济的一个隐患,但这种疾病似乎并没有在改革中得到彻底根治。到2005年底,中国的投资占GDP的比率达到48.6%。就此有人调侃道:如果投资率按照这样的速度增长下去,到2012年中国将是一个只有投资没有消费的经济体。从逻辑上说,长期的高投资率一定会导致产能过剩并最终引发经济的剧烈调整。
这一点在中国以前作为一个封闭的经济体系的时候,已经得到了反复验证。这也正是那个时代发生的故事。然而,在中国极速融于全球体系之后,这种过剩产能却在全球市场中得到了消化。加之,中国的各级地方政府通过对各种资源的控制能力将土地、税收、环境、资源以及人力成本压低到极致,这就使中国庞大的过剩产能拥有了其他国家无法匹敌的价格优势。这样,中国过剩产能的投资者不仅没有受到市场的惩罚,反而获得了利润。这反过来鼓励了政府以及企业的这种微观合理,但宏观上可能引发巨大灾难的投资冲动。这种短暂的成功迷惑了许多人,在他们看来,十数年如一日的超高投资率似乎并没有想象的那么吓人。不过,超高投资率与超低消费率的经济增长组合要想得以维系其脆弱的平衡,对外部市场和外部资源的高度依赖就是一个符合逻辑的结果。
其在经济指标上的表现,就是中国变成一个外贸依存度奇高的经济体。到2006年,中国的外贸依存度达到64%的罕见高度,远远超过其他大型经济体。作为一个拥有十几亿人口的大型经济体,中国的外贸依存度是非常不寻常的,也是相当危险的。这意味着,中国每1元的GDP就有0.64元是通过与外部经济交换而来的,这也同时意味着,一旦外部消费需求滑坡,中国经济就可能遭遇重创。2008年初冬中国经济的急跌已经对这种危险性做出了经典的诠释。可是,就在全球金融海啸发生之后,一些主流舆论还在安慰公众说:中国的实体经济是健康的,不会受到金融危机太大的影响。
不出所料,这种肤浅的“专家言论”以最快的速度沦为了笑柄。事实上,鉴于中国如此之高的外贸依存度,连中国经济这个概念本身都已经显得不合时宜。在相当程度上,中国经济在2008年三季度开始的急速下滑,很有可能就是长期积累的问题借助金融危机所发出的凶狠的报复。容易看到,在这三十年中,中国虽然取得了独步全球的增长速度,但其经济的基本体质仍然有相当脆弱的一面。当然,按照乐观看法,经济结构的扭曲可能正是中国经济最具潜力的一面,消费率低下正说明内需还有大幅提高的可能,产业落后正说明中国还有产业升级的潜力。我们不否认这种中国式辩证法中所包含的智慧,但显而易见,作为导致这些后果的原因,需要经济体制改革之外的改变。提振内需,调整结构,一直是被反复提出要完成的任务,然而多年努力之后,仍未竟其功,其谜底正在于政治体制改革之难。足可见,这绝对不是一桩轻松的任务。
自1990年代之后,以GDP为唯一标准,以特殊政治禀赋为条件的发展模式,不仅在经济层面造成了夸张的扭曲,也同样在社会层面及精神文化层面留下了严重的后遗症。后者由于不会反应在具体的经济指标上,因而也特别容易被隐匿和忽略。在这方面,最应该提及的是贫富差距问题。改革之前,虽然也同样存在着相当严重的不平等,但在经济上,中国仍然是一个相当平等的国家,但在短短三十年之后,中国一跃成为贫富最为悬殊的经济体之一。
从一个相对平等的社会急速演变成一个贫富差距极为悬殊的社会,社会中被压缩的潜在愤怒情绪可能相当具有爆炸性。一旦危机袭来,这种情绪就可能成为危险的导火索。换言之,中国在社会安定团结方面的一些问题,很有可能最先暴露在经济危机的火力之下,并成为社会整体性危机的第一个燃点。2008年的瓮安事件实际上就是这种危机最活生生的预言。如果正在加速成形的经济危机真的不幸到来的话,上述情形就将不再只是逻辑上和概率上的推论,而是很可能要直面的严峻现实。
不仅如此,在三十年改革之后,以世俗物质利益为主要目标的机会主义已经成为中国社会从精英到百姓,从政府到企业,几乎所有利益主体的压倒性行动文化。在这种毒性极大的文化中,人们被告知,只要对自己有利的,就是理性的,就是好的。只要能达到目标,最基础的商业伦理及文化准则都可以弃之如草芥。2008年引起轩然大波的“毒奶粉事件”,暴露的正是当今中国在文化精神层面这种最真实的面貌。在这方面,一些地方政府官员在经济增长上所展现出的赤裸裸的机会主义性格,为这种文化精神的形成起到推波助澜甚至直接示范的作用。
我们不能想象,构筑在这样一种文化基础上的社会如何能够应付一次巨大的经济冲击?当然,如果继续享有好运气的话,我们仍然可能侥幸过关。但一个只能不断祈灵于运气的国度又是一个何等脆弱的共同体,而其离所谓崛起又何止于十万八千里?虽然我们不愿相信,但我们必须非常遗憾地告诉人们:在危机日渐迫近的2008年,中国社会真正需要的是精神基础和起点。物质的成就可能因为一场危机而一夜散尽,而精神的力量却可以成为危机中最坚强的堡垒。在这个意义上,中国可能比其他经济体更接近危机。
正如我们上面所提示的那样,1978年之后,尤其是1990年代中期之后的中国经济增长,不仅是中国本身制度变革的结果,也是1990年代之后全球体系扩张的一个结果。换句话说,以美元为符号的全球体系实际上已经成为中国经济增长隐形但却非常重要的基础。因此,在考察中国经济增长的未来图景的时候,我们就不能离开全球体系的视域。二战之后,以美国为中心形成了一个新的全球体系。冷战之后,借助信息技术革命以及美国意识形态的魅力,这个全球体系急速膨胀。
以中国、印度、俄罗斯等巨型人口国家完全融入为标志,这个全球体系达到了其巅峰时期。然而,巅峰从来就意味着衰败的开始,以次贷危机为引信并在2008年被最终引爆的金融危机,实际上就是这个体系盛极而衰的一个明确信号。其本质是,通过滥发美元来吸纳全球过剩产能的美国消费能力的崩溃。在相当程度上,次贷危机就是美国通过所谓“金融创新”膨胀其消费需求并同时吸纳全球储蓄而导致的一场危机。
有人戏称:次贷危机的情形就好像是美国(像一个劳模一样扮演全球消费火车头)长期带病坚持工作最终不支倒地。显然,目前的危机不仅仅是美国的危机,也不是哪一国的危机,而是二战之后这个曾经创造过无数神话的全球体系的危机。也只有在全球体系的视角上,我们才能够更加接近目前这场危机的真相。与发生在体系边陲的亚洲金融危机不同,目前的金融危机发生在全球体系的心脏部分。亚洲金融危机之所以能够很快平息,乃是因为作为当时全球体系的发动机的美国消费需求(包括其他发达国家)依然强劲,而这种需求很快就将亚洲新兴经济体的过剩产能重新组织起来,从而迅速将体系推回到正常运转状态。
就此而论,这一次的全球金融危机显然要比十年之前的亚洲金融危机远为不测。道理非常简单,心肌梗死要比局部病痛危险得多。沿着这一线索,我们得知,美国及其他发达国家的消费需求何时能够修复,这场危机就何时能够结束。但我们已经了解,发达国家(尤以美国为甚)的消费需求多年来一直是通过金融杠杆被“吹”出来的,随着发达国家从企业到家庭全面的去杠杆化时代的到来,这种需求的恢复将是一个漫长的过程。实际上,到目前为止,发达国家的消费需求的萎缩可能还刚刚开始。接下来要发生的,则是体系内从金融到实体、从发达国家到新兴国家的一个全面负反馈——一个螺旋式下降的过程。
迄今可以旁证这个趋势的证据是,全球三大主要发达经济体,已经同时陷入衰退。这种情形是二战之后从未见过的。与此同时,那些原来被人们寄予厚望的诸如中国、印度这样的新兴经济体也在危机中迅速显露了他们对体系的高度依附性。希望这些出口导向型的新兴经济体能够一夜之间转型并拯救体系危机,不仅在经济上不现实,而且也将与目前的全球政治权力格局发生严重冲突。这提醒我们,全球体系的确遭遇到了二战之后最严峻的局面。我们不知道这种发展最终会指向哪里,但显然,变局已经开始。
改变的时刻已经到来
从1978年到2008年,中如果说前面十多年是以美国为中心的全球体系急剧扩张的时代的话,那么接下来我们将要进入的,则是这个体系不断收缩的时代。显然,这并不是一个令人愉快的时代。在这个过程中,体系内的任何国家都不能幸免于收缩所带来的痛苦。这是一个相当确定的全球体系前景。不确定的则是,如果其中有任何一个大国承受不了这种收缩所带来的痛苦而导致剧变,那么,二战之后的这个全球体系的爆炸就会立即被引发。老练的投资者都知道,不确定是人类智慧的天敌。无论是确定的经济收缩,还是不确定的体系考验,都将是中国在2009年将要面对的艰难处境。
虽然预测一向被认为是一件愚蠢的事业,但一些与中国高度相关的重要趋势仍然值得我们冒险予以提示。其一是,2009年,美元可能出现大幅贬值,并导致全球金融市场的进一步动荡。其二是,在中国,虽然已经紧急采取了大规模的刺激措施,但经济下滑的幅度仍然超过预期,延续了三十年的发展模式可能被迫转变。其三,在经济增长下滑的背景下,积累多年的各种矛盾趋于临界,政治体制改革空前紧迫,改革会获得新动力。往更远处看则有,在对立意识形态“休克”之后,为本轮全球化提供主要意识形态的保守主义也将面临退潮。继之而起的是各种替代性方案和思想的兴起。无疑,这将是一个大时代。
无论从全球体系的角度,还是从中国内部的情况看,一幅未来图景都已经无比清晰:中国正在遭遇三十年未见之变局。以一种乐观主义的态度来观察,危机从来就不仅仅只意味着崩溃,它同样意味着涅槃,意味着巨大的机会。虽然我从来就不是一个乐观主义者,但今天,我愿意以这样一种态度期待中国的2009,正如奥巴马在其胜选演讲中所说的那样:改变的时刻已经到来。
在这个即将完结的年份中,人们目不暇接地见证了一场又一场几近完美的崩溃,从资产市场到宏观经济,仿佛永不落幕的盛宴一瞬间戛然而止。
此刻,惊魂未定的大众仍然处于危机令人窒息的追击之中,但已经有足够多的理由让我们相信:这些接踵而至的崩塌,虽然伴随着诸多偶然,但也绝不是祸从天降。
房地产负重死扛
一场不期而至的雪灾,蹊跷地发生在很少下雪的中国南方,为2008年的中国做出了巫术般的预言。它似乎在暗示:2008年将注定是诡异而又充满转折意味的一年。接下来的汶川地震、瓮安事件、股市崩溃、三鹿奶粉,以及惊涛拍岸的金融海啸,无不暗合了这个预言。显然,对所有这些事件加以解读并理解其中的神秘联系,已经超出了我们的智力范围,因此,我们仅仅能够对那些我们可能理解的一部分予以追索和评论。
2008年,在经过长达接近十年的牛市之后,中国炽热的房地产市场,终于冷却下来,价格下滑,成交锐减。如果说在奥运会之前,人们还对房地产市场抱有某种不切实际的预期的话,那么奥运会之后,绝大多数人都应该承认,中国的房地产市场已经出现了趋势性的逆转。
起始于1998年的这一轮房地产的超级牛市,大致有如下几个动力。一是所谓住房制度改革,即由以前主要由单位分配住房的制度变为个人购买住房。这个改革所释放的需求形成了中国房地产市场第一级动力。在住房制度改革动力释放的同时,中国的大规模城市化成为中国房地产市场的第二级动力,城市化所引发的大量外来人口的进入进一步刺激了房地产的市场需求。中国房地产市场的第三个动力来自于投资需求。其中,国际资本以及中国中产阶层的投资和投机性介入是这一动力的主要成分。
不过,在中国的房地产市场上,还有一个更加隐蔽和更加强劲的动力,那就是政府。自1990年代中后期以降,中国各级地方政府出于对经济增长的渴求,开始强力介入房地产市场。而到了后期,房地产更成为政府推动GDP以及增加地方财政收入的法宝。数据表明,在中国的大城市中,卖地收入以及房地产的相关税收普遍占到地方政府收入的三分之一以上,而房地产对相关产业的拉动则更是为其他行业所望尘莫及。以上诸多因素的不断接力,相互叠加,共同推动了中国房地产市场的这一超级周期。
尤其需要指出的是,由于中国土地市场以及政治的特殊性,中国房地产市场充满了剥夺、腐败并因此充满了暴利,这使得各类资本对这一市场趋之若鹜,并极大地加剧了这一市场的狂热和泡沫程度。容易看出,在推动中国房地产市场的主要因素中,很多是其他经济体所不具备的所谓独特的中国因素。因是之故,中国房地产市场才能够不断让预言家们名声扫地,不管他是外来的和尚,还是本土的专家。
然而,再特殊的因素也无法阻止市场本身荣衰交替、牛熊循环的基本规则。在2008年,中国“特殊”的房地产市场终于暴露了它早应该暴露的疲态。面对这个迟到但显然是再正常不过的调整,赚惯了快钱、过惯了好日子的房地产商们开始集体跳脚。对于绝大部分没有经历过完整市场周期因而也显得特别幼稚的中国的房地产商来说,这种反应并不奇怪。
真正让人奇怪的是,部分地方政府在这个调整面前所表现出的轻率和疯狂。就数据和我们的观察而言,中国房地产市场的调整才刚刚开始。如果用一个更为人们所熟悉的指数来标示的话,今天的中国房地产价格仍然相当于上证指数的5000点,继续调整的空间依然巨大。
更有甚者,在东南沿海的一些主要城市中,地方政府竟然采取各种行政措施,禁止房地产商降价。这种行为非但不能起到阻止房价下跌的作用,反而更彰显了中国房地产市场内在的下跌趋势。同时,它也强化了消费者对房地产市场的熊市预期,从而推动房地产市场的加速下跌。显然,无论从房价收入比、月供收入比、租售比等最终决定房地产价格的基本面因素来看,中国的房地产市场依然泡沫飞溅。尤其需要提醒的是,随着一个从未见过的经济下滑周期的到来,潜在购房人群的未来收入预期将会恶化,这可能是压垮仍然在“负重死扛”的中国房地产市场并将其最终推入深渊的最后一根稻草。
毫无疑问,特殊的中国因素仍然会在未来的中国房地产市场中发挥作用。但同样毫无疑问的是,所有这些特殊因素都已是强弩之末。所以,假如在2009年,中国房地产市场发生一场真正意义上的崩溃并将国有银行以及经济整个拖下水的情况,我们丝毫也不应该感到惊讶。其中理由其实十分简单:以前透支了多少,以后就要还回去多少。从长期看,任何人都无法挑战这一铁律。事实上,我们已经看到高盛在2009年的投资策略中,将中国内地的房地产基本假设从降幅5%~10%扩大至35%~40%。
股市不坚强
如果2008年的中国房地产还显示了某种“猪坚强”的韧性的话,那么,2008年的中国股票市场就是一场标准的崩溃。在这一年中,中国股市一再打破人们的预期,从6000点一路狂泻到最低1600多点,最高跌幅达到将近75%。在前几年全球最牛的中国股市,2008年变成了全球最熊的股市。
与房地产市场相比,中国股市的许多投资者都已经经历过好几轮牛熊循环,对牛市之后的熊市已经有所准备,但如此跌法,仍然超出绝大多数人的预期。这除了显示了那些在集体癫狂时还在高喊“黄金十年”的分析师们的轻佻之外,也给我们留下了一个巨大的疑问:这里面究竟发生了什么?或者换一个问法:究竟有哪些因素远远超出了我们曾经坚信不疑的投资假设?
中国的消费率在2007年已经下降到35%的超低水平,而其他国家的同一指标大多在70%以上。居民在国民收入的所占比例不仅没有随着经济发展而增加,反而不断缩小。在1994年,政府、企业和居民三者的分配比例为18∶16∶66,而到了2005年,这个比例变化为23.8∶16.2∶60.0,居民收入占比不断下滑。
无疑,中国股票市场中的绝大部分投资者都深信这样一个假设:中国是全球最具有成长性的经济体,其高速成长的周期至少还可以再延续三十年。中国经济即便出现调整,也只是我们以前见过的那种周而复始的调整。这个信念被中国的主流媒体反复灌输和强化,成为中国大众思维框架中无需检验的假设。也正是因为这个原因,大多数人都相信,中国股票市场即便发生下跌,也只是正常的调整,最多也只是一个短暂的熊市周期。不幸的是,正是这样一个我们曾经笃信不疑的基础假设正在接受严峻的考验。就我们的智慧可以洞悉的,中国经济基本面的超预期下滑可能是解释中国股票市场超常下跌的最重要的因素。如果中国投资者一直坚信的基本假设——中国经济仍然可以高速成长三十年,果真受到侵蚀的话,2009年的中国股票市场就可能比2008年更为严酷。
在上一轮牛市过后,中国资本市场发生了一个重要的嬗变,那就是,大型垄断企业和国有银行已经成为中国资本市场的主体,占据了绝对份额。从相当程度上,为大型国企和银行上市铺路,正是政府方面推动上一轮牛市的重要动力之一。虽然在上一个经济扩张周期中,这些巨无霸企业的表现似乎差强人意,但这些主要依靠垄断地位以及国家财政挹注的大型企业自1990年代末期逐渐成形之后,毕竟没有经历过真正意义上的下行周期,更不用说像2008年这样突如其来且破坏性极强的下行周期。
所以,这些大型垄断企业在即将到来的惨淡日子中的抗击打能力将对股票市场的表现产生重要影响。对这一点,我们一点也不敢乐观。最新的证据是,三家最重要的大型航空公司已经陆续发生大面积亏损,而这种亏损竟然出现在GDP增速仍然达到10%的条件下。我们很难想像如果GDP增速跌落到8%或者更低的情况下,这些企业将如何应对。人们的确有理由担心,在几年之后,中国的纳税人是不是还需要对这些大型企业及银行再来一次巨额的输血?在资本市场上,唯一确定的就是不确定性。对于2009年及其之后的中国资本市场,则尤其如此。
中国股票市场的巨幅崩跌,虽然称得上是2008年中国的重要事件之一,但就其影响层面以及让人意外的程度,无疑都远远不敌中国经济在2008年三季度所开始呈现出的突然失速。
危机的气味
2008年下半年之后,在上半年还一路狂飙的中国的经济开始出现增长乏力的趋向,而到了九十月份,这种减缓开始演变成高空坠落的硬着陆态势。各种公开数据的转向之快,跌幅之巨,完全可以用急速崩落形容。在10月份,中国规模以上工业企业增加值同比增长仅为8.2%,比去年同期狂跌9.7个百分点,仅仅为过去5年平均增幅的一半。而到了11月份,这个数据更是跌落到5.4%,创2001年末以来的新低。
在财政收入方面,中国2008年11月的财政收入出现多年未见的同比负增长,比去年同月下降3.1%。而在被认为最能够真实反映经济增长的发电量方面,中国11月份统调电厂发电量同比急剧下降7%,是1989年以来的最大跌幅。与此同时,11月份发布的中国11月份采购经理人指数(PMI)也从10月份44.6大幅跌至38.8。与这些宏观数据相印证的则是,微观经济方面所呈现的萧条景象。按照目前最流行的说法就是,需求几乎在一夜之间就消失了。
一位为某家全球新能源巨头供货的朋友告知,这家曾经风光无限的厂商突然之间几乎冻结了所有原材料进货。而另外一位远在南方的朋友则透露,那边的港口出现了1989年以来最惨淡的景象。如果足够细心,我们就会发现,中国诸多宏观数据的下滑幅度,都是20年甚至30年以来所仅见。令人迷惑的是,中国宏观经济的这种急速转向几乎是在短短两三个月之间发生的。这一切,此前虽然多少有所察觉,但下滑之快仍然让人措手不及。仅仅在几个月之前,中央政府还在不断调高银行准备金及利率以抑制经济过热,但在几个月之后,同样是中央政府,却以罕见的频率和幅度大幅降低准备金及利率,并旋即推出了三十年来规模最大的四万亿财政刺激计划。如果你看到一个司机在猛踩刹车之后又突然猛踩油门,那他一定是遇到了某种紧急情况。
这可能正是隐藏在政策急转弯背后的东西。然而,无论是从决策层所表现出的“惊讶”中,还是从中国经济学家们所表现出的令人尴尬的集体失语中,抑或是中国企业界的惊慌失措中,都透露出了这样一个信息:我们对危机以及危机的形式几乎毫无准备。这不啻于证明,在蒙受了三十年难以置信的好运之后,在我们的主流意识中存在着一种危险的智力缺陷——近乎蒙昧的自大和乐观。有趣的是,最早嗅到危机气味的竟然是中国的几位最高领导人。
留意倾听他们含蓄的发言,我们或许可以更早地察觉目下这场危机。其中最直言不讳的是温家宝总理,早在春天,温家宝就直率地发出了预警,他说,2008年将是中国经济最困难的一年。为此,曾有人向媒体力荐温为2008年的最佳宏观分析师。显然,这不仅仅是一个相当有创意的幽默。不过,最困难的可能不是2008年,而是2009年或者再下一年。
相比于奥运会、“神七”上天、雪灾、地震、股市崩溃、房地产低迷,中国经济的突然失速无疑都是2008年头号事件,也必将成为2009年中国最重要的看点。在多年以GDP为中心,甚至以GDP为压倒一切的目标之后,没有什么比经济莫名其妙的失速更令人恐惧的了。因为它已经成为这个社会唯一的凝聚力和黏合剂。值得补充的是,就个人的观察而言,中国所谓精英阶层中的悲观气氛也为二十年来所仅见。
到现在为止,我们仍然无法判断,中国经济在2008年的怪异表现,究竟是一次短暂的意外事故,还是持续了三十年的中国发展模式的最后终结,但毫无疑问的是,我们已经越来越逼近一个危险的转捩点。然而,2008年并不是突然来到的,它不过是一段复杂而晦涩的历史最终显示了它惊人的力量而已。如果我们能够更好地理解过去的三十年,那么,2008年的变化可能就不再会显得如此诡异了,而中国刻下的处境也会被更清晰地呈现出来。
中国模式后遗症
从1978年到2008年,中国改革开放于今已经走过了三十个年头。三十年之后,在人们对市场化的成果已经习以为常的时候,中国独特发展模式所遗留下来的一些后果正在以一种尖利的方式为中国社会所感知。
二十年来,经济的高速增长,更加准确地说,GDP的高速增长成为缓解某种社会焦虑的唯一药方。如此,是不是能够迅速增加GDP,便成为衡量改革和真理的唯一标准。在这样一种思维的推动下,并基于当时的政治及自然禀赋,中国经济被迅疾推上了一条高投资率、高外向型、高污染、低内需、低福利、低保障的发展路径。在1990年代初期冷战结束之后,这种发展模式更被澎湃而至的全球化浪潮推向了极致水平。在经过十数年的酝酿并在新世纪(尤其是2001年加入WTO之后)发力冲刺之后,中国虽然取得了奇迹般的GDP增长速度,但也同样存在结构的扭曲以及内部问题的积累等弊端。
在内需方面,中国的消费率在2007年已经下降到35%的超低水平,而其他国家的同一指标大多在70%以上。隐藏在这种不不寻常的消费率背后的,实际上是财富分配结构的严重扭曲。资料表明,居民在国民收入的所占比例不仅没有随着经济发展而增加,反而不断缩小。在1994年,政府、企业和居民三者的分配比例为18∶16∶66,而到了2005年,这个比例变化为23.8∶16.2∶60.0,居民收入占比不断下滑。有评论者指出:
中国当今消费率比1950年代末期开始的“三年困难”时期还要低。这实际上就是中国内需长期低迷的主要因素。需要指出的是,中国内需低迷并不是一个市场博弈的后果,而是一个政治博弈的后果、其对应的是政府和企业在改革过程中权力的扩张,以及公民权利在同一时期的极度抑制。所谓低保障成本指的即是这样一种情形。
事实上,中国经济中的另外一个畸形指标,也源于同样的政治禀赋。从建国初期起,高投资率就一直是经济的一个隐患,但这种疾病似乎并没有在改革中得到彻底根治。到2005年底,中国的投资占GDP的比率达到48.6%。就此有人调侃道:如果投资率按照这样的速度增长下去,到2012年中国将是一个只有投资没有消费的经济体。从逻辑上说,长期的高投资率一定会导致产能过剩并最终引发经济的剧烈调整。
这一点在中国以前作为一个封闭的经济体系的时候,已经得到了反复验证。这也正是那个时代发生的故事。然而,在中国极速融于全球体系之后,这种过剩产能却在全球市场中得到了消化。加之,中国的各级地方政府通过对各种资源的控制能力将土地、税收、环境、资源以及人力成本压低到极致,这就使中国庞大的过剩产能拥有了其他国家无法匹敌的价格优势。这样,中国过剩产能的投资者不仅没有受到市场的惩罚,反而获得了利润。这反过来鼓励了政府以及企业的这种微观合理,但宏观上可能引发巨大灾难的投资冲动。这种短暂的成功迷惑了许多人,在他们看来,十数年如一日的超高投资率似乎并没有想象的那么吓人。不过,超高投资率与超低消费率的经济增长组合要想得以维系其脆弱的平衡,对外部市场和外部资源的高度依赖就是一个符合逻辑的结果。
其在经济指标上的表现,就是中国变成一个外贸依存度奇高的经济体。到2006年,中国的外贸依存度达到64%的罕见高度,远远超过其他大型经济体。作为一个拥有十几亿人口的大型经济体,中国的外贸依存度是非常不寻常的,也是相当危险的。这意味着,中国每1元的GDP就有0.64元是通过与外部经济交换而来的,这也同时意味着,一旦外部消费需求滑坡,中国经济就可能遭遇重创。2008年初冬中国经济的急跌已经对这种危险性做出了经典的诠释。可是,就在全球金融海啸发生之后,一些主流舆论还在安慰公众说:中国的实体经济是健康的,不会受到金融危机太大的影响。
不出所料,这种肤浅的“专家言论”以最快的速度沦为了笑柄。事实上,鉴于中国如此之高的外贸依存度,连中国经济这个概念本身都已经显得不合时宜。在相当程度上,中国经济在2008年三季度开始的急速下滑,很有可能就是长期积累的问题借助金融危机所发出的凶狠的报复。容易看到,在这三十年中,中国虽然取得了独步全球的增长速度,但其经济的基本体质仍然有相当脆弱的一面。当然,按照乐观看法,经济结构的扭曲可能正是中国经济最具潜力的一面,消费率低下正说明内需还有大幅提高的可能,产业落后正说明中国还有产业升级的潜力。我们不否认这种中国式辩证法中所包含的智慧,但显而易见,作为导致这些后果的原因,需要经济体制改革之外的改变。提振内需,调整结构,一直是被反复提出要完成的任务,然而多年努力之后,仍未竟其功,其谜底正在于政治体制改革之难。足可见,这绝对不是一桩轻松的任务。
自1990年代之后,以GDP为唯一标准,以特殊政治禀赋为条件的发展模式,不仅在经济层面造成了夸张的扭曲,也同样在社会层面及精神文化层面留下了严重的后遗症。后者由于不会反应在具体的经济指标上,因而也特别容易被隐匿和忽略。在这方面,最应该提及的是贫富差距问题。改革之前,虽然也同样存在着相当严重的不平等,但在经济上,中国仍然是一个相当平等的国家,但在短短三十年之后,中国一跃成为贫富最为悬殊的经济体之一。
从一个相对平等的社会急速演变成一个贫富差距极为悬殊的社会,社会中被压缩的潜在愤怒情绪可能相当具有爆炸性。一旦危机袭来,这种情绪就可能成为危险的导火索。换言之,中国在社会安定团结方面的一些问题,很有可能最先暴露在经济危机的火力之下,并成为社会整体性危机的第一个燃点。2008年的瓮安事件实际上就是这种危机最活生生的预言。如果正在加速成形的经济危机真的不幸到来的话,上述情形就将不再只是逻辑上和概率上的推论,而是很可能要直面的严峻现实。
不仅如此,在三十年改革之后,以世俗物质利益为主要目标的机会主义已经成为中国社会从精英到百姓,从政府到企业,几乎所有利益主体的压倒性行动文化。在这种毒性极大的文化中,人们被告知,只要对自己有利的,就是理性的,就是好的。只要能达到目标,最基础的商业伦理及文化准则都可以弃之如草芥。2008年引起轩然大波的“毒奶粉事件”,暴露的正是当今中国在文化精神层面这种最真实的面貌。在这方面,一些地方政府官员在经济增长上所展现出的赤裸裸的机会主义性格,为这种文化精神的形成起到推波助澜甚至直接示范的作用。
我们不能想象,构筑在这样一种文化基础上的社会如何能够应付一次巨大的经济冲击?当然,如果继续享有好运气的话,我们仍然可能侥幸过关。但一个只能不断祈灵于运气的国度又是一个何等脆弱的共同体,而其离所谓崛起又何止于十万八千里?虽然我们不愿相信,但我们必须非常遗憾地告诉人们:在危机日渐迫近的2008年,中国社会真正需要的是精神基础和起点。物质的成就可能因为一场危机而一夜散尽,而精神的力量却可以成为危机中最坚强的堡垒。在这个意义上,中国可能比其他经济体更接近危机。
正如我们上面所提示的那样,1978年之后,尤其是1990年代中期之后的中国经济增长,不仅是中国本身制度变革的结果,也是1990年代之后全球体系扩张的一个结果。换句话说,以美元为符号的全球体系实际上已经成为中国经济增长隐形但却非常重要的基础。因此,在考察中国经济增长的未来图景的时候,我们就不能离开全球体系的视域。二战之后,以美国为中心形成了一个新的全球体系。冷战之后,借助信息技术革命以及美国意识形态的魅力,这个全球体系急速膨胀。
以中国、印度、俄罗斯等巨型人口国家完全融入为标志,这个全球体系达到了其巅峰时期。然而,巅峰从来就意味着衰败的开始,以次贷危机为引信并在2008年被最终引爆的金融危机,实际上就是这个体系盛极而衰的一个明确信号。其本质是,通过滥发美元来吸纳全球过剩产能的美国消费能力的崩溃。在相当程度上,次贷危机就是美国通过所谓“金融创新”膨胀其消费需求并同时吸纳全球储蓄而导致的一场危机。
有人戏称:次贷危机的情形就好像是美国(像一个劳模一样扮演全球消费火车头)长期带病坚持工作最终不支倒地。显然,目前的危机不仅仅是美国的危机,也不是哪一国的危机,而是二战之后这个曾经创造过无数神话的全球体系的危机。也只有在全球体系的视角上,我们才能够更加接近目前这场危机的真相。与发生在体系边陲的亚洲金融危机不同,目前的金融危机发生在全球体系的心脏部分。亚洲金融危机之所以能够很快平息,乃是因为作为当时全球体系的发动机的美国消费需求(包括其他发达国家)依然强劲,而这种需求很快就将亚洲新兴经济体的过剩产能重新组织起来,从而迅速将体系推回到正常运转状态。
就此而论,这一次的全球金融危机显然要比十年之前的亚洲金融危机远为不测。道理非常简单,心肌梗死要比局部病痛危险得多。沿着这一线索,我们得知,美国及其他发达国家的消费需求何时能够修复,这场危机就何时能够结束。但我们已经了解,发达国家(尤以美国为甚)的消费需求多年来一直是通过金融杠杆被“吹”出来的,随着发达国家从企业到家庭全面的去杠杆化时代的到来,这种需求的恢复将是一个漫长的过程。实际上,到目前为止,发达国家的消费需求的萎缩可能还刚刚开始。接下来要发生的,则是体系内从金融到实体、从发达国家到新兴国家的一个全面负反馈——一个螺旋式下降的过程。
迄今可以旁证这个趋势的证据是,全球三大主要发达经济体,已经同时陷入衰退。这种情形是二战之后从未见过的。与此同时,那些原来被人们寄予厚望的诸如中国、印度这样的新兴经济体也在危机中迅速显露了他们对体系的高度依附性。希望这些出口导向型的新兴经济体能够一夜之间转型并拯救体系危机,不仅在经济上不现实,而且也将与目前的全球政治权力格局发生严重冲突。这提醒我们,全球体系的确遭遇到了二战之后最严峻的局面。我们不知道这种发展最终会指向哪里,但显然,变局已经开始。
改变的时刻已经到来
从1978年到2008年,中如果说前面十多年是以美国为中心的全球体系急剧扩张的时代的话,那么接下来我们将要进入的,则是这个体系不断收缩的时代。显然,这并不是一个令人愉快的时代。在这个过程中,体系内的任何国家都不能幸免于收缩所带来的痛苦。这是一个相当确定的全球体系前景。不确定的则是,如果其中有任何一个大国承受不了这种收缩所带来的痛苦而导致剧变,那么,二战之后的这个全球体系的爆炸就会立即被引发。老练的投资者都知道,不确定是人类智慧的天敌。无论是确定的经济收缩,还是不确定的体系考验,都将是中国在2009年将要面对的艰难处境。
虽然预测一向被认为是一件愚蠢的事业,但一些与中国高度相关的重要趋势仍然值得我们冒险予以提示。其一是,2009年,美元可能出现大幅贬值,并导致全球金融市场的进一步动荡。其二是,在中国,虽然已经紧急采取了大规模的刺激措施,但经济下滑的幅度仍然超过预期,延续了三十年的发展模式可能被迫转变。其三,在经济增长下滑的背景下,积累多年的各种矛盾趋于临界,政治体制改革空前紧迫,改革会获得新动力。往更远处看则有,在对立意识形态“休克”之后,为本轮全球化提供主要意识形态的保守主义也将面临退潮。继之而起的是各种替代性方案和思想的兴起。无疑,这将是一个大时代。
无论从全球体系的角度,还是从中国内部的情况看,一幅未来图景都已经无比清晰:中国正在遭遇三十年未见之变局。以一种乐观主义的态度来观察,危机从来就不仅仅只意味着崩溃,它同样意味着涅槃,意味着巨大的机会。虽然我从来就不是一个乐观主义者,但今天,我愿意以这样一种态度期待中国的2009,正如奥巴马在其胜选演讲中所说的那样:改变的时刻已经到来。
登録:
投稿 (Atom)