一.数据库SQL Server 行转列(Row To Column)
(一)需求
原始表的数据的结构如图1所示,把相同的guid的code值转换为列值。
(图1)
(二)目标
我们希望达到的效果如图2所示,这里的guid变成唯一的了,这行的记录中包含了这个guid所对应的code字段值。
(图2)
(三)分析与实现
要实现图1到图2的转变,这就是所谓的行转列,下面我们来讲讲具体的实现:
1. 首先我们先创建一个测试表,方便后面的效果展现;
--
创建表
if
exists
(
select
*
from
sysobjects
where
id
=
OBJECT_ID
(
'
[TempTable_Base]
'
)
and
OBJECTPROPERTY
(id,
'
IsUserTable
'
)
=
1
)
DROP
TABLE
[
TempTable_Base
]
CREATE
TABLE
[
TempTable_Base
]
(
[
id
]
[
int
]
IDENTITY
(
1
,
1
)
NOT
NULL
,
[
guid
]
[
varchar
]
(
50
)
NULL
,
[
code
]
[
varchar
]
(
50
)
NULL
)
SET
IDENTITY_INSERT
[
TempTable_Base
]
ON
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
1
,
'
91E92DCB-141A-30B2-E6CD-B59EABD21749
'
,
'
A
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
2
,
'
91E92DCB-141A-30B2-E6CD-B59EABD21749
'
,
'
C
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
3
,
'
91E92DCB-141A-30B2-E6CD-B59EABD21749
'
,
'
E
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
4
,
'
91E92DCB-141A-30B2-E6CD-B59EABD21749
'
,
'
O
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
5
,
'
91E92DCB-141A-30B2-E6CD-B59EABD21749
'
,
'
G
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
6
,
'
79DD7AB9-CE57-9431-B020-DF99731FC99D
'
,
'
A
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
7
,
'
79DD7AB9-CE57-9431-B020-DF99731FC99D
'
,
'
O
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
8
,
'
79DD7AB9-CE57-9431-B020-DF99731FC99D
'
,
'
E
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
9
,
'
79DD7AB9-CE57-9431-B020-DF99731FC99D
'
,
'
F
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
10
,
'
79DD7AB9-CE57-9431-B020-DF99731FC99D
'
,
'
O
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
11
,
'
79DD7AB9-CE57-9431-B020-DF99731FC99D
'
,
'
B
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
12
,
'
79DD7AB9-CE57-9431-B020-DF99731FC99D
'
,
'
D
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
13
,
'
79DD7AB9-CE57-9431-B020-DF99731FC99D
'
,
'
F
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
14
,
'
D61651D9-1B0A-0362-EE91-A805AA3E08F2
'
,
'
O
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
15
,
'
D61651D9-1B0A-0362-EE91-A805AA3E08F2
'
,
'
D
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
16
,
'
D61651D9-1B0A-0362-EE91-A805AA3E08F2
'
,
'
F
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
17
,
'
D61651D9-1B0A-0362-EE91-A805AA3E08F2
'
,
'
C
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
18
,
'
D61651D9-1B0A-0362-EE91-A805AA3E08F2
'
,
'
U
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
19
,
'
D61651D9-1B0A-0362-EE91-A805AA3E08F2
'
,
'
F
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
20
,
'
4802F0CD-B53F-A3F5-1C78-2D7424579C06
'
,
'
A
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
21
,
'
3CCBFF9F-827B-6639-4780-DA7215166728
'
,
'
O
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
22
,
'
3CCBFF9F-827B-6639-4780-DA7215166728
'
,
'
M
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
23
,
'
3CCBFF9F-827B-6639-4780-DA7215166728
'
,
'
C
'
)
INSERT
[
TempTable_Base
]
(
[
id
]
,
[
guid
]
,
[
code
]
)
VALUES
(
24
,
'
3CCBFF9F-827B-6639-4780-DA7215166728
'
,
'
M
'
)
SET
IDENTITY_INSERT
[
TempTable_Base
]
OFF
SELECT
*
FROM
[
TempTable_Base
]
2. 使用SQL Server2005的row_number()函数来进行分组排名,把同一个guid的数据进行标识,这样就可以让不同的guid具有数字1以后的数值,这样就可以作为新的列名来进行行转列了,执行下面的SQL就可以达到图3所示的效果了。
--
对数据进行分组
select
base.
*
,row_number()
over
(partition
by
guid
order
by
ID)
as
depth
from
[
TempTable_Base
]
as
base
order
by
id
(图3)
3. 生成分组表,把上面的结构插入到一张新的表中,把数据保存为一个表是为了方便后面的统计使用。
--
生成分组表
select
base.
*
,row_number()
over
(partition
by
guid
order
by
ID)
as
depth
into
[
TempTable_Depth
]
from
[
TempTable_Base
]
as
base
order
by
id
SELECT
*
FROM
[
TempTable_Depth
]
4. 动态行转列的SQL,因为你不知道列数据会有多少,所以我们使用下面的SQL来动态的生成列名,结果就如图2所示。
--
行转列
declare
@s
nvarchar
(
max
)
set
@s
=
''
Select
@s
=
@s
+
'
,
'
+
quotename
(
[
depth
]
)
+
'
=max(case when [depth]=
'
+
quotename
(
[
depth
]
,
''''
)
+
'
then code else null end)
'
from
[
TempTable_Depth
]
group
by
[
depth
]
order
by
[
depth
]
exec
(
'
select guid
'
+
@s
+
'
from [TempTable_Depth] group by guid order by guid
'
)
二.数据库 SQL Server 生成矩阵数据(Matrix)
(一)需求
我们回过头来看看图1,比如guid为91E92DCB-141A-30B2-E6CD-B59EABD21749的code值包括A,C,E,O,G这五个,我们可以想象guid为一个用户,这五个code就是这个用户访问网站页面的顺序,那如果我想看到一个从A->C,C-> E,E-> O,O -> G这个矩阵中两两对应在数据库中出现了多少次?
(二)目标
我们希望达到的效果如图4所示,这个图可以解读为A->A的个数是0,A->C的个数是1,D->F的个数是2,这些统计方法可能会在一些需要进行对比分析的过程中用到。
(图4)
(三)分析与实现
要实现图4的效果,这就需要我们对数据进行矩阵转换,下面我们来讲讲具体的实现:
1. 首先我们需要巧用left join和depth字段,depth就相当于用户访问的顺序,所以我们可以把之前有序的拷贝一份到右边去,效果如图5所示。
--
巧用左连接和depth字段进行处理
select
a.id,a.guid,a.code
as
code_from,b.code
as
code_to
from
[
TempTable_Depth
]
as
a
left
join
[
TempTable_Depth
]
as
b
on
a.guid
=
b.guid
and
a.
[
depth
]
=
b.
[
depth
]
-
1
(图5)
2. 为了方便统计,把上面的成果保存到一个表中,之后就是为所有唯一的code值生成一个以code作为元素的二维矩阵的原始数据,它就相当于一个临时表一样,用来保存每一个code对应的个数,效果如图6所示。
--
生成From和To的对应关系
select
a.id,a.guid,a.code
as
code_from,b.code
as
code_to
into
[
TempTable_FromTo
]
from
[
TempTable_Depth
]
as
a
left
join
[
TempTable_Depth
]
as
b
on
a.guid
=
b.guid
and
a.
[
depth
]
=
b.
[
depth
]
-
1
--
生成矩阵
select
distinct
code_from ,
1
as
num
into
[
TempTable_t1
]
from
[
TempTable_FromTo
]
order
by
code_from
select
*
from
[
TempTable_t1
]
--
生成一个矩阵表
select
a.code_from ,b.code_from
as
code_to,
0
as
counts
into
[
TempTable_t2
]
from
[
TempTable_t1
]
as
a
left
join
[
TempTable_t1
]
as
b
on
a.num
=
b.num
order
by
a.code_from ,b.code_from
select
*
from
[
TempTable_t2
]
(图6)
3. 现在就需要把统计的A->A这样数据的个数,并把它放入图6中的表中,效果如图7所示。
--
统计
select
code_from ,code_to,
count
(
1
)
as
counts
into
[
TempTable_t3
]
from
[
TempTable_FromTo
]
where
code_to
is
not
null
group
by
code_from ,code_to
--
,guid
order
by
code_from ,code_to
select
*
from
[
TempTable_t3
]
--
更新统计的数字
update
a
set
a.counts
=
b.counts
from
[
TempTable_t2
]
as
a,
[
TempTable_t3
]
as
b
where
a.code_from
=
b.code_from
and
a.code_to
=
b.code_to
and
b.counts
is
not
null
select
*
from
[
TempTable_t2
]
(图7)
4. 现在需要做的就是把行转列了,这也没有违背我们的标题啊,呵呵,效果如图8所示。
--
查询矩阵
declare
@s
nvarchar
(
max
)
set
@s
=
''
Select
@s
=
@s
+
'
,
'
+
quotename
(
[
code_to
]
)
+
'
=max(case when [code_to]=
'
+
quotename
(
[
code_to
]
,
''''
)
+
'
then counts else null end)
'
from
[
TempTable_t2
]
group
by
[
code_to
]
order
by
[
code_to
]
exec
(
'
select [code_from]
'
+
@s
+
'
from [TempTable_t2] group by [code_from] order by [code_from]
'
)
(图8)
三.总结&待续...(Summary & To Be Continued…)
其实这篇文章我想表达正是这个矩阵的生成方法,在现实中也许并不常用到,但是还是有些技巧性的东西在里面,比如那个dept的用处;再比如a.[depth] = b.[depth]-1这个SQL的巧用;再比如行转列解决矩阵问题;还有动态生成行转列的SQL,其实这里还可以使用SQL Server 2005的pivot来解决行转列的问题,这里就不列出来了。希望这篇文章能给你遇到的问题带来一些帮助和启示。