sql >> データベース >  >> RDS >> Mysql

mysqlテーブルに月が存在しない場合でも、各月を選択します

    次のスキーマについて考えてみます。3番目のテーブルは、前述の年/月のヘルパーテーブルです。ヘルパーテーブルは非常に一般的であり、コード全体で自然に再利用できます。実質的な日付データをロードするのはあなたに任せます。ただし、dbエンジンがうるう年を把握できるようにしながら、作業量を減らしたい人のために、各月の終了日をまとめた方法に注意してください。

    そのヘルパーテーブルには1つの列しか含めることができません。ただし、これには、一部の関数で終了日の関数呼び出しを使用する必要があり、それはより遅いことを意味します。私たちは速いのが好きです。

    スキーマ

    create table workerRecords
    (   id int auto_increment primary key,
        the_date date not null,
        staff_no int not null
    );
    -- truncate workerRecords;
    insert workerRecords(the_date,staff_no) values
    ('2016-06-10',1),
    ('2016-06-09',1),
    ('2016-05-09',1),
    ('2016-04-09',1),
    ('2016-03-02',2),
    ('2016-07-02',2);
    
    create table workers
    (   staff_no int primary key,
        full_name varchar(100) not null
    );
    -- truncate workers;
    insert workers(staff_no,full_name) values
    (1,'David Higgins'),(2,"Sally O'Riordan");
    

    以下のヘルパーテーブル

    create table ymHelper
    (   -- Year Month helper table. Used for left joins to pick up all dates.
        -- PK is programmer's choice.
        dtBegin date primary key,   -- by definition not null
        dtEnd date null
    );
    -- truncate ymHelper;
    insert ymHelper (dtBegin,dtEnd) values
    ('2015-01-01',null),('2015-02-01',null),('2015-03-01',null),('2015-04-01',null),('2015-05-01',null),('2015-06-01',null),('2015-07-01',null),('2015-08-01',null),('2015-09-01',null),('2015-10-01',null),('2015-11-01',null),('2015-12-01',null),
    ('2016-01-01',null),('2016-02-01',null),('2016-03-01',null),('2016-04-01',null),('2016-05-01',null),('2016-06-01',null),('2016-07-01',null),('2016-08-01',null),('2016-09-01',null),('2016-10-01',null),('2016-11-01',null),('2016-12-01',null),
    ('2017-01-01',null),('2017-02-01',null),('2017-03-01',null),('2017-04-01',null),('2017-05-01',null),('2017-06-01',null),('2017-07-01',null),('2017-08-01',null),('2017-09-01',null),('2017-10-01',null),('2017-11-01',null),('2017-12-01',null),
    ('2018-01-01',null),('2018-02-01',null),('2018-03-01',null),('2018-04-01',null),('2018-05-01',null),('2018-06-01',null),('2018-07-01',null),('2018-08-01',null),('2018-09-01',null),('2018-10-01',null),('2018-11-01',null),('2018-12-01',null),
    ('2019-01-01',null),('2019-02-01',null),('2019-03-01',null),('2019-04-01',null),('2019-05-01',null),('2019-06-01',null),('2019-07-01',null),('2019-08-01',null),('2019-09-01',null),('2019-10-01',null),('2019-11-01',null),('2019-12-01',null);
    -- will leave as an exercise for you to add more years. Good idea to start, 10 in either direction, at least.
    update ymHelper set dtEnd=LAST_DAY(dtBegin);    -- data patch. Confirmed leap years.
    alter table ymHelper modify dtEnd date not null;    -- there, ugly patch above worked fine. Can forget it ever happened (until you add rows)
    -- show create table ymHelper; -- this confirms that dtEnd is not null
    

    これがヘルパーテーブルです。一度設定すれば、数年は忘れてしまいます

    :上記の更新stmtを実行することを忘れないでください

    クエリのクイックテスト

    SELECT DATE_FORMAT(ymH.dtBegin,'%b %Y') as month,
    ifnull(COUNT(wr.the_date),0) as total_records,@soloName as full_name 
    FROM ymHelper ymH 
    left join workerRecords wr 
    on wr.the_date between ymH.dtBegin and ymH.dtEnd 
    and wr.staff_no = 1 and wr.the_date between '2016-04-01' and '2016-07-31' 
    LEFT JOIN workers w on w.staff_no = wr.staff_no 
    cross join (select @soloName:=full_name from workers where staff_no=1) xDerived 
    WHERE ymH.dtBegin between '2016-04-01' and '2016-07-31' 
    GROUP BY ymH.dtBegin 
    order by ymH.dtBegin; 
    
    +----------+---------------+---------------+
    | month    | total_records | full_name     |
    +----------+---------------+---------------+
    | Apr 2016 |             1 | David Higgins |
    | May 2016 |             1 | David Higgins |
    | Jun 2016 |             2 | David Higgins |
    | Jul 2016 |             0 | David Higgins |
    +----------+---------------+---------------+
    

    正常に動作します。最初のmysqlテーブルはヘルパーテーブルです。ワーカーレコードを取り込むための左結合(nullを許可)。ここで一時停止しましょう。それが結局のところあなたの質問のポイントでした:欠測データ 。最後に、クロス結合のワーカーテーブル。

    cross join 変数を初期化することです(@soloName )それは労働者の名前です。一方、リクエストした日付の欠落のnullステータスは、ifnull()を介して正常に取得されます。 関数が0を返す場合、ワーカーの名前にそのような贅沢はありません。これにより、cross joinが強制されます 。

    クロス結合はデカルト積です。しかし、それは単一の行であるため、デカルト主義者が結果セットの多くの行に道を譲るという通常の問題に悩まされることはありません。とにかく、それは機能します。

    しかし、ここに1つの問題があります。見られるように、6つの場所で値を維持してプラグインするのが難しすぎます。したがって、以下のストアドプロシージャを検討してください。

    ストアドプロシージャ

    drop procedure if exists getOneWorkersRecCount;
    DELIMITER $$
    create procedure getOneWorkersRecCount
    (pStaffNo int, pBeginDt date, pEndDt  date)
    BEGIN
        SELECT DATE_FORMAT(ymH.dtBegin,'%b %Y') as month,ifnull(COUNT(wr.the_date),0) as total_records,@soloName as full_name
        FROM ymHelper ymH 
        left join workerRecords wr 
        on wr.the_date between ymH.dtBegin and ymH.dtEnd 
        and wr.staff_no = pStaffNo and wr.the_date between pBeginDt and pEndDt
        LEFT JOIN workers w on w.staff_no = wr.staff_no 
        cross join (select @soloName:=full_name from workers where staff_no=pStaffNo) xDerived
        WHERE ymH.dtBegin between pBeginDt and pEndDt 
        GROUP BY ymH.dtBegin
        order by ymH.dtBegin;
    END$$
    DELIMITER ;
    

    ストアドプロシージャを何度もテストします

    call getOneWorkersRecCount(1,'2016-04-01','2016-06-09');
    call getOneWorkersRecCount(1,'2016-04-01','2016-06-10');
    call getOneWorkersRecCount(1,'2016-04-01','2016-07-01');
    call getOneWorkersRecCount(2,'2016-02-01','2016-11-01');
    

    ああ、操作がはるかに簡単です(PHP、c#、Javaでは、名前を付けます)。選択はあなた次第で、ストアドプロシージャかどうか。

    ボーナスストアドプロシージャ

    drop procedure if exists getAllWorkersRecCount;
    DELIMITER $$
    create procedure getAllWorkersRecCount
    (pBeginDt date, pEndDt  date)
    BEGIN
        SELECT DATE_FORMAT(ymH.dtBegin,'%b %Y') as month,ifnull(COUNT(wr.the_date),0) as total_records,w.staff_no,w.full_name
        FROM ymHelper ymH 
        cross join workers w 
        left join workerRecords wr 
        on wr.the_date between ymH.dtBegin and ymH.dtEnd 
        and wr.staff_no = w.staff_no and wr.the_date between pBeginDt and pEndDt
        -- LEFT JOIN workers w on w.staff_no = wr.staff_no 
        -- cross join (select @soloName:=full_name from workers ) xDerived
        WHERE ymH.dtBegin between pBeginDt and pEndDt 
        GROUP BY ymH.dtBegin,w.staff_no,w.full_name
        order by ymH.dtBegin,w.staff_no;
    END$$
    DELIMITER ;
    

    クイックテスト

    call getAllWorkersRecCount('2016-03-01','2016-08-01');
    +----------+---------------+----------+-----------------+
    | month    | total_records | staff_no | full_name       |
    +----------+---------------+----------+-----------------+
    | Mar 2016 |             0 |        1 | David Higgins   |
    | Mar 2016 |             1 |        2 | Sally O'Riordan |
    | Apr 2016 |             1 |        1 | David Higgins   |
    | Apr 2016 |             0 |        2 | Sally O'Riordan |
    | May 2016 |             1 |        1 | David Higgins   |
    | May 2016 |             0 |        2 | Sally O'Riordan |
    | Jun 2016 |             2 |        1 | David Higgins   |
    | Jun 2016 |             0 |        2 | Sally O'Riordan |
    | Jul 2016 |             0 |        1 | David Higgins   |
    | Jul 2016 |             1 |        2 | Sally O'Riordan |
    | Aug 2016 |             0 |        1 | David Higgins   |
    | Aug 2016 |             0 |        2 | Sally O'Riordan |
    +----------+---------------+----------+-----------------+
    

    お持ち帰り

    ヘルパーテーブルは何十年も使用されてきました。それらを使用することを恐れたり、恥ずかしがったりしないでください。実際、それらなしで特別な仕事を成し遂げようとすることは、時にはほとんど不可能です。



    1. MySQL全文検索とSOUNDEX

    2. datagridviewのすべてのデータをデータベースに一度に挿入します

    3. MySqlCommand()。ExecuteReader()。GetString()が機能しない

    4. MySQLユーザーがデータベースを作成するにはどのような権限が必要ですか?