SQL查询仅根据流体情况返回一行

时间:2018-12-25 03:07:25

标签: sql-server subquery

我知道这是可能的,但不确定如何设置。 基本上,我需要为每位员工提取数据,但前提是这些数据必须满足某些基于不同日期的条件。

例如,如果员工在6/1之前被分配到公司,他们将被自动计数。

如果员工在6/1之后被分配到公司,则只有在分配日期之后他们对该公司进行了审核(即,他们在6/25被分配并在7 / 1 ...这应该算在内。例如,如果他们在6/25接受调查,而审核在6/15进行,则不会计入该员工的人数

如果员工在4/1之前从公司离职,则不计算在内。如果它们在4/1或之后被删除,则算在内。

因此,关键列是审核的“创建日期”,“员工-客户”表中的“开始日期”和“结束日期”。

我认为这将需要是某种子查询,该子查询返回该雇员与该客户的雇员的开始日期,然后根据评估该日期的Case语句将审查日期与审查日期进行比较,但是我不确定确切如何为此。

任何帮助将不胜感激。

编辑:下面的表结构/数据:

员工-客户表

ID    EmpID   CustID  StartDate   EndDate
1       4       10    10/1/2017   2/21/2018
2       4       11    10/1/2017   7/31/2018
3       4       15    10/1/2017   4/8/2018
4       4       17    6/1/2018    NULL (means still active with this employee)
5       4       19    5/18/2018   NULL

客户数据表

ID    CustID   ActivityDate   Task
1       10       1/13/2018    Review
3       15       4/2/2018     Review
4       17       6/25/2018    Review
5       17       6/13/2018    Client Engagement
6       17       6/29/2018    Client Engagement
7       19       5/25/2018    Client Engagement
8       19       6/28/2018    Review

因此,在此示例中,我想要一个查询,该查询带回以下客户ID以及基于条件的数据:

  • 10:此客户没有被退回,因为该客户是在4/1截止日期之前从员工中撤出的。
  • 11:该客户会被退回,因为该员工的客户已超过5/31截止日期,即使该客户没有任何评论
  • 15:该客户之所以得到退货,是因为该员工在将其从客户中删除之前已经超过了4/1截止日期。
  • 17:返回了2018年6月29日的客户参与情况,但是返回了2018年6月13日的客户参与情况,因为它发生在对此客户进行了审核(实际上,当客户的员工开始日期为PAST 5/31时,该活动仅在他们对该客户进行审核之后才算在内-忽略此审核日期之前发生的所有活动)
  • 19:在这种情况下返回客户参与是因为在6/1之前将员工分配给他们,所以无论是否在其他项目发生之前进行了审核,任何活动都算在内。

希望这种解释和细分是有道理的。

更新:这是表脚本和预期结果:

CREATE TABLE Cust_Employee(

Cust_Emp_ID int IDENTITY(1,1) NOT NULL,

Cust_ID int NOT NULL,

Emp_ID int NULL,

Start_Date datetime NULL,

End_Date datetime NULL,

CONSTRAINT PK_Cust_Employee PRIMARY KEY CLUSTERED

(

Cust_Emp_ID ASC

)WITH (PAD INEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON PRIMARY

)ON PRIMARY

GO

CREATE TABLE Cust_Data(

Cust_Data_ID int IDENTITY(1,1) NOT NULL,

Cust_ID int NULL,

Activity_Date datetime NULL,

Task VARCHAR(50) NULL

)

CONSTRAINT PK_Client_Data PRIMARY KEY CLUSTERED

(

Cust_Data_ID ASC

)WITH (PAD INEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON PRIMARY

)ON PRIMARY

GO

INSERT INTO Cust_Employee VALUES(4, 10, '10/1/2017', '2/21/2018')

INSERT INTO Cust_Employee VALUES(4, 11, '10/1/2017', '7/31/2018')

INSERT INTO Cust_Employee VALUES(4, 15, '10/1/2017', '4/8/2018')

INSERT INTO Cust_Employee VALUES(4, 17, '6/1/2018', NULL)

INSERT INTO Cust_Employee VALUES(4, 19, '5/18/2018', NULL)

INSERT INTO Cust _Data VALUES(10, '1/13/2018', 'Review')

INSERT INTO Cust _Data VALUES(15, '4/2/2018', 'Review')

INSERT INTO Cust _Data VALUES(17, '6/25/2018', 'Review')

INSERT INTO Cust _Data VALUES(17, '6/13/2018', 'Client Engagement')

INSERT INTO Cust _Data VALUES(17, '6/29/2018', 'Client Engagement')

INSERT INTO Cust _Data VALUES(19, '5/25/2018', 'Client Engagement')

INSERT INTO Cust _Data VALUES(19, '6/28/2018', 'Review')

预期结果:

enter image description here

5 个答案:

答案 0 :(得分:5)

我不确定我是否理解您的所有要求。实际上,我错过了一些东西,因为我获得的结果与您不完全相同。 我准备的代码:

SELECT E.Cust_ID AS Emp_ID, E.Emp_ID AS Cust_ID, E.Start_Date, E.End_Date, 
        MAX(D.Activity_Date) AS Activity_Date, D.Task
    FROM Cust_Employee E
    LEFT OUTER JOIN Cust_Data D
        ON E.Emp_ID = D.Cust_ID
    WHERE COALESCE(E.End_Date, GETDATE()) > '20180401'
    GROUP BY 
            E.Cust_ID, E.Emp_ID, E.Start_Date, E.End_Date, 
            D.Task
    ORDER BY E.Cust_ID;[![enter image description here][1]][1]

因此,我的查询显示了Emp 19的额外一行,不确定要消除的条件是什么,如果您澄清自己的话,我将纠正响应。

我发现另一种解决方案更加完善,它更清晰,并且至少对于提供的数据集非常有效,并且具有易于维护的优点。

首先,我必须认识到对我的要求并不是100%明确的,因为这些示例基于现实生活中的常见情况。有必要明确确定必须应用的业务规则和需要应用的顺序(顺序)。 因此,根据我的猜测,我构建了以下解决方案。该解决方案的优点是非常容易调试为:

  1. 每个规则都用一个数字标识,该数字可以在调试模式下显示(此后在需要时省略),并告诉您已应用了哪个规则
  2. 规则是按顺序应用的,因此,如果一条规则可以显示一条记录,则其余规则将不适用。这是因为检查规则的顺序很重要
  3. 规则的
  4. 负数显示该规则意味着不应显示记录

WITH CTE AS (
    SELECT E.Cust_ID AS Emp_ID, E.Emp_ID AS Cust_ID, 
           E.Start_Date, E.End_Date, 
        MAX(D.Activity_Date) AS Activity_Date, D.Task,
        CASE 
            -- RULE -1: Removed Prior to 4/1 cutoff date
            WHEN E.End_Date < '20180401'                        THEN -1

            --  RULE 1: If the employee has had the customer past the 5/31 cutoff date, even though there is no review for the customer
            WHEN E.End_Date > '20180531'                        THEN 1

            --  RULE 2: If the employee had the customer past the 4/1 cutoff date before it was removed from them
            WHEN D.Activity_Date > '20180401' AND D.Activity_Date <= E.End_Date THEN 2

            --   RULE -2: Client engagement from 6/13/2018 does NOT get returned because it happened BEFORE the review was done with this client
            WHEN D.Task = 'Client Engagement' 
             AND NOT EXISTS (SELECT 1 FROM Cust_Data D2 WHERE D2.Cust_ID = E.Emp_ID AND D2.Task = 'Review' AND D2.Activity_Date <= D.Activity_Date)
                THEN -2

            --   RULE 12: If the employee was assigned to a company before 6/1 they get counted automatically.
            WHEN E.Start_Date <= '20180601'                     THEN 12

            --  RULE 14: If EndDate later than June-1-2018
            WHEN  COALESCE(E.End_Date, GETDATE()) > '20180601'  THEN 14

            -- RULE 0: Other cases
            ELSE 0 

        END AS [Rule]
    FROM Cust_Employee E
    LEFT OUTER JOIN Cust_Data D
        ON E.Emp_ID = D.Cust_ID
        --AND D.Activity_Date > '20180401'

    GROUP BY 
            E.Cust_ID, E.Emp_ID, E.Start_Date, E.End_Date, 
            D.Task, D.Activity_Date
    ) 
SELECT Emp_ID, Cust_ID, Start_Date, End_Date, Activity_Date, Task, [Rule]
    FROM CTE
    WHERE [Rule] > 0
    ORDER BY Cust_ID, Start_Date, Activity_Date;

此方法的最好之处在于,它计算并显示已应用的规则,因此,当查询显示已应用哪个规则时,可以非常容易地对其进行调试。如果规则或某个规则的顺序不正确,则可以非常快速地检测到并修复它。将来的更改也是如此,因为通常这些基于日期的规则确实经常更改,因此我们需要一种简单的方法来维护代码。 最后,我希望该练习将为将来的开发提供一些思路,因为在创建代码时可追溯性和可支持性非常重要。

答案 1 :(得分:2)

对于这种复杂的逻辑,我建议使用CTE语句,以创建有资格出现在最终结果集中的行的子组,以便创建更干净的查询来创建/维护和创建正/负规则,像这样:

;WITH AssignedBefore as
(
    --if the employee was assigned to a company before 6/1 they get counted automatically.
    SELECT Cust_ID, Emp_ID
    FROM Cust_Employee
    WHERE Start_Date <= '20180601'
),
AssignedReviewed as
(
    --If the employee was assigned to a company after 6/1 they only get counted IF they have a review with that company after the date they were assigned
    SELECT Cust_ID, Emp_ID
    FROM Cust_Employee E
    CROSS APPLY (
        SELECT 1 as Exist
        FROM Cust_Data D
        WHERE D.Cust_ID = E.Cust_ID
        AND D.Task = 'Review'
        AND D.Activity_Date > E.Start_Date
        ) C
    WHERE E.Start_Date > '20180601'
),
RemovedAfter as
(
    --If the employee gets removed from a company before 4/1 they dont get counted. If they are removed on or after 4/1 it counts.
    SELECT Cust_ID, Emp_ID
    FROM Cust_Employee
    WHERE End_Date >= '20180401'
),
RemovedBefore as
(
    --If the employee gets removed from a company before 4/1 they dont get counted. If they are removed on or after 4/1 it counts.
    SELECT Cust_ID, Emp_ID
    FROM Cust_Employee
    WHERE End_Date <= '20180401'
)
--Positive Rules
SELECT * FROM AssignedBefore
UNION
SELECT * FROM AssignedReviewed
UNION 
SELECT * FROM RemovedBefore
--Negative Rules
EXCEPT
SELECT * FROM RemovedBefore

一旦您有需要在输出中显示的Cust / Emp元组的结果,就可以添加所需的任何信息。

答案 2 :(得分:0)

我处理此查询的方式将对通用表表达式和EXISTS / NOT EXISTS都非常友好。

我聚集了一个给定的雇员,如果在该雇员与该顾客进行审查之前与该顾客进行过接触,则应在该雇员的范围内将其忽略。为此,我实现了一个公用表表达式(cte_engagements_to_ignore)来过滤掉它们。 该公用表表达式的结果最终是应该被忽略的employee / cust_data记录。 它的工作方式是首先过滤掉所有的参与,然后过滤掉那些在员工分配之后和我们要进行比较的参与之前没有进行过先前审查的审查。

接下来,我们查询员工/客户数据表,并自动包括客户是否在6/1之前开始,或者是否存在将其分配给员工之后的审核。然后,我们排除在4/1之前未分配的员工,或者对于给定员工应忽略的聘用情况。

确实非常令人困惑!

WITH cte_engagements_to_ignore AS 
    ( -- filter out client engagements that happened prior to reviews
        SELECT 
            A.Emp_ID,
            B.Cust_Data_ID
        FROM Cust_Employee A 
        INNER JOIN Cust_Data B 
        ON      A.Cust_ID = B.Cust_ID
        WHERE   B.Task = 'Client Engagement' 
            AND NOT EXISTS 
                    ( -- exclude this client engagement if there was not a review for this customer prior to it
                        SELECT
                            *
                        FROM Cust_Data X1 
                        WHERE   A.Cust_ID = X1.Cust_ID
                            AND X1.Task = 'Review'
                            AND A.Start_Date < X1.Activity_Date -- review happened after assignment
                            AND B.Activity_Date > X1.Activity_Date -- review happened prior to engagement
                    )
    )
SELECT 
    A.Emp_ID,
    A.Cust_ID,
    A.Start_Date,
    A.End_Date,
    B.Activity_Date,
    B.Task
FROM Cust_Employee A 
LEFT JOIN Cust_Data B 
ON      A.Cust_ID = B.Cust_ID
WHERE   (
            -- included automatically if started before 6/1
            A.Start_Date < '2018-06-01' 
            -- or include if there is a review after assignment
        OR EXISTS 
                ( 
                    SELECT 
                        *
                    FROM Cust_Data X1 
                    WHERE   A.Cust_ID = X1.Cust_ID
                        AND A.Start_Date < X1.Activity_Date
                        AND X1.Task = 'Review'
                )
        )
        -- exclude if unassigned prior to 4/1
    AND ISNULL(A.End_Date, '2050-01-01') >= '2018-04-01'
        -- filter out engagements we identified should be ignored
    AND NOT EXISTS 
        (
            SELECT 
                *
            FROM cte_engagements_to_ignore X1 
            WHERE   A.Emp_ID = X1.Emp_ID
                AND B.Cust_Data_ID = X1.Cust_Data_ID
        )

答案 3 :(得分:0)

我的方法是使用CTE获取各种规则条件,然后在最终查询中应用逻辑:

WITH Reviews AS (
    SELECT d1.Cust_ID, 'true' AS HasActiveReview
    FROM Cust_Data d1
    INNER JOIN Cust_Data d2 ON d1.Cust_ID = d2.Cust_id
    WHERE d1.Task = 'Review' 
        AND d2.Task = 'Client Engagement'
        AND d1.Activity_Date >= d2.Activity_Date
    ),
RuleData as (
SELECT e.Cust_Emp_ID,
    (CASE WHEN e.Start_Date >= '20180601' THEN 'true' ELSE 'false' END) AS StartAfter0601,
    (CASE WHEN e.End_Date <= '20180401' THEN 'true' ELSE 'false' END) as EndBefore0401,
    COALESCE(r.HasActiveReview, 'false') as HasReview
FROM Cust_Employee e
LEFT OUTER JOIN reviews r on e.Cust_ID = r.Cust_ID)
SELECT e.Emp_id, e.Cust_id, e.Start_Date, e.end_date, MAX(d.Activity_Date) AS Activity_Date, d.Task
FROM RuleData r
INNER JOIN Cust_Employee e on r.Cust_Emp_ID = e.Cust_Emp_ID
LEFT OUTER JOIN Cust_Data d on e.Cust_ID = d.Cust_ID
WHERE r.EndBefore0401 = 'false'
    AND ((r.StartAfter0601 = 'true' AND r.HasReview = 'true') OR r.StartAfter0601 = 'false')
GROUP BY e.Emp_id, e.Cust_id, e.Start_Date, e.end_date, d.Task, r.Cust_Emp_ID, r.StartAfter0601, r.EndBefore0401, r.HasReview
ORDER BY e.Emp_id, e.Cust_id;

如果需要调试,可以很容易地将规则数据添加到查询的末尾,以查看为什么返回行:

WITH Reviews AS (
    SELECT d1.Cust_ID, 'true' AS HasActiveReview
    FROM Cust_Data d1
    INNER JOIN Cust_Data d2 ON d1.Cust_ID = d2.Cust_id
    WHERE d1.Task = 'Review' 
        AND d2.Task = 'Client Engagement'
        AND d1.Activity_Date >= d2.Activity_Date
    ),
RuleData as (
SELECT e.Cust_Emp_ID,
    (CASE WHEN e.Start_Date >= '20180601' THEN 'true' ELSE 'false' END) AS StartAfter0601,
    (CASE WHEN e.End_Date <= '20180401' THEN 'true' ELSE 'false' END) as EndBefore0401,
    COALESCE(r.HasActiveReview, 'false') as HasReview
FROM Cust_Employee e
LEFT OUTER JOIN reviews r on e.Cust_ID = r.Cust_ID)
SELECT e.Emp_id, e.Cust_id, e.Start_Date, e.end_date, MAX(d.Activity_Date) AS Activity_Date, d.Task, r.Cust_Emp_ID, r.StartAfter0601, r.EndBefore0401, r.HasReview
FROM RuleData r
INNER JOIN Cust_Employee e on r.Cust_Emp_ID = e.Cust_Emp_ID
LEFT OUTER JOIN Cust_Data d on e.Cust_ID = d.Cust_ID
WHERE r.EndBefore0401 = 'false'
    AND ((r.StartAfter0601 = 'true' AND r.HasReview = 'true') OR r.StartAfter0601 = 'false')
GROUP BY e.Emp_id, e.Cust_id, e.Start_Date, e.end_date, d.Task, r.Cust_Emp_ID, r.StartAfter0601, r.EndBefore0401, r.HasReview
ORDER BY e.Emp_id, e.Cust_id;

我用“ true”和“ false”表示布尔值,如果愿意,可以使用1和0 BIT。

运行查询将返回Cust_Id 19的额外行,同时返回“客户参与”记录和“审阅”记录。我不确定为什么当您想要Cust_Id 17的两行时为什么不应该这样做,并且似乎同样适用于Cust_Id 19

Emp_Id,Cust_id,Start_Date,End_Date,Activity_Date,Task
4,11,2017-10-01 00:00:00.000,2018-07-31 00:00:00.000,NULL,NULL
4,15,2017-10-01 00:00:00.000,2018-04-08 00:00:00.000,2018-04-02 00:00:00.000,Review
4,17,2018-06-01 00:00:00.000,NULL,2018-06-29 00:00:00.000,Client Engagement
4,17,2018-06-01 00:00:00.000,NULL,2018-06-25 00:00:00.000,Review
4,19,2018-05-18 00:00:00.000,NULL,2018-05-25 00:00:00.000,Client Engagement
4,19,2018-05-18 00:00:00.000,NULL,2018-06-28 00:00:00.000,Review

答案 4 :(得分:0)

也许这会有所帮助。

PS C:\> $notepad = Start-Process notepad++ -Wait -PassThru
<close the application at this point>
PS C:\> $notepad.exitcode
PS C:\> 0