web-dev-qa-db-fra.com

Requête SQL pour renvoyer uniquement une ligne en fonction de conditions fluides

Je sais que cela est possible mais je ne sais pas comment le configurer . En gros, je dois extraire des données pour chaque employé, mais uniquement si elles répondent à certains critères basés sur quelques dates différentes.

Par exemple, si l'employé a été affecté à une entreprise avant le 6/1, il est compté automatiquement.

Si l'employé a été affecté à une entreprise après le 6/1, il n'est comptabilisé que s'il a passé un examen avec cette entreprise après la date à laquelle il a été affecté (c'est-à-dire qu'il a été affecté le 6/25 et que l'examen a été effectué le 7/1. cela devrait être compté. Si, par exemple, ils étaient assignés le 6/25 et que la révision se produisait le 6/15, ils ne compteraient pas pour cet employé)

Si l'employé est renvoyé d'une entreprise avant le 4/1, il n'est pas comptabilisé. S'ils sont supprimés à partir du 4/1, cela compte.

Les colonnes de clé sont donc Date de création de la révision, Date de début et Date de fin dans la table employé-client.

Je crois que cela devrait être soit un type de sous-requête qui renvoie la date de début pour l'employé avec ce client, puis compare la date de révision sur la base d'une instruction de cas évaluant cette date par rapport à la date de révision, mais je ne sais pas exactement comment procéder. .

Toute aide serait appréciée.

EDIT: Structure de la table/Données ci-dessous:

Table Employé-Client

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

Tableau de données client

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

Donc, pour cet exemple, je souhaite une requête qui renvoie les ID client suivants avec des données basées sur les critères:

  • 10: Ce client NE _ PAS est renvoyé car il a été congédié de son employé avant la date limite du 4/1.
  • 11: Ce client NE est renvoyé car le client a dépassé la date limite du 5/31, alors qu'il n'y a pas d'avis pour le client.
  • 15: Ce client NE est renvoyé car le client avait dépassé la date limite du 4/1 avant d'être retiré de son compte.
  • 17: L'engagement client du 29/06/2018 NE est retourné, mais l'engagement du client du 13/06/2018 ne PAS est renvoyé, car cela s'est produit AVANT que la révision ait été effectuée avec ceci client (effectivement, lorsque la date de début d'un employé pour un client est PASSÉE le 5/31, l'activité ne compte qu'après avoir passé en revue le client - toutes les activités effectuées avant cette date sont ignorées)
  • 19: L'engagement client NE est renvoyé dans ce cas parce que l'employé lui a été affecté avant le 6/1. Par conséquent, toute activité est prise en compte, qu'une révision ait été effectuée ou non avant que l'autre élément ne se produise.

Espérons que cette explication et cette panne ont du sens.

UPDATE: Voici les scripts de table et les résultats attendus:

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')

Résultats attendus:

 enter image description here

3
MattE

Je ne suis pas sûr d'avoir compris toute votre demande. En fait, il me manque quelque chose car les résultats que j’obtiens ne sont pas exactement les mêmes que vous .. Le code que j’ai préparé:

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]

Donc, ma requête montre une ligne supplémentaire pour Emp 19, ne sachant pas quelle est la condition qui éliminera, si vous me clarifiez, je corrigerai la réponse.

J'ai trouvé une autre solution plus raffinée qu'elle est plus claire et qui fonctionne très bien au moins pour l'ensemble de données fourni avec l'avantage d'être très facile à gérer.

Premièrement, je dois reconnaître que les exigences pour moi ne sont pas claires à 100% car elles sont basées sur des exemples de ce qui est habituel dans la vie réelle. Il est nécessaire d'identifier clairement les règles de gestion à appliquer et la séquence (ordre) à appliquer. Donc, basé sur mon hypothèse, j'ai construit la solution suivante. L'avantage de cette solution est qu'il est très facile de déboguer en tant que:

  1. Chaque règle est identifiée par un numéro qui peut être affiché en mode débogage (omis si nécessaire par la suite) et indique quelle règle a été appliquée.
  2. Les règles sont appliquées séquentiellement , ainsi, si une règle crée un enregistrement à afficher, le reste des règles ne sera pas appliqué. C'est parce qu'il est important l'ordre dans lequel les règles sont vérifiées
  3. Le nombre négatif pour une règle indique que la règle signifie que l'enregistrement ne doit pas être affiché .

:

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;

Le meilleur de cette méthode est qu'il calcule et affiche la règle qui a été appliquée. Elle peut donc être déboguée très facilement car la requête indique quelle règle a été appliquée. Si l'ordre des règles ou une règle est incorrect, il peut être détecté très rapidement et corrigé. Il en va de même pour les modifications futures, car normalement, ces règles basées sur les dates changent très souvent et nous avons besoin d’un moyen simple de conserver le code ..__ Enfin, j’espère que cet exercice vous donnera quelques idées pour les développements futurs, car la traçabilité et la prise en charge sont optimales. très important lors de la création de code.

5
Angel M.

Pour des logiques aussi complexes, je conseillerais les instructions CTE afin de créer des sous-groupes de lignes susceptibles d'apparaître dans le jeu de résultats final, afin de disposer d'une requête plus claire pour créer/maintenir et créer des règles positives/négatives, comme suit:

;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

Une fois que vous obtenez ce résultat pour les n-uplets de Cust/Emp devant apparaître dans le résultat, vous pouvez ajouter les informations dont vous avez besoin.

2
Emanuele Meazzo

La façon dont j'aborderais cette requête deviendrait très conviviale avec les expressions de table courantes et EXISTS/NOT EXISTS.

Je me suis rendu compte que pour un employé donné, s’il y avait des engagements avec le client avant que l’employé ait eu une révision avec ce client, ils devraient être ignorés dans le cadre de cet employé. Pour cela, j'ai implémenté une expression de table commune (cte_engagements_to_ignore) afin de filtrer ceux-ci . Les résultats de cette expression de table commune finissent par être des enregistrements employee/cust_data qui doivent être ignorés . Cela fonctionne en filtrant d'abord vers le bas toutes les missions, puis filtrer pour ne retenir que celles pour lesquelles il n’existait pas d’examen précédent ayant eu lieu après l’affectation de l’employé et avant la mission à laquelle nous comparons.

Nous interrogerons ensuite les tables de données employé/client et inclurons automatiquement si le client a démarré avant le 6/1 OR s'il existe une vérification qui a eu lieu après leur affectation à l'employé. Nous excluons ensuite les personnes non affectées avant le 4/1 ou celles dont l’engagement a été identifié devraient être ignorées pour l’employé concerné.

Très intriguant, en effet! 

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
        )
0
Eilert Hjelmeseth

Cela aidera peut-être.

SELECT employee
FROM employee emp
JOIN employee-customer ecust ON ecust.empID = ecust.empID
JOIN customer cust ON cust.custID = ecust.custID and cust.Task = 'Review'
WHERE 
(emp.StartDate <= '01062019' and (emp.EndDate <= '01042019' or emp.EndDate = NULL) or 
( emp.StartDate <= cust.ActivityDate )
0
Omkar

Mon approche consiste à obtenir les différents critères de règle à l'aide de CTE, puis à appliquer la logique dans la requête finale:

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;

Si vous avez besoin de déboguer, il est facile d'ajouter les données de la règle à la fin de la requête pour voir pourquoi une ligne est renvoyée:

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;

J'ai utilisé 'true' et false 'pour représenter les valeurs booléennes, vous pouvez utiliser 1 et 0 bits si vous préférez.

L'exécution de la requête renvoie une ligne supplémentaire pour Cust_Id 19, en renvoyant à la fois l'enregistrement "Engagement client" et celui "Révision". Je ne suis pas sûr de savoir pourquoi cela ne devrait pas être fait lorsque vous voulez les deux lignes pour Cust_Id 17 et il semble que la même chose devrait s'appliquer à 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
0
PhilS