Premessa
Benvenuti alla seconda delle quattro parti di cui è
formata questa mia serie su JDOQL. Gli articoli in questa
serie illustrano le nuove potenzialità che l'imminente
standard JDO 2.0 fornirà a JDOQL.
Questa prima parte della serie comprende:
- JDOQL
Parte I - Nuovi operatori e metodi supportati, il
paging dei risultati delle query e query di cancellazione
delegate al dB.
- JDOQL
Parte II - Proiezione, aggregazione e raggruppamento
senza duplicazione dei risultati delle query.
Questo
particolare articolo utilizza per il dominio il modello
ad oggetti, discusso separatamente, e l'insieme di dati
per Hammer.org in Online Auction Domain. Qui viene fornito
un veloce link al diagramma UML per il dominio persistente.
Altre
utili risorse includono due pagine di SolarMetric JDOQL
2.0 Quick Reference e la PDF Edition non stampabile
del mio libro, Java Data Objects (Addison-Wesley).
Selezione
di offerte vincenti come dati e non come oggetti - La
Proiezione
In JDO 1.0 tutte le query restituiscono collezioni d'istanze
persistenti. Per esempio, una query su un'estensione
dell'AuctionItem (oggetto d'asta) restituirebbe una
collezione d'istanze di AuctionItem che soddisfano le
condizioni del filtro. Nella maggior parte dei casi
questo è ciò che si desidera. JDO gestisce
la corrispondenza fra gli oggetti in memoria e la loro
rappresentazione nella base di dati; l'applicazione
disporrà quindi sia di oggetti reali che del
loro comportamento.
Tuttavia, in determinate circostanze l'applicazione
non è interessata agli oggetti reali. Potrebbe
essere che tutto ciò che si desidera sia l'accesso
ai dati persistenti e quindi una struttura di dati semplice
potrebbe essere preferibile ad un oggetto da cui quegli
stessi dati devono essere poi estrapolati.
Si consideri la richiesta di selezionare tutti gli AuctionItem
per cui l'offerta vincente è più alta
del prezzo di base e quindi di estrarre il titolo, il
prezzo di riserva, l'importo dell'offerta vincente e
il nome dell'offerente per mostrarlo all'utente.(facendo
uso di JSP, o Swing, ecc).
JDOQL (#17) senza proiezione
Ecco come potrebbe essere realizzato ciò, senza
l'uso della nuova caratteristica della proiezione di
JDO 2.0:
Query q = pm.newQuery(AuctionItem.class, "winningBid.bidAmount
>
reservePrice");
Collection results = (Collection) q.execute ();
Collection data = new ArrayList();
Iterator iter = results.iterator();
while (iter.hasNext()) {
AuctionItem item = (AuctionItem) iter.next();
Object[] row = new Object[4];
row[0] = item.getTitle();
row[1] = item.getReservePrice();
row[2] = item.getWinningBid().getBidAmount();
row[3] = item.getWinningBid().getBidder().getUser().getUserName();
data.add(row);
}
Alla fine dell'iterazione l'applicazione ha ottenuto
una Collection di array Object[] di lunghezza 4, ciascuno,
appunto, con quattro dati recuperati dall'appropriato
AuctionItem e dagli oggetti che con esso correlati.
In ogni modo ottenere tale risultato è complicato
dal punto di vista dell'implementazione e non sufficientemente
efficiente ai fini dell'esecuzione.
JDOQL (#18) con proiezione
JDO 2.0 soddisfa la richiesta in oggetto supportando
la proiezione, che è rappresentata dalla clausola
"result" di una Query. La clausola result
è una lista di campi separati dalla virgola,
che potrebbero essere campi persistenti della classe
candidata oppure campi d'altre classi raggiungibile
dalla classe candidata. La parola chiave "this"
può essere usata per riferirsi all'istanza stessa
che soddisfa la richiesta. L'accesso agli ID degli oggetti
persistenti è fornito con il metodo JDOHelper.getObjectId().
Query
q = pm.newQuery (AuctionItem.class, "winningBid.bidAmount
>
reservePrice");
q.setResult ("title, reservePrice, winningBid.bidAmount,
winningBid.bidder.user.userName");
Collection results = (Collection)q.execute ();
Risultati
I risultati dei due frammenti
di codice sono identici, sia nella struttura che nei
contenuti, ma la versione che utilizza la clausola result
per la proiezione è molto più semplice
da scrivere e può essere più facilmente
implementata in modo efficiente.
Proiezione
o no?
JDO è una tecnologia ORM (Object-Relational Mapping).
Come tale permette un maggior livello di astrazione,
sollevando gli sviluppatori da gran parte del lavoro
necessario alla composizione degli oggetti a partire
dai campi e al processo inverso di decomposizione degli
oggetti nuovamente dentro i campi. La proiezione permette
agli utenti di evitare questo lavoro.
Ogni campo proiettato può essere di tipo semplice
(dati) o di tipo capace di persistenza (oggetti). Quando
un oggetto di quest'ultimo tipo è parte di una
proiezione, JDO continua a gestire l'oggetto proiettato.
L'esempio successivo presenta una proiezione che include
un oggetto RegisteredUser anziché solo il valore
del campo del nome dell'oggetto stesso.
Id
degli Oggetti
E' molto comodo restituire una struttura tabellare di
dati per un client o per visualizzarli tramite una JSP.
Tuttavia, alle volte, il client può richiedere
di conoscere l'ID dell'oggetto per ogni riga, in modo
che l'ID corretto possa essere riutilizzato per richieste
successive. Per esempio, un client Swing potrebbe visualizzare
la JTable degli articoli dell'asta, permettendo all'utente
di selezionarne uno per un'interrogazione più
dettagliata. Quest'ultima interrogazione può
essere soddisfatta più efficientemente se l'applicazione
client rinvia l'ID dell'oggetto. Per facilitare ciò
l'ID dell'oggetto deve fare parte della struttura dati
che il client riceve.
q.setResult
("title, reservePrice, winningBid.bidAmount,
winningBid.bidder.user");
Alla
luce di ciò: quando si dovrebbe proiettare i
dati a livello di campi e quando si dovrebbe proiettare
tipi di oggetti capaci di persistenza?
Si
DEVE proiettare dati a livello di campo quando:
- i
dati saranno usati, ma non saranno modificati, tipicamente
quando i valori devono essere trasmessi fuori dallo
strato di JDO per sola lettura (per sola visualizzazione).
NON SI DEVE proiettare dati a livello di campo quando:
- si
dovrebbero creare oggetti (in modo analogo alle classi
capaci di persistenza) a partire dai campi; i tal
caso si consideri il "detachment" anziché
la proiezione.
- si
devono modificare i dati e avere tali modifiche sincronizzate
nella base di dati; in tal caso si progettino invece
oggetti capaci di persistenza e si lasci a JDO il
compito di svolgere il lavoro più arduo .
- se
intendete semplicemente imporre dei pattern di caricamento
per le query; in tal caso si usi piuttosto la funzionalità
di JDO 2.0 di pianificazione del caricamento.
JDOQL
(#19)
Gli ID degli oggetti possono essere inclusi nei risultati
proiettati usando il metodo JDOHelper.getObjectId()
nella clausola result, come illustrato dall'esempio
seguente.
Query
q = pm.newQuery (AuctionItem.class,
"winningBid.bidAmount > reservePrice");
q.setResult ("JDOHelper.getObjectId(this), title,
reservePrice,
winningBid.bidAmount,
winningBid.bidder.user.userName");
Collection results = (Collection)q.execute ();
Risultati
I risultati ora includono l'ID dell'oggetto per ogni
articolo selezionato dell'asta.
SQL
Per riferimento, ecco l'SQL che è stato generato
dalla query proiettata includendo l'ID dell'oggetto:
SELECT
t0.JDOID, t0.TITLE, t0.RESERVEPRICE, t1.BIDAMOUNT, t3.USERNAME
FROM AUCTIONITEM t0 INNER JOIN BID t1 ON t0.WINNINGBID_JDOID
= t1.JDOID
LEFT OUTER JOIN BIDDER t2 ON t1.BIDDER_JDOID = t2.JDOID
LEFT OUTER JOIN
REGISTEREDUSER t3 ON t2.USER_JDOID = t3.JDOID
WHERE (t1.BIDAMOUNT > t0.RESERVEPRICE)
Proiezione
di singoli campi
Ci sono due casi speciali di proiezione degni di essere
qui illustrati. Richiedono entrambi le proiezioni d'espressioni
con un singolo campo. Nel caso in cui la proiezione
contiene soltanto un campo, non è necessario
l'utilizzo di un contenitore Object[]. I risultati della
query sono poi una collezione di tipi del campo (o del
tipo di wrapper corrispondente) o, se la proprietà
unique della query ha valore "vero", una singola
istanza del tipo del campo (o del tipo di wrapper corrispondente).
Questi casi possono essere meglio illustrati mediante
due esempi.
JDOQL
(#20) per la proiezione di un campo singolo unique
Il primo esempio seleziona soltanto gli indirizzi email
degli utenti registrati. Il singolo campo è di
tipo String, ma poiché la query non è
unica il risultato è una collezione di oggetti
di tipo String.
Query
q = pm.newQuery (RegisteredUser.class);
q.setResult ("emailAddress");
Collection results = (Collection)q.execute ();
// l'esecuzione ritorna una collezione
Iterator iter = results.iterator();
while (iter.hasNext()) {
String email = (String) iter.next();
// la collezione contiene tipi String e non Object[]
// altra implementazione richiesta
}
Risultato
JDOQL (#21) per la proiezione di un
campo singolo unique
La seconda query seleziona l'indirizzo e-mail di uno
specifico utente. Qui è stata esplicitamente
posta uguale a "vero" la proprietà
unique della query, poiché ci si aspetta che
vi sia al più una sola istanza che soddisfa il
filtro. Il risultato è quindi un singolo oggetto
di tipo String, senza contenitore al contorno.
Query
q = pm.newQuery (RegisteredUser.class, "userName
== 'Sophie'");
q.setResult ("emailAddress");
q.setUnique(true);
String email = (String) q.execute();
// esecuzione ritorna un singolo oggetto di tipo String
Risultato
Selezione
di dati distinct per le offerte vincenti
Quando una query JDOQL restituisce le istanze persistenti
della classe candidata non vi è alcuna ragione
di rimuovere i duplicati; l'unicità delle istanze
nella collezione restituita è garantita da JDO.
Tuttavia, in una query che fa uso della proiezione (elencando
uno o più campi nella clausola result), è
facile che il risultato contenga duplicati.
Si consideri la seguente query che seleziona solo il
nome dell'offerente che ha fatto l'offerta vincente
per ogni articolo in cui il prezzo di riserva è
stato superato:
JDOQL (#22) senza distinct - kodo
workbench screenshot
Query
q = pm.newQuery (AuctionItem.class);
q.setResult ("winningBid.bidder.user.userName");
Collection results = (Collection)q.execute ();
Jaques e Sophie hanno entrambi piazzato offerte vincente
per più di un articolo.
A seconda del particolare caso d'uso dell'applicazione
può essere preferibile avere risultati distinti.
Rendere non duplicabili tali dati non può essere
realizzato in modo efficiente sul client e, qualora
si abbia a che fare con insiemi di dati di grandi dimensione,
potrebbe risultare al quanto impraticabile.
Per risolvere questo problema JDO 2.0 ha aggiunto il
supporto della parola chiave distinct, legittima solo
come prima parola nella clausola result della query.
Questa possibilità è tradotta in un'istruzione
SQL valida per il database sottostante al fine di rimuovere
i duplicati nel modo più efficiente possibile.
JDOQL
(#23) usando distinct
Ecco la query rivista:
Query
q = pm.newQuery (AuctionItem.class);
q.setResult ("distinct winningBid.bidder.user.userName");
Collection results = (Collection)q.execute ();
E
i risultati:
SQL
Infine ecco l'SQL che è stato generato:
SELECT
DISTINCT t3.USERNAME
FROM AUCTIONITEM t0 LEFT OUTER JOIN BID t1 ON t0.WINNINGBID_JDOID
=
t1.JDOID LEFT OUTER JOIN BIDDER t2 ON t1.BIDDER_JDOID
= t2.JDOID
LEFT OUTER JOIN REGISTEREDUSER t3 ON t2.USER_JDOID =
t3.JDOID
Selezione
di offerte vincenti con JavaBeans dedicati - Result
class
Senza proiezione una query JDOQL restituisce sempre
una o più istanze della classe candidata. La
proiezione permette a chi scrive la query di identificare
una lista di campi e di espressioni sui campi d'interesse;
quindi il risultato dell'esecuzione della query è
formato da uno o più Object[], ciascuno dei quali
contiene il valore del campo selezionato corrispondente
ad una delle istanze che soddisfa il criterio del filtro.
A volte, tuttavia, non è desiderabile ottenere
come risultato un Object[] per ogni istanza che soddisfa
il criterio del filtro. Lavorare con Object[] può
risultare difficoltoso, e una classe JavaBeans può
essere più appropriata. In questo contesto un
JavaBeans è una classe con un costruttore pubblico
senza parametri e con metodi get/set per ogni sua proprietà.
JDO 2.0 permette di specificare nelle query il descrittore
di una classe JavaBeans che dovrà contenere il
risultato della query. Questa è conosciuta come
"result class". I campi delle istanze che
soddisfano il criterio della query sono in corrispondenza
con proprietà di questa classe, un'istanza di
JavaBeans per ogni istanza persistente della classe
candidata che soddisfa il criterio della query. La corrispondenza
di default tra i campi della proiezione e le proprietà
del JavaBeans è basata sul nome stesso del campo,
ma se necessario l'autore della query può specificare
un'alias per ogni campo proiettato. L'alias specificato
viene poi usato per determinare la proprietà
corrispondente del JavaBeans.
Tecnicamente la result class può essere più
semplice di un vero e proprio JavaBeans; JDO non richiede
metodi accessori (get) per le proprietà ed è
legittimo usare campi pubblici anziché proprietà.
Proiezione
senza result class
Qui di seguito viene riportata una query d'esempio,
tratta da una precedente sezione, che proietta una coppia
di campi e di espressioni del campo raggiungibili per
gli articoli dell'asta nei quali i prezzi di riserva
sono stati superati.
Query q = pm.newQuery (AuctionItem.class,
"winningBid.bidAmount > reservePrice");
q.setResult ("title, reservePrice, winningBid.bidAmount,
winningBid.bidder.user.userName");
Collection results = (Collection)q.execute ();
Iterator iter = results.iterator();
while (iter.hasNext()) {
Object[] row = (Object[]) iter.next();
// additional processing as required
}
Risultati
Il risultato di questa query è una collezione
di Object[]. Per l'insieme di dati considerati, i risultati
erano:
Definizione
della result class JavaBeans
Ecco qui il codice sorgente di un JavaBeans realizzato
per contenere le stesse informazioni.
public
class WinningBidBean {
private String title;
private double reservePrice;
private double bestBid;
private String userName;
public String getTitle() {return title;}
public double getReservePrice()
{return reservePrice;}
public double getBestBid() {return bestBid;}
public String getUserName() {return userName;}
public void setTitle(String title)
{this.title = title;}
public void setReservePrice(double reservePrice)
{this.reservePrice = reservePrice;}
public void setBestBid(double bestBid)
{this.bestBid = bestBid;}
public void setUserName(String userName)
{this.userName = userName;}
}
Il costruttore di default è pubblico e senza
argomenti.
Proiezione
con la result class
Ora che abbiamo la classe JavaBeans è facile
far popolare alla query le istanze di WinningBidBean
anziché Object[]. A tal fine si deve inizializzare
la result class della query. E' anche necessario creare
un'alias per l'importo dell'offerta vincente, che deve
corrispondere alla proprietà "bestBid"
del bean. Anche il campo winningBid.bidder.user.userName
necessita di un'alias poiché non è un
campo della classe candidata, perciò gli è
stato esplicitamente assegnato l'alias "userName".
JDOQL (#24)
Qui di seguito viene riportata la query che ne risulta.
Query
q = pm.newQuery (AuctionItem.class,
"winningBid.bidAmount > reservePrice");
q.setResult ("title, reservePrice, winningBid.bidAmount
as bestBid, winningBid.bidder.user.userName as userName");
q.setResultClass(WinningBidBean.class);
Collection results = (Collection)q.execute ();
Iterator iter = results.iterator();
while (iter.hasNext()) {
WinningBidBean row = (WinningBidBean) iter.next();
// additional processing as required
}
Risultati
Naturalmente la quey selezionerà e restituirà
gli stessi valori. Tutta la differenza sta nel tipo
dell'elemento che contiene i valori restituiti. In questo
caso il risultato viene ritornato all'interno di una
collezione d'istanze di WinningBidBean.
Proiezione
in una concreta implementazione di Map
Su questo tema vi è una variazione finale degna
di nota. Invece di indicare una classe JavaBeans come
"result Class" di una query, si può
specificare una classe concreta che implementa l'interfaccia
java.util.Map. Ogni campo proiettato dalla query viene
copiato dentro l'oggetto map in corrispondenza di un
campo chiave che contiene il nome del campo stesso (o
l'alias).
JDOQL
(#25)
Questo esempio proietta gli stessi dati dentro una Map
ed illustra le chiavi in corrispondenza delle quali
risiedono i valori di ciascun campo.
Query
q = pm.newQuery (AuctionItem.class,
"winningBid.bidAmount > reservePrice");
q.setResult ("title, reservePrice, winningBid.bidAmount
as bestBid, winningBid.bidder.user.userName as userName");
q.setResultClass(HashMap.class);
Collection results = (Collection)q.execute ();
Iterator iter = results.iterator();
while (iter.hasNext()) {
Map row = (Map) iter.next();
System.out.println(row.get("title"));
System.out.println(row.get("reservePrice"));
System.out.println(row.get("bestBid"));
System.out.println(row.get("userName"));
}
Riepilogo
dei dati dell'Asta - Aggregazione
Storicamente JDOQL era focalizzato sul recupero dal
database di istanze di classi persistenti. Il risultato
restituito dall'esecuzione della query era una collezione
d'istanze che soddisfavano la condizione del filtro.
L'aggiunta della proiezione a JDOQL ha permesso di scrivere
query che recuperano strutture di dati anziché
istanze, mediante la specificazione di una clausola
result della query, clausola che elenca campi ed espressioni
dei campi delimitati da virgole. Mediante l'esecuzione
di una tale query vengono restituiti uno o più
Object[] o istanze di JavaBeans.
Un'altra nuova caratteristica di JDO 2.0 è l'aggiunta
delle funzioni aggregate al linguaggio di query. Qui
di seguito vengono riportate le funzioni aggregate supportate
e i loro tipi Java:
La
clausola result della query può contenere espressioni
di aggregazione in aggiunta ai campi e alle espressioni
dei campi. Ogni query che usa le espressioni di aggregazione
e non ha una clausola di raggruppamento (si veda dopo)
implicitamente restituisce al più una riga, così
non è necessario invocare esplicitamente il setUnique(true).
JDOQL
(#26)
Ecco la query nel quale sono illustrati le funzioni
d'aggregazione.
Query
q = pm.newQuery (AuctionItem.class);
q.setResult ("count(this), min(reservePrice), max(reservePrice),
avg(reservePrice), sum(reservePrice)");
// unique=true implicitly since the query result includes
aggregazione senza raggruppamento
// Object[] ritornato non è contenuto nella collezione.
Object[] result = (Object[]) q.execute ();
Risultato
Il campo reservePrice è di tipo double, quindi
le funzioni aggregate applicate ad esso restituiscono
istanze di Double. Count restituisce Long.
SQL
SELECT
COUNT(t0.JDOID), MIN(t0.RESERVEPRICE), MAX(t0.RESERVEPRICE),
AVG(t0.RESERVEPRICE), SUM(t0.RESERVEPRICE)
FROM AUCTIONITEM t0
Query unique con singolo aggregato
Se una query JDOQL è configurata come unique
(l'autore si aspetta non più di una riga che
soddisfa la condizione) allora il risultato della query
non è "confezionato" in una collezione.
Inoltre, se la clausola result della query contiene
solo una singola espressione, allora il valore risultante
non è "confezionato" in un Object[].
Infine, se la clausola result di una query contiene
soltanto espressioni aggregate, allora la query è
implicitamente unica. Così il caso semplice diventa
veramente molto semplice.
Qui sotto è riportata la query che seleziona
l'esposizione totale di un utente specifico. Questa
è la somma di tutte le offerte che hanno piazzato
e che attualmente sono le offerte vincenti.
JDOQL (#27)
Query
q = pm.newQuery(Bid.class, "bidder.user.userName==name
&& this == item.winningBid");
q.setResult ("sum(bidAmount)");
q.declareParameters ("String name");
Double result = (Double) q.execute("Sophie");
Risultato
Si noti che l'oggetto restituito dall'esecuzione della
query è un Double. Non è necessario utilizzare
collezioni e Object[] per estrarre il risultato voluto.
SQL
Ecco l'SQL che è stato eseguito rispetto allo
schema qui considerato per la base di dati:
SELECT
SUM(t0.BIDAMOUNT)
FROM BID t0 INNER JOIN BIDDER t1 ON t0.BIDDER_JDOID
=
t1.JDOID INNER JOIN REGISTEREDUSER t2 ON t1.USER_JDOID
=
t2.JDOID INNER JOIN AUCTIONITEM t3 ON t0.ITEM_JDOID
= t3.JDOID
WHERE (t2.USERNAME = 'Sophie' AND t0.JDOID = t3.WINNINGBID_JDOID)
Raggruppamento
di Risultati Aggregati
Poter aggregare risultati è incredibilmente utile.
Inoltre, con il raggruppamento l'aggregazione diventa
ancor più potente.
Si consideri la richiesta d'individuare le maggiori
offerte che un utente specifico ha piazzato. Mediante
il solo utilizzo dell'aggregazione possiamo stabilire
che la più grande offerta piazzata da Sophie
è pari a 18,75.
JDOQL (#28)
Query
q = pm.newQuery (Bid.class, "bidder.user.userName
== name");
q.setResult ("max(bidAmount)");
q.declareParameters ("String name");
Double result = (Double) q.execute ("Sophie");
SQL
SELECT
MAX(t0.BIDAMOUNT)
FROM BID t0 INNER JOIN BIDDER t1 ON t0.BIDDER_JDOID
= t1.JDOID
INNER JOIN REGISTEREDUSER t2 ON t1.USER_JDOID = t2.JDOID
WHERE (t2.USERNAME = 'Sophie')
Ma
non è forse vero che è certamente più
utile conoscere la miglior offerta che Sophie ha fatto
per ogni articolo dell'asta? Ciò può essere
ottenuto raggruppando il max(bidAmount) articolo per
articolo.
JDOQL
(#29)
Query
q = pm.newQuery (Bid.class, "bidder.user.userName==name");
q.setResult ("max(bidAmount), item.description");
q.setGrouping ("item");
q.declareParameters ("String name");
Collection results = (Collection) q.execute ("Sohpie");
Risultati
SQL
SELECT
MAX(t0.BIDAMOUNT), t3.DESCRIPTION
FROM BID t0 INNER JOIN BIDDER t1 ON t0.BIDDER_JDOID
= t1.JDOID INNER
JOIN REGISTEREDUSER t2 ON t1.USER_JDOID = t2.JDOID LEFT
OUTER JOIN
AUCTIONITEM t3 ON t0.ITEM_JDOID = t3.JDOID
WHERE (t2.USERNAME = 'Sophie')
GROUP BY t0.ITEM_JDOID
Having
Per concludere, forse sono considerate significative
soltanto le massime offerte superiori ad una soglia
fissata. I dati aggregati non possono essere filtrati
entro la clausola stessa del filtro, ma le condizioni
che includono gli aggregati possono essere specificate
nella clausola having.
L'esempio sotto raggruppa per le massime offerte per
articolo fatte da Sophie e mostra soltanto quelle per
cui l'offerta massima eccede 10.00.
JDOQL (#30)
Query
q = pm.newQuery(Bid.class, "bidder.user.userName==name");
q.setResult ("max(bidAmount), item.description");
q.setGrouping ("item having max(bidAmount) >
10.00");
q.declareParameters ("String name");
Collection results = (Collection) q.execute("Sophie");
SQL
Ecco l'SQL per l'esempio finale. Si tenga a mente che,
anche se non tutti i database supportano la clausola
HAVING, le query di JDOQL che usano questa caratteristica
sono del tutto portabili. Se manca il supporto del database,
l'esclusione del risultato sarà realizzata mediante
l'implementazione di JDO.
SELECT
MAX(t0.BIDAMOUNT), t3.DESCRIPTION
FROM BID t0 INNER JOIN BIDDER t1 ON t0.BIDDER_JDOID
= t1.JDOID INNER
JOIN REGISTEREDUSER t2 ON t1.USER_JDOID = t2.JDOID LEFT
OUTER JOIN
AUCTIONITEM t3 ON t0.ITEM_JDOID = t3.JDOID
WHERE (t2.USERNAME = 'Sophie')
GROUP BY t0.ITEM_JDOID HAVING MAX(t0.BIDAMOUNT) >
10
Robin
Roos è l'autore di of Java Data Objects (Addison-Wesley)
e membro del gruppo di esperti di JDO 2.0 (JSR-243)
e servizi professionali di VP presso SolarMetric.
|