交叉表:在字段中计算相同的字符串并将其显示为字段名称

时间:2018-01-15 12:22:51

标签: database select firebird procedure firebird-3.0

我想在字段中有一个特定的字符串,对于PAYROLL_PAYMONTH字段中的此示例。例如,我会计算' HELLO'在现场并将其显示在一个组中。

-- DATA 
EMP_SURNAME   PAYROLL_PAYYEAR    PAYROLL_PAYMONTH
    X              1999                JAN
    X              1999                JAN
    X              1999                FEB

-- OUTPUT 
EMP_SURNAME   PAYROLL_PAYYEAR       JAN   FEB   MAR
    X              1999              2     1     0

为了计算字段中的相同字符串并显示它,我使用SQL Manager for Firebird在Firebird 3中创建了一个组选择过程

CREATE PROCEDURE PAID_LISTING(
  SORT_PAYROLL_YEAR VARCHAR(50) CHARACTER SET ISO8859_1 COLLATE ISO8859_1)
RETURNS(
  EMP_SURNAME VARCHAR(50) CHARACTER SET ISO8859_1 COLLATE ISO8859_1,
  PAYROLL_PAYYEAR VARCHAR(50) CHARACTER SET ISO8859_1 COLLATE ISO8859_1,
  PAYROLL_MON_JAN VARCHAR(50) CHARACTER SET ISO8859_1 COLLATE ISO8859_1)
AS
BEGIN
  FOR
    SELECT
      B.EMP_SURNAME,
      A.PAYROLL_PAYYEAR,
      COUNT (A.PAYROLL_PAYMONTH)

    FROM PAYROLL A, EMP B
    WHERE A.EMP_PK = B.EMP_PK AND  A.PAYROLL_YEAR =: SORT_PAYROLL_YEAR

    GROUP BY
      B.EMP_SURNAME,
      A.PAYROLL_PAYYEAR

    ORDER BY B.EMP_SURNAME ASC
    INTO
      :EMP_SURNAME,
      :PAYROLL_PAYYEAR,
      :PAYROLL_MON_JAN
  DO
    BEGIN
      SUSPEND;
    END
END;

但这不是我想要的结果。下一步做什么?

1 个答案:

答案 0 :(得分:1)

您想要得到的是"交叉表报告" - https://en.wikipedia.org/wiki/Crosstab

生成它的常规方法分为两个步骤:

  1. 您在数据库中进行通常的单向查询,具有固定列数,列在语义上不同,不会相互重复。因此,几年和几个月 - JAN,FEB等 - 会分成不同的行,而不是相邻的列。

  2. 然后您通过客户端应用程序方式在所需的双向表中表示该1D查询的结果。您使用它们制作客户端应用程序的语言和库应提供通过常规一维查询制作交叉表的方法。

  3. 问题是,数据库是一种保存和提取数据的工具,而不是让人眼前一亮。而您的客户端应用程序是一种以简单易用的方式呈现数据的工具。 " Divide et empera",使用每个工具完成创建和优化的任务。强制SQL服务器尽可能地进行可视化表示,这将是对荣耀的追求。既不自然又相对缓慢。

    但是,如果您打算在纯SQL中实现它而不管它的低效率,那么您可以使用CTE。

    再次," divide et empera",将复杂的任务分成更简单的任务。我将处理您在问题中提供的样本数据。

    CREATE TABLE DATA (
      EMP_SURNAME VARCHAR(10) NOT NULL,
      PAYROLL_PAYYEAR SMALLINT NOT NULL,
      PAYROLL_PAYMONTH CHAR(3) NOT NULL);
    
    
    /*
    EMP_SURNAME   PAYROLL_PAYYEAR    PAYROLL_PAYMONTH
      X              1999                JAN
      X              1999                FEB
      X              1999                JAN
    */
    

    你必须做三个步骤。

    1. 折叠数据 - 计算每月行数。这是通常的GROUP BY查询,通常它是唯一的查询,因为交叉表将由您的应用程序从其结果中完成。

    2. 制作"骨架"结果表将包含的行列表。这意味着 - 所有对PERSON + YEAR都有任何数据。如果没有一个月的数据,这将跳过任何一年。

    3. 一起执行这些查询结果,并使它们逐列水平对齐,而不是SQL行 - 行结构的正常对齐。

    4. 我们走了。

      第1步:

       select EMP_SURNAME, PAYROLL_PAYYEAR, PAYROLL_PAYMONTH, Count(*) as QTY
       from DATA 
       group by EMP_SURNAME, PAYROLL_PAYYEAR, PAYROLL_PAYMONTH
      
      
      EMP_SURNAME PAYROLL_PAYYEAR PAYROLL_PAYMONTH    QTY
      X       1999        FEB                 1
      X       1999        JAN                 2
      

      第2步:

      select distinct EMP_SURNAME, PAYROLL_PAYYEAR from DATA
      
      EMP_SURNAME PAYROLL_PAYYEAR
      X       1999
      

      第3步:

      with EMP_YEAR as ( select distinct EMP_SURNAME, PAYROLL_PAYYEAR from DATA )
      ,GROUPED as
      (
        select EMP_SURNAME, PAYROLL_PAYYEAR, PAYROLL_PAYMONTH, Count(*) as QTY
        from DATA group by EMP_SURNAME, PAYROLL_PAYYEAR, PAYROLL_PAYMONTH
      )
      
      select EMP_YEAR.EMP_SURNAME, EMP_YEAR.PAYROLL_PAYYEAR
        ,coalesce( emp_jan.qty, 0) as JAN
        ,coalesce( emp_feb.qty, 0) as FEB
        ,coalesce( emp_mar.qty, 0) as MAR
      from EMP_YEAR
      left join GROUPED as EMP_JAN on
           EMP_YEAR.EMP_SURNAME = EMP_JAN.EMP_SURNAME and
           EMP_YEAR.PAYROLL_PAYYEAR = EMP_JAN.PAYROLL_PAYYEAR and
           EMP_JAN.PAYROLL_PAYMONTH = 'JAN'
      left join GROUPED as EMP_FEB on
           EMP_YEAR.EMP_SURNAME = EMP_FEB.EMP_SURNAME and
           EMP_YEAR.PAYROLL_PAYYEAR = EMP_FEB.PAYROLL_PAYYEAR and
           EMP_FEB.PAYROLL_PAYMONTH = 'FEB'
      left join GROUPED as EMP_MAR on
           EMP_YEAR.EMP_SURNAME = EMP_MAR.EMP_SURNAME and
           EMP_YEAR.PAYROLL_PAYYEAR = EMP_MAR.PAYROLL_PAYYEAR and
           EMP_MAR.PAYROLL_PAYMONTH = 'MAR'
      

      ......这就是你想得到的:

      EMP_SURNAME PAYROLL_PAYYEAR JAN FEB MAR
      X           1999            2   1   0
      

      现在,这个查询很难看,它很脆弱(很多复制粘贴,你很容易犯错,然后发现它会更难),而且它很慢。 只需查看此请求的查询计划 - 您可以一次又一次地为每个列加入表格!

      PLAN JOIN (JOIN (JOIN (SORT (EMP_YEAR DATA NATURAL), SORT (EMP_JAN DATA NATURAL)), SORT (EMP_FEB DATA NATURAL)), SORT (EMP_MAR DATA NATURAL))
      

      所以...这就是你在SQL服务器上做到的方法,但是再想一想,尝试在适当的工具之间分配任务,从而只在服务器上进行逐个查询#1,并让你的客户端应用程序重新组合它而是进入跨表报告

      PS。将此查询包装到存储过程中在Firebird中不是一个好主意。程序和功能用于编程。如果您只想将复杂查询保留为命​​名SQL对象 - 这就是SQL VIEW的用途。

      create view CTE_CROSSTAB (EMP_SURNAME,PAYROLL_PAYYEAR,JAN,FEB,MAR) as   
      with EMP_YEAR as ( select distinct EMP_SURNAME, PAYROLL_PAYYEAR from data )
      ......etc