【问题标题】:Retrieving values from a config table for use in a query从配置表中检索值以用于查询
【发布时间】:2018-01-28 19:41:47
【问题描述】:

我正在考虑在选项 2 here 中设置的配置表,其中每个配置选项(或“事实”)有一行,其中包含事实的名称和事实的值。

将这些信息合并到具有一个或多个连接的另一个查询中的适当/最佳方法是什么?请注意,我明确不考虑过程 SQL 或 ORM 系统;仅限纯 SQL。

例如,考虑这些表作为一个人为的例子:

tbl_facts (Option_Name, Option_Value)
tbl_employees (Employee_ID, Name, Salary)
tbl_projects (Project_ID, Name, Manager_ID, Revenue)

Manager_ID 链接到 tbl_employees 以获取管理项目的员工。每个项目只有一名经理,但一名员工可以管理多个项目。在本示例中,Option_Value 为 numeric/double。

忽略项目其他员工的成本等,让我们找出每个项目的毛利润作为收入 - [经理]工资:

SELECT tbl_projects.Project_ID, tbl_projects.Name, 
    (tbl_projects.Revenue - tbl_employees.Salary) AS Gross_Profit
FROM tbl_projects INNER JOIN tbl_employees ON 
    tbl_projects.Manager_ID = tbl_employees.Employee_ID;

很简单。现在假设我们知道利润的税率是 20%,所以我们将 ("Tax Rate", 0.2) 存储在 tbl_facts(以及其他事实)中。我们想知道净利润。问题是,目前还没有明确的方法将此信息与我们现有的信息结合起来。

我看到/考虑过的选项:

  1. 与 tbl_facts 的笛卡尔/交叉连接。但是,某些 SQL 风格不支持这一点,或者至少不能始终如一地支持它(有时有效,有时无效)。

    SELECT tbl_projects.Project_ID, tbl_projects.Name, 
        (tbl_projects.Revenue - tbl_employees.Salary) AS Gross_Profit,
        tbl_facts.Option_Value * (tbl_projects.Revenue - tbl_employees.Salary) AS Net_Profit
    FROM 
        (tbl_projects INNER JOIN tbl_employees ON 
            tbl_projects.Manager_ID = tbl_employees.Employee_ID),
        tbl_facts
    WHERE tbl_facts.Option_Name="Tax Rate";
    
  2. 某些类型的 SQL 支持查找函数(例如,MS Access 的 dlookup())。但是,这不太好,因为您必须将值转换为所需的类型,而且我怀疑这会影响性能。

    SELECT tbl_projects.Project_ID, tbl_projects.Name, 
        (tbl_projects.Revenue - tbl_employees.Salary) AS Gross_Profit,
        cdbl(dlookup("Option_Value","tbl_facts","Option_Name='Tax Rate'")) *
            (tbl_projects.Revenue - tbl_employees.Salary) AS Net_Profit
    FROM 
        tbl_projects INNER JOIN tbl_employees ON 
            tbl_projects.Manager_ID = tbl_employees.Employee_ID;
    
  3. 使用子查询添加人为的“连接列”。这似乎是一个糟糕的选择。

    SELECT tbl_projects.Project_ID, tbl_projects.Name, 
        (tbl_projects.Revenue - sq_employees.Salary) AS Gross_Profit,
        sq_facts.Option_Value * (tbl_projects.Revenue - sq_employees.Salary) AS Net_Profit
    FROM 
        tbl_projects INNER JOIN 
            (SELECT 1 AS Join_Col, tbl_employees.* FROM tbl_employees) AS sq_employees ON 
            tbl_projects.Manager_ID = sq_employees.Employee_ID),
        INNER JOIN
            (SELECT 1 AS Join_Col, Option_Value FROM tbl_facts WHERE Option_Name="Tax Rate") AS sq_facts ON
                sq_employees.Join_Col = sq_facts.Join_Col;
    

从这样的配置表中获取此信息的首选方法是什么?它是上述选项之一,还是我没有考虑过的其他选项?

【问题讨论】:

  • 您的标签让人们感到困惑:您是在追求 Oracle 还是 MS Access 解决方案?这两种风格支持不同的语法,因此您的问题不太可能有一个统一的解决方案。
  • 我追求的是通用解决方案。我目前正在研究 MS Access 中的一个项目并参考上面的 dlookup,但问题陈述一般适用于 RDBMS。我也经常在 Oracle 工作,在那里遇到过这个问题。
  • 没有通用的解决方案。不同的数据库产品将适合不同的实现。
  • 好的,那么我将我的范围限制为 Access 和 Oracle,如已标记。我知道可能有特定于实现的解决方案,但我对如何最好地使用 SQL 标准来处理这种情况很感兴趣。如果我在这里天真/理想主义,我深表歉意——我试图了解这个非常常见的问题在多大程度上得到了解决。

标签: sql database oracle ms-access


【解决方案1】:

通常的方法是在SELECT 语句中使用简单的子查询:

SELECT (SELECT TOP 1 Option_Value As Tax_Rate FROM tbl_facts WHERE Option_Name='Tax Rate'), other_columns
FROM tbl_projects

让我们快速回顾一下这些选项:

  1. 一个简单的子查询。这可以充分利用任何现有的索引,没有类型转换,没有其他问题。它只能返回单列,因此可能对多列的优化较少,如果返回多行会出错
  2. 交叉连接。与单个子查询相比,这可以返回多个列。它还可以利用任何现有的索引,并且没有类型转换,因此没有理由降低它的效率。
  3. 查找函数。这些在大多数情况下都是一个糟糕的计划,但主要是在 Access 中的 UPDATE 查询中,您需要有一个可更新的子查询,并且可以使用 DLookUp。

所以真正的答案是:视情况而定。简单子查询是最明显的候选者,交叉连接在您需要多个值时很有用,而 DLookUp 对于避免不可更新查询的错误很有用。

当然,当我们将 VBA 引入比较时,它会变得更复杂一些。使用 VBA 的另一种方法是拥有一个 Options 类,该类在数据库打开时被初始化,并且可以缓存选项查找。

【讨论】:

  • 我怀疑这是否是“通常的方式”,尤其是因为它使用了在 Oracle SQL 中无效的语法。
  • 这是 Access SQL 中的常用方式。由于 OP 正在考虑使用DLookUp,因此我假设他正在使用链接表,然后这是有效的 SQL,并且会以最佳方式工作(因为它只查询选项表一次)
  • 我可能是错的,但是像你写的内联子查询不会为每一行重新运行吗?也就是说,你得到 n*m 行搜索,其中 n 是 tbl_projects 中的行数,m 是 tbl_facts 中的行数。在 FROM 子句中包含该子查询不是更好吗,例如,像@APC 提到的交叉连接?另外,APC,您不能将他的代码重写为有效的 Oracle SQL,如下所示:SELECT (SELECT Option_Value AS Tax_Rate FROM tbl_facts WHERE Option_Name='Tax Rate' WHERE rownum
  • 只有在引用外部查询中的列时,内联子查询才会重新运行。在这种情况下,它只会运行一次。
  • 我希望 OPTION_NAME 是唯一键,否则此处编写的 TOP 子查询返回一个基本上随机的值,这在我看来在财务计算中并不理想。如果它是唯一键,则 Oracle 优化器足够聪明,可以运行一次子查询。我无法与 MS-Access 优化器通话。
【解决方案2】:

“你必须将值转换为你想要的类型,我怀疑这会影响性能”

你有一个存储为字符串的数值,你想用它做算术运算。因此,您必须将其转换为一个数字:您不妨明确地进行数据转换,就像您将采用的任何方式一样。

Oracle 确实支持投影中的标量游标。就我个人而言,我会将其作为内联视图上的 CROSS JOIN 来执行,在我看来,这是最具表现力的做事方式。您的里程可能会有所不同。

SELECT tbl_projects.Project_ID
       , tbl_projects.Name
       , (tbl_projects.Revenue - tbl_employees.Salary) AS Gross_Profit
       , tf.tax_rate * (tbl_projects.Revenue - tbl_employees.Salary) AS Net_Profit
FROM tbl_projects 
  INNER JOIN tbl_employees ON 
    tbl_projects.Manager_ID = tbl_employees.Employee_ID
  CROSS JOIN ( select to_number(Option_Value) as tax_rate
               from tbl_facts
               WHERE Option_Name='Tax Rate') tf
;

如果您要存储键值对,请使用索引组织表。无论如何,它基本上是一个索引查找,那么为什么要打扰表格呢?至少对于甲骨文来说。 Find out more.

【讨论】:

    【解决方案3】:

    在开始编写查询之前修复数据模型。OPTION_VALUE 拆分为多个列,例如OPTION_NUMBEROPTION_STRINGOPTION_DATE

    永远不要以错误的类型存储数据。这是一场等待发生的灾难。名为 OPTION_VALUE 的单个列可能看起来是一个简单的解决方案,但您稍后会为此付出代价,无论是间歇性错误还是复杂查询。

    这是在the top answer in the question you referenced 中推荐的。我也在this post 中写过这个。

    总而言之,在 EAV 模型中使用多个列:

    1. 提高性能 - Oracle 可以更好地理解数据并制定更好的计划
    2. 减少存储 - 对特定数据类型进行更好的优化以存储数据
    3. 简化验证 - 验证数字和日期并不像您想象的那么简单
    4. 提高类型安全性 - Oracle 不必按照编写的顺序运行 SQL 语句。此 SQL 语句很危险,会间歇性失败:

      select *
      from
      (
          select to_number(option_value) tax_rate
          from tbl_facts
          where option_name='tax rate'
      )
      where tax_rate >= 1
      
    5. 反正也不难。实际上,在对列进行任何操作之前,您总是会知道列的类型。

    【讨论】:

      猜你喜欢
      • 2020-06-27
      • 1970-01-01
      • 2018-05-26
      • 1970-01-01
      • 2012-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多