13

MS SQL 2005 テーブルにさまざまな「もの」の価格を含むテーブルがあります。1 日あたり何百ものレコードがあり、さまざまなモノの価格がさまざまな時間に更新されます。

ID uniqueidentifier not null,
ThingID int NOT NULL,
PriceDateTime datetime NOT NULL,
Price decimal(18,4) NOT NULL

あるグループのものについて、今日の最新の価格を取得する必要があります。以下のクエリは機能しますが、何百もの行が戻ってくるので、それらをループして ThingID ごとに最新のものだけを抽出する必要があります。ThingID ごとに最新のものを取得するにはどうすればよいですか (たとえば、GROUP BY を介して)。または、サブクエリを使用する必要がありますか?

SELECT * 
FROM Thing
WHERE ThingID IN (1,2,3,4,5,6)
  AND PriceDate > cast( convert(varchar(20), getdate(), 106) as DateTime) 

更新:複雑さを隠すために、ID 列を int に入れました。実際には GUID です (シーケンシャルではありません)。上記のテーブル定義を更新して、uniqueidentifier を使用しました。

4

10 に答える 10

21

テーブル構造の唯一の解決策は、サブクエリを使用することだと思います。

SELECT *
   FROM Thing
   WHERE ID IN (SELECT max(ID) FROM Thing 
                   WHERE ThingID IN (1,2,3,4)
                   GROUP BY ThingID)

(最も高い ID は最新の価格を意味します)

ただし、最新の価格でない場合は 0、最新の場合は 1 である "IsCurrent" 列を追加することをお勧めします。これにより、データの一貫性が失われる可能性が高くなりますが、テーブルが大きくなると (インデックス内にある場合)、プロセス全体が大幅に高速化されます。その後、あなたがする必要があるのは...

SELECT *
   FROM Thing
   WHERE ThingID IN (1,2,3,4)
     AND IsCurrent = 1

アップデート

OK、Markus は質問を更新して、ID が int ではなく uniqueid であることを示しました。これにより、クエリの作成がさらに複雑になります。

SELECT T.* 
   FROM Thing T
   JOIN (SELECT ThingID, max(PriceDateTime)
            WHERE ThingID IN (1,2,3,4)
            GROUP BY ThingID) X ON X.ThingID = T.ThingID 
                                AND X.PriceDateTime = T.PriceDateTime
   WHERE ThingID IN (1,2,3,4)

「IsCurrent」列を使用するか、回答にある他の提案を使用して、「現在の価格」テーブルと別の「価格履歴」テーブルを使用することをお勧めします(価格を保持するため、最終的にはこれが最速になります)テーブル自体は小さい)。

(一番下の ThingID が冗長であることはわかっています。その「WHERE」の有無にかかわらず、より高速かどうかを試してみてください。オプティマイザーが作業を行った後、どちらのバージョンが高速になるかはわかりません。)

于 2008-09-08T10:16:28.780 に答える
3

次のサブクエリのようなものを試して、データ構造を変更することを忘れます。

SELECT
 *
FROM
 Thing
WHERE 
 (ThingID, PriceDateTime) IN 
 (SELECT 
   ThingID, 
   max(PriceDateTime ) 
  FROM 
   Thing 
  WHERE 
   ThingID IN (1,2,3,4)
  GROUP BY 
   ThingID
 )

上記の編集はANSI SQLであり、サブクエリに複数の列があるとT SQLでは機能しないと推測しています。マリウス、次のことをテストすることはできませんが、試してみてください。

SELECT
 p.*
FROM
 Thing p,
 (SELECT ThingID, max(PriceDateTime ) FROM Thing WHERE ThingID IN (1,2,3,4) GROUP BY ThingID) m
WHERE 
 p.ThingId = m.ThingId
 and p.PriceDateTime = m.PriceDateTime

別のオプションとして、日付を文字列に変更し、ID と連結して、列が 1 つだけになるようにすることもできます。ただし、これは少し厄介です。

于 2008-09-08T10:24:40.383 に答える
2

サブクエリ ルートが遅すぎる場合は、価格の更新を監査ログとして扱い、ThingPrice テーブルを維持することを検討します (おそらく、価格更新テーブルのトリガーとして)。

ThingID int not null,
UpdateID int not null,
PriceDateTime datetime not null,
Price decimal(18,4) not null

主キーは単に ThingID であり、「UpdateID」は元のテーブルの「ID」です。

于 2008-09-08T10:29:04.163 に答える
2

SQL Server 2005 を使用しているため、新しい (CROSS|OUTTER) APPLY 句を使用できます。APPLY 句を使用すると、テーブル値関数でテーブルを結合できます。

この問題を解決するには、最初にテーブル値関数を定義して、特定の ID、注文日について Thing から上位 n 行を取得します。

CREATE FUNCTION dbo.fn_GetTopThings(@ThingID AS GUID, @n AS INT)
  RETURNS TABLE
AS
RETURN
  SELECT TOP(@n) *
  FROM Things
  WHERE ThingID= @ThingID
  ORDER BY PriceDateTime DESC
GO

関数を使用して、クエリの上位 1 レコードを取得します。

SELECT *
   FROM Thing t
CROSS APPLY dbo.fn_GetTopThings(t.ThingID, 1)
WHERE t.ThingID IN (1,2,3,4,5,6)

ここでの魔法は、左側の結果セットのすべての行に関数を適用し、関数によって返された結果セットと結合して、最終的な結果セットを返す APPLY 句によって行われます。(注: 適用のような左結合を行うには、左側からすべての行を返す OUTTER APPLY を使用し、右側に一致する行のみを返す CROSS APPLY を使用します)

BlaM: 私はまだコメントを投稿できないので (レポポイントが低いため)、私自身の回答でもありません ^^, メッセージの本文で回答します: -テーブル値関数を使用している場合でも、APPLY 句左側の結果セットのすべての行に対して関数を呼び出すのではなく、関数から内部の sql を取得し、残りのクエリとの結合句に変換するように、SQL Server によって内部的に最適化されます。そのため、パフォーマンスはサブクエリを使用したクエリのパフォーマンスと同等またはそれ以上です (計画が SQL サーバーによって正しく選択され、さらに最適化を行うことができる場合)。インデックスが作成され、統計が最新の状態になっている (サブクエリを使用した通常のクエリがそのような状況で動作するのと同じように)

于 2008-09-08T11:44:08.007 に答える
1

最大値を取得できるように、uniqueidentifier をバイナリに変換しています。これにより、同一の ThingID と PriceDateTimes を持つ複数のレコードから重複を取得しないようにする必要があります。

SELECT * FROM Thing WHERE CONVERT(BINARY(16),Thing.ID) IN
(
 SELECT MAX(CONVERT(BINARY(16),Thing.ID))
  FROM Thing
  INNER JOIN
   (SELECT ThingID, MAX(PriceDateTime) LatestPriceDateTime FROM Thing
    WHERE PriceDateTime >= CAST(FLOOR(CAST(GETDATE() AS FLOAT)) AS DATETIME)
    GROUP BY ThingID) LatestPrices
  ON Thing.ThingID = LatestPrices.ThingID
   AND Thing.PriceDateTime = LatestPrices.LatestPriceDateTime
 GROUP BY Thing.ThingID, Thing.PriceDateTime
) AND Thing.ThingID IN (1,2,3,4,5,6)
于 2008-09-08T10:44:43.207 に答える
1

データがどのように使用されるかによって異なりますが、古い価格データが現在の価格データほど定期的に使用されない場合は、価格履歴テーブルに関する議論がここにある可能性があります。このようにして、新しい価格が入ってくると、最新でないデータが (おそらくトリガーによって) 価格履歴テーブルにアーカイブされる可能性があります。

私が言うように、アクセス モデルによっては、これがオプションになる可能性があります。

于 2008-09-08T10:25:01.973 に答える
1

ID は連続していないため、ThingID と PriceDateTime に一意のインデックスがあると仮定します。そのため、特定のアイテムの最新の価格は 1 つだけです。

このクエリは、リスト内のすべてのアイテムが今日価格設定されている場合に取得します。PriceDate の where 句を削除すると、日付に関係なく最新の価格が取得されます。

SELECT * 
FROM Thing thi
WHERE thi.ThingID IN (1,2,3,4,5,6)
  AND thi.PriceDateTime =
     (SELECT MAX(maxThi.PriceDateTime)
      FROM Thing maxThi
      WHERE maxThi.PriceDateTime >= CAST( CONVERT(varchar(20), GETDATE(), 106) AS DateTime)
        AND maxThi.ThingID = thi.ThingID)

1 日の開始時に価格を設定できるため、">" を ">=" に変更したことに注意してください。

于 2008-09-08T11:44:07.587 に答える
0

これを試してください(その価格の識別子や日時ではなく、最新の価格のみが必要な場合)

SELECT ThingID, (SELECT TOP 1 Price FROM Thing WHERE ThingID = T.ThingID ORDER BY PriceDateTime DESC) Price
FROM Thing T
WHERE ThingID IN (1,2,3,4) AND DATEDIFF(D, PriceDateTime, GETDATE()) = 0
GROUP BY ThingID
于 2008-09-08T12:01:56.560 に答える