Jak wybrać z podzapytania za pomocą Laravel Query Builder?

102

Chciałbym uzyskać wartość za pomocą następującego kodu SQL przy użyciu elokwentnego ORM.

- SQL

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

Następnie rozważałem następujące kwestie.

- Kod

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

Szukam lepszego rozwiązania.

Proszę powiedz mi najprostsze rozwiązanie.

quenty658
źródło

Odpowiedzi:

133

Oprócz odpowiedzi @ delmadord i twoich komentarzy:

Obecnie nie ma metody tworzenia podzapytania w FROMklauzuli, więc musisz ręcznie użyć instrukcji raw, a następnie, jeśli to konieczne, scalisz wszystkie powiązania:

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

Pamiętaj, że musisz scalić wiązania w odpowiedniej kolejności . Jeśli masz inne powiązane klauzule, musisz je umieścić po 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
źródło
3
Zwróć uwagę, że jeśli masz złożone zapytanie jako belongsToManypodselekcja, musisz dodać getQuery()dwukrotnie =>$sub->getQuery()->getQuery()
Jordi Puigdellívol
1
@Skyzer Nie czytasz tego, co piszę. Nic nie ucieknie, gdy zadzwonisz toSql. Przeczytaj o PDO php.net/manual/en/book.pdo.php i zobacz wynik swojego$query->toSql()
Jarek Tkaczyk
5
W odniesieniu do -> mergeBindings ($ sub-> getQuery ()) po prostu zrób -> mergeBindings ($ sub)
Jimmy Ilenloa
1
@JimmyIlenloa Jeśli $subzapytanie jest elokwentnym konstruktorem , nadal potrzebujesz ->getQuery()części, w przeciwnym razie pojawi się błąd, ponieważ ta metoda jest wskazana na typ Query\Builderklasy.
Jarek Tkaczyk
1
@Kannan nope. to chyba kandydat na PR, ale ostatecznie nie jest to zbyt powszechny przypadek użycia. Prawdopodobnie to jest powód, dla nie mając go tam do dziś ..
Jarek Tkaczyk
80

Dodano Laravel v5.6.12 (14.03.2018) fromSub()i fromRaw()metody do konstruktora zapytań (# 23476) .

Przyjęta odpowiedź jest poprawna, ale można ją uprościć do:

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

Powyższy fragment kodu generuje następujący kod SQL:

select count(*) as aggregate from (select * from `abc` group by `col1`) as `a`
mpskovvang
źródło
16

Rozwiązanie @JarekTkaczyk jest dokładnie tym, czego szukałem. Jedyne, czego mi brakuje, to jak to zrobić, gdy używasz DB::table()zapytań. W tym przypadku tak to robię:

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

Specjalna uwaga jak wykonać mergeBindingsbez użycia getQuery()metody

Thiago Mata
źródło
Używanie DB::raw()wykonało pracę za mnie
Nino Škopac
7

Od laravel 5.5 istnieje dedykowana metoda dla podzapytań i możesz jej używać w następujący sposób:

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

lub

Abc::selectSub(Abc::select('*')->groupBy('col1'), 'a')->count('a.*');
Sasa Blagojevic
źródło
1
Wygląda na to, że subSelect może być użyte tylko do dodania zapytania podrzędnego do SELECT, a nie FROM.
hagabaka
1
Call to undefined method subSelect()wygląda na to, subSelectże nie istnieje.
Maruf Alom,
3
Dziękuję za zwrócenie mi uwagi, błędnie napisałem imię, a powinno selectSub. Zaktualizowałem teraz moją odpowiedź.
Sasa Blagojevic
3

Lubię robić coś takiego:

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

Nie jest zbyt elegancki, ale jest prosty.

Guy Mazuz
źródło
Dzięki temu zadziałało dla mnie, na marginesie, uważaj na wybrane treści, ponieważ laravel dodał kilka cudzysłowów i musiałem użyć -> select (\ DB :: raw ('Your select')), aby się ich pozbyć.
Wak
2

Nie mogłem zrobić twojego kodu, aby wykonać żądane zapytanie, AS jest aliasem tylko dla tabeli abc , a nie dla tabeli pochodnej. Laravel Query Builder nie obsługuje niejawnie pochodnych aliasów tabel, najprawdopodobniej do tego potrzebny jest DB :: raw.

Najprostsze rozwiązanie, jakie mogłem wymyślić, jest prawie identyczne z twoim, jednak generuje zapytanie, o które prosiłeś:

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

Utworzone zapytanie to

select count(*) as aggregate from (select * from `abc` group by `col1`) AS a;
peter.babic
źródło
Dziękuję za odpowiedź. Występuje problem w metodzie „Abc :: from (???) i DB :: table (???)”. $ sql = Abc :: where ('id', '=', $ id) -> groupBy ('col1') -> toSql (); $ count = DB :: table (DB :: raw ("($ sql) AS a")) -> count (); W powyższym kodzie wystąpił błąd SQL. - gdzie i przypisać parametry!
quenty658
2

Poprawny sposób opisany w tej odpowiedzi: https://stackoverflow.com/a/52772444/2519714 Najpopularniejsza obecnie odpowiedź nie jest do końca poprawna.

W ten sposób https://stackoverflow.com/a/24838367/2519714 nie jest poprawny w niektórych przypadkach, takich jak: sub select ma gdzie powiązania, następnie dołącza tabelę do sub select, a następnie inne gdzie są dodawane do wszystkich zapytań. Na przykład zapytanie: select * from (select * from t1 where col1 = ?) join t2 on col1 = col2 and col3 = ? where t2.col4 = ? Aby wykonać to zapytanie, napiszesz kod taki jak:

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

Podczas wykonywania tego zapytania jego metoda $query->getBindings()zwróci powiązania w nieprawidłowej kolejności, jak ['val3', 'val1', 'val4']w tym przypadku, zamiast poprawnej ['val1', 'val3', 'val4']dla surowego sql opisanego powyżej.

Jeszcze raz poprawny sposób, aby to zrobić:

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

Również powiązania zostaną automatycznie i poprawnie połączone z nowym zapytaniem.

dkop
źródło
Wielkie dzięki! To bardzo pomogło!
Hasnat Babur