- ベストアンサー
テーブル格納して表示した記事を重複で表示させない方法について
- テーブル格納して表示した記事を重複させないためには、select existsを使用することができます。カウント対象件数が多い場合やインデックスが効かない場合、繰り返し実行することが多い場合には特に効果的です。
- 以下のコードでは、複数のRSSをテーブルに格納し、重複を避ける方法を示しています。select existsを使ってすでに表示された記事を除外しています。
- この方法を使用することで、重複したコンテンツを表示させずに効率的に記事を処理することができます。ただし、適切なインデックスを設定するなどの最適化が必要です。
- みんなの回答 (8)
- 専門家の回答
質問者が選んだベストアンサー
データベースがパンクしているとのことですが、そんなに容量が少ないことはないと思うのですが、何回も実行してデータが多くなったのでしょうか。 時々テーブルを空にしてくださいね。 phpMyAdminでしたらテーブルの一覧画面で「空にする」でできます。 その時の確認メッセージに「TRUNCATE ` rss_feed`」と表示されます。 このSQLを実行すれば空にできるので、SQLタブで自分で実行もできます。
その他の回答 (7)
- dell_OK
- ベストアンサー率13% (766/5721)
別の方に先に回答してしまいましたが、あれはあれでおいておいて。 確かに保存してしまって表示(データベースからの読み込み)の時にどうにかした方が楽です。 もっとも簡単な方法です。 SELECT DISTINCT title,link,date from teblename これだけです。 「DISTINCT」を付けると重複するものはひとつだけになります。 それはそれとして。 重複とか関係のない話しで、いつ実行するのかについて。 RSSを取得して保存するのを閲覧時にはしない方法です。 サーバーでcronなどのスケジュール機能を使って定期的にPHPを実行できるのであれば、それでデータベースへの保存をさせた方が負荷がぐっと減るかもしれません。 閲覧時点での最新RSSと言うわけにはいきませんが、例えば1時間ごとに実行すれば最長で1時間で、1時間以内のRSSを閲覧できます。
お礼
色々な意見をもとにwordpressのファイル構成を決めたのですが、わからない点があります。 以下は決めた内容です。 データベース関連→db.php 共通部分→データベースの接続とページナビはfunctions.php 表示部分→page.php index.php→目次の役割?page.phpがエラーが出ているときにメンテナンス中表示されるもの 接続の際にtry文をfunctions.phpに書きたいのですが、他の記述も読み込まれるためrequire_onceでfunctions.phpを読み込ませられないようです。 ファイルを読み込ませずに記述のみでpage.phpのDB処理につなげることは可能でしょうか? <?php try { $dsn = 'mysql:dbname=hlxclitx_wp1;host=localhost'; $user = 'hlxclitx_wp1'; $password = 'E.HrypHWxNmltXgC5eS26'; $dbh = new PDO($dsn, $user, $password); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //エラーが発生した時に、例外を投げる echo 'データベースへの接続が出来ました'; } catch (PDOException $e) { echo $e->getMessage(); // err時はメッセージを表示 exit; }
補足
どうやらデーベースがパンクしているようです。 格納数の制限をするためのコードを考えています。
- dell_OK
- ベストアンサー率13% (766/5721)
・処理を省いてデータのチェックを行うよう調べています。 そうですね。 私が思っていたのは、各サイトごとにデータベースの最新の日付と取得したRSSの日付を比較して、データベースの方が古い場合に、新しいRSSを追加する方法です。 RSS取得に日付指定ができると、データベースの最新の日付より大きいものと指定すれば、RSS取得の時間もデータベース保存の時間も最小にできそうな気がします。 各サイトごとに処理するために、テーブルにサイトのURLを保存するカラムを追加する必要があります。 サイトのURLがリンクのURLと前方一致するのであれば、カラムを追加しなくてもできるとは思うのですが、追加した方が楽そうです。
お礼
もしRSSの中身が変わったとしても重複しないように記事を取得することができれば問題ない気はします。 例えば1時間前に取得したRSSとコンテンツが切り替わったRSSの内容が違っていても保存をしていけばキャッシュのタイミングもこちらでコントロールできます。
補足
データベースの最新の日付と取得したRSSの日付を比較して、データベースの方が古い場合に、新しいRSSを追加する方法です。 A.IDで最新の日付けを判別するようですが、データが切り替わる場合割り当てるのはかなり困難な気がします。 表示する際に重複を避けるのが一番楽ではないでしょうか?
- dell_OK
- ベストアンサー率13% (766/5721)
リフレッシュは重要です。 心と体を大切にしてください。 補足 2022/01/21 03:45 $stmt->execute(); これはその文字の通りで「実行する」と言う意味です。 なにを実行するかと言うと、 $stmt = $dbh->prepare($sql); でPDOに準備させておいたSQL文で、 $sql = 'insert into ~'; だったり、 $sql = 'SELECT * FROM ~'; だったりするわけです。 テーブルを作成する、 $sql = 'CREATE TABLE ~'; も実行できます。 実はexecute()以外にquery()でも実行できます。 その違いについては割愛しますが、execute()の方が便利で安全と思っています。 質問者さまの言われている重複の意味をよく理解していませんでした。 重複登録したものを単一のものとして呼び出したいのかと思って、その方法が①select countか②select existsのどちらかだと思っていました。 それよりは重複登録をしないようにした方がいいと思って提案した次第です。 それもあったかも知れませんが、1ページ目に何件か表示して、2ページ目に遷移した時に続きから表示したい(最初から表示しない)と言うのもあると言う事でしょうか。 それは現在のページ番号がわかると思うので、SELECTのLIMIT句を指定して読み飛ばすことができます。 例えば、1ページあたり36件のRSSを表示するとします。 1ページ目は LIMIT 0,36 (0番目から36件)、 2ページ目は LIMIT 35,36 (35番目から36件)、 3ページ目は LIMIT 71,36 (71番目から36件)、と言う感じです。 変動値はひとつ目の値の、0、35、71、なので、これを算出します。 現在のページ番号の変数を $page だとして、1ページ目を1とします。 1ページあたりの件数は $one_page_count = 36; としておきます。 $start_row = ( $page - 1 ) * $one_page_count; SQL文はこうなり、 $sql = 'SELECT * FROM teblename ORDER BY date DESC LIMIT ?,?'; 実行パラメータはこうなります。 $stmt->execute([$start_row, $one_page_count]); この変更は少しなのでいつでも実装できると思いますし、もしかしたらSQL文を大幅に変更することになってこの方法ではなくなるかも知れません。 なので、ページまたがりのことよりも、ページを期待のデザインで表示することを優先しましょう。 補足 2022/01/21 03:58 そうですね、上限設定は重要だと私も思います。 現在は3サイトからRSS取得していますが、実際はそれより多くなる予定でしょうか。 それが360件分取得できればデータベースを使う必要はありませんが、 データベースの勉強も兼ねてのことなのでこれはこれで続けましょう。 問題はデータを削除するタイミングと件数です。 もしRSSを360件分取得できるとしたら、全件削除して全件(360件)登録する、と言う簡単な話しなので、いつ誰が実行しても問題なさそうに思えます。 個人的なサイトで質問者さまのみが閲覧するのでしたらなおのことそれでかまいません。 ですが、いつ誰が閲覧するかわからない場合に全件削除全件登録は簡単にはすみません。 細かい話しをすると余計に難しくなりますが、例えばこんな問題が発生します。 2人がほぼ同時に閲覧したとしましょう。 PHPとデータベースがどのタイミングでサーバーに処理されるかはわかりませんが、並列で処理されていると思っています。 2人が閲覧すればそれぞれのタイミングでほぼ同時に進行している、と言うことです。 1人目で全件削除全件登録した直後でかつ読み込み表示の直前のタイミングで、2人目の全件削除が処理されると、1人目が読み込むタイミングではデータが1件もないことになります。 すごく短い時間なのでそんなことはまれのまれのまれですが、起こりうるわけです。 同時に閲覧する人が多ければ多いほど発生する確率が上がることになります。 「あれ、表示されない」と思ってブラウザで再表示すればそれですむ話しでもあります。 ですが、人間が補助するのは間違っていると思います。 それを回避する方法もいろいろあります。 そのひとつとして、最新の360件は常に確保し、それより古いものを削除する、です。 データが360件より少ない場合は削除しません。 データが360件より多くても360件までしか読み込みません。 これであれば、誰かが削除したタイミングで、他の誰かが読み込み表示してもデータが1件もないことはなくなります。 実はそれでもまだ問題があるのですが、前の問題ほど重要視するほどではないかも知れないので今はやめておきます。
お礼
順番がおかしいと整理がつかず混乱してしまうので1度流れを整理してみます。 1,データベースに接続する 2,RSSをまとめる 3,insertの準備 4,テーブルに格納 5,テーブルの格納数や重複コンテンツに関するオプションを付ける 6,表示処理の分割 文字のみと文字画像両方で表示する(クラスを分けて表示する) こちらに向けて組んでみます。
補足
$stmt->execute();は実行するという意味なんですね 勉強になりました。ありがとうございます。 1ページ目に何件か表示して、2ページ目に遷移した時に続きから表示したい(最初から表示しない)と言うのもあると言う事でしょうか。ページを期待のデザインで表示することを優先しましょう。 A.そうですね。2ページ目で重複することを危惧しています。 了解いたしました。デザインを優先に考えます。 現在は3サイトからRSS取得していますが、実際はそれより多くなる予定でしょうか。 A.実際は10~になるかもしれません。 同時に閲覧する人が多ければ多いほど発生する確率が上がることになります。 A.同時に多くの人が閲覧されることが想定されますので、できるだけ遅延はさけたいですね。処理を省いてデータのチェックを行うよう調べています。
- dell_OK
- ベストアンサー率13% (766/5721)
④については、いろいろな方法があると思います。 まずは実現のために私が思いつく方法を提案してみます。 他の人からの提案があればそちらも検討してみてください。 いずれにしてもテーブルの変更は余儀なくされることになると思います。 まずは一番簡単そうな方法から。 テーブルの変更としては、linkカラムを主キーまたはインデックスにしておきます。 RSSのlinkと同じlinkのデータが存在しているかをチェックします。 存在していなければINSERTします。 存在していればなにもしないか、UPDATEします。 これだけです。 linkは主キーまたはインデックスにしなくてもできるのですが、存在チェックのパフォーマンスのためにしておいた方がいいと思います。 パフォーマンスとは言っても、古いデータを定期的に削除して総レコード数が多くならないようにするのでしたら、もしかしたら主キーやインデックスがない方が早いかも知れません。 なにもしないか、UPDATEするかは、質問者さまが検討してください。 なにを検討するのかと言うと、一度配信されたRSSの内容が変更されることがあるのか、です。 私はRSSのことをまったく知らないので想像でこのことをあげています。 変更されないのであれば、なにもしない、です。 変更されるとしたらどのような変更があるのかですが、それは配信サイトによって違いがあるかも知れないので、必ずそうなるとは言えないものかも知れません。 そうだとしても、タイトルや日付が変更されるのであれば、UPDATEでtitleやdateを更新しておいた方がいいとは思います。 問題はリンクが変更される場合で、そうなると存在チェックで存在しないことになりINSERTしてしまいます。 それはそれで、元のリンク先がアクセスできないか、アクセスできても古い情報、と言うだけのことですませてしまうかです。 存在チェックとは言ったものの、重複キーがなければINSERT、あればUPDATEする、と言う便利な記述方法がMySQL(MariaDB)にはあります。 SQL文と実行パラメータを少し変更する程度です。 linkを主キーか、ユニークインデックスにする必要があります。 $sql = 'insert into teblename (title, link, date) values (?, ?, ?) on duplicate key update title = ?, date = ?'; $stmt = $dbh->prepare($sql); $stmt->execute([$title, $link, $date, $title, $date]); RSSは変更されないのでなにもしない、変更されるかも知れないけどなにもしない、と言うのであれば、存在チェックの方法を改めて回答します。
お礼
1つ1つの課題を丁寧につぶす必要がありそうなので、調べていきます。 1.RSSの格納数について 2.ページをまたいでも修復されない仕組みを作る(表示の際に確認したほうがいいのではないか) 3,classを分けて表示する方法を調べる 4,最後にコードを合体させる かなりの応用コードになると思いますので、丁寧に調べていこうと思います。
補足
④については、いろいろな方法があると思います。 まずは実現のために私が思いつく方法を提案してみます。 他の人からの提案があればそちらも検討してみてください。 いずれにしてもテーブルの変更は余儀なくされることになると思います。 A.RSSに関しましては定期的にリンクURLは切り替わります。タイトルも変更されるため、重複コンテンツを制御するのは難しいのではないかと考えております。 そこで表示の際に重複コンテンツを避けるように表示しようと試行錯誤していたのですが、データベースで関数のチェックを行うようアドバイスをいただきました。 自分の考えではコンテンツを最大360個を上限に保存していき(格納上限を付けないとサーバーがパンクするため) 最新の記事ページから10ページ分まで格納して重複チェックして表示するようなアイデアを考えたのですが。こちらもかなり難しそうです。 自分でも正直でもどうすればよいのかわかりません。 容量があるのでページ分丸ごとRSSを取得するわけにもいきませんし、重複コンテンツのチェックもURLごとにRSSクローラーが定期的に巡回して定期的に自動更新されるためまともに行うこともできません。
- dell_OK
- ベストアンサー率13% (766/5721)
②と③についてですが、先の方法で保存の処理はできたことにして、データベースから読み込んで表示する処理を考えてみます。 出力結果のデザインや見た目は考えずに、読み込んで表示する、ことdだけに注目してください。 私はデータベースのアクセスにはmysqliを使うためPDOには詳しくなく、この場しのぎで調べた方法にしているので、他にいい方法や一般的な方法があるのかも知れません。 問題がなければこのままで、問題があれば適宜調整してください。 それと私はprintfも使わないので、質問者さまのコードをなるべくくずさないようにコーディングしています。 $sql = 'SELECT * FROM teblename ORDER BY date DESC'; $stmt = $dbh->prepare($sql); $stmt->execute(); while ($item = $stmt->fetch(PDO::FETCH_OBJ)) { print('<ul>'); printf('<li class="sitelink"><a href="%s">%s</a></li>', $item->link, $item->title); printf('<li class="sitelink"><a href="%s">site</a></li>', $item->link); printf('<li class="sitedate">%s</li>', $item->date); print('</ul>'); } SQL文はいったん変数にした方がいいと思います。 INSERTのところも直接prepareに渡さない方がいいです。 $sql = 'insert into teblename (title, link, date) values (?, ?, ?)'; $stmt = $dbh->prepare($sql); 理由は説明しませんが、もしかしたらそのうちそう思えてくるようになるかも知れません。 SQLはphpMyAdminなどで実行して試してください。 プログラムで確認する前には必ずやった方がいいと思います。 変更するたびに内容を確認しておいて、プログラムの結果と比較する、と言う具合です。 カラム「date」の降順に取得しているだけです。 本当はサイトごとにまとめたりしたいかも知れませんが、今はこのままで。 「date」はMySQLの予約語なのでできれば他の名前にした方がいいと思います。 予約語かカラム名かはMySQLが判定してくれて正しく動作してくれていますが、もしかしたらバージョンアップにより突然使えなくなるかも知れません。 なにより、他人が見るとデータ型なのかカラム名なのか戸惑います。 ループも今はこんな感じで試しておいてください。 これでデータベースへ保存の処理とデータベースから読み込んで表示の処理を別々にできたかと思います。
お礼
もう1点調べておこうと思うのですが function.phpでRSSをDBに保存せずに重複を避ける事が出来れば問題は解決するので、そちらも調べてみます DB保存は勉強にもなる為、最後まで作り上げようと思いますが。
補足
私はデータベースのアクセスにはmysqliを使うためPDOには詳しくなく、この場しのぎで調べた方法にしているので、他にいい方法や一般的な方法があるのかも知れません。 A.丁寧に調べていただきありがとうございます。 1点お聞きしたいのですが、$stmt->execute();はどういう意味でしょうか?executeはデータベースに入れる処理だけでなく出す処理もこなすように読み取れたのですが。 ※while ($item = $stmt->fetch(PDO::FETCH_OBJ)) { この部分でループを抜けるのはわかったのですが、ページをまたぐ場合の重複表示について気になるのですが、 ②select existsを使わずに重複は避けれるのでしょうか?
- dell_OK
- ベストアンサー率13% (766/5721)
箇条書きですみませんでした。 実は考えなくてはいけないことがたくさんあるため、一度に説明できなくて省略してしまいました。 質問者さまご自身でいろいろと考えられて、方向が見えているようには思えます。 ①については、「$stmt->execute」がなくなっていたのでinsertが実行されなくなっていたためです。 以前は実行されていたと思うので、いつからかなくなってしまっただけだと思います。 なのでテーブル名は問題ありません。 8件までの理由はなんでしょうか。 表示したいのが8件であれば、保存の処理からはなくしてしまいましょう。 以前からデータベースに保存せずに直接表示した方がいいと思っているのですが、それだと現在取得できるものだけになってしまうので、過去のRSSも表示するために保存しておきたい、と考えると、最後の実行から次の実行までの間に新しいRSSが8件より多い場合に保存からもれるものがあります。 なので保存の処理では取得したものすべてを対象にしておいた方がもれが少なくなると思います。 以上のことからひとまず保存することだけのコードとしてはこんな感じになると思います。 foreach ($url1 as $url) { if (($rss = @simplexml_load_file($url)) === false) { continue; } foreach ($rss->item as $item) { $title = $item->title; $link = $item->link; $dc = $item->children('http://purl.org/dc/elements/1.1/'); $date = date('Y-m-d H:i:s', strtotime($dc->date)); $stmt->execute([$title, $link, $date]); } } 新しい質問で「$sql->」になっているようなので「$stmt->」に直してください。 これで私の環境では保存できるようになっています。 RSSからサムネイルのURLが取得できるのかどうかわかりませんが、「$item->thumb->url」が空でないものは、現在の3件のRSSのサイトからはみつかっていません。 みつかるサイトがないと表示できませんし、そもそも取得できないのであれば不要な処理ですのでなくしていいと思います。 みつかるとして、以下の2か所で変数名が違うのでどちらかに合わせてください。 もしこの2か所の途中で$thumbから$thumbnailに編集加工するのでしたら、その処理も入れてください。 $thumb = $item->thumb->url;//画像を取得 printf('<li class="sitethumb"><a href="%s"><img src="%s"></a></li>', $link, $thumbnail);
お礼
①については、「$stmt->execute」がなくなっていたのでinsertが実行されなくなっていたためです。 以前は実行されていたと思うので、いつからかなくなってしまっただけだと思います。 なのでテーブル名は問題ありません。 A.executeでinsert実行を行うんですね。明確にわからなかったので勉強になりました。 8件までの理由はなんでしょうか。表示したいのが8件であれば、保存の処理からはなくしてしまいましょう。 A.すみません。8ではなく正確には18ですね。本当はrssを形を変えて3つのブロックで表示するつもりだったんですが、いかんせん難しくて… 理想は文字で表示するもの1と画像と文字で表示するもの2を3つ別の形にして表示するつもりです。なのでクラスを分ける必要があります。 最後の実行から次の実行までの間に新しいRSSが8件より多い場合に保存からもれるものがあります。 なので保存の処理では取得したものすべてを対象にしておいた方がもれが少なくなると思います。 A.データベースを理解できていないので正確にはわかりませんが、18×2(Iページ36コンテンツ)×10ページ分をデータベースに格納しようと思っております。 もとは記事→RSS→記事→RSSでしたが、元々考えていた構想ではRSS→記事→RSS→記事となっており、表示の順番が逆でした。申し訳ありません。 新しい質問で「$sql->」になっているようなので「$stmt->」に直してください。 これで私の環境では保存できるようになっています。 A.了解いたしました。 RSSからサムネイルのURLが取得できるのかどうかわかりませんが、「$item->thumb->url」が空でないものは、現在の3件のRSSのサイトからはみつかっていません。 A.こちらはURLのミスです。テストを行った際に画像のないサイトかどうか確認していなかったです。本来は画像のあるサイトのみ表示いたします。
補足
申し訳ありません。1週間近く空いた時間をコード修正に費やしていたので1日リフレッシュいたしました。 また頑張ってコード修正、勉強していきますのでよろしくお願いいたします。
- dell_OK
- ベストアンサー率13% (766/5721)
①現在のコードはデータベースに保存されなくなっています。 ②(以前からですが)表示されているのは取得したRSSの内容で、データベースのものではありません。 ③取得したRSSを保存する処理と、データベースから読み込んで表示する処理は別々にした方がいいと思います。 ④データベースには重複しないように保存した方がいいと思います。
お礼
このようにして、格納と表示を書いていたので基盤に作り直してみます。 $stmt = $pdo->prepare('insert into teblename (data) values (?)'); $out = []; foreach ($url_arr as $url) { if (($rss = simplexml_load_file($url)) === false) continue; foreach ($rss->channel->item as $item) { $stmt->execute([json_encode($item)]); $out[] = sprintf('<li>%s:%s</li>', $item->title, $item->description); } } (続く) shimixさん 2022/1/10 23:50 if (count($out)) { array_unshift($out, '<ul>'); $out[] = '</ul>'; } ?> <!DOCTYPE html> <!-- 省略 --> <?= implode(PHP_EOL, $out) ?>
補足
①現在のコードはデータベースに保存されなくなっています。 A.table nameにrss_feedと差し替えてもデータ保存されてないでしょうか? ②(以前からですが)表示されているのは取得したRSSの内容で、データベースのものではありません。 A.以前からこの部分は気になっていたのですがやはり紐ずいてませんよね… ③取得したRSSを保存する処理と、データベースから読み込んで表示する処理は別々にした方がいいと思います。 A.はじめはエンコードとデコードで保存したデータを表示させようとしていたんですが、途中から複雑になりコードの収集がつかなくなっております。 $stmt = $pdo->prepare('insert into teblename (data) values (?)'); foreach ($rss->item as $item) $stmt->execute([json_encode($item)]); として各 item を格納していれば、 foreach ($pdo->query('select data from tablename order by id desc') as $row) { $item = json_decode($row['data']); // $item を使って処理 } ④データベースには重複しないように保存した方がいいと思います。 A.重複を避けて登録する方向にしてみます。
お礼
後々のことを考えたのですが、固定ページと投稿ページはクリックの前後でデザインが変わるため、表示が変わらないRSSとページナビにつきましてはfunctionsを使用したほうがよさそうな気がします。 そこでfunctionsを使うタイミングを考えたのですが、通常はDB接続から行う必要がありそうです。 自分なりに学んだ知識を使い組んでみます。
補足
10ページ以降は1ページに戻るよう組んでみました。 投稿ページのページネーションとの兼ね合いの問題もありますが、そこはどうなるかまだ分かりません… ※$sql = 'SELECT title FROM rss_feed ORDER BY date'; でテーブルをなぜか選択できていないため、数が取得できません。 $sql = 'SELECT title FROM rss_feed ORDER BY date';//テーブルを選択 $stmt = $dbh -> prepare($sql);//一回の関数コールの中で SQL ステートメントを準備して実行し、 結果を PDOStatement オブジェクトとして返します。 $count = $stmt -> rowCount();//SELECT 文によって返された行をカウントする echo $count.'件SELECTしました。';//テスト確認 $perPage = 36; // 1ページあたりのデータ件数 //「URLのパラメータにpageが存在しない場合」「pageが0以下」「pageがトータルページを超えている場合」に1ページ目を表示するように処理しています。9行目では悪意のある値を受け付けないように数値にキャスト(変換)しています。 $totalPage = 10; // 最大ページ数 if ( isset($_GET["page"]) && $_GET["page"] > 0 && $_GET["page"] <= $totalPage ) { $page = (int)$_GET["page"]; } else { $page = 1; } $page = (int) $_GET['page'];//現在のページ番号