Subject | Coalesce and OR condition didn't use indices... other ways? |
---|---|
Author | gabry_a73 |
Post date | 2009-09-08T13:13:59Z |
Hi all,
I have a procedure that should filter the records in a table, but it's too slow. In this moment I use FB 2.0.3, but I've made test on 2.1 and 2.5 (beta 2) too.
my table contains about 30 fields (some of these are under index), for
600.000 records and I would filter for one or more field, but the query doesn't use indices. I've made a small example to explain the problem:
I create 'mytable' and some indices:
CREATE TABLE mytable (
idmytable INTEGER NOT NULL,
field_a VARCHAR(10),
field_b VARCHAR(10))
^
ALTER TABLE mytable ADD CONSTRAINT pk_mytable PRIMARY KEY (idmytable)
^
CREATE UNIQUE INDEX mytable_idx1 ON mytable (field_a);
^
CREATE INDEX mytable_idx2 ON mytable (field_b);
^
I insert some record:
INSERT INTO mytable (idmytable, field_a, field_b)
VALUES (1, 'A', 'X');
^
INSERT INTO mytable (idmytable, field_a, field_b)
VALUES (2, 'B', 'X');
^
INSERT INTO mytable (idmytable, field_a, field_b)
VALUES (3, 'C', 'X');
^
INSERT INTO mytable (idmytable, field_a, field_b)
VALUES (4, 'D', 'Y');
^
INSERT INTO mytable (idmytable, field_a, field_b)
VALUES (5, 'E', 'Y');
^
now I create the procedure to filter on mytable: the procedure accepts a filter type that could be 0 (=> coalesce), 1 (=> OR condition) or 2 (=> cursor), so I can see the difference between these way to filter.
CREATE OR ALTER PROCEDURE filter_on_mytable (
i_filtertype INTEGER,
i_field_a VARCHAR(10),
i_field_b VARCHAR(20))
RETURNS (
idmytable INTEGER)
AS
DECLARE filter_a CURSOR FOR (
SELECT t.idmytable
FROM mytable t
WHERE t.field_a = :i_field_a);
DECLARE filter_b CURSOR FOR (
SELECT t.idmytable
FROM mytable t
WHERE t.field_b = :i_field_b);
BEGIN
IF (i_filtertype = 0) THEN
BEGIN
/* try with coalesce */
FOR SELECT t.idmytable
FROM mytable t
WHERE t.field_a = COALESCE(:i_field_a, t.field_a)
AND t.field_b = COALESCE(:i_field_b, t.field_b)
INTO :idmytable DO
BEGIN
SUSPEND;
END
END
IF (i_filtertype = 1) THEN
BEGIN
/* try with OR */
FOR SELECT t.idmytable
FROM mytable t
WHERE (t.field_a = :i_field_a OR :i_field_a IS NULL)
AND (t.field_b = :i_field_b OR :i_field_b IS NULL)
INTO :idmytable DO
BEGIN
SUSPEND;
END
END
IF (i_filtertype = 2) THEN
BEGIN
/* try with cursor */
IF (i_field_a IS NOT NULL) THEN
OPEN filter_a;
ELSE
OPEN filter_b;
WHILE (0=0) DO
BEGIN
IF (i_field_a IS NOT NULL) THEN
FETCH filter_a INTO :idmytable;
ELSE
FETCH filter_b INTO :idmytable;
IF (ROW_COUNT = 0) THEN
BEGIN
IF (i_field_a IS NOT NULL) THEN
CLOSE filter_a;
ELSE
CLOSE filter_b;
LEAVE;
END
ELSE
BEGIN
SUSPEND;
END
END
END
END
^
the query:
select *
from filter_on_mytable (0, 'C', null)
doesn't use indices, and I can see that fetches 5 records. The same result for
select *
from filter_on_mytable (1, 'C', null)
Now I try with
select *
from filter_on_mytable (2, 'C', null)
and I see that uses the index, and fetches 1 record. The problem is that I cannot use this way, because I have many fields in my filter, at least 10!
I know that there are problems in the plan when I use "OR" or "COALESCE", but, are there other way to "build" my where condition according to the input parameters?
thanks in advance!
Gabry
I have a procedure that should filter the records in a table, but it's too slow. In this moment I use FB 2.0.3, but I've made test on 2.1 and 2.5 (beta 2) too.
my table contains about 30 fields (some of these are under index), for
600.000 records and I would filter for one or more field, but the query doesn't use indices. I've made a small example to explain the problem:
I create 'mytable' and some indices:
CREATE TABLE mytable (
idmytable INTEGER NOT NULL,
field_a VARCHAR(10),
field_b VARCHAR(10))
^
ALTER TABLE mytable ADD CONSTRAINT pk_mytable PRIMARY KEY (idmytable)
^
CREATE UNIQUE INDEX mytable_idx1 ON mytable (field_a);
^
CREATE INDEX mytable_idx2 ON mytable (field_b);
^
I insert some record:
INSERT INTO mytable (idmytable, field_a, field_b)
VALUES (1, 'A', 'X');
^
INSERT INTO mytable (idmytable, field_a, field_b)
VALUES (2, 'B', 'X');
^
INSERT INTO mytable (idmytable, field_a, field_b)
VALUES (3, 'C', 'X');
^
INSERT INTO mytable (idmytable, field_a, field_b)
VALUES (4, 'D', 'Y');
^
INSERT INTO mytable (idmytable, field_a, field_b)
VALUES (5, 'E', 'Y');
^
now I create the procedure to filter on mytable: the procedure accepts a filter type that could be 0 (=> coalesce), 1 (=> OR condition) or 2 (=> cursor), so I can see the difference between these way to filter.
CREATE OR ALTER PROCEDURE filter_on_mytable (
i_filtertype INTEGER,
i_field_a VARCHAR(10),
i_field_b VARCHAR(20))
RETURNS (
idmytable INTEGER)
AS
DECLARE filter_a CURSOR FOR (
SELECT t.idmytable
FROM mytable t
WHERE t.field_a = :i_field_a);
DECLARE filter_b CURSOR FOR (
SELECT t.idmytable
FROM mytable t
WHERE t.field_b = :i_field_b);
BEGIN
IF (i_filtertype = 0) THEN
BEGIN
/* try with coalesce */
FOR SELECT t.idmytable
FROM mytable t
WHERE t.field_a = COALESCE(:i_field_a, t.field_a)
AND t.field_b = COALESCE(:i_field_b, t.field_b)
INTO :idmytable DO
BEGIN
SUSPEND;
END
END
IF (i_filtertype = 1) THEN
BEGIN
/* try with OR */
FOR SELECT t.idmytable
FROM mytable t
WHERE (t.field_a = :i_field_a OR :i_field_a IS NULL)
AND (t.field_b = :i_field_b OR :i_field_b IS NULL)
INTO :idmytable DO
BEGIN
SUSPEND;
END
END
IF (i_filtertype = 2) THEN
BEGIN
/* try with cursor */
IF (i_field_a IS NOT NULL) THEN
OPEN filter_a;
ELSE
OPEN filter_b;
WHILE (0=0) DO
BEGIN
IF (i_field_a IS NOT NULL) THEN
FETCH filter_a INTO :idmytable;
ELSE
FETCH filter_b INTO :idmytable;
IF (ROW_COUNT = 0) THEN
BEGIN
IF (i_field_a IS NOT NULL) THEN
CLOSE filter_a;
ELSE
CLOSE filter_b;
LEAVE;
END
ELSE
BEGIN
SUSPEND;
END
END
END
END
^
the query:
select *
from filter_on_mytable (0, 'C', null)
doesn't use indices, and I can see that fetches 5 records. The same result for
select *
from filter_on_mytable (1, 'C', null)
Now I try with
select *
from filter_on_mytable (2, 'C', null)
and I see that uses the index, and fetches 1 record. The problem is that I cannot use this way, because I have many fields in my filter, at least 10!
I know that there are problems in the plan when I use "OR" or "COALESCE", but, are there other way to "build" my where condition according to the input parameters?
thanks in advance!
Gabry