T-SQL 过于昂贵的查询,在 where/have 条件和复合主键中选择

Posted

技术标签:

【中文标题】T-SQL 过于昂贵的查询,在 where/have 条件和复合主键中选择【英文标题】:T-SQL too expensive query with select in where/having condition and composite primary key 【发布时间】:2018-05-16 13:14:39 【问题描述】:

我创建了一个http://www.sqlfiddle.com/#!18/f8137/2 来向您展示架构

我有三个实体“Invoice”->1:n->“Payment”->1:n->“Taking”

每个实体都有自己的总计(金额)和一个标志,显示我是否必须添加或减去一个值。

问题是我找不到有效的方法来“选择”具有未结账单的发票(收取的总和与发票的金额不同)。 我有数千条记录,这两个选择需要很长时间才能执行(从 25 到 30 秒)。

这里是架构的创建

CREATE TABLE Sign (
  sign_code INT NOT NULL IDENTITY(1,1),
  sign_value INT NOT NULL,
  description VARCHAR(255) NOT NULL,
  PRIMARY KEY (sign_code)
);

CREATE TABLE Invoice (
    invoice_year int NOT NULL,
    invoice_number int NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    sign INT NOT NULL,
    PRIMARY KEY (invoice_year, invoice_number) ,
    FOREIGN KEY (sign) REFERENCES Sign(sign_code)
);

CREATE TABLE Payment (
    invoice_year int NOT NULL,
    invoice_number int NOT NULL,
    payment_row int NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    sign INT NOT NULL,
    PRIMARY KEY (invoice_year, invoice_number, payment_row), 
    FOREIGN KEY (invoice_year, invoice_number) REFERENCES Invoice(invoice_year, invoice_number),
    FOREIGN KEY (sign) REFERENCES Sign(sign_code)
);

CREATE TABLE Taking (
    taking_year int NOT NULL,
    taking_row INT NOT NULL,
    invoice_year int NOT NULL,
    invoice_number int NOT NULL,
    payment_row int NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    sign INT NOT NULL,
    PRIMARY KEY (taking_year, taking_row), 
    FOREIGN KEY (invoice_year, invoice_number, payment_row) REFERENCES Payment(invoice_year, invoice_number, payment_row),
    FOREIGN KEY (invoice_year, invoice_number) REFERENCES Invoice(invoice_year, invoice_number),
    FOREIGN KEY (sign) REFERENCES Sign(sign_code)
);

发票

invoice_year    invoice_number  amount  sign
2018            1               100.2   1
2018            2               98.4    1

付款

invoice_year    invoice_number  payment_row amount  sign
2018            1               1           50      1
2018            1               2           50.2    1
2018            2               1           90.4    1
2018            2               2           8       1

服用

taking_year taking_row  invoice_year    invoice_number  payment_row amount  sign
2018        1           2018            1               1           80      1
2018        2           2018            1               1           80      2
2018        3           2018            1               1           25      1
2018        4           2018            1               1           25      1
2018        5           2018            1               2           25.1    1
2018        6           2018            1               2           24.1    1
2018        7           2018            2               1           90.4    1
2018        8           2018            2               2           8       1

签名

sign_code   sign_value  description
1           1           CREDIT
2           -1          DEBT

这些是我写的查询

SELECT COUNT(*) 
FROM Invoice AS I
INNER JOIN Sign S1 ON I.sign = S1.sign_code
WHERE I.amount*S1.sign_value - (SELECT SUM(T.amount*S2.sign_value)
                                FROM Taking T
                                INNER JOIN Sign S2 ON T.sign = S2.sign_code
                                WHERE T.invoice_year = I.invoice_year AND T.invoice_number = I.invoice_number
                               ) <> 0;

SELECT I.*
FROM Invoice AS I
INNER JOIN Sign S1 ON I.sign = S1.sign_code
WHERE I.amount*S1.sign_value - (SELECT SUM(T.amount*S2.sign_value)
                                FROM Taking T
                                INNER JOIN Sign S2 ON T.sign = S2.sign_code
                                WHERE T.invoice_year = I.invoice_year AND T.invoice_number = I.invoice_number
                               ) <> 0

此外,这些实体具有复合主键,我必须与学说和 knp-paginator-bundle 一起使用,所以我必须“计数”行数

https://github.com/doctrine/doctrine2/issues/2910

Single id is not allowed on composite primary key in entity using knp paginator

关于如何至少提高执行时间的任何想法?

【问题讨论】:

样本数据和期望的结果真的很有帮助。 在您的 SQL 中,Invoice 和 Take 之间没有任何关联。我也没有看到任何关于付款的参考。正如@GordonLinoff 所说,你能展示一些数据吗? 看看小提琴有模式、数据和查询。我也在编辑以添加表格数据。 @JackSkeletron.. 用您想要的结果编辑问题。 PinnyM 有一个非常有效的分数,在加入 Sign 的 Invoice 和加入 Take to Sign (S2) 的子查询之间没有相关性 - 这是不正确的。 【参考方案1】:

您的查询忽略了Payment table。您需要确保外部select 中的记录与内部select 中的记录相匹配:

SELECT I.*
FROM Invoice AS I
INNER JOIN Sign S1 ON I.sign = S1.sign_code
WHERE I.amount*S1.sign_value - (SELECT SUM(T.amount*S2.sign_value)
                                FROM Taking T
                                INNER JOIN Payment P ON T.invoice_year = P.invoice_year and T.invoice_number = P.invoice_number and T.payment_row = P.payment_row and P.invoice_year = I.invoice_year and P.invoice_number = I.invoice_number
                                INNER JOIN Sign S2 ON T.sign = S2.sign_code
                               ) <> 0;

如果不将这部分添加到您的查询中,查询显然是不正确的,并且需要很长时间才能执行。另外,我建议不要在 SELECT 子句中使用 * 运算符,因为它们可能不安全(例如,如果有 password 字段)并且对性能不利(如果有一个 column 包含非常大的数据在这种情况下,这对您来说是不必要的)。

【讨论】:

对不起,我不是数据库的设计者,我必须对很多工作进行逆向工程。我在 Take entity 中编辑了架构“因为我忘记了FOREIGN KEY (invoice_year, invoice_number) REFERENCES Invoice(invoice_year, invoice_number)”。我还用我使用的查询更正了查询。 @JackSkeletron 我想你误解了我的回答。答案不是批评模式,而是将模式作为您需要处理的情况。我的回答涉及您在问题中指定的问题。您已经观察到您的查询不是很高效。但是,这种行为的至少一个原因可能是最重要的原因是,您的内部查询不仅获取与发票相关的收入,还获取与其他发票相关的收入。 问题是我忘记了,在这篇文章和小提琴中“重写”我的代码,一些部分,例如查询中的 where 子句和直接与发票相关的 FOREIGN KEY.. . @JackSkeletron 查看您在问题中向我们展示的查询并问自己:我如何确保内部查询将仅计算与发票相关的项目的总和在外部查询中?您将看到没有这样的保证,因此您的查询不正确。要更正您的查询,您需要确保您正在使用的收入完全限制为与您的查询实际使用的发票相关的付款相关的收入。这是我看到的问题,也是我的答案正在解决的问题。 @JackSkeletron 哦,我明白了。我的答案正在处理的问题已经解决。您的查询性能是否有所提高?当前的性能与修复前的性能相比如何?【参考方案2】:

尝试分组

SELECT COUNT(*) 
  FROM Invoice AS I
  JOIN Sign S1 
    ON I.sign = S1.sign_code
  JOIN ( SELECT T.invoice_year, T.invoice_number
              , SUM(T.amount*S2.sign_value) as sm
           FROM Taking T
           JOIN Sign S2 
          GROUP BY T.invoice_year, T.invoice_number 
       ) t
   ON T.invoice_year   = I.invoice_year 
  AND T.invoice_number = I.invoice_number
  AND I.amount*S1.sign_value <> t.sm

【讨论】:

谢谢,我试过了,结果几乎一样,count select需要4秒(4064.58 ms) @JackSkeletron 我认为你没有。没什么大不了的。您甚至测试并声明您得到了相同的结果。

以上是关于T-SQL 过于昂贵的查询,在 where/have 条件和复合主键中选择的主要内容,如果未能解决你的问题,请参考以下文章

T-SQL进阶02.理解SQL查询的底层原理

理解SQL查询的底层原理

SQL Server 活动监视器在多长时间内查看最近的昂贵查询

如何在这个特定的 T-SQL 查询中使用表变量?

《T-SQL查询》读书笔记Part 1.逻辑查询处理知多少

T-SQL查询语句