Question

Hello i am having a weird problem currently i have the following query

SELECT *
FROM `txt_campaign_message` `cm`
         LEFT JOIN `txt_campaign` `c` ON c.id = cm.campaign_id
         LEFT JOIN `txt_message` `m` ON m.id = cm.message_id
         LEFT JOIN `txt_campaign_subscriber` `cs` ON cs.campaign_id = cm.campaign_id
WHERE ((`c`.`user_id` = 2) AND (`m`.`message_type` = 'blast') AND (`c`.`active` = 1))
  AND (NOT (`cs`.`subscriber_id` IS NULL))
  AND cs.subscriber_id = (
    (IF
        (cm.date_range_from IS NOT NULL,
         (SELECT cs1.subscriber_id
           from `txt_campaign_subscriber` cs1
           WHERE DATE(cs.created_at) BETWEEN DATE(cm.date_range_from) AND DATE(cm.date_range_to)
         ),
         (cs.subscriber_id)
        )
    )
  )
  AND (`cm`.`campaign_id` = 1386)

which gave me an error

Sub-query returns multiple rows

and i changed the query part AND cs.subscriber_id = (IF to use IN rather than = before the condition like below

SELECT *
FROM `txt_campaign_message` `cm`
         LEFT JOIN `txt_campaign` `c` ON c.id = cm.campaign_id
         LEFT JOIN `txt_message` `m` ON m.id = cm.message_id
         LEFT JOIN `txt_campaign_subscriber` `cs` ON cs.campaign_id = cm.campaign_id
WHERE ((`c`.`user_id` = 2) AND (`m`.`message_type` = 'blast') AND (`c`.`active` = 1))
  AND (NOT (`cs`.`subscriber_id` IS NULL))
  AND cs.subscriber_id IN (
    (IF
        (cm.date_range_from IS NOT NULL,
         (SELECT cs1.subscriber_id
           from `txt_campaign_subscriber` cs1
           WHERE DATE(cs.created_at) BETWEEN DATE(cm.date_range_from) AND DATE(cm.date_range_to)
         ),
         (cs.subscriber_id)
        )
    )
  )
  AND (`cm`.`campaign_id` = 1386)

But the error remains the same, to my surprise if i remove the if condition and just use the sub query all runs fine

SELECT *
FROM `txt_campaign_message` `cm`
         LEFT JOIN `txt_campaign` `c` ON c.id = cm.campaign_id
         LEFT JOIN `txt_message` `m` ON m.id = cm.message_id
         LEFT JOIN `txt_campaign_subscriber` `cs` ON cs.campaign_id = cm.campaign_id
WHERE ((`c`.`user_id` = 2) AND (`m`.`message_type` = 'blast') AND (`c`.`active` = 1))
  AND (NOT (`cs`.`subscriber_id` IS NULL))
  AND cs.subscriber_id IN (
         (SELECT cs1.subscriber_id
           from `txt_campaign_subscriber` cs1
           WHERE DATE(cs.created_at) BETWEEN DATE(cm.date_range_from) AND DATE(cm.date_range_to)
         )
  )
  AND (`cm`.`campaign_id` = 1386)

and when i add the condition back it starts throwing the same error, what am i doing wrong or what do i need to change if i am using the IF condition for the sub-query, i already changed the = to IN as i needed multiple rows to be compared.

I am using MariaDB (10.3) where i am facing the problem.

Was it helpful?

Solution

The problem is that the IF statement cannot contain a set, it needs to be a scalar. Let's illustrate with:

create table T (x int not null);
insert into T (x) values (1),(2);
-- invalid since `SELECT x FROM T` is a set of rows
SELECT * FROM T WHERE 1 IN ( IF (1=1, SELECT x FROM T, 1) );

Fiddle

Let's see if your query can be reformulated (I changed it slightly to make it more readable):

SELECT *
FROM txt_campaign_message cm
LEFT JOIN txt_campaign c 
    ON c.id = cm.campaign_id
LEFT JOIN txt_message m 
    ON m.id = cm.message_id
LEFT JOIN txt_campaign_subscriber cs 
    ON cs.campaign_id = cm.campaign_id
WHERE c.user_id = 2 
  AND m.message_type = blast 
  AND c.active = 1
  -- AND cs.subscriber_id IS NOT NULL, redundant and can be removed 
  AND cs.subscriber_id IN (
    IF (cm.date_range_from IS NOT NULL
       , (SELECT cs1.subscriber_id
          FROM txt_campaign_subscriber cs1
          WHERE DATE(cs.created_at) BETWEEN DATE(cm.date_range_from) 
                                        AND DATE(cm.date_range_to)
         )
       , cs.subscriber_id
       )
  )
  AND cm.campaign_id = 1386;

This is the problematic part:

AND cs.subscriber_id IN (
    IF (cm.date_range_from IS NOT NULL
       , (SELECT cs1.subscriber_id
          FROM txt_campaign_subscriber cs1
          WHERE DATE(cs.created_at) BETWEEN DATE(cm.date_range_from) 
                                        AND DATE(cm.date_range_to)
         )
       , cs.subscriber_id
       )
  )

Two cases:

a) cm.date_range_from IS NOT NULL

AND cs.subscriber_id IN (
       SELECT cs1.subscriber_id
       FROM txt_campaign_subscriber cs1
       WHERE DATE(cs.created_at) BETWEEN DATE(cm.date_range_from) 
                                     AND DATE(cm.date_range_to)
)


b) cm.date_range_from IS NULL

cs.subscriber_id = cs.subscriber_id

i.e. TRUE

This is equivalent to:

(
AND cs.subscriber_id IN (
       SELECT cs1.subscriber_id
       FROM txt_campaign_subscriber cs1
       WHERE DATE(cs.created_at) BETWEEN DATE(cm.date_range_from) 
                                     AND DATE(cm.date_range_to)
    )
AND cm.date_range_from IS NOT NULL
)
OR cm.date_range_from IS NULL

All and all we end up with:

SELECT
    *
FROM
    txt_campaign_message cm
LEFT JOIN txt_campaign c ON
    c.id = cm.campaign_id
LEFT JOIN txt_message m ON
    m.id = cm.message_id
LEFT JOIN txt_campaign_subscriber cs ON
    cs.campaign_id = cm.campaign_id
WHERE c.user_id = 2 
  AND m.message_type = blast 
  AND c.active = 1
  AND (
      cs.subscriber_id IN (
          SELECT cs1.subscriber_id
          FROM txt_campaign_subscriber cs1
          WHERE DATE(cs.created_at) BETWEEN DATE(cm.date_range_from) 
                                        AND DATE(cm.date_range_to)
        ) AND cm.date_range_from IS NOT NULL
      OR 
      cm.date_range_from IS NULL
  ) 
  AND cm.campaign_id = 1386;

Totally untested, but it should give you an idea

You may also consider rewriting the in-predicate as a JOIN

OTHER TIPS

(I need formatting not available in a Comment.)

DATE(...) is not sargable. There is probably no reason to use the DATE function; simply do

cs.created_at  BETWEEN  cm.date_range_from  AND  cm.date_range_to

But, that won't really help -- comparing something to a range computed from other columns cannot be optimized.

For this:

AND (NOT (`cs`.`subscriber_id` IS NULL))
AND cs.subscriber_id = ...

The first test is unnecessary. The second will fail if it is NULL.

A note on LEFT:

LEFT JOIN `txt_campaign` `c` ...
...
WHERE ((`c`.`user_id` = 2)

The LEFT says that there might not be a row in txt_campaign; the WHERE says there must be. Remove the word LEFT.

Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top