web-dev-qa-db-ja.com

min(date)でグループ化し、同じテーブルの別の列を選択する方法

私はそれのようなものを返すクエリがあります:

Name   Gender Job         date of hire
John   M      mechanic    2012-05-08
John   M      electrician 2010-01-01
Vicky  F      scientific  2012-11-11
Bob    M      NULL        NULL

各人の最初の仕事の名前、性別、仕事名が必要です。しかし、私は方法がわかりません。私のクエリは次のようになります:

select name,gender,jobname,hiredate
from person p
left join job j on p.personid = j.personid

Microsoft SQL Server 2000を使用しています

結果としてこれが必要です:

Name   Gender Job         
John   M      electrician
Vicky  F      scientific 
Bob    M      NULL       
6
Jonlabr

あなたのデータは次のようになっていると思います:

個人テーブル

_╔══════════╦═══════╦════════╗
║ PersonID ║ Name  ║ Gender ║
╠══════════╬═══════╬════════╣
║        1 ║ John  ║ M      ║
║        2 ║ Vicky ║ F      ║
║        3 ║ Bob   ║ M      ║
╚══════════╩═══════╩════════╝
_

ジョブテーブル

_╔══════════╦═════════════╦════════════╗
║ PersonID ║   JobName   ║  HireDate  ║
╠══════════╬═════════════╬════════════╣
║        1 ║ Electrician ║ 2010-01-01 ║
║        1 ║ Mechanic    ║ 2012-05-08 ║
║        2 ║ Scientific  ║ 2012-11-11 ║
╚══════════╩═════════════╩════════════╝
_

最初のタスクは、各人の最初の仕事を(入社日で)見つけることです。これを行う1つのきちんとした方法は、相関サブクエリを使用することです。

_SELECT j.*
FROM dbo.Job AS j
WHERE 
    j.HireDate =
    (
        SELECT MIN(j2.HireDate)
        FROM dbo.Job AS j2
        WHERE j2.PersonID = j.PersonID
    );
_

そこの内部クエリと外部クエリの間の相関_WHERE j2.PersonID = j.PersonID_に注意してください。そのクエリの出力は次のとおりです。

_╔══════════╦═════════════╦════════════╗
║ PersonID ║   JobName   ║  HireDate  ║
╠══════════╬═════════════╬════════════╣
║        1 ║ Electrician ║ 2010-01-01 ║
║        2 ║ Scientific  ║ 2012-11-11 ║
╚══════════╩═════════════╩════════════╝
_

実行計画(クラスター化された_PRIMARY KEY_が_PersonID, HireDate_である場合)は次のとおりです。

First job plan

このプランの興味深い点は、元のクエリにJobテーブルへの参照が2つあるにもかかわらず、Jobテーブルが1回だけスキャンされることです。この計画では、私が Segment Top と呼ぶ最適化を使用しています。基本的に、実行エンジンはインデックスの順序を利用して新しいグループ(セグメント)の開始を検出し、各グループの最初の行だけを取得します(上)。

これで結果が得られたので、それをPersonテーブルに結合するだけです。

_SELECT
    p.PersonName,
    p.Gender, 
    j.JobName
FROM dbo.Person AS p
LEFT JOIN
(
    -- Previous query
    SELECT j.*
    FROM dbo.Job AS j
    WHERE 
        j.HireDate =
        (
            SELECT MIN(j2.HireDate)
            FROM dbo.Job AS j2
            WHERE j2.PersonID = j.PersonID
        )
) AS j ON 
    j.PersonID = p.PersonID
OPTION (MERGE JOIN);
_

実行計画は次のとおりです。

Final plan

OPTION (MERGE JOIN)は必須ではありません。テーブルにこの小さな例よりも多くの行が含まれている場合に得られる可能性が高いプランを示すために追加しました。


テーブル定義とサンプルデータ:

_CREATE TABLE dbo.Person
(
    PersonID    integer NOT NULL,
    PersonName  varchar(30) NOT NULL,
    Gender      char(1) NOT NULL,

    PRIMARY KEY (PersonID)
);

CREATE TABLE dbo.Job
(
    PersonID    integer NOT NULL,
    JobName     varchar(30) NOT NULL,
    HireDate    datetime NOT NULL,

    PRIMARY KEY (PersonID, HireDate)
);

INSERT dbo.Person
    (PersonID, PersonName, Gender)
SELECT 1, 'John', 'M' UNION ALL
SELECT 2, 'Vicky', 'F' UNION ALL
SELECT 3, 'Bob', 'M';

INSERT dbo.Job
    (PersonID, JobName, HireDate)
SELECT 1, 'Mechanic', '20120508' UNION ALL
SELECT 1, 'Electrician', '20100101' UNION ALL
SELECT 2, 'Scientific', '20121111';
_
10
Paul White 9

コメントのクエリを間違えました。グループでの性別を忘れました。このクエリは私にとってはうまくいきます:

    select name,gender,SUBSTRING(job, 9, 50) as 'jobname'
    from(
        select name,gender,min(CONVERT(CHAR(8), hiredate, 112)+jobname) as'job' 
        from person p 
        left join job j on p.personid = j.personid 
        group by name,gender
     ) as query`
1
Jonlabr