Using subquery within IF condition [closed]
-
24-02-2021 - |
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.
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) );
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
.