Dinaminis SQL užklausos „where” sąlygų keitimas

sql_geekSituacija dažnai sutinkama kasdieniniame darbe. Turim select’ą su daug where sąlygų. Vieną kartą select’ą tenka įvykdyti su vienomis sąlygomis, antrą kartą su kitomis, trečią – su visomis išskyrus vieną ir pan. Jeigu select’as trumpas, bėdos nėra: užkomentuojam nereikalingas eilutes ir tiek. Jeigu select’as yra labai ilgas, su daug join’ų, union’ų ir daug daug kintančių sąlygų, problema pasidaro gan rimta. Galima lengvai susipainioti ar kažką praleisti. Taip pat toks „užkomentavimo” metodas nelabai tinka automatiniam ataskaitų generavimui. Na bet pasirodo yra gan paprastas ir patogus where sąlygų įjungimo/išjungimo būdas.

Apie viską iš pradžių. Tarkim, turim lentelę t, kurioje yra saugomi įrašai susidedantys iš id, date ir type stulpelių:

ID    DT                     TYPE
--    -------------------    -----
1     2014-08-01 00:00:00     A
2     2014-08-01 01:00:00     B
3     2014-08-01 02:00:00     A
4     2014-08-01 03:00:00     B
5     2014-08-01 04:00:00     A
6     2014-08-01 05:00:00     B
7     2014-08-01 06:00:00     A
8     2014-08-01 07:00:00     B

Pasibandymui (pav. ant sqlfiddle.com) tokią lentelę galit pasigaminti taip:

create table t
 (id number, DT date, type VARCHAR2(5 CHAR));

insert into t (id, DT, type)
 SELECT
 rownum as id
 ,TO_DATE('2014-08-01', 'yyyy-mm-dd') + (rownum-1)*(1/24) AS DT
 ,case when mod(rownum,2) = 1 then 'A' else 'B' end as type
 FROM dual
 CONNECT BY LEVEL <=  (TO_DATE('2014-08-05 00:00:00', 'yyyy-mm-dd HH24:MI:SS') - TO_DATE('2014-08-01 00:00:00', 'yyyy-mm-dd HH24:MI:SS'))*2
 order by 1;
 

Įsivaizduokim situaciją, kad dažnai reikia paselectinti tam tikro laiko visų arba tik ‘A’ tipo duomenis. Kitaip sakant, dažnai tenka vykdyti arba vieną arba kitą selectą:

select * from t
where DT > to_date('2014-08-01 02:00:00', 'yyyy-mm-dd HH24:MI:SS')

order by ID

select * from t
where DT > to_date('2014-08-01 02:00:00', 'yyyy-mm-dd HH24:MI:SS')
and type = 'A'
order by ID

Jeigu select’as trumpas, bėdos nėra. Jeigu ilgas ir sudėtingas – atsiranda šiokių tokių problemų. Gerai, kad protingi žmonės sugalvojo genialiai paprastą sprendimą, išnaudojantį loginį ARBA (OR). Naudojantys SQL Developer, gali šį metodą išsibandyti taip:

select * from t
where DT > to_date('2014-08-01 02:00:00', 'yyyy-mm-dd HH24:MI:SS')
and (1=:ignore_type OR type = 'A')
order by ID

Čia, trečiąją kodo eilutę papildžiau išmone 1=:ignore_type. Kas čia per padaras, paklausit. O gi viskas labai paprasta. Šį daiktą reikia suprasti taip: jeigu parametrą ignore_type nustatysime lygų vienam, sąlygą type = ‘A’ bus ignoruojama. Jeigu nustatysime, kad ignore_type=0 į sąlygą type = ‘A’ bus atsižvelgta.

Kitaip sakant, parametro ignore_type reikšmę nustatę 1 arba 0 selecto sąlygą vienu ar kitu atveju pakeisime taip:

select * from t
where DT > to_date('2014-08-01 02:00:00', 'yyyy-mm-dd HH24:MI:SS')
and (1=1 OR type = 'A')
order by ID

select * from t
where DT > to_date('2014-08-01 02:00:00', 'yyyy-mm-dd HH24:MI:SS')
and (1=0 OR type = 'A')
order by ID

Nuo seno SQL kalboje išraiška 1=1 yra tolygi TRUE, o 1=0 yra lygi FALSE. O ką daro operatorius OR? Jis pasirenka arba vieną sąlygą arba kitą. Jeigu pirma sąlyga yra teisinga, antroji yra tiesiog ignoruojama. T. y. viršuje aprašyti selectai pavirsta tokiais:

select * from t
where DT > to_date('2014-08-01 02:00:00', 'yyyy-mm-dd HH24:MI:SS')
and (1=1)
order by ID

select * from t
where DT > to_date('2014-08-01 02:00:00', 'yyyy-mm-dd HH24:MI:SS')
and (type = 'A')
order by ID

Ir tai veikia! Tiesiog mind blow:

tim-and-eric-mind-blown