-2

次のように、2 つのテーブルがあります。

master
---------
empcode    INT PRIMARY KEY
name       VARCHAR
dept       VARCHAR 

emp_tx
----------
empcode    INT        references MASTER(empcode)
s_date     DATETIME

このemp_txテーブルは、従業員の「入」および「出」取引を記録します。この列s_dateには、「in」または「out」イベントが発生した時刻が (DATETIME 値として) 格納されます。トランザクションは、オフィス地域から記録されます (指紋生体認証システムを介して)。

emp_tX テーブルのデータ例:

    empcode   s_datetime
    -------   ------------------    
    1110      2012-12-12 09:31:42  (employee in  time to the office)
    1110      2012-12-12 13:34:17  (employee out time for lunch)
    1110      2012-12-12 14:00:17  (employee in  time after lunch)
    1110      2012-12-12 18:00:12  (employee out time after working hours)
    1112
    etc.

注: 従業員が特定の日にオフィスを欠席した場合、emp_txその日付の行はトランザクション テーブルに挿入されません。特定の日付に従業員が不在の場合は、その従業員とその日付の「行方不明」行によって示されます。

従業員不在レポートを作成するために、従業員が欠勤した日付を返す SQL クエリを取得するのを手伝ってくれる人はいますか?

クエリへの入力は、日付の範囲を指定する「開始日」と「終了日」の 2 つの DATE 値になります。クエリは、「開始日」と「終了日」の間の任意の日付のEMP_TXテーブルに行が見つからない場合、「不在」のすべての発生 (または、発生しないというよりむしろ発生しない) を返す必要があります。empcode

期待される出力:

「開始」日付として「2012-12-12」を入力し、「終了」日付として「2012-12-20」を入力すると、クエリは次のような行を返す必要があります。

Empcode  EmpName  Department  AbsentDate  TotalNoofAbsent days
-------  -------  ----------  ----------- --------------------
1110     ABC      Accounts    2012-12-12
1110     ABC      Accounts    2012-12-14                     2   
1112     xyz      Software    2012-12-19
1112     xyz      Software    2012-12-17                     2

このクエリを試してみましたが、必要な行が返されていないと確信しています:

select tx.date from Emp_TX as tx where Date(S_Date) not between '2012-12-23' and '2012-12-30'

ありがとう。

4

2 に答える 2

2

「不在」が、特定の日付 (日付 = 真夜中から真夜中の 24 時間)emp_txの特定のテーブルに行が表示されないこととして定義されている場合、および ...empcode

その日付のテーブルにトランザクションがない場合、その日付の「不在」を表示しないことが許容される場合emp_tx(つまり、その日付にすべての empcode が存在しない日付を除外する)、...

次のようなクエリを使用して、指定した結果セットの最初の 4 列を取得できます: (未テスト)

SELECT m.empcode     AS `EmpCode` 
     , m.name        AS `EmpName`
     , m.dept        AS `Department`
     , d.dt          AS `AbsentDate`
  FROM ( SELECT DATE(t.s_date) AS dt
           FROM emp_tx t
          WHERE t.s_date >= '2012-12-12' 
            AND t.s_date < DATE_ADD( '2012-12-20' ,INTERVAL 1 DAY)
          GROUP BY DATE(t.s_date)
          ORDER BY DATE(t.s_date)
       ) d
 CROSS
  JOIN master m
  LEFT
  JOIN emp_tx p
    ON p.s_date >= d.dt
   AND p.s_date <  d.dt + INTERVAL 1 DAY
   AND p.empcode = m.empcode
 WHERE p.empcode IS NULL
 ORDER
    BY m.empcode
     , d.dt

同じ結果セットで返される 5 番目の列を取得することTotalNoofAbsentは可能ですが、そのクエリは非常に面倒になります。この詳細は、返された結果セットを処理するときに、クライアント側でより効率的に処理される場合があります。


クエリの仕組み

as というエイリアスが付けられたインライン ビューdは、チェックしている「日付」値のセットを取得します。これらの「日付」値のソースとしてテーブルを使用するemp_txと、これを行うのに便利な方法です。DATE()関数が DATETIME 引数の「日付」部分だけを返すわけではありません。日付の個別のリストを取得するために aを使用していGROUP BYます (つまり、重複する値はありません)。(このインライン ビュー クエリで求めているのは、引数として渡された 2 つの値の間の異なる DATE 値のセットです。DATE 値のリストを生成する、より複雑な方法は他にもあります。)

「不在」と見なすすべての「日付」値がテーブルのどこかに表示されている限り (つまりempcode、関心のある各日付に 1 つのトランザクションが少なくとも 1 つ存在する場合)、およびテーブルが過剰でない場合、emp_txインライン ビュー クエリは適切に機能します。

(注: インライン ビューのクエリを個別に実行して、結果が正しく、期待どおりであることを確認できます。)

次のステップは、インライン ビューから結果を取得し、CROSS JOIN(デカルト積を生成する) 操作を実行して、インライン ビューから返されたEVERYempcodeと EVERYを一致させることです。dateこの操作の結果は、「出席」のすべての可能な発生を表します。

LEFT JOINクエリの最後の手順は、述語と述語を使用して「アンチ結合」操作を実行することWHERE IS NULLです。(LEFT JOIN外部結合) は、テーブルから一致する行 (出席レコード) を持たないものを含め、(左側から) 可能なすべての出席の発生を返しemp_txます。

「トリック」は、一致する出席レコードが見つかったすべての行を破棄する述語を (WHERE 句に) 含めることです。これにより、残っているのは、empcodeおよびdate(可能な出席の発生) のすべての組み合わせであり、NO はありませんでした。一致する出席トランザクション。

(注: s_date (DATETIME) 列への参照を意図的に述語に「むき出し」のままにし、範囲述語を使用しました。これにより、MySQL はその列を含む適切なインデックスを効果的に使用できるようになります。)

関数内の述語で列参照をラップするDATE(p.s_date)と、MySQL は列のインデックスを効果的に使用できなくなりs_dateます。


(あなたの質問に対する) コメントの 1 つが指摘しているように、従業員を「入社」または「退職」としてマークするトランザクションを区別していません。特定の 24 時間の「真夜中から真夜中」の期間に、その empcode のトランザクションの存在のみを探しています。


同じ結果セットを取得する方法は他にもありますが、通常は「アンチ結合」パターンが大規模なセットで最高のパフォーマンスを発揮します。

最高のパフォーマンスを得るには、インデックスをカバーする必要があります。

... ON master (empcode, name, dept)

... ON emp_tx (s_date, empcode)
于 2012-12-24T18:12:10.907 に答える
0

残念ながら、このクエリでは大量の結果が得られます... 指定した範囲外の従業員のすべての日付が常に返されます。日付の間に NOT EXISTS レコードがあるかどうかを確認したいとします。

純粋なSQLでこれを行うことは可能かもしれません...カーソルまたはDB固有のものを使用せずに手に負えない方法は考えられません。この Java 疑似コードは、1 人の従業員の欠勤を示します。

List<Date> findAbsences(int empCode, Date inDate, Date outDate) {

    List<Date> result = new LinkedList<Date>();

    Calendar c = new Calendar();
    c.setTime(new Date(2012,12,12));

    while (!c.getTime().after(outDate)) {
        // run query for EMP_TX records between inDate & outDate
        //SELECT 1 FROM EMP_TX WHERE EmpCode = :empid AND S_Date BETWEEN :in AND :out;

        if (!query.hasNext()) {
            result.add(c.getTime);
        }

        c.add(Calendar.DATE, 1);
    }


}
于 2012-12-24T17:03:03.093 に答える