如何从单行提取成多行(TSQL)?

Outline:

  • 问题产生背景
  • 交叉连接
  • UNION操作
  • WITH子句
  • 性能比较
  • 抛砖引玉(欢迎指教!)

问题产生背景


一个仅有数额指标的交易表中创建一个查询,这个查询用于会计日志条目。因此如果一个日志条目应该有存款和借款,但是其他的值应该是相同的,因此我想通过
SQL查询从一行提取2行记录。听起来很模糊?我Google之后发现没有任何结果,所以我就写了这篇文章。为了简化,这里举个例子。我有一张表叫做
SampleTable,它包含如下记录:


ID


FirstName


LastName


1


Anna


Gates


2


John


Doe


3


Joe


Bloggs


4


Raj


Kumar

现在你的任务是为每个记录创建3行,因此它将显示如下:


ID


FirstName


LastName


ItemNumber


ItemDescription


1


Anna


Gates


1


Item 1


1


Anna


Gates


2


Item 2


1


Anna


Gates


3


Item 3


2


John


Doe


1


Item 1


2


John


Doe


2


Item 2


2


John


Doe


3


Item 3


3


Joe


Bloggs


1


Item 1


3


Joe


Bloggs


2


Item 2


3


Joe


Bloggs


3


Item 3


4


Raj


Kumar


1


Item 1


4


Raj


Kumar


2


Item 2


4


Raj


Kumar


3


Item 3

现在,你如何实现它呢?有许多种方法,但是我们要找出哪个方法查询效率最高。有下面是那种方法:

  • 交叉连接
  • UNION查询
  • WITH查询

1、交叉连接

为了激活大家对交叉连接沉睡的记忆,首先介绍下什么是交叉连接。所谓交叉连接,就是两个表的笛卡尔积的另一称谓。交叉连接为将第一张表的每一行与第二张表的每一行组合产生一新的元组。设两张表R、S分别有k1、k2条记录,每条记录的列数分别为m、n,则交叉连接的结果元组数为k1*k2,每个元组的列数为m+n(前面m列是R的,后面n列是S的)。当然这是在没有where条件的情况下,如果加了where添加可能会过滤掉一部分不符合条件的记录。

所以上面的结果可以看成下面两张表的交叉连接产生的:

因此可以用如下SQL语句:

SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, Extender.ItemNumber, Extender.ItemDescription
FROM SampleTable CROSS JOIN
(SELECT 1 AS ItemNumber, 'Item 1' AS ItemDescription
UNION ALL
SELECT 2 AS ItemNumber, 'Item 2' AS ItemDescription
UNION ALL
SELECT 3 AS ItemNumber, 'Item 3' AS ItemDescription) AS Extender;

本来是打算对表2构建一张临时表,但考虑到SQL Server与Oracle构建临时表是有差异的,考虑到这个我就用上面这种方式(UNION ALL,集合查询)。

2、UNION操作

UNION是集合操作中的一种,SELECT语句的查询结果是元组的集合,所以多个SELECT语句的结果可进行集合操作。集合操作主要包括并操作UNION、交操作INTERSECT和差操作EXCEPT。注意,参加集合操作的各查询结果的列数必须相同;对应项的数据类型也必须相同。

下面是实现代码:

SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, 1 AS ItemNumber, 'Item 1' AS ItemDescription
FROM SampleTable
UNION ALL
SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, 2 AS ItemNumber, 'Item 2' AS ItemDescription
FROM SampleTable
UNION ALL
SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, 3 AS ItemNumber, 'Item 3' AS ItemDescription
FROM SampleTable;

3、WITH子句

WITH AS短语,也叫做子查询部分(subquery
factoring),可以让你做很多事情,定义一个SQL片断,该SQL片断会被整个SQL语句所用到。有的时候,是为了让SQL语句的可读性更高些,
也有可能是在UNION ALL的不同部分,作为提供数据的部分。特别对于UNION ALL比较有用。因为UNION
ALL的每个部分可能相同,但是如果每个部分都去执行一遍的话,则成本太高,所以可以使用WITH AS短语,则只要执行一遍即可。如果WITH
AS短语所定义的表名被调用两次以上,则优化器会自动将WITH
AS短语所获取的数据放入一个TEMP表里,如果只是被调用一次,则不会。而提示materialize则是强制将WITH
AS短语里的数据放入一个全局临时表里。很多查询通过这种方法都可以提高速度。

其实with子句提供定义一个临时视图的方法,这个定义只对with子句出现的那条查询有效。换就话说,就是把查询结果放入一个临时表,然后通过查询语句从临时表查询结果。

如果你对with子句还是不熟悉理解的话,看下面的例子。下面的查询是:查询具有最大余额的账户的语句,如果具有同样最大查询的账户有很多,他们都会被选择。即假设如果最大余额是10000的账户有3个,则这三个账户都会显示出来。
with max-balance(value) as
        select max(balance)
        from account
select account-number
from account,max-balance
where account.balance=max-balance.value
我想看到这,with子句大家都理解了。下面是上面问题的实现代码:

WITH ExtendedTable(ID, FirstName, LastName) AS
(SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName
FROM SampleTable)
SELECT *, 1 AS ItemNumber, 'Item 1' AS ItemDescription from ExtendedTable
UNION ALL
SELECT *, 2 AS ItemNumber, 'Item 2' AS ItemDescription from ExtendedTable
UNION ALL
SELECT *, 3 AS ItemNumber, 'Item 3' AS ItemDescription from ExtendedTable

 

性能比较

上面三种方法都可以得到相同的结果,那到底它们的性能到底如何呢?下面我们把这三种方法的代码放到同一个查询中执行,如下:
-----------------------------------------
--方法一、交叉连接
-----------------------------------------
SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, Extender.ItemNumber, Extender.ItemDescription
FROM SampleTable CROSS JOIN
(SELECT 1 AS ItemNumber, 'Item 1' AS ItemDescription
UNION ALL
SELECT 2 AS ItemNumber, 'Item 2' AS ItemDescription
UNION ALL
SELECT 3 AS ItemNumber, 'Item 3' AS ItemDescription) AS Extender;

-----------------------------------------
--方法二、UNION操作
-----------------------------------------
SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, 1 AS ItemNumber, 'Item 1' AS ItemDescription
FROM SampleTable
UNION ALL
SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, 2 AS ItemNumber, 'Item 2' AS ItemDescription
FROM SampleTable
UNION ALL
SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName, 3 AS ItemNumber, 'Item 3' AS ItemDescription
FROM SampleTable;

-----------------------------------------
--方法三、WITH子句
-----------------------------------------
WITH ExtendedTable(ID, FirstName, LastName) AS
(SELECT SampleTable.ID, SampleTable.FirstName, SampleTable.LastName
FROM SampleTable)
SELECT *, 1 AS ItemNumber, 'Item 1' AS ItemDescription from ExtendedTable
UNION ALL
SELECT *, 2 AS ItemNumber, 'Item 2' AS ItemDescription from ExtendedTable
UNION ALL
SELECT *, 3 AS ItemNumber, 'Item 3' AS ItemDescription from ExtendedTable

执行查询时选中包括实际的执行计划(在SQL Server的查询菜单下面),得到执行计划如下图所示(由于图太大分三张贴上)。

方法一:

方法二:

方法三:

从图中我们可以清楚地看到方法一得开销仅占15%,而方法二、三相同都占43%。由此可见交叉连接的性能最好,而union操作与with子句性能相对较低。

抛砖引玉

ps.不知道您有没有看到这里,可能很多看官还没看到这节就把网页给关了(⊙﹏⊙b汗)。如果您看到这里了,你能说出造成这个性能差异的原因吗?欢迎大家回帖,包括拍砖。

我指出其中一点:交叉连接和with子句一样都是用构建一张临时表与SampleTable做连接,但是他们的性能差异源于交叉连接时执行select语句时做了聚簇索引。(just maybe,I'am not sure!)

 

时间: 2024-12-21 03:10:02

如何从单行提取成多行(TSQL)?的相关文章

js正则表达式中的单行模式与多行模式实例分析_正则表达式

本文实例分析了js正则表达式中的单行模式与多行模式.分享给大家供大家参考.具体如下: js正则表达式是不支持单行模式的.也就是说,不能把某段内容(有换行),采用模式修正符来处理,使整段内容当成一行来处理. 正则多行替换,需要添加/mg模式增强符. 复制代码 代码如下: <html> <head> <script type="text/javascript"> //正则替换链接,链接有换行 function t1(){  var con = docum

js正则表达式中的单行模式与多行模式实例分析

本文实例分析了js正则表达式中的单行模式与多行模式.分享给大家供大家参考.具体如下: js正则表达式是不支持单行模式的.也就是说,不能把某段内容(有换行),采用模式修正符来处理,使整段内容当成一行来处理. 正则多行替换,需要添加/mg模式增强符. <html> <head> <script type="text/javascript"> //正则替换链接,链接有换行 function t1(){ var con = document.getEleme

oracle 管道函数bug-oracle 管道函数返回行,通过ROWNUM提取前20行报错

问题描述 oracle 管道函数返回行,通过ROWNUM提取前20行报错 各位:使用如下语句:SELECT * FROM TABLE(FUN_TEST('工单')) where ROWNUM<=2;当SELECT * FROM TABLE(FUN_TEST('工单')) 返回的行数>=2时,报ORA-06548:不再需要更多的行错误,但不加ROWNUM条件时则一切正常.那位大神碰到了这种情况?给小弟指一条明路.

Oracle单行函数和多行函数实例

单行函数和多行函数示意图: 单行函数分为五种类型:字符函数.数值函数.日期函数.转换函数.通用函数 单行函数: --大小写控制函数 select lower('Hello World') 转小写, upper('Hello World') 转大写 from dual; --initcap: 首字母大写 select initcap('hello world') 首字符大写 from dual; --字符控制函数 -- concat: 字符连接函数, 等同于  || select concat('

在Excel 2013换行单元格中提取出第二行的数据

  案例教学 ①启动Excel2013,打开要进行提取的表格,看到D列中有很多换行单元格,我要将第二行数据提取出来,在E2单元格输入公式: =REPLACE(D2,1,FIND(CHAR(10),D2),"") ②回车,得到结果珠海.关于公式的详细说明,我在文章末尾为大家作详细介绍. ③为了简便输入,下面利用单元格填充的方法完成余下的操作,将鼠标移动到E2单元格右下角,出现+号,双击,即可快速填充完毕. 公式说明 replace(要替换的文本,从文本的第几个开始替换,一共要替换多少个,

乐视网股价25天涨六成 员工行权将大赚140多万

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 本报讯 (记者张忠安)在近期多个股吧或股票QQ里,讨论最多的莫过于乐视网,虽然该公司股价昨日下跌5.5%,但最近25个交易日里,大涨63%左右. 在A股大跌拖累下,牛股乐视网昨日也出现回调,全天下跌1.60%.不过,这并不影响该公司员工的"发财梦".记者从乐视网了解到,该公司第一批享受期权的员工即将行权."我们那

剑指offer系列之五十五:把二叉树打印成多行

题目描述 从上到下按层打印二叉树,同一层结点从左至右输出.每一层输出一行. 此题实际上与上面一题是重复了,总体还是层序遍历的思路,只不过现在不需要在打印每一行之前对打印顺序进行判断了,所以可以在前面一题的代码进行简单的修改就可以实现题目的要求了.不多说,直接看代码(已被牛客AC): package com.rhwayfun.offer; import java.util.ArrayList; import java.util.LinkedList; import java.util.Queue;

编写程序将次行风格的java源代码转变成末行风格

问题描述 例如源代码publicclasschange{publicstaticvoidmain(String[]args){//Ijustwanttotryit}}程序运行后publicclasschange{publicstaticvoidmain(String[]args){//Ijustwanttotryit}}使用I/O实现

第二节:思维导图学习Shell脚本编程之命令之美

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://dba10g.blog.51cto.com/764602/1607658 介绍一些常用的命令 cat命令一些用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #cat -s命令压缩空行