24. ledna 2010

Hibernate - řazení NULL hodnot

Dnes výjímečně nebudu publikovat svůj příspěvek, ale příspěvek mého současného kolegy Vaška Hrdiny. Řešený problém mi přišel natolik zajímavý, že jsem ho požádal o publikaci na mém blogu.

Řazení v Hibernate

Pro řazení přes Criteria API existuje metoda addOrder():
crit.addOrder(Order.asc(DOdatovy_objekt.POLE)); //vzestupně
crit.addOrder(Order.desc(DOdatovy_objekt.POLE)); //sestupně
pro HQL je možné použít klauzuli order by:
createQuery("from " + DOdatovy_objekt.class.getSimpleName() +  " as do order by do." + DOdatovy_objekt.POLE); //vzestupně 
createQuery("from " + DOdatovy_objekt.class.getSimpleName() +  " as do order by do." + DOdatovy_objekt.POLE + " desc"); //sestupně

Řazení NULL hodnot

Problém ale nastává pro řazení NULL hodnot. Pokud je políčko NULL, pak jej MSSQL řadí na první místo, Oracle oproti tomu jako poslední. Ne vždy můžeme použít řazení až na aplikačním serveru (např. při tisku velké sestavy, kdy musíme získávat data stránkovaně), přesto je třeba řadit na všech DB stejně.
Nepřišel jsem na vhodný způsob, jak toto vyřešit, hibernate JIRA obsahuje požadavek, který se tohoto týká. Jediný způsob, na který jsem byl schopný přijít je řešení přes formule.

Jedná se o to, že do mapovaného objektu přidáte políčko (stačí políčko, nemusí mít ani getter), které se naplní předem definovaným SQL příkazem (pozor, jedná se skutečně o SQL, je tedy třeba hlídat mezidatabázovou kompatibilitu). Podle něj se dá řadit. A pokud zajistíte, aby se v políčku vyskytovaly takové znaky, aby se řadilo podle Vašich potřeb (typicky NULLy půjdou jako první), je vyhráno.

Následující příklad ukazuje, jak řadit písmeno orientačního čísla tak, aby pokud není vyplněné, bylo první (tedy např. 16, 16a, 16b...):
@Entity
@Table(name = "adresa")
public class DOadresa extends cz.marbes.daisy.modules.x.mdo.DOGadresa {

@Formula("case when PISMENO_CO is null then ' ' else PISMENO_CO end")
private String pismenoCONotNull;
}

V kódu, který potřebuje takto řadit, pak stačí uvést:
crit.addOrder(Order.asc("pismenoCONotNull")); //pro Criteria API
createQuery("from " + DOdatovy_objekt.class.getSimpleName() + " order by pismenoCONotNull"); //pro HQL

Políčko pismenoCONotNull je pak normálně přístupné, jako kterékoliv jiné políčko, obsahuje buď mezeru nebo obsah pole PISMENO_CO.

3 komentáře:

Viktor řekl(a)...

Tenhle způsob se mi moc nelíbí. Teď mě napadla ještě jedna možnost, ale jednalo by se pouze o read-only entitu. Udělat v db view, kde by si v příslušném sloupci přidával místo null hodnoty mezeru. Tahla entita by se používala pro selecty a druhá entita, mapovaná na tabulku, by se používala pro zápis. Teď ale co je větší zlo :)

Anonymní řekl(a)...

Pre HQL ma ešte napadá použiť funkciu COALESCE(atribut, ' '), čo by malo byť ekvivalentné uvedenej ukážke pomocou formule a zároveň netreba nič pridávať do mapovania.
Momentálne si však nespomínam, či existuje aj možnosť použiť COALESCE cez Criteria API.

Anonymní řekl(a)...

Jen bych doplnil, že Oracle má podporu řazení null hodnot:
... order by x NULLS FIRST/LAST