Laravel 데이터베이스: 쿼리 빌더

Jmnote (토론 | 기여)님의 2024년 6월 29일 (토) 12:55 판 (→‎참고)

1 개요

Crystal Clear action info.png 작성 중인 문서입니다.
Database: Query Builder
데이터: 쿼리 빌더

https://laravel.com/docs/11.x/queries

2 소개

Laravel의 데이터베이스 쿼리 빌더는 데이터베이스 쿼리를 생성하고 실행하기 위한 편리하고 유연한 인터페이스를 제공합니다. 이 빌더는 애플리케이션에서 대부분의 데이터베이스 작업을 수행하는 데 사용할 수 있으며, Laravel이 지원하는 모든 데이터베이스 시스템과 완벽하게 호환됩니다.

Laravel 쿼리 빌더는 SQL 인젝션 공격으로부터 애플리케이션을 보호하기 위해 PDO 파라미터 바인딩을 사용합니다. 쿼리 빌더에 전달되는 문자열을 클리닝(clean)하거나 정화(sanitize)할 필요가 없습니다.

PDO는 컬럼 이름 바인딩을 지원하지 않습니다. 따라서 사용자 입력이 쿼리에서 참조되는 컬럼 이름, 특히 "order by" 컬럼을 결정하도록 허용해서는 안 됩니다.

3 데이터베이스 쿼리 실행

테이블에서 모든 행 조회

다음은 DB 파사드에서 제공하는 table 메소드를 사용하여 쿼리를 시작하는 방법에 대한 설명입니다. table 메소드는 주어진 테이블에 대한 유창한 쿼리 빌더 인스턴스를 반환하며, 이를 통해 쿼리에 더 많은 제약조건을 연쇄적으로 추가하고, 마지막으로 get 메소드를 사용하여 쿼리 결과를 검색할 수 있습니다:

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
 
class UserController extends Controller
{
    /**
     * Show a list of all of the application's users.
     */
    public function index(): View
    {
        $users = DB::table('users')->get();
 
        return view('user.index', ['users' => $users]);
    }
}

get 메소드는 쿼리 결과를 포함하는 Illuminate\Support\Collection 인스턴스를 반환하며, 각 결과는 PHP의 stdClass 객체의 인스턴스입니다. 각 컬럼의 값은 객체의 속성으로 접근하여 가져올 수 있습니다.

use Illuminate\Support\Facades\DB;
 
$users = DB::table('users')->get();
 
foreach ($users as $user) {
    echo $user->name;
}

Laravel 컬렉션은 데이터를 매핑하고 축소하는 데 매우 강력한 다양한 메소드를 제공합니다. Laravel 콜렉션에 대한 자세한 내용은 콜렉션 문서를 참조하십시오.

테이블에서 단일 행 / 컬럼 조회

데이터베이스 테이블에서 단일 행만 검색해야 하는 경우, DB 파사드의 first 메소드를 사용할 수 있습니다. 이 메소드는 단일 stdClass 객체를 반환합니다:

$user = DB::table('users')->where('name', 'John')->first();
 
return $user->email;

전체 행이 필요하지 않다면, value 메소드를 사용하여 레코드에서 단일 값을 추출할 수 있습니다. 이 메소드는 해당 컬럼의 값을 직접 반환합니다:

$email = DB::table('users')->where('name', 'John')->value('email');

id 컬럼 값을 기준으로 단일 행을 검색하려면, find 메소드를 사용하세요:

$user = DB::table('users')->find(3);
컬럼 값 목록 조회

단일 컬럼의 값을 포함하는 Illuminate\Support\Collection 인스턴스를 검색하려면 pluck 메소드를 사용할 수 있습니다. 이 예제에서는 사용자 타이틀의 콜렉션을 검색합니다:

use Illuminate\Support\Facades\DB;
 
$titles = DB::table('users')->pluck('title');
 
foreach ($titles as $title) {
    echo $title;
}

결과 콜렉션이 사용할 키를 지정하려면 pluck 메소드의 두 번째 인수로 지정할 수 있습니다:

$titles = DB::table('users')->pluck('title', 'name');
 
foreach ($titles as $name => $title) {
    echo $title;
}

3.1 결과 청킹

수천 개의 데이터베이스 레코드를 작업해야 하는 경우, DB 파사드에서 제공하는 chunck 메소드를 사용하는 것을 고려하십시오. 이 메소드는 한 번에 작은 청크의 결과를 가져와 각 청크를 처리할 클로저로 전달합니다. 예를 들어, 한 번에 100개의 레코드를 청크로 나누어 전체 사용자 테이블을 가져오는 방법은 다음과 같습니다:

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
 
DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) {
    foreach ($users as $user) {
        // ...
    }
});

클로저에서 false를 반환하여 추가 청크 처리를 중단할 수 있습니다:

DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) {
    // 레코드 처리...
 
    return false;
});

결과를 청크로 나누는 동안 데이터베이스 레코드를 업데이트하는 경우, 청크 결과가 예기치 않게 변경될 수 있습니다. 청크로 나눈 후 검색된 레코드를 업데이트할 계획이라면, 항상 chunkById 메소드를 사용하는 것이 좋습니다. 이 메소드는 레코드의 기본키를 기준으로 자동으로 결과를 페이지로 나눕니다:

DB::table('users')->where('active', false)
    ->chunkById(100, function (Collection $users) {
        foreach ($users as $user) {
            DB::table('users')
                ->where('id', $user->id)
                ->update(['active' => true]);
        }
    });

청크 콜백 내부에서 레코드를 업데이트하거나 삭제할 때, 기본키나 외래키에 대한 변경사항이 청크 쿼리에 영향을 줄 수 있습니다. 이로 인해 레코드가 청크 결과에 포함되지 않을 수 있습니다.

3.2 게으른(lazy) 결과 스트리밍

lazy 메소드는 chunk 메소드와 유사하게 쿼리를 청크 단위로 실행합니다. 그러나 각 청크를 콜백으로 전달하는 대신, lazy() 메소드는 LazyCollection을 반환하여 결과를 단일 스트림으로 처리할 수 있게 합니다:

use Illuminate\Support\Facades\DB;
 
DB::table('users')->orderBy('id')->lazy()->each(function (object $user) {
    // ...
});

마찬가지로, 검색된 레코드를 반복하면서 업데이트하려는 경우 lazyById 또는 lazyByIdDesc 메소드를 사용하는 것이 좋습니다. 이 메소드들은 레코드의 기본키를 기준으로 결과를 자동으로 페이지화합니다:

DB::table('users')->where('active', false)
    ->lazyById()->each(function (object $user) {
        DB::table('users')
            ->where('id', $user->id)
            ->update(['active' => true]);
    });

레코드를 반복하면서 업데이트하거나 삭제할 때, 기본 키 또는 외래 키에 대한 변경은 청크 쿼리에 영향을 미칠 수 있습니다. 이로 인해 레코드가 결과에 포함되지 않을 수 있습니다.

3.3 집계

쿼리 빌더는 count, max, min, avg, sum과 같은 집계 값을 조회하기 위한 다양한 메소드를 제공합니다. 쿼리를 작성한 후 이러한 메소드 중 하나를 호출할 수 있습니다:

use Illuminate\Support\Facades\DB;
 
$users = DB::table('users')->count();
 
$price = DB::table('orders')->max('price');

물론, 이러한 메소드를 다른 절과 결합하여 집계 값을 세밀하게 조정할 수 있습니다:

$price = DB::table('orders')
                ->where('finalized', 1)
                ->avg('price');
레코드 존재 여부 확인

쿼리의 제약 조건에 맞는 레코드가 존재하는지 확인하기 위해 count 메소드를 사용하는 대신, existsdoesntExist 메소드를 사용할 수 있습니다:

if (DB::table('orders')->where('finalized', 1)->exists()) {
    // ...
}
 
if (DB::table('orders')->where('finalized', 1)->doesntExist()) {
    // ...
}

4 Select 문

Select 절 지정하기

데이터베이스 테이블에서 모든 컬럼을 선택하고 싶지 않을 때가 있습니다. select 메소드를 사용하여 쿼리에 대한 커스텀 "select" 절을 지정할 수 있습니다:

use Illuminate\Support\Facades\DB;
 
$users = DB::table('users')
            ->select('name', 'email as user_email')
            ->get();

distinct 메소드를 사용하면 쿼리가 고유한 결과를 반환하도록 강제할 수 있습니다:

$users = DB::table('users')->distinct()->get();

이미 쿼리 빌더 인스턴스를 가지고 있고 기존 select 절에 열을 추가하려면 addSelect 메소드를 사용할 수 있습니다:

$query = DB::table('users')->select('name');
 
$users = $query->addSelect('age')->get();

5 원시 표현

때로는 쿼리에 임의의 문자열을 삽입해야 할 때가 있습니다. 원시 문자열 표현을 만들기 위해서는 DB 파사드에서 제공하는 raw 메소드를 사용할 수 있습니다.

$users = DB::table('users')
             ->select(DB::raw('count(*) as user_count, status'))
             ->where('status', '<>', 1)
             ->groupBy('status')
             ->get();

원시 문은 문자열로 쿼리에 삽입되므로 SQL 인젝션 취약성을 피하기 위해 매우 신중해야 합니다.

원시 메소드

DB::raw 메소드 대신에 다양한 쿼리의 부분에 원시 표현식을 삽입하기 위해 다음 메소드들을 사용할 수 있습니다. 원시 표현식을 사용하는 쿼리는 SQL 인젝션 취약성으로부터 보호되지 않을 수 있음을 유의하십시오.

selectRaw

selectRaw 메소드는 addSelect(DB::raw(/* ... */)) 대신 사용할 수 있습니다. 이 메소드는 선택적으로 두 번째 인수로 바인딩 배열을 받습니다:

$orders = DB::table('orders')
                ->selectRaw('price * ? as price_with_tax', [1.0825])
                ->get();
where Raw / orWhereRaw

whereRaworWhereRaw 메소드는 쿼리에 원시 "where" 절을 삽입하는 데 사용됩니다. 이 메소드는 선택적으로 두 번째 인수로 바인딩 배열을 받습니다:

$orders = DB::table('orders')
                ->whereRaw('price > IF(state = "TX", ?, 100)', [200])
                ->get();
havingRaw / orHavingRaw

havingRaworHavingRaw 메소드는 "having" 절의 값으로 원시 문자열을 제공하는 데 사용됩니다. 이 메소드는 선택적으로 두 번째 인수로 바인딩 배열을 받습니다:

$orders = DB::table('orders')
                ->select('department', DB::raw('SUM(price) as total_sales'))
                ->groupBy('department')
                ->havingRaw('SUM(price) > ?', [2500])
                ->get();
orderByRaw

orderByRaw 메소드는 "order by" 절의 값으로 원시 문자열을 제공하는 데 사용됩니다:

$orders = DB::table('orders')
                ->orderByRaw('updated_at - created_at DESC')
                ->get();
groupByRaw

groupByRaw 메소드는 "group by" 절의 값으로 원시 문자열을 제공하는 데 사용됩니다:

$orders = DB::table('orders')
                ->select('city', 'state')
                ->groupByRaw('city, state')
                ->get();

6 조인

7 유니온

8 기본 Where 절

8.1 Where 절

쿼리 빌더의 where 메소드를 사용하여 쿼리에 "where" 절을 추가할 수 있습니다. where 메소드의 가장 기본적인 호출에는 세 개의 인수가 필요합니다. 첫 번째 인수는 컬럼의 이름입니다. 두 번째 인수는 데이터베이스에서 지원하는 연산자 중 하나입니다. 세 번째 인수는 컬럼의 값과 비교할 값입니다.

예를 들어, 다음 쿼리는 votes 컬럼의 값이 100이고 age 컬럼의 값이 35보다 큰 사용자를 조회합니다:

$users = DB::table('users')
                ->where('votes', '=', 100)
                ->where('age', '>', 35)
                ->get();

편의를 위해, 컬럼이 주어진 값과 같음(=)을 확인하려는 경우, 두 번째 인수로 값을 전달할 수 있습니다. Laravel은 = 연산자를 사용하려 한다고 가정합니다:

$users = DB::table('users')->where('votes', 100)->get();

앞서 언급했듯이, 데이터베이스 시스템에서 지원하는 모든 연산자를 사용할 수 있습니다:

$users = DB::table('users')
                ->where('votes', '>=', 100)
                ->get();
 
$users = DB::table('users')
                ->where('votes', '<>', 100)
                ->get();
 
$users = DB::table('users')
                ->where('name', 'like', 'T%')
                ->get();

또한 조건의 배열을 where 함수에 전달할 수도 있습니다. 배열의 각 요소는 일반적으로 where 메소드에 전달되는 세 개의 인수를 포함하는 배열이어야 합니다:

$users = DB::table('users')->where([
    ['status', '=', '1'],
    ['subscribed', '<>', '1'],
])->get();

PDO는 컬럼 이름 바인딩을 지원하지 않습니다. 따라서 쿼리에서 참조되는 컬럼 이름을 사용자 입력에 의해 결정되도록 해서는 안 됩니다. 여기에는 "order by" 컬럼이 포함됩니다.

8.2 Or Where 절

Query Builder의 where 메소드를 연속으로 호출할 때, "where" 절들은 and 연산자로 결합됩니다. 그러나 orWhere 메소드를 사용하여 or 연산자로 절을 쿼리에 추가할 수 있습니다. orWhere 메소드는 where 메소드와 동일한 인수를 받습니다:

$users = DB::table('users')
                    ->where('votes', '>', 100)
                    ->orWhere('name', 'John')
                    ->get();

괄호 안에 "or" 조건을 그룹화해야 하는 경우, orWhere 메소드의 첫 번째 인수로 클로저를 전달할 수 있습니다:

$users = DB::table('users')
            ->where('votes', '>', 100)
            ->orWhere(function (Builder $query) {
                $query->where('name', 'Abigail')
                      ->where('votes', '>', 50);
            })
            ->get();

위의 예제는 다음 SQL을 생성합니다:

select * from users where votes > 100 or (name = 'Abigail' and votes > 50)

전역 스코프가 적용될 때 예기치 않은 동작을 방지하기 위해 항상 orWhere 호출을 그룹화해야 합니다.

8.3 Where Not 절

whereNotorWhereNot 메소드는 주어진 그룹의 쿼리 제약 조건을 부정하는 데 사용할 수 있습니다. 예를 들어, 다음 쿼리는 클리어런스 중이거나 가격이 10보다 적은 제품을 제외합니다:

$products = DB::table('products')
                ->whereNot(function (Builder $query) {
                    $query->where('clearance', true)
                          ->orWhere('price', '<', 10);
                })
                ->get();

8.4 Where Any / All 절

8.5 JSON Where 절

8.6 추가 Where 절

8.7 논리적 그룹화

9 고급 Where 절

9.1 Where Exists 조항

9.2 서브쿼리 Where 절

9.3 풀텍스트 Where 절

10 정렬, 그룹화, 리미트, 오프셋

10.1 정렬

10.2 그룹화

10.3 리미트, 오프셋

11 조건절

12 Insert 문

12.1 Upsert

13 Update 문

13.1 JSON 컬럼 업데이트

13.2 증가 및 감소

14 Delete 문

15 비관적 락

16 디버깅

문서 댓글 ({{ doc_comments.length }})
{{ comment.name }} {{ comment.created | snstime }}