(Oops, on dirait que les balises ## du markdown pour les titres ne fonctionnent pas, ce qui rend le post moins lisible. Je vais mettre les titres et sous-titres en gras).

Edit : j'ai trouvé une solution (voir réponse). A voir si c'est opti par contre !

Hello ! J'ai un soucis pour retranscrire une requête que je voudrais créer.
La description est un peu longue, mais c'est à mon avis un cas simple, je suis simplement mauvais en SQL.

# Contexte #
Boilà brièvement ce que je veux faire. J'ai deux formulaires identiques, avec une date range en input. Je récupère les utilisateurs qui ont leur propriété remind_atqui sont dans cette date range. Je parle de deux formulaires, car l'un est de type "mail", l'autre de type "SMS" (voir ci après).

Ensuite, je récupère une liste d'utilisateurs, je sélectionne ceux que je veux, et ça les insérera dans la table selection, avec une date fixe de "date de contact" : reminded_at, ainsi que le type de relance (sms ou mail), qui sera défini par un id à mail_model ou sms_model. (Si le champs mail_model est renseigné, sms_model sera null, et vice-versa).

Si un client a déjà été sélectionné, ils ne doivent pas apparaître dans le résultat. Mais j'ai mis une option qui permet de les récupérer s'ils ont déjà été sélectionnés par l'AUTRE type de contact (exemple dans le cas d'un formulaire "mail" : Inclure les clients qui ont déjà été sélectionnés par SMS)

Il y a donc deux tables (en excluant les tables mail_model et sms_model) :

## customer ##

+----+------------+
| id | remind_at  |
+----+------------+
|  1 | 2021-10-18 |
|  2 | 2021-10-20 |
|  3 | 2021-11-25 |
+----+------------+

Colonnes:

  • remind_at : la date maximum actuelle où l'utilisateur doit être rappellé

## selection ##

+----+-------------+---------------+--------------+------------+
| id | customer_id | mail_model_id | sms_model_id | reminded_at|
+----+-------------+---------------+--------------+------------+
| 20 |           1 | 10            | null         | 2021-10-18 |
| 21 |           2 | 11            | null         | 2021-10-20 |
| 22 |           3 | null          | 15           | 2021-11-25 |
+----+-------------+---------------+--------------+------------+

Les clients sélectionnés, qui seront contacté par mail ou sms, en fonction de la colonne renseignée.

  • customer_id : l'id de l'user
  • mail_model_id : l'id du modèle de mail (En cas d'insertion depuis un formulaire "Mail". Peut être null si c'est une sélection SMS)
  • sms_model_id : the id of the sms model (En cas d'insertion depuis un formulaire "SMS". Peut être null si c'est une sélection Mail)
  • reminded_at : la date de contact de cette sélection. C'est une date fixe, qui ne concerne que cette sélection. Contrairement à la date remind_at de l'user associé qui peut changer au cours du temps (pour la prochaine vague de contact).

Bon, allons trouver des clients à relancer SANS l'option inclure les clients déjà sélectionnés par mail|sms.
Jusque là, tout va bien : je récupère les clients qui ont ZERO sélection, ou bien qui en ont déjà, mais pas dans la date range renseignée.

SELECT
    *
FROM
    customer c
    LEFT JOIN selection s ON c.id = s.customer_id
WHERE
    -- L'utilisateur n'a aucune sélection
    s.id IS NULL
    OR s.id IS NOT NULL
    -- Okay, il en a déjà , mais pas dans la date range renseignée, donc je récupère quand même !
    AND s.reminded_at NOT BETWEEN 
        '2021-09-15'        -- input parameter date
        AND '2021-11-15'    -- input parameter date

# Probleme :( #

Maintenant, voilà le problème, causé par l'option inclure les clients déjà sélectionnés par mail|sms.

## Cas 1 (ça marche): le client a déjà été sélectionné dans la daterange par UN SEUL TYPE de contact (sms|mail) ##

Si je fais, depuis un formulaire mail avec l'option inclure les clients déjà sélectionnés par sms. :

SELECT
    *
FROM
    customer c
    LEFT JOIN selection s ON c.id = s.customer_id
WHERE
    -- L'utilisateur n'a aucune sélection
    s.id IS NULL
    OR (s.id IS NOT NULL
    -- Okay, il en a déjà dans la date range, mais le mail_model_id est NULL, ça veut dire que ce sont des relances SMS, récupère !
    AND s.reminded_at BETWEEN (
        '2021-09-15'        -- input parameter date
        AND '2021-11-15'    -- input parameter date
    ) AND s.mail_model_id IS NULL )

Ca va marcher, mais si seulement le client a déjà été sélectionné UNIQUEMENT par le type de contact opposé.
Ici, ça va récupérer les clients qui ont ZERO sélection, ou qui ont des sélections dans la date range uniquement de type SMS.

## Cas 2 (le cas problématique :p) : l'utilisateur a été sélectionné par les DEUX TYPES de contact ##

Si un utilisateur a été sélectionné par les deux types de contacts, il y a deux lignes dans la table selection :

Table selection

+----+-------------+---------------+--------------+-------------+
| id | customer_id | mail_model_id | sms_model_id | reminded_at |
+----+-------------+---------------+--------------+-------------+
|  9 |           4 | 1             | NULL         | 2021-10-18  |
| 10 |           4 | NULL          | 1            | 2021-10-18  |
+----+-------------+---------------+--------------+-------------+

Et là, le client ne devrait JAMAIS apparaître dans les résultats, même avec l'option cochée. Et pourtant, il apparait, logiquement. La raison est : avec la requête actuelle, il va trouver les sélections avec un mail_model_id à NULL, hop, il retourne le client. Mais il y a l'autre ligne à considérer : celle qui a un mail_model_id renseigné.
Je souhaiterais donc récupérer les clients qui n'a pas de sélection dans la période donnée, ou s'il a UNIQUEMENT des sélections du type de contact opposé, mais pas des deux !

J'ai l'impression que ce n'est pas compliqué, mais je bloque. Je pensais trouver la solution en écrivant ce post (ça aide souvent pour avoir un déclic, mais non xD)

Voilà un dump de la base de donnée ici (4 tables, très light), si voulu : https://bpa.st/3B2A

En vous remerciant pour votre aide :)

3 réponses


Konijj
Auteur

Bon, he bien juste après avoir écrit ce post, m'est venu l'idée d'utiliser la fonction EXISTS que je n'avais jamais utilisé jusque là, et il semblerait que ça fait le taf xd.

Cas formulaire mail :

SELECT
    *
FROM
    customer c
    LEFT JOIN selection s ON c.id = s.customer_id
WHERE
    -- L'utilisateur n'a aucune sélection
    s.id IS NULL
    -- Ou bien en tout cas il n'en a pas dans la période donnée avec le mail_model_id renseigné !
    OR NOT EXISTS(
        SELECT * FROM selection ss
        WHERE ss.customer_id = c.id
        AND ss.reminded_at BETWEEN '2021-09-15' AND '2021-11-15'
        AND ss.mail_model_id IS NOT NULL)

Je ne sais pas si c'est très opti par contre. Mais ça marche :)

Tu n'es pas obligé de mettre un SELECT * dans le NOT EXISTS. Tu peux mettre un SELECT 1, ça évitera qu'il aille chercher chaque colonne alors que ce qui t'intéresse, c'est de savoir si ça existe ou pas et non de récupérer un ou plusieurs champs. Ce serait un peu plus opti mais je ne pense pas que la différence sera visible...

Konijj
Auteur

En effet, j'avais au final mis SELECT ss.id, mais un SELECT 1 sera encore mieux :). Merci !