【问题标题】:How could I simplify this SQL query in Oracle?如何在 Oracle 中简化这个 SQL 查询?
【发布时间】:2011-01-04 23:13:34
【问题描述】:

经过一番搜索和阅读后,我为我的应用程序提出了以下 SQL 查询:

SELECT
  ROUND(AVG(CASE WHEN gender = 'M' THEN rating END), 1) avgAllM,
  COUNT(CASE WHEN gender = 'M' THEN rating END) countAllM,
  ROUND(AVG(CASE WHEN gender = 'F' THEN rating END), 1) avgAllF,
  COUNT(CASE WHEN gender = 'F' THEN rating END) countAllF,
  ROUND(AVG(CASE WHEN gender = 'M' AND UserAge(birth_date) <= 18 THEN rating END), 1) avgU18M,
  COUNT(CASE WHEN gender = 'M' AND UserAge(birth_date) <= 18 THEN rating END) countU18M,
  ROUND(AVG(CASE WHEN gender = 'F' AND UserAge(birth_date) <= 18 THEN rating END), 1) avgU18F,
  COUNT(CASE WHEN gender = 'F' AND UserAge(birth_date) <= 18 THEN rating END) countU18F
FROM movie_ratings mr INNER JOIN accounts a
  ON mr.aid = a.aid
WHERE mid = 5;

如果可能的话,我想知道如何简化这一点。 birth_date 字段的类型为 DATEUserAge 是一个根据该日期字段计算年龄的函数。

表结构如下:

[ACCOUNTS]
aid(PK), birth_date, gender

[MOVIE_RATINGS]
mid(PK), aid(PK,FK), rating

我正在寻找两件事:

  • 以上代码的一般简化,更有经验的用户知道我不知道。
  • 我在 PHP 中执行此操作,对于每条记录,我将拥有一个包含所有这些变量的关联数组。我正在寻找一种将它们分组为多维数组的方法,以便 PHP 代码更易于阅读。当然我不想在 PHP 本身中这样做,那将毫无意义。

例如,像这样的:

$info[0]['avgAllM']
$info[0]['countAllM']
$info[1]['avgAllF']
$info[1]['countAllF']
$info[2]['avgU18M']
$info[2]['countU18M']
$info[3]['avgU18F']
$info[3]['countU18F']

代替:

$info['avgAllM']
$info['countAllM']
$info['avgAllF']
$info['countAllF']
$info['avgU18M']
$info['countU18M']
$info['avgU18F']
$info['countU18F']

我什至不知道这是否可能,所以我真的很想知道它是否可行以及如何做到。

为什么我想要这一切?好吧,上面的 SQL 查询只是我需要做的完整 SQL 的一个片段。我还没有完成,因为在完成所有工作之前,我想知道是否有更紧凑的 SQL 查询来实现相同的结果。基本上我会再添加几行与上面类似的行,但条件不同,特别是在日期上。

【问题讨论】:

  • 如果没有更多关于表结构的详细信息,为什么会有一个过滤 mid = 5 的 where 子句?你真正想要达到什么目的?对于您的多维数组,sql 帮助中的 group by 不是吗?
  • 更新了表结构,尽管我认为它们并不相关。我不确定如何使用 GROUP BY...

标签: php sql oracle simplify


【解决方案1】:

您可以使用以下定义创建VIEW

SELECT
      CASE WHEN gender = 'M' THEN rating END AS AllM,
      CASE WHEN gender = 'F' THEN rating END AS AllF,
      CASE WHEN gender = 'M' AND UserAge(birth_date) <= 18 THEN rating END AS U18M,
      CASE WHEN gender = 'F' AND UserAge(birth_date) <= 18 THEN rating END AS U18F
      FROM movie_ratings mr INNER JOIN accounts a
        ON mr.aid = a.aid
      WHERE mid = 5

然后从中选择

SELECT ROUND(AVG(AllM), 1) avgAllM,
       COUNT(AllM)         countAllM,
       ROUND(AVG(AllF), 1) avg,
       COUNT(AllF)         countAllF,
       ROUND(AVG(U18M), 1) avgU18M,
       COUNT(U18M)         countU18M,
       ROUND(AVG(U18F), 1) avgU18F,
       COUNT(U18F)         countU18F
FROM  yourview

可以稍微简化一下吗?

【讨论】:

  • 我对视图了解不多,但我相信这是不可能的,因为 WHERE 子句依赖于一个变量,并不总是mid = 5,这只是一个例子。
  • 在了解了更多关于视图的信息后,我编辑了您发布的视图并将mid 字段插入 SELECT 并将 WHERE 子句移至另一个选择,似乎工作正常。我会接受这个作为正确答案,除非有人提供更好的方法或在这种情况下对观点有很好的论据。
  • PHP 部分怎么样,有什么想法吗?
【解决方案2】:

这可能只是过早优化的情况。该查询可以满足您的需求,并且看起来确实很复杂,因为它确实如此。我不确定是否有任何技巧会有所帮助。这可能取决于您的数据的特征。查询慢吗?你认为它可以更快吗?

可能值得以下列方式重新排列它。由于所有条件都依赖于ACCOUNTS 表,我认为该表将比MOVIE_RATINGS 表小得多,因此您可以在较小的数据集上进行所有计算,这可能会更快。虽然如果您一次只选择一部电影 (mid = 5),那么情况可能并非如此。

我不完全确定这会奏效,但我认为应该会。

SELECT
  ROUND(AVG(rating * AllM), 1) avgAllM,
  COUNT(rating * AllM) countAllM,
  ROUND(AVG(rating * AllF), 1) avgAllF,
  COUNT(rating * AllF) countAllF,
  ROUND(AVG(rating * AllM * U18), 1) avgU18M,
  COUNT(rating * AllM * U18) countU18M,
  ROUND(AVG(rating * AllM * U18), 1) avgU18F,
  COUNT(rating * AllM * U18) countU18F
FROM 
  movie_ratings mr 
  INNER JOIN (
    select 
      aid,
      case when gender = 'M' then 1 end as AllM,
      case when gender = 'F' then 1 end as AllF,
      case when UserAge(birth_date) <= 18 then 1 end as U18
    from accounts) a ON mr.aid = a.aid
WHERE mid = 5;

不过,总的来说,我可能会保留您的查询原样。您拥有的查询很容易理解,并且可能执行得相当好。

【讨论】:

  • 有什么理由不使用Martin提出的VIEW方法?
  • 顺便说一句:我从来没有建议使用 View 是一种优化。 OP询问了简化。据我所知,无论哪种方式,它对性能的影响都是零。
  • 好吧,我想这完全取决于您要达到的目标。正如我所说,我认为您的原始查询还是相当不错的。视图的优点是可以提供可以被所有此类查询使用的一致定义。
猜你喜欢
  • 2010-09-29
  • 2021-12-04
  • 1970-01-01
  • 2015-02-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-31
相关资源
最近更新 更多