Wie wähle ich mit Laravel Query Builder aus einer Unterabfrage aus?

102

Ich möchte mit Eloquent ORM einen Wert durch das folgende SQL erhalten.

- SQL

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

Dann habe ich folgendes bedacht.

- Code

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

Ich suche nach einer besseren Lösung.

Bitte sagen Sie mir die einfachste Lösung.

quenty658
quelle

Antworten:

131

Zusätzlich zu der Antwort von @ delmadord und Ihren Kommentaren:

Derzeit gibt es keine Methode zum Erstellen einer Unterabfrage in einer FROMKlausel. Daher müssen Sie die Raw-Anweisung manuell verwenden. Bei Bedarf werden dann alle Bindungen zusammengeführt:

$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();

Beachten Sie, dass Sie die Bindungen in der richtigen Reihenfolge zusammenführen müssen . Wenn Sie andere gebundene Klauseln haben, müssen Sie diese nach setzen 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();
Jarek Tkaczyk
quelle
3
Beachten Sie, dass Sie, wenn Sie eine komplexe Abfrage als belongsToManygetQuery()$sub->getQuery()->getQuery()
Unterauswahl
1
@Skyzer Du liest nicht was ich schreibe. Wenn Sie anrufen, wird nichts entkommen toSql. Lesen Sie über PDO php.net/manual/en/book.pdo.php und sehen Sie das Ergebnis Ihrer$query->toSql()
Jarek Tkaczyk
5
In Bezug auf -> mergeBindings ($ sub-> getQuery ()) tun Sie einfach -> mergeBindings ($ sub)
Jimmy Ilenloa
1
@JimmyIlenloa Wenn es sich bei der $subAbfrage um einen Eloquent Builder handelt , benötigen Sie das ->getQuery()Teil weiterhin, andernfalls wird eine Fehlermeldung angezeigt, da diese Methode für die Query\BuilderKlasse typisiert ist .
Jarek Tkaczyk
1
@ Kanan nein. Es ist ein Kandidat für eine PR, denke ich, aber am Ende ist das kein sehr häufiger Anwendungsfall. Wahrscheinlich ist dies der Grund dafür, dass es bis heute nicht dort ist.
Jarek Tkaczyk
76

Laravel v5.6.12 (14.03.2018) hinzugefügt fromSub()und fromRaw()Methoden zum Abfragen des Builders (# 23476) .

Die akzeptierte Antwort ist korrekt, kann aber vereinfacht werden in:

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

Das obige Snippet erzeugt das folgende SQL:

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

Die Lösung von @JarekTkaczyk ist genau das, wonach ich gesucht habe. Das einzige, was ich vermisse, ist, wie es geht, wenn Sie DB::table()Abfragen verwenden. In diesem Fall mache ich das so:

$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();

Besondere Aufmerksamkeit, wie man das mergeBindingsohne Verwendung der getQuery()Methode macht

Thiago Mata
quelle
Mit DB::raw()hat den Job für mich
erledigt
7

Ab Laravel 5.5 gibt es eine spezielle Methode für Unterabfragen, die Sie folgendermaßen verwenden können:

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

oder

Abc::selectSub(Abc::select('*')->groupBy('col1'), 'a')->count('a.*');
Sasa Blagojevic
quelle
1
Es scheint, dass subSelect nur verwendet werden kann, um SELECT eine Unterabfrage hinzuzufügen, nicht FROM.
Hagabaka
1
Call to undefined method subSelect()scheint subSelectnicht zu existieren.
Maruf Alom
3
Vielen Dank, dass Sie mich darauf aufmerksam gemacht haben. Ich habe den Namen falsch geschrieben, es hätte sein sollen selectSub. Ich habe meine Antwort jetzt aktualisiert.
Sasa Blagojevic
3

Ich mache gerne so etwas:

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

Es ist nicht sehr elegant, aber es ist einfach.

Guy Mazuz
quelle
Vielen Dank, dass dies für mich funktioniert hat. Seien Sie vorsichtig mit dem ausgewählten Inhalt, da Laravel einige Anführungszeichen hinzugefügt hat und ich -> select (\ DB :: raw ('Your select')) verwenden musste, um sie zu entfernen.
Wak
2

Ich konnte Ihren Code nicht für die gewünschte Abfrage erstellen. Der AS ist nur ein Alias ​​für die Tabelle abc, nicht für die abgeleitete Tabelle. Laravel Query Builder unterstützt nicht implizit abgeleitete Tabellenaliasnamen, DB :: raw wird höchstwahrscheinlich dafür benötigt.

Die einfachste Lösung, die ich finden konnte, ist fast identisch mit Ihrer, erzeugt jedoch die Abfrage, wie Sie sie angefordert haben:

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

Die erzeugte Abfrage ist

select count(*) as aggregate from (select * from `abc` group by `col1`) AS a;
peter.babic
quelle
Danke für Ihre Antwort. Es gibt ein Problem bei der Methode "Abc :: from (???) und DB :: table (???)". $ sql = Abc :: where ('id', '=', $ id) -> groupBy ('col1') -> toSql (); $ count = DB :: table (DB :: raw ("($ sql) AS a")) -> count (); SQL-Fehler treten im obigen Code auf. - wo und Parameter zuweisen!
Quenty658
2

In dieser Antwort beschriebener korrekter Weg: https://stackoverflow.com/a/52772444/2519714 Die derzeit beliebteste Antwort ist nicht ganz korrekt.

Auf diese Weise ist https://stackoverflow.com/a/24838367/2519714 in einigen Fällen nicht korrekt, z. B.: Unterauswahl hat wo Bindungen, dann Verknüpfung von Tabelle zu Unterauswahl, dann werden andere Abfragen zu allen Abfragen hinzugefügt. Beispiel: Abfrage: select * from (select * from t1 where col1 = ?) join t2 on col1 = col2 and col3 = ? where t2.col4 = ? Um diese Abfrage durchzuführen, schreiben Sie Code wie folgt:

$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');

Während der Ausführung dieser Abfrage gibt seine Methode $query->getBindings()Bindungen in falscher Reihenfolge zurück, wie ['val3', 'val1', 'val4']in diesem Fall, stattdessen korrekt ['val1', 'val3', 'val4']für die oben beschriebene unformatierte SQL.

Noch ein richtiger Weg, dies zu tun:

$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');

Auch Bindungen werden automatisch und korrekt mit einer neuen Abfrage zusammengeführt.

dkop
quelle
Vielen Dank! Es hat sehr geholfen!
Hasnat Babur