18

以下のデータで

create table #ph (product int, [date] date, price int)
insert into #ph select 1, '20120101', 1
insert into #ph select 1, '20120102', 1
insert into #ph select 1, '20120103', 1
insert into #ph select 1, '20120104', 1
insert into #ph select 1, '20120105', 2
insert into #ph select 1, '20120106', 2
insert into #ph select 1, '20120107', 2
insert into #ph select 1, '20120108', 2
insert into #ph select 1, '20120109', 1
insert into #ph select 1, '20120110', 1
insert into #ph select 1, '20120111', 1
insert into #ph select 1, '20120112', 1

次の出力を生成したいと思います。

product | date_from | date_to  | price
  1     | 20120101  | 20120105 |   1
  1     | 20120105  | 20120109 |   2
  1     | 20120109  | 20120112 |   1

価格でグループ化し、最大日付と最小日付を表示すると、次のようになりますが、これは私が望むものではありません (日付の重複を参照してください)。

product | date_from | date_to  | price
  1     | 20120101  | 20120112 |   1
  1     | 20120105  | 20120108 |   2

したがって、本質的に私がやろうとしているのは、グループ列の製品と価格に基づくデータの段階的な変化によってグループ化することです。

これを達成するための最もクリーンな方法は何ですか?

4

5 に答える 5

32

ROW_NUMBER()次のような 2 つの呼び出しを含む、この種の問題を解決する (多かれ少なかれ) 既知の手法があります。

WITH marked AS (
  SELECT
    *,
    grp = ROW_NUMBER() OVER (PARTITION BY product        ORDER BY date)
        - ROW_NUMBER() OVER (PARTITION BY product, price ORDER BY date)
  FROM #ph
)
SELECT
  product,
  date_from = MIN(date),
  date_to   = MAX(date),
  price
FROM marked
GROUP BY
  product,
  price,
  grp
ORDER BY
  product,
  MIN(date)

出力:

product  date_from   date_to        price 
-------  ----------  -------------  ----- 
1        2012-01-01  2012-01-04     1     
1        2012-01-05  2012-01-08     2     
1        2012-01-09  2012-01-12     1     
于 2012-04-28T19:53:46.977 に答える
2

このフォーラムは初めてなので、私の投稿がお役に立てば幸いです。

本当に CTE を使用したくない場合 (おそらくそれが最善の方法だと思いますが)、セット ベースのコードを使用して解決策を得ることができます。このコードのパフォーマンスをテストする必要があります!.

各レコードに一意の識別子を使用できるように、追加の一時テーブルを追加しましたが、ソース テーブルにこの列が既にあると思われます。これが一時テーブルです。

    If Exists (SELECT Name FROM tempdb.sys.tables WHERE name LIKE '#phwithId%')
        DROP TABLE #phwithId    

    CREATE TABLE #phwithId
    (
        SaleId INT
        , ProductID INT
        , Price Money
        , SaleDate Date 
    )
    INSERT INTO #phwithId SELECT row_number() over(partition by product order by [date] asc) as SalesId, Product, Price, Date FROM ph 

Selectステートメントの本体

    SELECT 
        productId 
        , date_from
        , date_to
        , Price
    FROM
        (   
            SELECT 
                dfr.ProductId
                , ROW_NUMBER() OVER (PARTITION BY ProductId ORDER BY ChangeDate) AS rowno1          
                , ChangeDate AS date_from
                , dfr.Price
            FROM
                (       
                    SELECT
                        sl1.ProductId AS ProductId
                        , sl1.SaleDate AS ChangeDate
                        , sl1.price
                    FROM
                        #phwithId sl1
                    LEFT JOIN
                        #phwithId sl2
                        ON sl1.SaleId = sl2.SaleId + 1
                    WHERE
                        sl1.Price <> sl2.Price OR sl2.Price IS NULL
                ) dfr
        ) da1
    LEFT JOIN
        (   
            SELECT 
                ROW_NUMBER() OVER (PARTITION BY ProductId ORDER BY ChangeDate) AS rowno2
                , ChangeDate AS date_to     
            FROM
                (   
                    SELECT 
                        sl1.ProductId
                        , sl1.SaleDate AS ChangeDate
                    FROM
                        #phwithId sl1
                    LEFT JOIN
                        #phwithId sl3
                        ON sl1.SaleId = sl3.SaleId - 1  
                    WHERE
                        sl1.Price <> sl3.Price OR sl3.Price IS NULL         
                ) dto

        ) da2 
        ON da1.rowno1 = da2.rowno2  

データ ソース オフセットを 1 レコード (+ または -) でバインドすることにより、価格バケットがいつ変更されるかを特定でき、バケットの開始日と終了日を 1 つのレコードに戻すだけで済みます。

少し手間がかかり、パフォーマンスが向上するかどうかはわかりませんが、挑戦を楽しんでいました.

于 2012-04-28T18:16:48.983 に答える
0

私が思いついた比較的「クリーン」な解決策の1つは、次のとおりです。

;with cte_sort (product, [date], price, [row])
as
    (select product, [date], price, row_number() over(partition by product order by [date] asc) as row
     from #ph)

select a.product, a.[date] as date_from, c.[date] as date_to, a.price 
from cte_sort a
left outer join cte_sort b on a.product = b.product and (a.row+1) = b.row and a.price = b.price
outer apply (select top 1 [date] from cte_sort z where z.product = a.product and z.row > a.row order by z.row) c
where b.row is null
order by a.[date] 

でCTEを使用したのは、のrow_numberような関数を使用する場合に日付が欠落しているかどうかを心配する必要がないためですdateadd。明らかに、date_to列(私が行う)が必要な場合にのみ、外部適用が必要です。

このソリューションは私の問題を解決しますが、500万行のテーブルで希望する速度で実行するためのわずかな問題があります。

于 2012-04-12T07:56:38.940 に答える
-1
Create function [dbo].[AF_TableColumns](@table_name nvarchar(55))
returns nvarchar(4000) as
begin
declare @str nvarchar(4000)
    select @str = cast(rtrim(ltrim(column_name)) as nvarchar(500)) + coalesce('         ' + @str , '            ') 
    from information_schema.columns
    where table_name = @table_name
    group by table_name, column_name, ordinal_position 
    order by ordinal_position DESC
return @str
end

--select dbo.AF_TableColumns('YourTable') Select * from YourTable
于 2013-10-01T00:31:49.220 に答える