Come selezionare una sottoquery utilizzando Laravel Query Builder?


102

Vorrei ottenere valore dal seguente SQL utilizzando Eloquent ORM.

- SQL

 SELECT COUNT(*) FROM 
 (SELECT * FROM abc GROUP BY col1) AS a;

Poi ho considerato quanto segue.

- Codice

 $sql = Abc::from('abc AS a')->groupBy('col1')->toSql();
 $num = Abc::from(\DB::raw($sql))->count();
 print $num;

Sto cercando una soluzione migliore.

Per favore dimmi la soluzione più semplice.

Risposte:


131

Oltre alla risposta di @ delmadord e ai tuoi commenti:

Attualmente non esiste un metodo per creare sottoquery nella FROMclausola, quindi è necessario utilizzare manualmente l'istruzione raw, quindi, se necessario, unirai tutti i collegamenti:

$sub = Abc::where(..)->groupBy(..); // Eloquent Builder instance

$count = DB::table( DB::raw("({$sub->toSql()}) as sub") )
    ->mergeBindings($sub->getQuery()) // you need to get underlying Query Builder
    ->count();

Tieni presente che devi unire le associazioni nell'ordine corretto . Se hai altre clausole vincolate, devi metterle dopo mergeBindings:

$count = DB::table( DB::raw("({$sub->toSql()}) as sub") )

    // ->where(..) wrong

    ->mergeBindings($sub->getQuery()) // you need to get underlying Query Builder

    // ->where(..) correct

    ->count();

3
Nota che se hai una query complessa belongsToManycome sottoselezione devi aggiungere getQuery()due volte =>$sub->getQuery()->getQuery()
Jordi Puigdellívol

1
@Skyzer Non stai leggendo quello che scrivo. Niente è sfuggito quando chiami toSql. Leggi di PDO php.net/manual/en/book.pdo.php e guarda il risultato del tuo$query->toSql()
Jarek Tkaczyk

5
Per quanto riguarda -> mergeBindings ($ sub-> getQuery ()) basta fare -> mergeBindings ($ sub)
Jimmy Ilenloa

1
@JimmyIlenloa Se la $subquery è un Eloquent Builder , allora hai ancora bisogno della ->getQuery()parte, altrimenti ottieni un errore, poiché questo metodo è tipizzato rispetto alla Query\Builderclasse.
Jarek Tkaczyk

1
@Kannan no. è un candidato per un PR immagino, ma alla fine non è un caso d'uso molto comune. Probabilmente questo è il motivo per non averlo lì fino ad oggi ..
Jarek Tkaczyk

76

Laravel v5.6.12 (2018-03-14) aggiunti fromSub()e fromRaw()metodi per Query Builder (# 23476) .

La risposta accettata è corretta ma può essere semplificata in:

DB::query()->fromSub(function ($query) {
    $query->from('abc')->groupBy('col1');
}, 'a')->count();

Lo snippet di cui sopra produce il seguente SQL:

select count(*) as aggregate from (select * from `abc` group by `col1`) as `a`

15

La soluzione di @JarekTkaczyk è esattamente quello che stavo cercando. L'unica cosa che mi manca è come farlo quando usi le DB::table()query. In questo caso, ecco come lo faccio:

$other = DB::table( DB::raw("({$sub->toSql()}) as sub") )->select(
    'something', 
    DB::raw('sum( qty ) as qty'), 
    'foo', 
    'bar'
);
$other->mergeBindings( $sub );
$other->groupBy('something');
$other->groupBy('foo');
$other->groupBy('bar');
print $other->toSql();
$other->get();

Particolare attenzione a come fare mergeBindingssenza usare il getQuery()metodo


L'utilizzo ha DB::raw()fatto il lavoro per me
Nino Škopac

7

Da laravel 5.5 esiste un metodo dedicato per le sottoquery e puoi usarlo in questo modo:

Abc::selectSub(function($q) {
    $q->select('*')->groupBy('col1');
}, 'a')->count('a.*');

o

Abc::selectSub(Abc::select('*')->groupBy('col1'), 'a')->count('a.*');

1
Sembra che subSelect possa essere utilizzato solo per aggiungere una sottoquery a SELECT, non FROM.
hagabaka

1
Call to undefined method subSelect()sembra subSelectche non esista.
Maruf Alom

3
Grazie per avermelo fatto notare, ho scritto male il nome, avrebbe dovuto essere selectSub. Ho aggiornato la mia risposta ora.
Sasa Blagojevic

3

Mi piace fare qualcosa del genere:

Message::select('*')
->from(DB::raw("( SELECT * FROM `messages`
                  WHERE `to_id` = ".Auth::id()." AND `isseen` = 0
                  GROUP BY `from_id` asc) as `sub`"))
->count();

Non è molto elegante, ma è semplice.


Grazie, questo ha funzionato per me, come nota a margine, fai attenzione con il contenuto selezionato perché laravel ha aggiunto alcune virgolette e ho dovuto usare -> select (\ DB :: raw ('Your select')) per sbarazzartene.
Sveglia

2

Non sono riuscito a creare il tuo codice per eseguire la query desiderata, l'AS è un alias solo per la tabella abc, non per la tabella derivata. Laravel Query Builder non supporta implicitamente gli alias di tabella derivata, DB :: raw è molto probabilmente necessario per questo.

La soluzione più semplice che ho potuto trovare è quasi identica alla tua, tuttavia produce la query che hai richiesto:

$sql = Abc::groupBy('col1')->toSql();
$count = DB::table(DB::raw("($sql) AS a"))->count();

La query prodotta è

select count(*) as aggregate from (select * from `abc` group by `col1`) AS a;

Grazie per la risposta. C'è un problema nel metodo di "Abc :: from (???) e DB :: table (???)". $ sql = Abc :: where ('id', '=', $ id) -> groupBy ('col1') -> toSql (); $ count = DB :: table (DB :: raw ("($ sql) AS a")) -> count (); Si verifica un errore SQL nel codice precedente. - dove e parametrizzazione!
quenty658

2

Modo corretto descritto in questa risposta: https://stackoverflow.com/a/52772444/2519714 La risposta più popolare al momento attuale non è del tutto corretta.

In questo modo https://stackoverflow.com/a/24838367/2519714 non è corretto in alcuni casi come: sub select ha i collegamenti dove, quindi unendo la tabella alla sottoselezione, quindi altri luoghi aggiunti a tutte le query. Ad esempio query: select * from (select * from t1 where col1 = ?) join t2 on col1 = col2 and col3 = ? where t2.col4 = ? per fare questa query scriverai codice come:

$subQuery = DB::query()->from('t1')->where('t1.col1', 'val1');
$query = DB::query()->from(DB::raw('('. $subQuery->toSql() . ') AS subquery'))
    ->mergeBindings($subQuery->getBindings());
$query->join('t2', function(JoinClause $join) {
    $join->on('subquery.col1', 't2.col2');
    $join->where('t2.col3', 'val3');
})->where('t2.col4', 'val4');

Durante l'esecuzione di questa query, il suo metodo $query->getBindings()restituirà i collegamenti in un ordine errato come ['val3', 'val1', 'val4']in questo caso invece corretto ['val1', 'val3', 'val4']per sql grezzo descritto sopra.

Ancora una volta il modo corretto per farlo:

$subQuery = DB::query()->from('t1')->where('t1.col1', 'val1');
$query = DB::query()->fromSub($subQuery, 'subquery');
$query->join('t2', function(JoinClause $join) {
    $join->on('subquery.col1', 't2.col2');
    $join->where('t2.col3', 'val3');
})->where('t2.col4', 'val4');

Anche i collegamenti verranno automaticamente e correttamente uniti alla nuova query.


Molte grazie! Ha aiutato molto!
Hasnat Babur
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.