23

状況

私の目標は、年齢に基づいてデータベースから特定のデータを削除する年 1 回の cronjob を持つことです。私の処分では、Bash と MySQL の力があります。私は bash スクリプトの作成から始めましたが、1 つの SQL クエリだけですべてを実行できるのではないかと思いました。

私は本質的にプログラマーであり、データ構造の経験があまりないため、助けが必要です。

テーブル/データ構造

このクエリに関連するテーブルと列は次のとおりです。

登録:

+-----+-------------------+
| Id  | Registration_date |
+-----+-------------------+
|   2 | 2011-10-03        | 
|   3 | 2011-10-06        | 
|   4 | 2011-10-07        | 
|   5 | 2011-10-07        | 
|   6 | 2011-10-10        | 
|   7 | 2011-10-13        | 
|   8 | 2011-10-14        | 
|   9 | 2011-10-14        | 
|  10 | 2011-10-17        |
+-------------------------+ 

関連付けられたクライアント:

+-----------+-----------------+
| Client_id | Registration_id |
+-----------+-----------------+
|         2 |               2 | 
|         3 |               2 | 
|         3 |               4 | 
|         4 |               5 | 
|         3 |               6 | 
|         5 |               6 | 
|         3 |               8 | 
|         8 |               9 | 
|         7 |              10 | 
+-----------------------------+

クライアント: ここでは ID のみが関連します。

ご覧のとおり、これは単純な多対多の関係です。クライアントは自分の名前に対して複数の登録を持つことができ、登録は複数のクライアントを持つことができます。

目標

5 年間新規登録していないクライアントのすべての登録とクライアント データを削除する必要があります。シンプルですね。

トリッキーな部分

特定のクライアントからの登録に関する他のクライアントが 5 年以内に新しい登録を行った場合、データを保持する必要があります。

したがって、クライアント A が 4 つの登録を持ち、その中に彼だけがいて、1 つの登録が彼自身とクライアント B であると想像してください。5 つの登録はすべて 5 年以上前のものです。クライアント B が 5 年間新規登録をしていない場合、クライアント A の登録と記録のすべてを削除する必要があります。Bが5 年以内に新しい登録を行った場合、クライアント A のすべてのデータは、彼自身の古い登録を含めて保持する必要があります。

私が試したこと

私のクエリを構築して、私はこれまでのところ得ました:

DELETE * FROM `Registration` AS Reg
WHERE TIMESTAMPDIFF(YEAR, Reg.`Registration_date`, NOW()) >= 5
AND 
    (COUNT(`Id`) FROM `Registration` AS Reg2
     WHERE Reg2.`Id` IN (SELECT `Registration_id` FROM `AssociatedClient` AS Clients
                         WHERE Clients.`Client_id` IN (SELECT `Client_id` FROM `AssociatedClient` AS Clients2
                                                       WHERE Clients2.`Registration_id` IN -- stuck
               #I need all the registrations from the clients associated with the first
               # (outer) registration here, that are newer than 5 years.

    ) = 0 -- No newer registrations from any associated clients

私はSQLの経験が非常に限られていることを理解してください。これまでに得たものでさえ、(結合などを使用して)大幅に最適化でき、正しくない場合さえあることに気付きました。

私が行き詰まった理由は、ある種のループを使用できれば、私が念頭に置いていた解決策が機能するためであり、これはこの種の SQL クエリで簡単に実行できるものではないことに気付きました。

どんな助けでも

とても感謝しています。

4

6 に答える 6

19

登録の他のクライアントの登録を識別することから始めます。ここにビューがあります:

create view groups as 
select   a.Client_id
       , c.Registration_id
from AssociatedClient as a 
join AssociatedClient as b on a.Registration_id = b.Registration_id 
join AssociatedClient as c on b.Client_id = c.Client_id;

これにより、次のことがわかります。

select Client_id
    , min(Registration_id) as first
    , max(Registration_id) as last
    , count(distinct Registration_id) as regs
    , count(*) as pals
from  groups 
group by Client_id;
Client_id   first       last        regs        pals      
----------  ----------  ----------  ----------  ----------
2           2           8           4           5         
3           2           8           4           18        
4           5           5           1           1         
5           2           8           4           5         
7           10          10          1           1         
8           9           9           1           1         

もちろん、ビューは必要ありません。それは便宜上のものです。仮想テーブルを使用できます。しかし、慎重に調べて、クライアントごとに適切な範囲の「pal 登録」が生成されていることを確認してください。ビューは を参照しないことに注意してくださいRegistration。それを使用して from を削除した後でも同じ結果が生成されるため、これは重要ですRegistration。そのため、2 番目の delete ステートメントに使用できます。

これで、クライアントとその「pal 登録」のリストができました。各パルの最終登録日は?

select g.Client_id, max(Registration_date) as last_reg
from groups as g join Registration as r
on g.Registration_id = r.Id
group by g.Client_id;
g.Client_id  last_reg  
-----------  ----------
2            2011-10-14
3            2011-10-14
4            2011-10-07
5            2011-10-14
7            2011-10-17
8            2011-10-14

特定の時間より前に最新の日付を持っているのはどれですか?

select g.Client_id, max(Registration_date) as last_reg
from groups as g join Registration as r
on g.Registration_id = r.Id
group by g.Client_id
having max(Registration_date) < '2011-10-08';
g.Client_id  last_reg  
-----------  ----------
4            2011-10-07

IIUC は、クライアント #4 を削除する必要があり、彼が登録したものはすべて削除する必要があることを意味します。登録は

select * from Registration
where Id in (
      select Registration_id from groups as g
      where Client_id in ( 
            select g.Client_id
            from groups as g join Registration as r
            on g.Registration_id = r.Id
            group by g.Client_id
            having max(Registration_date) < '2011-10-08'
      )
);
Id          Registration_date
----------  -----------------
5           2011-10-07       

そして、案の定、クライアント #4 は登録 #5 にあり、このテストによって削除される唯一のクライアントです。

そこからdeleteステートメントを作成できます。ルールは「クライアントと彼が登録したものはすべて削除する」だと思います。もしそうなら、私はおそらく登録 ID を一時テーブルに書き込み、両方の削除を書き込み、それRegistrationAssociatedClient結合します。

于 2013-03-26T05:43:01.787 に答える
0

私はSQLServerの人ですが、この構文はMySQLでも機能すると思います。このクエリは、削除されるべきではないクライアントをプルします。

SELECT A3.Client_id
FROM AssociatedClient A1
#Get clients with registrations in the last 5 years
JOIN Registration R1 ON A1.Registration_id = R1.Id 
    AND TIMESTAMPDIFFERENCE(YEAR, R1.Registration_Date, Now()) <= 5
#get the rest of the registrations for those clients
JOIN AssociatedClient A2 ON A1.Client_id = A2.Client_id
#get other clients tied to the rest of the registrations
JOIN AssociatedClient A3 ON A2.Registration_id = A3.Registration_id
于 2013-03-20T17:40:36.973 に答える
0

これにより、リンクされたクライアントの 1 レベル下の適切なクライアント情報が得られます。必要な情報がすべて得られないことは承知しています。ただし、コメントに記載されているように、現時点では 1 レベルの実装で十分です。これは最適ではない場合があります。

SELECT
AC1.Client_id,
MAX(R.Registration_date) AS [LatestRegistration]
FROM
#AssociatedClient AC1
JOIN #AssociatedClient AC2
    ON  AC1.Registration_id = AC2.Registration_id
JOIN #AssociatedClient AC3
    ON  AC2.Client_id = AC3.Client_id
JOIN #Registration R
    ON  AC3.Registration_id = R.Id
GROUP BY
AC1.Client_id

ループを使用する関数を調べる必要があります。今考えられるのはそれだけです。

于 2013-03-15T13:20:17.870 に答える
0

一時テーブルを使用します。

INSERT INTO LockedClient(client_id) --select clients that should not be deleted
SELECT DISTINCT ac.client_id 
FROM AssociatedClient ac
JOIN Registration r ON r.Id = ac.ID
WHERE TIMESTAMPDIFF(YEAR, Reg.`Registration_date`, NOW()) >= 5;

DELETE  * FROM Registration r -- now delete all except locked clients
JOIN AssociatedClient ac ON ac.registration_id = r.id
LEFT JOIN LockedClient lc ON lc.client_id = ac.client_id
WHERE TIMESTAMPDIFF(YEAR, Reg.`Registration_date`, NOW()) >= 5 AND lc.client_id IS NULL
于 2013-02-26T11:09:12.493 に答える
0

2 つのテーブルから削除するため、2 つの sql delete ステートメントが必要です。

どちらの削除ステートメントも、保持されている登録と削除されている登録を区別する必要があるため、登録テーブルからの削除は 2 番目に行う必要があります。

制御問題は、ID (登録 ID またはクライアント ID) に関連付けられた最新の登録です。そのため、ID に基づいて集計し、最大登録日を見つけます。

クライアント ID を削除するときは、集約登録 ID が 5 年より古いものを削除します。この削除により、以前にリンクされていた登録 ID の関連付けが解除されますが、これは問題ありません。このアクションでは、関連付けられた最新の登録日が得られないためです。

つまり、クライアント ID を取得したら、関連する登録 ID を見つける登録 ID に結合する必要があります。その部分を正しく機能させるには、クライアント ID に参加してから、登録 ID に再び参加する必要があります。登録に関連付けられたすべてのクライアント ID を削除した場合は、それらの登録も削除する必要があります。

私のSQLは少し錆びており、私のmysqlはより錆びており、これはテストされていないコードですが、これはあなたがする必要があると私が考えるものにかなり近いはずです:

delete from associatedclient where client_id in (
  select client_id from (
    select ac.client_id, max(r.registration_date) as dt
      from associatedclient ac
        inner join registration r
          on ac.registration_id = r.id
      group by ac.client_id
  ) d where d.dt < cutoff
)

次のステップは次のようになります。

delete from registration where id in (
  select id from (
    select r1.id, max(r2.date) dt
      from registration r1
        inner join associated_client ac1
          on r1.id = ac1.registration_id
        inner join associated_client ac2
          on ac1.client_id = ac2.client_id
        inner join registration r2
          on ac2.registration_id = r2.id
) d
  where d.dt < cutoff
  or d.dt is null

思い出させていただいてもかまいませんが、先に進んで削除する前に、最初に削除なしで選択ステートメントを実行し、結果の妥当性を検査することをお勧めします。

(そして、これが機能しない制約やインデックスがある場合は、それらにも対処する必要があります。)

于 2014-09-19T17:36:07.793 に答える