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.

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

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`

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


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.*');

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.


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;

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.


Wielkie dzięki! To bardzo pomogło!
— Hasnat Babur
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.