【问题标题】:Tabular view of data from normalized SQL database标准化 SQL 数据库中数据的表格视图
【发布时间】:2013-10-31 17:42:47
【问题描述】:

我已尝试创建规范化数据库,但无法以表格格式显示数据。

在下面的示例中,数据库用于按日期跟踪数字(在各种任意命名的类别中)。例如,用户可以跟踪每天有多少水果和蔬菜被送到他的杂货店。用户定义水果和蔬菜类别的名称,以及存在多少类别。以下是与此示例对应的表格:

tracker 表:

 id |      name       
----+---------------------
  1 | Grocery deliveries 
  2 | Sports cars

entries 表:

 id |      datetime       | tracker_id 
----+---------------------+------------
  1 | 2013-10-01 00:00:00 |          1
  2 | 2013-10-02 00:00:00 |          1
  3 | 2013-10-03 00:00:00 |          1

values 表:

 id | number | entry_id | category_id 
----+--------+----------+-------------
  1 |   10.0 |        1 |          1
  3 |   20.0 |        1 |          2
  5 |   21.0 |        1 |          3
  7 |   18.0 |        2 |          2
  8 |    4.0 |        3 |          1
  9 |    9.0 |        3 |          2

还有一个category 表:

 id |     name        | tracker_id 
----+-----------------+------------
  1 | Tomatoes        |          1
  2 | Carrots         |          1
  3 | Brussel sprouts |          1
  4 | Ferraris        |          2

我想为跟踪器 1 打印一个表格,每一行对应一个日期(没有重复的日期)。这些列将是:日期、第 1 类(西红柿)、第 2 类(胡萝卜)、第 3 类(球芽甘蓝)。如果在给定日期没有给定类别的值,它将为空或显示为 null。所以,理想情况下,它看起来像这样:

 datetime            | Tomatoes | Carrots | Brussel sprouts
---------------------+----------+---------+-----------------
 2013-10-01 00:00:00 | 10.0     | 20.0    | 21.0
 2013-10-02 00:00:00 | Null     | 18.0    | Null
 2013-10-03 00:00:00 | 4.0      | Null    | 9.0

我不确定如何执行此操作,或者是否有更好的方法来存储这些数据。有什么建议吗?

entriesvalues 由一个表表示(条目是行,值是列)时,显示数据很容易。但在这种情况下,最大类别数受到我表中列数的限制。我更喜欢标准化方法如何允许每个“跟踪器”表示任意数量的类别。

【问题讨论】:

  • 如果你想在查询中这样做,并且你事先不知道可能的列,你需要创建一个透视查询,这在标准 SQL 中是不可能的,但是一些 DBMS 支持.你知道它只是三列(西红柿、胡萝卜、球芽甘蓝)吗?
  • 您的确切 RDBMS 是什么(SQL Flavor)?您知道设计时的列数及其标题吗?
  • @FabianBüttner 不,我希望每个“跟踪器”都有一组由用户定义的不同类别。类别的名称和数量将由用户定义。
  • @PM77-1 PostgreSQL。不,我不知道列数或其标题。我已经考虑将列数固定到某个最大值(比如 10 ......谁需要超过 10 个类别??)。但允许每个跟踪器任意数量的类别是理想的。
  • 看看crosstab函数。

标签: sql database postgresql database-design normalization


【解决方案1】:

我想出了一个使用 PostgreSQL 的 crosstab 函数的替代方案,正如 @PM77-1 所建议的那样。

具体来说,我使用的是crosstab(text source_sql, text category_sql)形式的函数,如下:

SELECT * FROM 
  crosstab('SELECT e.datetime, v.category_id, v.number 
            FROM entries e, values v 
            WHERE v.entry_id = e.id AND e.tracker_id = 1 ORDER BY 1, 2', 
           'SELECT id FROM categories WHERE tracker_id = 1 ORDER BY 1')
  AS (row_name timestamp without time zone,
      tomatoes numeric,
      carrots numeric,
      brussel_sprouts numeric);

使用这种方法,AS (...) 术语对于每个跟踪器必须是唯一的,因为每个跟踪器的类别数量及其名称可能不同。就我而言,我正在使用 Python 和 psycopg2 模块执行查询,因此动态生成查询很简单。例如,

# Retrieve the category names for the current tracker
cur.execute("SELECT name FROM categories WHERE tracker_id = " + 
            str(tracker_id) + ";")
categories = cur.fetchall()
category_count = len(categories)

# Generate category string
cat_str = '';
for n in range(category_count):
    cat_str = cat_str + ", cat_" + str(n) + " numeric"

cur.execute("SELECT * FROM crosstab("
            "'SELECT e.datetime, v.category_id, v.number FROM entries e, values v"
            " WHERE v.entry_id = e.id"
            " AND e.tracker_id = " + str(tracker_id) +
            " ORDER BY 1, 2;',"
            " 'SELECT id FROM categories WHERE tracker_id =" + 
            str(tracker_id) + "')"
            " AS (row_name timestamp without time zone" + cat_str + ");")

results = cur.fetchall()

结果具有通用列名称cat_0, cat_1, etc.,而不是tomatoes, carrots, etc.。但是,我将 categoriesresults 都传递给 HTML 模板,以使用正确的标题呈现表格。

【讨论】:

    【解决方案2】:

    我会这样定义你的表:

    deliveries
        id          unsigned int(P)
        good_id     unsigned int(F goods.id)
        qwhen       datetime
        quantity    double
    
    +----+---------+------------+----------+
    | id | good_id | qwhen      | quantity |
    +----+---------+------------+----------+
    |  1 |       1 | 2013-10-01 |     10.0 |
    |  2 |       2 | 2013-10-01 |     20.0 |
    |  3 |       3 | 2013-10-01 |     21.0 |
    |  4 |       2 | 2013-10-02 |     18.0 |
    |  5 |       1 | 2013-10-03 |      4.0 |
    |  6 |       2 | 2013-10-03 |      9.0 |
    |  7 |       1 | 2013-10-01 |      3.0 |
    | .. | ....... | ...........| ........ |
    +----+---------+------------+----------+
    
    good_types
        id                  unsigned int(P)
        name                varchar(50)
    
    +----+-------------+
    | id | name        |
    +----+-------------+
    |  1 | Groceries   |
    |  2 | Sports cars |
    +----+-------------+
    
    goods
        id              unsigned int(P)
        good_type_id    unsigned int(F good_types.id)
        name            varchar(50)
    
    +----+--------------+-----------------+
    | id | good_type_id | name            |
    +----+--------------+-----------------+
    |  1 |            1 | Tomatoes        |
    |  2 |            1 | Carrots         |
    |  3 |            1 | Brussel Sprouts |
    |  4 |            2 | Ferraris        |
    | .. | ............ | ............... |
    +----+--------------+-----------------+
    

    这是获取列名的 SQL:

    SELECT id, name
    FROM goods
    WHERE good_type_id = 1
    
    +----+-----------------+
    | id | name            |
    +----+-----------------+
    |  1 | Tomatoes        |
    |  2 | Carrots         |
    |  3 | Brussel Sprouts |
    +----+-----------------+
    

    这是获取数据以显示在表中的 SQL:

    SELECT qwhen, good_id, sum(quantity) AS total
    FROM deliveries d
    LEFT JOIN goods g ON d.good_id = g.id
    WHERE good_type_id = 1
    GROUP BY qwhen, good_id
    
    +------------+---------+-------+
    | qwhen      | good_id | total |
    +------------+---------+-------+
    | 2013-10-01 |       1 |    13 |
    | 2013-10-01 |       2 |    20 |
    | 2013-10-01 |       3 |    21 |
    | 2013-10-02 |       2 |    18 |
    | 2013-10-03 |       1 |     4 |
    | 2013-10-03 |       2 |     9 |
    +------------+---------+-------+
    

    那么您将使用 PHP、Java 或任何您的高级语言来循环遍历两个查询结果以显示数据。下面是显示数据的 PHP 代码,PHP 代码下面是显示显示内容的图像。

    // Get the column headers
    $sql = 'SELECT id, name FROM goods WHERE good_type_id = 1';
    $stmt = $pdo->prepare($sql);
    $stmt->execute();
    
    // Start our table.
    echo '<table border="1" cellspacing="0"><thead>';
    
    // Print out the headers.
    echo '<tr>';
    echo '<th>Date</th>';
    while ($row = $stmt->fetch()){
        echo '<th>'. $row['name'] .'</th>';
        $columns[$row['id']] = $row['name'];
    }
    echo '</tr>';
    echo '</thead><tbody>';
    
    // Get the data.
    $sql = 'SELECT qwhen, good_id, sum(quantity) AS total FROM deliveries d LEFT JOIN goods g ON d.good_id = g.id WHERE good_type_id = 1 GROUP BY qwhen, good_id';
    $stmt = $pdo->prepare($sql);
    $stmt->execute();
    
    // Manipulate the data into an array.
    $save_date = NULL;
    while ($row = $stmt->fetch()){
        if ($save_date !== $row['qwhen']){
            $save_date = $row['qwhen'];
            $data[$row['qwhen']] = array();
        }
        $data[$row['qwhen']][$row['good_id']] = $row['total'];
    }
    
    // Print out the table data.
    foreach ($data AS $date => $cell){
        echo '<tr>';
        echo '<td>'. $date .'</td>';
        foreach ($columns AS $id => $name){
            echo '<td align="right">';
            if (isset($cell[$id])){
                echo $cell[$id];
            }else{
                echo '&nbsp;';
            }
            echo '</td>';
        }
        echo '</tr>';
    }
    
    // End our table.
    echo '</tbody></table>';
    

    【讨论】:

    • 我对 PHP 不是很熟悉,但是你能确认这个结果表每个日期会给出一行吗?我不知道这将如何发生。
    • @LorenzForvang - 我添加了一张图片,显示了生成的 HTML 表格的外观以及创建 HTML 表格的 PHP 代码。您会看到它每个日期都有一个费率,并且每个 good 的总数计算正确。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-13
    • 2022-01-07
    • 2014-02-10
    • 1970-01-01
    • 2016-07-27
    • 1970-01-01
    • 2010-09-30
    相关资源
    最近更新 更多