데이터베이스: 쿼리 빌더

번역일: 2026년 6월 25일

데이터베이스: 쿼리 빌더

소개

Laravel의 데이터베이스 쿼리 빌더는 데이터베이스 쿼리를 편리하고 유창하게(fluent) 작성·실행할 수 있는 인터페이스를 제공합니다. 애플리케이션에서 발생하는 대부분의 데이터베이스 작업을 처리할 수 있으며, Laravel이 지원하는 모든 데이터베이스 시스템과 완벽하게 동작합니다.

쿼리 빌더는 PDO 파라미터 바인딩을 사용하여 SQL 인젝션 공격으로부터 애플리케이션을 보호합니다. 쿼리 빌더에 전달하는 문자열을 별도로 정제하거나 이스케이프 처리할 필요가 없습니다.

WARNING

PDO는 컬럼명 바인딩을 지원하지 않습니다. 따라서 "order by" 컬럼을 포함하여 쿼리에서 참조하는 컬럼명을 사용자 입력으로 결정하게 해서는 안 됩니다.

데이터베이스 쿼리 실행

테이블의 모든 행 조회

DB 파사드의 table 메서드로 쿼리를 시작할 수 있습니다. table 메서드는 지정한 테이블에 대한 쿼리 빌더 인스턴스를 반환하며, 이를 통해 다양한 조건을 체이닝한 뒤 get 메서드로 결과를 조회할 수 있습니다.

<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\DB; use Illuminate\View\View; class UserController extends Controller { /** * 애플리케이션의 모든 사용자 목록을 표시합니다. */ 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; }

NOTE

Laravel 컬렉션은 데이터 매핑과 가공을 위한 강력한 메서드를 다양하게 제공합니다. 자세한 내용은 컬렉션 문서를 참고하세요.

단일 행 / 컬럼 조회

테이블에서 단일 행만 조회하려면 DB 파사드의 first 메서드를 사용하세요. 단일 stdClass 객체를 반환합니다.

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

일치하는 행이 없을 때 Illuminate\Database\RecordNotFoundException을 발생시키려면 firstOrFail 메서드를 사용하세요. 이 예외를 별도로 처리하지 않으면 클라이언트에게 자동으로 404 HTTP 응답이 반환됩니다.

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

행 전체가 아닌 특정 컬럼 값 하나만 필요하다면 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; }

두 번째 인수로 컬럼명을 지정하면, 해당 컬럼 값을 컬렉션의 키로 사용할 수 있습니다.

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

결과 청크 처리

수천 건의 레코드를 처리해야 할 때는 DB 파사드의 chunk 메서드를 활용하세요. 이 메서드는 결과를 일정 크기씩 나눠 가져와 클로저로 전달합니다. 예를 들어 users 테이블 전체를 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]); } });

chunkByIdlazyById 메서드는 내부적으로 자체 where 조건을 쿼리에 추가하므로, 직접 작성하는 조건은 클로저 안에서 논리 그룹화하는 것이 좋습니다.

DB::table('users')->where(function ($query) { $query->where('credits', 1)->orWhere('credits', 2); })->chunkById(100, function (Collection $users) { foreach ($users as $user) { DB::table('users') ->where('id', $user->id) ->update(['credits' => 3]); } });

WARNING

청크 콜백 내에서 레코드를 업데이트하거나 삭제할 때, 기본 키나 외래 키 값이 변경되면 청크 쿼리 결과에 영향을 줄 수 있습니다. 이 경우 일부 레코드가 결과에서 누락될 수 있습니다.

결과 지연 스트리밍

lazy 메서드는 chunk 메서드와 유사하게 내부적으로 청크 단위로 쿼리를 실행하지만, 각 청크를 콜백에 전달하는 대신 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]); });

WARNING

반복 중 레코드를 업데이트하거나 삭제할 때, 기본 키나 외래 키 값이 변경되면 청크 쿼리 결과에 영향을 줄 수 있습니다. 일부 레코드가 결과에서 누락될 수 있습니다.

집계 함수

쿼리 빌더는 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()) { // ... }

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

Raw 표현식

쿼리에 임의의 SQL 문자열을 직접 삽입해야 할 때는 DB 파사드의 raw 메서드를 사용하세요.

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

WARNING

Raw 구문은 문자열 그대로 쿼리에 삽입됩니다. SQL 인젝션 취약점이 생기지 않도록 각별히 주의해야 합니다.

Raw 메서드

DB::raw 외에도 쿼리의 특정 부분에 Raw 표현식을 삽입하는 전용 메서드들이 있습니다. Raw 표현식을 사용하는 쿼리는 Laravel이 SQL 인젝션으로부터 보호할 수 없다는 점을 반드시 기억하세요.

`selectRaw`

addSelect(DB::raw(/* ... */)) 대신 selectRaw를 사용할 수 있습니다. 두 번째 인수로 바인딩 배열을 선택적으로 전달할 수 있습니다.

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

`whereRaw / orWhereRaw`

whereRaworWhereRaw는 Raw "where" 절을 쿼리에 삽입할 때 사용합니다. 두 번째 인수로 바인딩 배열을 전달할 수 있습니다.

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

`havingRaw / orHavingRaw`

havingRaworHavingRaw는 "having" 절에 Raw 문자열을 사용할 때 씁니다. 두 번째 인수로 바인딩 배열을 전달할 수 있습니다.

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

`orderByRaw`

orderByRaw는 "order by" 절에 Raw 문자열을 사용할 때 씁니다.

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

`groupByRaw`

groupByRawgroup by 절에 Raw 문자열을 사용할 때 씁니다.

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

조인

Inner Join

기본적인 "inner join"은 join 메서드로 수행합니다. 첫 번째 인수는 조인할 테이블명이며, 나머지 인수는 조인 조건을 지정합니다. 하나의 쿼리에서 여러 테이블을 조인할 수도 있습니다.

use Illuminate\Support\Facades\DB; $users = DB::table('users') ->join('contacts', 'users.id', '=', 'contacts.user_id') ->join('orders', 'users.id', '=', 'orders.user_id') ->select('users.*', 'contacts.phone', 'orders.price') ->get();

Left Join / Right Join

"left join"이나 "right join"을 수행하려면 leftJoin 또는 rightJoin 메서드를 사용하세요. 사용법은 join과 동일합니다.

$users = DB::table('users') ->leftJoin('posts', 'users.id', '=', 'posts.user_id') ->get(); $users = DB::table('users') ->rightJoin('posts', 'users.id', '=', 'posts.user_id') ->get();

Cross Join

crossJoin 메서드를 사용하면 두 테이블의 카테시안 곱(Cartesian product)을 구할 수 있습니다.

$sizes = DB::table('sizes') ->crossJoin('colors') ->get();

고급 조인 절

더 복잡한 조인 조건이 필요하다면 join 메서드의 두 번째 인수로 클로저를 전달하세요. 클로저는 Illuminate\Database\Query\JoinClause 인스턴스를 받아 조인 조건을 세밀하게 지정할 수 있습니다.

DB::table('users') ->join('contacts', function (JoinClause $join) { $join->on('users.id', '=', 'contacts.user_id')->orOn(/* ... */); }) ->get();

조인에 "where" 조건을 추가하려면 JoinClause 인스턴스의 whereorWhere 메서드를 사용하세요. 두 컬럼을 비교하는 대신 컬럼과 값을 비교합니다.

DB::table('users') ->join('contacts', function (JoinClause $join) { $join->on('users.id', '=', 'contacts.user_id') ->where('contacts.user_id', '>', 5); }) ->get();

서브쿼리 조인

joinSub, leftJoinSub, rightJoinSub 메서드를 사용하면 서브쿼리와 조인할 수 있습니다. 각 메서드는 서브쿼리, 테이블 별칭, 연결 컬럼을 정의하는 클로저를 인수로 받습니다. 아래 예시에서는 각 사용자의 최신 게시글 작성 시각을 함께 조회합니다.

$latestPosts = DB::table('posts') ->select('user_id', DB::raw('MAX(created_at) as last_post_created_at')) ->where('is_published', true) ->groupBy('user_id'); $users = DB::table('users') ->joinSub($latestPosts, 'latest_posts', function (JoinClause $join) { $join->on('users.id', '=', 'latest_posts.user_id'); })->get();

Lateral 조인

WARNING

Lateral 조인은 현재 PostgreSQL, MySQL 8.0.14 이상, SQL Server에서 지원됩니다.

joinLateralleftJoinLateral 메서드를 사용하면 서브쿼리와 "lateral join"을 수행할 수 있습니다. 각 메서드는 서브쿼리와 테이블 별칭을 인수로 받으며, 조인 조건은 서브쿼리 내부의 where 절로 지정합니다. Lateral 조인은 각 행마다 평가되며, 서브쿼리 외부의 컬럼도 참조할 수 있습니다.

아래 예시에서는 사용자별로 최근 게시글 3개를 함께 조회합니다. 결과 집합에서 한 사용자가 최대 3개의 행을 가질 수 있습니다.

$latestPosts = DB::table('posts') ->select('id as post_id', 'title as post_title', 'created_at as post_created_at') ->whereColumn('user_id', 'users.id') ->orderBy('created_at', 'desc') ->limit(3); $users = DB::table('users') ->joinLateral($latestPosts, 'latest_posts') ->get();

유니온

쿼리 빌더의 union 메서드를 사용하면 두 개 이상의 쿼리를 합칠 수 있습니다.

use Illuminate\Support\Facades\DB; $usersWithoutFirstName = DB::table('users') ->whereNull('first_name'); $users = DB::table('users') ->whereNull('last_name') ->union($usersWithoutFirstName) ->get();

union 메서드는 중복 결과를 제거합니다. 중복을 그대로 유지하려면 unionAll 메서드를 사용하세요. 사용법은 union과 동일합니다.

기본 WHERE 절

Where 절

where 메서드는 쿼리에 "where" 조건을 추가합니다. 기본적으로 컬럼명, 연산자, 비교 값의 세 인수를 받습니다.

아래 예시는 votes100이고 age35보다 큰 사용자를 조회합니다.

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

= 연산자를 사용할 때는 두 번째 인수로 값만 전달해도 됩니다.

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

연관 배열로 여러 컬럼에 대한 조건을 한 번에 지정할 수 있습니다.

$users = DB::table('users')->where([ 'first_name' => 'Jane', 'last_name' => 'Doe', ])->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();

조건 배열을 중첩 배열로 전달할 수도 있습니다.

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

WARNING

PDO는 컬럼명 바인딩을 지원하지 않습니다. "order by" 컬럼을 포함하여 쿼리에서 참조하는 컬럼명을 사용자 입력으로 결정하게 해서는 안 됩니다.

WARNING

MySQL과 MariaDB는 문자열-숫자 비교 시 문자열을 자동으로 정수형으로 변환합니다. 이 과정에서 숫자가 아닌 문자열은 0으로 변환되어 예상치 못한 결과가 발생할 수 있습니다. 예를 들어 secret 컬럼에 aaa 값이 있을 때 User::where('secret', 0)을 실행하면 해당 행이 반환됩니다. 이런 상황을 피하려면 쿼리에 사용하는 값을 적절한 타입으로 변환한 뒤 전달하세요.

Or Where 절

여러 where 호출은 기본적으로 and로 연결됩니다. or 연산자로 연결하려면 orWhere 메서드를 사용하세요. 인수는 where와 동일합니다.

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

"or" 조건을 괄호로 그룹화해야 한다면, 첫 번째 인수로 클로저를 전달하세요.

use Illuminate\Database\Query\Builder; $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)

WARNING

전역 스코프가 적용될 때 예상치 못한 동작을 방지하려면 항상 orWhere 호출을 그룹화하세요.

Where Not 절

whereNotorWhereNot 메서드는 주어진 조건 그룹을 부정합니다. 아래 예시에서는 재고 정리 상품이거나 가격이 10 미만인 상품을 제외합니다.

undefined

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 6월 25일