用SQL行列转换实现数据透视的一些思考

用SQL行列转换实现数据透视的一些思考

 

摘  要:根据对报表开发过程中碰到的需要用SQL行列转换进行解决的一类查询统计问题的分析,逐步探索求解得到一种较通用的解决思路,并用函数进行实现。该解决思路及函数实现可用于Oracle数据库开发中行列转换、数据透视等查询统计的应用开发,可直接供前端报表展示工具调用,也可用于后端存储过程中调用。

关键词:行列转换;数据透视;查询统计;数据库开发;SQL;函数。

1.开篇

在银行新资本协议风险相关的系统报表开发过程中,遇到以下需求:

根据损失数据收集表(见表1)生成某一时间段内损失数据条线类型分布表(表样见表2)。

表1:损失数据收集表

事件编号

发生日期

发生机构

业务条线

损失事件类型

损失金额

LE0001

2010-5-11

分行1

B4.商业银行

E1.内部欺诈

200

LE0002

2010-6-12

分行2

B5.支付和结算

E1.内部欺诈

400

LE0003

2010-7-14

分行3

B4.商业银行

E2.外部欺诈

300

LE0004

2010-8-15

分行4

B5.支付和结算

E2.外部欺诈

600

LE0005

2010-9-16

分行5

B4.商业银行

E1.内部欺诈

500

LE0006

2010-10-18

分行6

B5.支付和结算

E1.内部欺诈

200

 

 

 

 

 

表2 :损失数据条线类型分布表

业务条线

E1.内部欺诈

E2.外部欺诈

E3.就业制度和工作场所安全事件

E4.客户、产品和业务活动事件

E5.实物资产的损坏

E6.信息科技系统事件

E7.执行、交割和流程管理事件

B1.公司金融

0

0

200

0

0

0

0

B2.交易和销售

0

0

0

400

0

0

0

B3.零售银行

0

0

0

0

300

0

0

B4.商业银行

200

800

0

0

0

0

0

B5.支付和结算

600

600

0

0

0

0

0

B6.代理服务

0

0

0

0

0

600

0

B7.资产管理

0

0

0

0

0

0

500

B8.零售经纪

0

0

200

0

0

0

0

1是事件记录表,记录了每个损失事件的发生日期、机构、业务条线、损失事件类型、损失金额等信息。表是交叉表,纵向为业务条线,取值同表1中的业务条线(可称为分组列);横向为损失事件类型,取值同表1中的损失事件类型(可称为转置列),所不同的是由表1中的纵向排列改为横向排列;表2中的数据项为将表1中的损失金额根据业务条线和损失事件类型汇总而得。从加工的规则和表样看,表2是对表1的一个数据透视表,行标签为业务条线,列标签为损失事件类型。

为得到从表1求出表2的通用解决方案,现对表1和表2进行以下抽象:

抽象一:将表1抽象为只有三列:分组列(group_col)、转置列(column_col)和值列(value_col),表名为Ts。按一般的设计规则,业务条线和损失事件类型都应分别设计到数据字典,表1中仅保留对应的码值。为简化讨论,在此不做此处理。

抽象二:将表2抽象为行标签(row_tag)、列标签(col_tag)和统计值(stat_val),表名为Tt。由于不想因为增加实体表而增加系统的复杂性,要求Tt是个临时表,或只是一个SQL

TsTt的规则描述为:

规则一:行标签:SELECT DISTINCT group_col FROM Ts

规则二:列标签:SELECT DISTINCT column_col FROM Ts

规则三:统计值ijSELECT SUM(value_col) FROM Ts WHERE group_col = 行标签i  and column_col = 列标签j。这里的SUM是一个聚合函数。

现在的需求抽象为:从表Ts按照规则一、规则二和规则三,生成Tt

以下的讨论假定使用Oracle 10g数据库,并假定不使用存储过程实现。

2.使用SQL硬编码实现

首先想到的是使用SQL硬编码实现:用group_col进行分组,使用decode函数或case语句对column_col的值进行判别,对value_col进行求和。

使用decode函数求解Tt的代码见代码1

代码1:使用SQL硬编码

SELECT group_col 
  ,nvl(sum(decode(to_char(column_col),'E1.内部欺诈',value_col)),0.0)"E1.内部欺诈"
  ,nvl(sum(decode(to_char(column_col),'E2.外部欺诈',value_col)),0.0)"E2.外部欺诈"
  ,nvl(sum(decode(to_char(column_col),'E3.就业制度和工作场所安全事件',value_col)),0.0)"E3.就业制度和工作场所安全事件"
  ,nvl(sum(decode(to_char(column_col),'E4.客户、产品和业务活动事件',value_col)),0.0)"E4.客户、产品和业务活动事件"
  ,nvl(sum(decode(to_char(column_col),'E5.实物资产的损坏',value_col)),0.0)"E5.实物资产的损坏"
  ,nvl(sum(decode(to_char(column_col),'E6.信息科技系统事件',value_col)),0.0)"E6.信息科技系统事件"
  ,nvl(sum(decode(to_char(column_col),'E7.执行、交割和流程管理事件',value_col)),0.0)"E7.执行、交割和流程管理事件" 
FROM Ts 
WHERE 1=1 
GROUP BY group_col 


 

也可以使用case when …then…end语句来代替上式中的decode函数。再此不另附编码。

引种方案是最直接的解决方案。其优点是直观。缺点是使用了太多的硬编码,看起来非常的繁琐,特别是当column_col的值较多或需要使用多个column_col对应的value_col进行计算时,重复性的硬编码将会在代码中多次出现。不论从可读性、可维护性和扩展性上来讲,这都不能称为一个好的解决方案。

如果能有什么方法直接将column_col的值转置成列名,然后在SQL直接按列名取对应的统计值就好了。那样的话,生成报表的代码就可以写成类似这样的代码:

SELECT group_colE1.内部欺诈,E2.外部欺诈

FROM  (…)

有没有这样的方法呢?

3.TABLE

能直接按列名访问的,可以是TABLE,可以是VIEW,也可以是CURSOR

先看TABLE

使用Table,有两种思路,一种是批量时使用Tt。由于不想因为增实体表而增大系统的复杂性,此种思路暂不考虑。

另一种思路是在SQL中生成临时表,生成报表后,在SQL中将临时表删除。这种思路带来的问题是如何解决临时表的命名冲突或数据一致性问题。

对于临时表的命名冲突,可以根据查询的条件组成临时表名,但这样会造成生成很多临时表的混乱局面,也无法避免两个查询条件一致时的并发问题。出于此种考虑,放弃掉使用临时表的想法。

4.VIEW

再看VIEW

View是基于一个表或多个表或视图的逻辑表。建立View不会带来物理存储上的混乱。看起来是个不错的选择。但在解决此问题时,并不适合使用View,原因如下:

通用性问题:如果为这个需求建立了视图,那么再出现一个类似的需求是否也要建立一个视图?而这样的需求可以说是无穷尽的。只对表1,就可以提出很多种类似于表2的查询统计。如按机构、业务条线统计损失金额,再如按机构、损失事件类型统计损失金额等,可以按时间、机构、业务条线、损失事件类型、金额范围等多个维护的两两组合。

命名问题:如果使用临时生成的View,存在命名冲突的问题。

对于临时生成View,在技术上似乎可行,示例见代码2

代码2:使用临时视图

WITH t0 AS(
  SELECT create_v('v_test') FROM dual  --生成临时视图v_test
) 
,t1 AS (
  SELECT * FROM v_test                 --使用视图v_test
)
, t2 AS(
  SELECT delete_v('v_test') FROM dual  --删除视图v_test
) SELECT * FROM t1, t2;


 

以上代码试图在t0段中调用create_v函数生成临时视图v_test,在t1段中使用该视图,在t2段中删除视图。

但代码2中至少存在两外错误:一是如果v_testcreate_v()函数生成的临时视图,则t1段时无法直接使用;二是如果v_test视图早就存在,create_v()函数只是修改,则执行时报ORA-14552: cannot perform a DDL, commit or rollback inside a query or DML,即违反了“在查询或数据管理语言中不能执行数据定义语言、提交或回滚”的约束。

由此看来,使用VIEW并非可行之选。

5.CURSOR

Cursor又叫游标,其作用是用于临时存储从数据库中提取的数据。

考虑到封装性,可以将用一个函数动态生成代码1,然后用代码1生成游标,并返回这个游标给外层的SQL,期望外层的SQL能能象使用table那样使用返回的游标。

Oracle中有三种Cursor,分别是隐式Cursor、显式CursorRef Cursor(动态Cursor)。其中Ref Cursor可以作为函数参数或函数返回值进行传递,这将成为首先考虑的途径。Oracle还提供了DBMS_SQL包,该包中提供了更象是类似于CJava等高级编程语言中的Cursor及操作,这也给我们提供了一种选择。

不管使用哪种Cursor,首要的任务是编制动态生成代码1的函数,在这里命名为GETNVSQL(),含义为GET Name Value SQL

5.1GETNVSQL函数

GETNVSQL函数的功能为动态生成代码1。执行分以下两个步骤进行:

步骤一:生成列标签cursor

步骤二:对于列标签cursor每一行,组装成decode()语句。

步骤三:组装上SELECT WHERE子句、GROUP子句和ORDER子句。

考虑到通用性,将表名、分组列、转置列、值列、聚合函数名都设计为函数输入参数。为限制选择数据、排序方式、缺值处理,将选择条件、行列排序和空值处理也设计为函数输入参数。完整的GETNVSQL函数参见代码3

代码3GETNVSQL函数

CREATE OR REPLACE FUNCTION GETNVSQL(TABNAME         IN VARCHAR2, -- 需要进行行转列操作的表名;
       GROUP_COL      IN VARCHAR2, -- 查询结果要按某列或某些列分组的字段名;
       COLUMN_COL    IN VARCHAR2, -- 要从行转成列的字段;
       VALUE_COL       IN VARCHAR2, -- 需要聚合的值字段;
       AGGREGATE_FUNC  IN VARCHAR2 DEFAULT 'max', -- 选用的聚合函数,可选;
       CONDITIONS     IN VARCHAR2 DEFAULT ' 1 = 1', -- 条件
       COLORDER       IN VARCHAR2 DEFAULT NULL, -- 行转列后列的排序,可选;
       ROWORDER      IN VARCHAR2 DEFAULT NULL, -- 行转列后记录的排序,可选;
       WHEN_VALUE_NULL IN VARCHAR2 DEFAULT NULL) -- 空值处理,可选。
  RETURN VARCHAR2 AS
  SQLSTR   VARCHAR2(32767) := 'SELECT ' || GROUP_COL || ' ';
  C1       SYS_REFCURSOR;
  ValueStr   VARCHAR2(100);
BEGIN
  OPEN C1 FOR 'SELECT distinct ' || COLUMN_COL || ' FROM ' || TABNAME || ' WHERE ' || CONDITIONS || CASE WHEN COLORDER IS NOT NULL THEN ' ORDER BY ' || COLORDER END;
  LOOP
    FETCH C1
      INTO ValueStr;
    EXIT WHEN C1%NOTFOUND;
    SQLSTR := SQLSTR || CHR(10) || ',' || CASE WHEN WHEN_VALUE_NULL IS NOT NULL THEN 'nvl(' END || AGGREGATE_FUNC || '(decode(to_char(' || COLUMN_COL || '),''' || ValueStr || ''',' || VALUE_COL || '))' || CASE WHEN WHEN_VALUE_NULL IS NOT NULL THEN CHR(44) || WHEN_VALUE_NULL || CHR(41) END || '"' || ValueStr || '"';
  END LOOP;
  CLOSE C1;
  SQLSTR := SQLSTR || ' FROM ' || TABNAME || ' WHERE ' || CONDITIONS || ' GROUP BY ' || GROUP_COL || CASE WHEN ROWORDER IS NOT NULL THEN ' ORDER BY ' || ROWORDER END;
  RETURN(SQLSTR);
END GETNVSQL;


 

用以下代码对GETNVSQL函数进行测试:

SELECT getnvsql('Ts', 'group_col', 'column_col','value_col', 'sum', '1=1', 'column_col', 'group_col', '0.0') FROM dual

得到GETNVSQL函数生成的代码同代码1一致,完成了生成标签和统计值的功能。

5.2 Ref Cursor

Ref Cursor提供了在函数间传递游标的可能性。对Ref Cursor的使用一般按OpenFetchClose三个步骤进行。

在函数中可以利用代码3可以动态生成代码1,可以为代码1利用Open生成Ref Cursor。但是这个RefCursor如何能在SQL中直接使用呢?又在什么时候执行Close以释放Ref Cursor占用的资源呢?经过多种尝试,也没有找到在SQL中直接使用Ref Cursor的办法。

5.3 DBMS_SQL

再看DBMS_SQLDBMS_SQL包提供了动态定义Cursor、动态生成Cursor、动态进行变量绑定等功能,使用方法更像是用CJava等高级语言操作数据库游标的方式。DBMS_SQL包中所用的Cursor更像是一个文件指针,其类型是int型的。其使用一般按OpenParseDescribe_ColumnsDefine_Columns,Fetch_RowsClose等步骤进行。DBMS_SQL给了开发者更多的自由来操纵Cursor

然而,这里的Cursor也无法直接提供给SQL使得。

要想把Cursor提供给SQLTABLE那样使用,只能再经过包装。

6.TABLE函数

Oracle提供了TABLE函数,可以接收自定义数组参数,返回值可以被视为表或视图一样进行访问。

使用TABLE函数一般按以下步骤进行:

步骤一:定义行数据类型;

步骤二:定义行数据类型的表类型;

步骤三:生成表类型的数据;

步骤四:在SQLFROM子句中使用TABLE(表类型数据)进行调用。

其中步骤一、二决定了步骤三、四的设计,是解决问题的关键。设计什么样的行数据类型合适呢?

6.1类型定义

我们可以按Tt的结构来设计行数据类型。但带来的问题,每一个种应用有不同的列标签,我们不可能穷举所有可能的结构(见4.VIEW中的讨论)。如何能定义一种可以包含这些可能的结构的结构呢?这样的结构是否存在呢?

我们先来分析一下行(表中的一条记录)的本质。表中的一行是表结构类型的一个具体实例,也可以解释为表结构类型的一个赋了值结构体变量,对行中列值的引用是通过列名来进行的。在这种意义上,我们可以认为表中的一行是一个名值对的列表。由此可进行如下定义:

名值对:=<|>                                                 (1)

:=名值对1…,名值对i…,名值对N                                  (2)

根据此定义,我们可以定义行数据类型是一个字符串,字符串中按名值对的形式存放数据。

由此可进行行数据和表数据类型定义,见代码4

代码4:类型定义

CREATE OR REPLACE TYPE nvs_row AS OBJECT(
  nvs VARCHAR2(32767)
);
CREATE OR REPLACE TYPE nvs_tab AS TABLE OF nvs_row;


 

其中32767varchar2类型变量的长度最大值。

以下是一行数据的例子:

,FGROUP_COL1|Commercial,F121|200,F125|500

至于从这样的字符串中取出名对应的值,就是件纯技术上的事情了,实现参见代码5

代码5GETNV函数

CREATE OR REPLACE FUNCTION GETNV(PROPERTIES    IN VARCHAR2,
                                 PROPERTY_NAME IN VARCHAR2)
  RETURN VARCHAR2 AS
  
  RESULT         VARCHAR2(100) := '0.0';
  NAMELOCAL      VARCHAR2(100) := 'F' || PROPERTY_NAME || '|';
  POSBEGIN       INT := INSTR(PROPERTIES, NAMELOCAL);
  POSEND         INT := 0;
  LENGTHPROPERTY INT := 0;
  VALUEBEGIN     INT := 0;
BEGIN
  IF POSBEGIN > 1 THEN
    BEGIN
      VALUEBEGIN := POSBEGIN + LENGTH(NAMELOCAL);
      POSEND     := INSTR(PROPERTIES, ',F', VALUEBEGIN);
      IF POSEND = 0 THEN
        POSEND := LENGTH(PROPERTIES);
      END IF;
      LENGTHPROPERTY := POSEND - VALUEBEGIN;
    
      RESULT := SUBSTR(PROPERTIES, VALUEBEGIN, LENGTHPROPERTY);
    END;
  END IF;
  RETURN(RESULT);
END GETNV;


 

6.2生成自定义表

选择使用DBMS_SQL包编写生成自定义表的函数,原因如下:

原因一:代码3所生成SQL语句执行的结果集字段名不确定;

原因二:结果集字段个数不确定;

原因三:DBMS_SQL包为探索和访问动态游标提供了可能性。

根据SQL语句生成自定义表的函数实现,见代码6

为了使用上的方便,编写GETNVST函数作为开给SQL的最终接口。在GETNVST函数中依次调用GETNVSQL函数和GETNVSTA函数,并返回自定义表,函数实现见代码7

7.完整解决方案

综合以上讨论,完整的解决方案包含以下五个构件:

构件一:NVS_ROWNVS_TAB类型定义。见代码4

构件二:主控函数GETNVST。见代码7

构件三:动态生成SQL函数GETNVSQL。见代码3

代码6GETNVSTA函数

CREATE OR REPLACE FUNCTION GETNVSTA(SQLSTR IN VARCHAR2) RETURN NVS_TAB AS
  NVS       NVS_TAB := NVS_TAB();
  L_CURSOR    INTEGER := DBMS_SQL.OPEN_CURSOR;
  L_VALUE     VARCHAR2(4000) := 'null';  L_STATUS    INTEGER;
  L_COLCOUNTS INTEGER;               L_DESCTBL   DBMS_SQL.DESC_TAB;
  ROWSTR      VARCHAR2(32766);      I           INTEGER := 0;
BEGIN
  --分析sql
  DBMS_SQL.PARSE(L_CURSOR, SQLSTR, DBMS_SQL.NATIVE);
  --获取列数
  DBMS_SQL.DESCRIBE_COLUMNS(L_CURSOR, L_COLCOUNTS, L_DESCTBL);
  --对每一列设置
  FOR I IN 1 .. L_COLCOUNTS LOOP
    DBMS_SQL.DEFINE_COLUMN(L_CURSOR, I, L_VALUE, 4000);
  END LOOP;
  L_STATUS := DBMS_SQL.EXECUTE(L_CURSOR);
  WHILE (DBMS_SQL.FETCH_ROWS(L_CURSOR) > 0) LOOP
    NVS.EXTEND;
    ROWSTR := '';
    FOR I IN 1 .. L_COLCOUNTS LOOP
      --依次取值
      DBMS_SQL.COLUMN_VALUE(L_CURSOR, I, L_VALUE);
      ROWSTR := ROWSTR || ',F' || L_DESCTBL(I).COL_NAME || '|' || L_VALUE;
    END LOOP;
    NVS(NVS.COUNT) := nvs_row(ROWSTR);
  END LOOP;
  DBMS_SQL.CLOSE_CURSOR(L_CURSOR);
  RETURN(NVS);
END GETNVSTA;


 

代码7GETNVST函数

CREATE OR REPLACE FUNCTION GETNVST(TABNAME IN VARCHAR2, GROUP_COL IN VARCHAR2, 
       COLUMN_COL IN VARCHAR2, VALUE_COL IN VARCHAR2,
	AGGREGATE_FUNC  IN VARCHAR2 DEFAULT 'max',
	CONDITIONS IN VARCHAR2 DEFAULT ' 1 = 1', COLORDER IN VARCHAR2 DEFAULT NULL, 
       ROWORDER IN VARCHAR2 DEFAULT NULL, WHEN_VALUE_NULL IN VARCHAR2 DEFAULT NULL)  RETURN NVS_TAB AS
  QLSTR VARCHAR2(32767) := ' ';
BEGIN
  SQLSTR := GETNVSQL(TABNAME, GROUP_COL, COLUMN_COL, VALUE_COL, AGGREGATE_FUNC, CONDITIONS, COLORDER, ROWORDER, WHEN_VALUE_NULL);
  RETURN(GETNVSTA(SQLSTR));
END GETNVST;


 

构件四:根据SQL生成自定义表函数GETNVSTA。见代码6

构件五:取名值函数GETNV。见代码5

构件关系图见图1

1:构件关系图

 

至此开篇所列行列转换数据透视的需求问题得以解决,应用示例代码参见代码 8

代码8:应用示例

SELECT getnv(nvs, 'GROUP_COL') "业务条线"
       , getnv(nvs,'E1.内部欺诈') + getnv(nvs,'E2.外部欺诈') "欺诈" 
       , getnv(nvs,'E6.信息科技系统事件') "系统"
FROM table(getnvst('Ts', 'group_col', 'column_col','value_col', 'sum', '1=1', 'column_col', 'group_col', '0.0'))


 

8.讨论

该解决方案存在以下限制:

限制一:无法处理column_colvalue_col值中含有字符’|’的情况。

限制二:GETNVST参数中tabname不接受会话级临时表名。

限制三:生成的动态SQL存储在VARCHAR2字符串变量中,而其最大长度为32767NVS_ROW中的NVS也定义为VARCHAR2,同样受32767的长度限制。

限制四:无法支持从外VALUE_COL,或对一个VALUE_COL同时使用多个聚合函数。变通的方法是多次调用生成多个临时表,但这有性能上的损耗。

该解决方案可以有以下扩展应用:

扩展应用一:五个构件中除GETNVST外都可单独使用。互相之间不存在较依赖关系。

扩展应用二:需求和例子中只给出了一个GROUP_COL的例子。实际上,该解决方案支持使用多个GROUP_COL,只需要在传递参数时将多个GROUP_COL之间用逗号分隔即可。

扩展应用三:需求和例子中只给出一个TABNAME的例子。实际上,支持使用多个TABNAME。注意两点:一是在传递TABNAME参数时用类似于’tabname1,tabname2’的形式;二是在Conditions中使用正确的连接方式。

为了不使代码占太多的篇幅,在代码中省略了很多注释和规范性的空行,可能造成阅读上的很多不便。

代码3的函数接口定义和实现部分参照了网上的内容,由于转载次数过多,最初出处已不能明确标明。

MS Query基础语法讲解: 一、单表查询 单表查询是指仅涉及一个表的查询 1、查询指定列 例1、查询材料进货明细表中的定额名称及规格 SELECT 定额名称,规格 FROM [材料进货明细表$] 注意:在excel中,一个工作表的表示是这样的:[工作表名称$] select的意思是:查询 上面那一句SQL语句的意思就是:从表材料进货明细表中取出定额名称及规格的值 例2、查询材料进货明细表的详细记录 SELECT * FROM [材料进货明细表$] 等价于: SELECT 序号, 定额名称,规格,日期,单位,数量 FROM [材料进货明细表$] 注意:*的用法 2、查询经过计算的值 例3、查询材料进货明细表的定额名称、规格、年份及数量 SELECT 定额名称,规格,year(日期) as 年份,数量 FROM [材料进货明细表$] 注意:里面的年份已经通过了计算的了,成为一个新的变量。 加多一列自定义的列A厂: SELECT “A厂” ,定额名称,规格,year(日期) as 年份,数量 FROM [材料进货明细表$] SELECT “A厂” as 工厂 ,定额名称,规格,year(日期) as 年份,数量 FROM [材料进货明细表$] 比较一下上面两句SQL语句的区别,没有as 工厂与有as 工厂的区别 二、选择表中若干元组 1、消除取重复的行 例4、查询材料进货明细表的定额名称 SELECT 定额名称 FROM [材料进货明细表$] 该查询结果会包含很多重复的行。消除重复行的话,必须指定关键词 distinct SELECT distinct 定额名称 FROM [材料进货明细表$] 2、查询满足条件的元组 例5、查询材料进货明细表中单位为“套”的所有记录 SELECT * FROM [材料进货明细表$] where 单位= ‘套’ 本句语句中,要学会where的用法: 要查询满足指定条件的元组,可以通过where子句实现。where子句查询条件是: 比较: =,>,<,>=,<=,!=,<>,!>,!<;not +上述比较运算符 确定范围: between and ,not between and 确定集合:in,not in 字符匹配:like,not like 空值:is null,is not null 多重条件:and,or,not 例6、查询材料进货明细表中数量在50—100之间的所有记录 SELECT * FROM [材料进货明细表$] where 数量 between 50 and 100 如果是不在50-100之间的话,直接改成: SELECT * FROM [材料进货明细表$] where 数量 not between 50 and 100 例7、查询材料进货明细表中单位为“只”或“支”的所有记录 SELECT * FROM [材料进货明细表$] where 单位 in(‘只’,'支’) 3、字符匹配 可以用like来实现,通配符%和_ a、%代表任意长度的字符串,如a%b表示以a开头,以b结尾的任意的字符串 b、_代表任意单个字符 例8、查询材料进货明细表中定额名称以“天津”开头的所有记录 SELECT * FROM [材料进货明细表$] where 定额名称 like ‘天津%’ 例9、查询材料进货明细表中定额名称以“天津”开头且字符为4个的所有记录 SELECT * FROM [材料进货明细表$] where 定额名称 like ‘天津__’ 例10、查询材料进货明细表中定额名称不以“天津”开头的所有记录 SELECT * FROM [材料进货明细表$] where 定额名称 not like ‘天津%’ 4、涉及空值的查询 主要是以:null出现 例11、查询材料进货明细表中规格为空的的所有记录 SELECT * FROM [材料进货明细表$] where 规格 is null 5、多条件查询 例12、查询材料进货明细表中定额名称为“天津三通”的并且数量大于30的所有记录 SELECT * FROM [材料进货明细表$] where 定额名称 = ‘天津三通’ and 数量>30 三、order by 子句 desc(降序)、asc(升序) 例13、查询材料进货明细表中数量大于30的所有记录,并且要按照数量来降序排列。 SELECT * FROM [材料进货明细表$] where 数量>30 order by 数量 desc 四、聚集函数 count(distinct/all 列名):统计元组个数 sum:求和 avg:求平均值 max:最大值 min:最小值 例14、查询材料进货明细表中天津大小头的最大数量。 SELECT max(数量) FROM [材料进货明细表$] where 定额名称 = ‘天津大小头’ 或:SELECT max(数量) as 最大数量 FROM [材料进货明细表$] where 定额名称 = ‘天津大小头’ 2. Excel源数据及分析下载:Excel数据透视表教程:分类百分比 SQL语句: select *, Hz1.分类销量/Hz2.分类销量 as 分类百分比 from (select 品种,分公司, sum(数量) as 分类销量 from [数据$] group by 品种,分公司) Hz1, (select 分公司, sum(数量) as 分类销量 from [数据$] group by 分公司) Hz2 where hz1.分公司=Hz2.分公司 SQL语句解释: select 品种,分公司, sum(数量) as 分类销量 from [数据$] group by 品种,分公司 实现对品种、分公司两字段分组的统计求和 select 分公司, sum(数量) as 分类销量 from [数据$] group by 分公司 实现对分公司字段分组的统计求和 对分公司分组统计求和数是对品种、分公司两字段分组统计求和项目再对不同品种的总就和,也就是后者包含前者。 整句语句的意思就是,将两个查询结果作为新的查询表分别命名为Hz1、Hz2,用 ”where hz1.分公司=Hz2.分公司“来组合数据, 没有条件的制约的话,将统计的结果再进行除数运算, 各品种的分组统计数(分组含品种字段)除以各品种已求和了的分组统计数(分组不含品种字段)求得所占比率, 最后在字段单元格设置为百分数就可以了。 3 在Excel中使用MS Query查询外部数据库的内容的优点是:不用设置公式、编写VBA代码、源数据库不用打开。 但要注意:源数据库的记录要有字段名,由于设置查询时的路径固定,。 因此源数据库文件不能随意移动(如确实要移动可以通过手工修改查询或VBA解决) 以下示例采用MS Query在“查询”工作簿中查询关闭的“销售”工作簿中sheet1的指定 “店铺”和指定“颜色”的内容(sheet1有“店铺”、“数量”、“颜色”等字段名及若干数据)。 操作前请先确定是否安装有MS Query。附上举例文件,请解压到D盘根目录下: 销售.xls为源数据,查询.xls中设置了msquery查询。 下载:MS Query查询未打开工作簿的内容例子 1、 新建一个工作表,选择菜单【数据】—【导入外部数据】—【新建数据库查询】, 界面如图,由于查询excel数据库的内容,因此选择【Excel Files*】并确定; 2、 弹出〖选择工作簿〗对话框,选择“销售”工作簿,〖确定〗; 3、 弹出〖选择列〗对话框,如果此时弹出“没用内容”,确定后在〖选项〗中将“系统表”勾上。 将所选工作簿的各个工作表及工作表中的字段名添加到查询结果中,全选可直接将工作表名添加, 〖下一步〗〖下一步〗,选择“在MS query中继续编辑查询”; 4、 弹出MS Query查询编辑,点击【显示/隐藏条件】图标,在“条件字段”中添加“店铺”, 值改为“[店]”(方括号中内容随意),再添加个条件为“颜色”,值改为“[色]”,点击【将数据返回Excel】 5、 回到excel的“导入数据”对话框,点击〖参数〗,选中“店”字段,再选择“从下列单元格中获取数据”, 选择一个用来更改查询关键字的单元格(如B1),并勾选“单元格值更改时自动刷新”, “色”字段改成从单元格“B2”中获取,〖确定〗。数据放置位置选择“A3”。〖确定〗。 6、 当更改B1和B2单元格的内容(做个数据有效性)时,A3及以下的数据会即时刷新。 此法对于需要经常在局域网中查询数据非常方便,比如:数据放在局域网内的一台主机上,通过MS Query即可不打开工作簿查询数据
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值