昔は無理矢理なクエリを書いてましたが、9.4以降のunnest...with ordinalityによって「2次元配列 ⇒ 複数行の1次元配列にバラす」のが割と簡単になりました。逆の「複数行の1次元配列 ⇒ 一つの2次元配列に束ねる」は、9.5array_agg関数が強化されたので楽々。両方の簡単な例を紹介します。動作確認バージョンは9.5.2。


今日は3行×4列の小さな2次元配列 ↓ を例に、まず複数行の1次元配列化から。
select '{
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 0, 1, 2}
}' :: int[];    
+---------------------------------+
|              int4               |
+---------------------------------+
| {{1,2,3,4},{5,6,7,8},{9,0,1,2}} |
+---------------------------------+
(1 row)


↓ 完成形のクエリ。上の2次元配列をWITH句の最初でmatと名付け、後はSELECT一文で処理してます。説明は画像の下に。
with a (mat) as (
    select '{
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 0, 1, 2}
    }' :: int[] -- albitary matrix
)
select i, array_agg(val order by j)
from a, unnest(mat) with ordinality as x(val, j)
    , cast((j - 1) / array_length(mat, 2) as int) as i group by i; 
+---+-----------+
| i | array_agg |
+---+-----------+
| 0 | {1,2,3,4} |
| 1 | {5,6,7,8} |
| 2 | {9,0,1,2} |
+---+-----------+
(3 rows)


以下、段階的な説明です。最初にunnest...with ordinalityの結果だけ示すと ↓ こう。unnestは、2次元に限らず何次元でも「全次元をキャンセル」してフラットに一要素ずつに分けてしまう。他には配列をバラす関数がなくて不便だけど、PostgreSQL9.4で追加されたwith ordinalityが要素添字を付けるので(下のj)、これを活用します。
with a (mat) as (
    select '{
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 0, 1, 2}
    }' :: int[]
)
select val, j
from a, unnest(mat) with ordinality as x(val, j); 
+-----+----+
| val | j  |
+-----+----+
|   1 |  1 |
|   2 |  2 |
|   3 |  3 |
|   4 |  4 |
|   5 |  5 |
|   6 |  6 |
|   7 |  7 |
|   8 |  8 |
|   9 |  9 |
|   0 | 10 |
|   1 | 11 |
|   2 | 12 |
+-----+----+
(12 rows)


2次元配列の列数は固定だから、要素添字と計算すれば行番号が出ます(下のi)。列数を出すのはarray_length関数。castは実質的な意味がなく、fromの後に置くため便宜的なもの。式のままだとselect...fromの間にしか置けず、見にくいので。
with a (mat) as (
    select '{
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 0, 1, 2}
    }' :: int[]
)
select i, j, val
from a, unnest(mat) with ordinality as x(val, j)
    , cast((j - 1) / array_length(mat, 2) as int) as i; 
+---+----+-----+
| i | j  | val |
+---+----+-----+
| 0 |  1 |   1 |
| 0 |  2 |   2 |
| 0 |  3 |   3 |
| 0 |  4 |   4 |
| 1 |  5 |   5 |
| 1 |  6 |   6 |
| 1 |  7 |   7 |
| 1 |  8 |   8 |
| 2 |  9 |   9 |
| 2 | 10 |   0 |
| 2 | 11 |   1 |
| 2 | 12 |   2 |
+---+----+-----+
(12 rows)


後は行番号iごとにグループ化し、array_agg1次元配列に束ねるだけ。元の列順(下のj)を維持するためarray_agg内にorder byを使ってます。↓ 最初のクエリ再掲。
with a (mat) as (
    select '{
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 0, 1, 2}
    }' :: int[] -- albitary matrix
)
select i, array_agg(val order by j)
from a, unnest(mat) with ordinality as x(val, j)
    , cast((j - 1) / array_length(mat, 2) as int) as i group by i; 
+---+-----------+
| i | array_agg |
+---+-----------+
| 0 | {1,2,3,4} |
| 1 | {5,6,7,8} |
| 2 | {9,0,1,2} |
+---+-----------+
(3 rows)



2次元配列をバラすのは以上。逆に、複数行の1次元配列を2次元配列化するには、冒頭で書いたとおりPostgreSQL9.5からarray_agg関数で一発。↓ 最初に作った2次元配列が復元されてます。
with a (ary) as (
    values ( '{1, 2, 3, 4}' :: int[] ),
        ( '{5, 6, 7, 8}' ),
        ( '{9, 0, 1, 2}' )
)
select array_agg(ary) from a;
+---------------------------------+
|            array_agg            |
+---------------------------------+
| {{1,2,3,4},{5,6,7,8},{9,0,1,2}} |
+---------------------------------+
(1 row)


array_agg内のorder byは、1次元配列を束ねる時も有効。動作の詳細は未確認ですが、少なくとも先頭要素が重複してなければその順番になるっぽい。
with a (ary) as (
    values ( '{1, 2, 3, 4}' :: int[] ),
        ( '{5, 6, 7, 8}' ),
        ( '{9, 0, 1, 2}' )
)
select array_agg(ary order by ary desc) from a;
+---------------------------------+
|            array_agg            |
+---------------------------------+
| {{9,0,1,2},{5,6,7,8},{1,2,3,4}} |
+---------------------------------+
(1 row)


もう少しサイズの大きい2次元配列も作ってテストしましたが、結果が同じだし記事が長過ぎくなるので割愛します。そのサンプル配列を作る際、手入力でなく適当な文字列から自動的に作るクエリ(Rmatrix(vector, nrow, ncol)みたいな)を考えたので、意味ありそうだったら明日書きます。