【问题标题】:Create flag column after comparing two tables比较两个表后创建标志列
【发布时间】:2019-10-09 08:00:59
【问题描述】:

我有两个不同的报告表,其中包含日期时间和报告所有者。我想选择至少写过一次报告的人。我还需要一个计算字段来显示他们编写的报告编号。报告 1 优先,因此如果有人在任何时候编写了报告 1,则新的 report_number 列应为 1,否则为 2(用于报告 2)。

'people' table
| person_id | full_name
--------------------------
| 1         | John L Smith
| 2         | Carl M Selt
| 3         | Another Person

'report_1' table
| report_1_id | author_person_id | date_entered | other_columns
---------------------------------------------------------------
| 1           | 1                | 2018-01-12   | foo
| 2           | 1                | 2018-02-18   | foo foo

'report_2' table
| report_2_id | author_person_id | date_entered | other_columns
---------------------------------------------------------------
| 1           | 1                | 2018-03-21   | bar
| 2           | 1                | 2018-03-28   | bar bar
| 3           | 2                | 2018-04-16   | baz
| 4           | 2                | 2018-04-30   | baz baz

期望的结果:

| full_name    | report_number
---------------------------
| John L Smith | 1
| Carl M Smelt | 2

请注意,John 的 report_number1,尽管他还撰写了报告 2。

报告 1 和报告 2 具有不同的附加列,尽管它们在上面看起来相同。

我尝试了什么:

    /* Get people from both reports */
WITH report_1_people AS (
    SELECT P.full_name
    FROM report_1 R1
    INNER JOIN people P ON R1.author_person_id = P.person_id
    WHERE P.full_name IS NOT NULL 
    AND P.full_name <> ''
), report_2_people AS (
    SELECT P2.full_name
    FROM report_2 R2
    INNER JOIN people P2 ON R2.author_person_id = P2.person_id
    WHERE P2.full_name IS NOT NULL 
    AND P2.full_name <> ''
)
SELECT 
    P.full_name,
    CASE WHEN P.full_name IN ( /* Check if in report 1 */
                    SELECT full_name
                    FROM report_1)
                    THEN 1
            ELSE 2
            END AS report_number
FROM people P
WHERE P.full_name IS NOT NULL AND P.full_name <> ''
/* Eliminate duplicate names */
GROUP BY P.full_name 
/* Filter only who either authored report 1 or report 2 */
HAVING P.full_name IN (SELECT full_name
                       FROM report_1_people)
OR P.full_name IN (SELECT full_name
                   FROM report_2_people)

注意:在 people 表中有一个 GROUP BY,因为由于某种原因存在重复条目。

查询花了很长时间才与数据库断开连接(24 小时以上),所以我认为我做错了什么。有没有更好的方法来完成这个基于两个表的标志计算列?对 SQL 来说相对较新,所以我想知道是否有另一种思维方式让我过度使用 SQL 逻辑。

【问题讨论】:

  • 在 CTE 查询中,请改用 person_id。然后在你的Select LEFT JOIN 2 CTE 表到people 表。删除 Group ByHaving... 删除 Case 语句并用 ISNULL(CTE1, CTE2) 替换 ReportID 看看你会得到什么。
  • with 子句检查P.full_name,很奇怪,因为您已经在P.full_name 上分组。将检查移至 where 子句。然后,使用 OR 来检查它们是否在两个表中,可以重写为使用两个查询的 UNION 来检查。

标签: sql sql-server performance tsql sql-server-2017


【解决方案1】:

CTE 上的 INs 很可能会杀死它。

另一种方法是使用EXISTS 检查某人是否写过报告。 CASE 表达式可以处理优先级。

SELECT p.full_name,
       CASE
         WHEN EXISTS (SELECT *
                             FROM report_1 r1
                             WHERE r1.author_person_id = p.person_id) THEN
           1
         WHEN EXISTS (SELECT *
                             FROM report_2 r2
                             WHERE r2.author_person_id = p.person_id) THEN
           2
       END report_number
       FROM people p
       WHERE EXISTS (SELECT *
                            FROM report_1 r1
                            WHERE r1.author_person_id = p.person_id)
              OR EXISTS (SELECT *
                                FROM report_2 r2
                                WHERE r2.author_person_id = p.person_id);

为了提高性能,请尝试使用 report_1 (author_person_id)report_2 (author_person_id) 上的索引。对于people,您可以尝试在person_id(可能已经存在)上使用索引,或者在person_idfull_name 上使用复合索引。

【讨论】:

  • 这太棒了!我没有考虑这种方法,因为我认为相关子查询会更慢。这种方法似乎是建议的解决方案中最快的。我删除了 case 语句中的第二个 EXIST 检查,因为底部 where 子句中的 2 确保它们是报告 1 或报告 2。
【解决方案2】:

你可以使用OUTER APPLY:

SELECT person_id, full_name, COALESCE(ca1.report_num, ca2.report_num)
FROM people
OUTER APPLY (SELECT TOP (1) 1 FROM report_1 WHERE author_person_id = people.person_id) AS ca1(report_num)
OUTER APPLY (SELECT TOP (1) 2 FROM report_2 WHERE author_person_id = people.person_id) AS ca2(report_num)

Demo on db<>fiddle

【讨论】:

  • 感谢您的建议!这会在新列中返回很多 NULL 值,因为它正在执行 OUTER APPLY(我相信这就像 LEFT JOIN)。虽然我还没有听说过 APPLY,所以我会研究一下这是否是我应该更经常使用的功能。
【解决方案3】:

这只是获得结果的另一种方式。

SELECT 
    P.full_name,
    MIN( R.Report_Number) AS report_number
FROM people P
OUTER APPLY (SELECT 1 WHERE EXISTS(SELECT * FROM report_1 R1 WHERE R1.author_person_id = P.person_id)
             UNION ALL
             SELECT 2 WHERE EXISTS(SELECT * FROM report_2 R2 WHERE R2.author_person_id = P.person_id)) AS R(Report_Number)
WHERE P.full_name IS NOT NULL AND P.full_name <> ''
/* Eliminate duplicate names */
GROUP BY P.full_name;

【讨论】:

    猜你喜欢
    • 2018-09-30
    • 1970-01-01
    • 2011-06-19
    • 1970-01-01
    • 1970-01-01
    • 2022-08-12
    • 1970-01-01
    • 2017-08-10
    相关资源
    最近更新 更多