web-dev-qa-db-ja.com

複数の列から最小値を選択する最良の方法は何ですか?

SQL Server 2005の次の表を考えます。

ID   Col1   Col2   Col3
--   ----   ----   ----
1       3     34     76  
2      32    976     24
3       7    235      3
4     245      1    792

次の結果(つまり、最終列-Col1、Col2、およびCol 3からの最小値を含む列各行)を生成するクエリを記述する最良の方法は何ですか?

ID   Col1   Col2   Col3  TheMin
--   ----   ----   ----  ------
1       3     34     76       3
2      32    976     24      24
3       7    235      3       3
4     245      1    792       1

UPDATE:

実際のシナリオでの説明のために(コメントで述べたように)、データベースは適切に正規化されたです。これらの「配列」列は、実際のテーブルにはありませんが、レポートに必要な結果セットにあります。新しい要件は、レポートにもこのMinValue列が必要であることです。基礎となる結果セットを変更することはできません。そのため、T-SQLに便利な「脱獄カード」を探していました。

以下で説明するCASEアプローチを試してみましたが、少し面倒ですが、動作します。また、同じ行に2つの最小値があるという事実に対応する必要があるため、回答に記載されているよりも複雑です。

とにかく、私は現在の解決策を投稿したいと思いました。 UNPIVOT演算子を使用します。

with cte (ID, Col1, Col2, Col3)
as
(
    select ID, Col1, Col2, Col3
    from TestTable
)
select cte.ID, Col1, Col2, Col3, TheMin from cte
join
(
    select
        ID, min(Amount) as TheMin
    from 
        cte 
        UNPIVOT (Amount for AmountCol in (Col1, Col2, Col3)) as unpvt
    group by ID
) as minValues
on cte.ID = minValues.ID

私はこれが最高のパフォーマンスを提供することを期待していないことを前もって言いますが、状況を考えて(新しいMinValue列の要件のためだけにすべてのクエリを再設計することはできません)、それはかなりエレガントですカード"。

64
stucampbell

これを達成する多くの方法がありそうです。私の提案は、Case/Whenを使用することです。 3列で、それほど悪くはありません。

Select Id,
       Case When Col1 < Col2 And Col1 < Col3 Then Col1
            When Col2 < Col1 And Col2 < Col3 Then Col2 
            Else Col3
            End As TheMin
From   YourTableNameHere
48
G Mastros

CROSS APPLYを使用:

SELECT ID, Col1, Col2, Col3, MinValue
FROM YourTable
CROSS APPLY (SELECT MIN(d) MinValue FROM (VALUES (Col1), (Col2), (Col3)) AS a(d)) A

SQL Fiddle

43
Nizam
SELECT ID, Col1, Col2, Col3, 
    (SELECT MIN(Col) FROM (VALUES (Col1), (Col2), (Col3)) AS X(Col)) AS TheMin
FROM Table
11
dsz

それを行うための最良の方法は、おそらくnotである-SQLの「体操」を必要とする方法でデータを保存することを人々が主張するのは奇妙だスキーマをもう少し良く構成するだけで目的の結果を達成するはるかに簡単な方法がある場合に、意味のある情報を抽出します:-)

私の意見では、これを行うためのright方法は次の表を持つことです:

_ID    Col    Val
--    ---    ---
 1      1      3
 1      2     34
 1      3     76

 2      1     32
 2      2    976
 2      3     24

 3      1      7
 3      2    235
 3      3      3

 4      1    245
 4      2      1
 4      3    792
_

_ID/Col_を主キーとして(必要に応じてColを追加キーとして)使用します。その後、クエリは単純なselect min(val) from tblになり、他のクエリで_where col = 2_を使用することで、個々の「古い列」を個別に処理できます。これにより、「古い列」の数が増えた場合でも簡単に拡張できます。

これにより、クエリsoがはるかに簡単になります。私が使用する傾向がある一般的なガイドラインは、everがデータベース行の配列のように見えるものを持っている場合、おそらく何か間違ったことをしていて、データの再構築について考える必要があります。


ただし、何らかの理由でこれらの列を変更できない場合、挿入トリガーと更新トリガーを使用して追加することをお勧めしますanotherこれらのトリガーが_Col1/2/3_で最小に設定する列。これにより、操作の「コスト」が選択から、それが属する更新/挿入に移動します-私の経験では、ほとんどのデータベーステーブルは、書き込みよりもはるかに頻繁に読み取られるため、書き込みコストは時間の経過とともに効率的になる傾向があります。

つまり、行の最小値は、他の列の1つが変更された場合にのみ変更されるため、毎回ではなく、計算する必要がある場合にthats選択(データが変更されていない場合は無駄になります)。次に、次のようなテーブルが作成されます。

_ID   Col1   Col2   Col3   MinVal
--   ----   ----   ----   ------
 1      3     34     76        3
 2     32    976     24       24
 3      7    235      3        3
 4    245      1    792        1
_

データは挿入/更新時にのみ変化するため、selectの時点で決定を下す必要がある他のオプションは通常、パフォーマンスの面で悪い考えです。別の列を追加するとDBのスペースが大きくなり、挿入と更新はわずかに遅くなりますが、選択はmuch速くなります-優先されるアプローチは優先順位に依存しますが、前述のように、ほとんどのテーブルは読むよりも頻繁にfarを読んでください。

7
paxdiablo

ひねりを加えた「ブルートフォース」アプローチを使用できます。

SELECT CASE
    WHEN Col1 <= Col2 AND Col1 <= Col3 THEN Col1
    WHEN                  Col2 <= Col3 THEN Col2
    ELSE                                    Col3
END AS [Min Value] FROM [Your Table]

最初のwhen条件が失敗すると、Col1が最小値ではないことが保証されるため、残りの条件からCol1を削除できます。後続の条件についても同様です。 5列の場合、クエリは次のようになります。

SELECT CASE
    WHEN Col1 <= Col2 AND Col1 <= Col3 AND Col1 <= Col4 AND Col1 <= Col5 THEN Col1
    WHEN                  Col2 <= Col3 AND Col2 <= Col4 AND Col2 <= Col5 THEN Col2
    WHEN                                   Col3 <= Col4 AND Col3 <= Col5 THEN Col3
    WHEN                                                    Col4 <= Col5 THEN Col4
    ELSE                                                                      Col5
END AS [Min Value] FROM [Your Table]

2つ以上の列が同点の場合は、<=は、CASEステートメントをできるだけ早く終了するようにします。

7
Salman A

これを使って:

select least(col1, col2, col3) FROM yourtable
6
user3493139

例のように列が整数の場合、関数を作成します。

create function f_min_int(@a as int, @b as int) 
returns int
as
begin
    return case when @a < @b then @a else coalesce(@b,@a) end
end

それから私はそれを使用する必要があるとき、私はします:

select col1, col2, col3, dbo.f_min_int(dbo.f_min_int(col1,col2),col3)

あなたが5列を持っている場合、上記はなります

select col1, col2, col3, col4, col5,
dbo.f_min_int(dbo.f_min_int(dbo.f_min_int(dbo.f_min_int(col1,col2),col3),col4),col5)
6
Georgios

ユニオンクエリを使用してこれを行うこともできます。列の数が増えると、クエリを変更する必要がありますが、少なくとも単純な変更です。

Select T.Id, T.Col1, T.Col2, T.Col3, A.TheMin
From   YourTable T
       Inner Join (
         Select A.Id, Min(A.Col1) As TheMin
         From   (
                Select Id, Col1
                From   YourTable

                Union All

                Select Id, Col2
                From   YourTable

                Union All

                Select Id, Col3
                From   YourTable
                ) As A
         Group By A.Id
       ) As A
       On T.Id = A.Id
5
G Mastros

これはブルートフォースですが、動作します

 select case when col1 <= col2 and col1 <= col3 then col1
           case when col2 <= col1 and col2 <= col3 then col2
           case when col3 <= col1 and col3 <= col2 then col3
    as 'TheMin'
           end

from Table T

... min()は1つの列でのみ機能し、列全体では機能しないためです。

3
Learning

両方 この質問 そして この質問 これに答えようとします。

要約すると、Oracleにはこのための組み込み関数があり、SQL Serverでは、ユーザー定義関数を定義するか、caseステートメントを使用するかのどちらかです。

2
Sam Saffron

ストアドプロシージャを作成できる場合、値の配列を受け取ることができ、それを呼び出すことができます。

1
Kev
select *,
case when column1 < columnl2 And column1 < column3 then column1
when columnl2 < column1 And columnl2 < column3 then columnl2
else column3
end As minValue
from   tbl_example
1
Phil Corcoran
SELECT [ID],
            (
                SELECT MIN([value].[MinValue])
                FROM
                (
                    VALUES
                        ([Col1]),
                        ([Col1]),
                        ([Col2]),
                        ([Col3])
                ) AS [value] ([MinValue])
           ) AS [MinValue]
FROM Table;

SQL 2005を使用している場合は、次のようなきちんとしたことができます。

;WITH    res
          AS ( SELECT   t.YourID ,
                        CAST(( SELECT   Col1 AS c01 ,
                                        Col2 AS c02 ,
                                        Col3 AS c03 ,
                                        Col4 AS c04 ,
                                        Col5 AS c05
                               FROM     YourTable AS cols
                               WHERE    YourID = t.YourID
                             FOR
                               XML AUTO ,
                                   ELEMENTS
                             ) AS XML) AS colslist
               FROM     YourTable AS t
             )
    SELECT  YourID ,
            colslist.query('for $c in //cols return min(data($c/*))').value('.',
                                            'real') AS YourMin ,
            colslist.query('for $c in //cols return avg(data($c/*))').value('.',
                                            'real') AS YourAvg ,
            colslist.query('for $c in //cols return max(data($c/*))').value('.',
                                            'real') AS YourMax
    FROM    res

このように、多くのオペレーターで迷子になりません:)

ただし、これは他の選択よりも遅くなる可能性があります。

それはあなたの選択です...

1
leoinfo

複数の列の場合はCASEステートメントを使用するのが最適ですが、2つの数値列iおよびjの場合は単純な数学を使用できます。

min(i、j)=(i + j)/ 2-abs(i-j)/ 2

この式は、複数の列の最小値を取得するために使用できますが、実際には乱雑な過去2、min(i、j、k)はmin(i、min(j、k))になります

1
user3658750

以下では、いくつかの日付の最小値を取得するために一時テーブルを使用しています。最初の一時テーブルはいくつかの結合テーブルを照会してさまざまな日付(およびクエリの他の値)を取得し、2番目の一時テーブルは日付列と同じ数のパスを使用してさまざまな列と最小日付を取得します。

これは基本的にユニオンクエリに似ており、同じ回数のパスが必要ですが、より効率的かもしれません(経験に基づいていますが、テストが必要です)。この場合、効率は問題になりませんでした(8,000レコード)。インデックスなどを作成できます.

--==================== this gets minimums and global min
if object_id('tempdb..#temp1') is not null
    drop table #temp1
if object_id('tempdb..#temp2') is not null
    drop table #temp2

select r.recordid ,  r.ReferenceNumber, i.InventionTitle, RecordDate, i.ReceivedDate
, min(fi.uploaddate) [Min File Upload], min(fi.CorrespondenceDate) [Min File Correspondence]
into #temp1
from record r 
join Invention i on i.inventionid = r.recordid
left join LnkRecordFile lrf on lrf.recordid = r.recordid
left join fileinformation fi on fi.fileid = lrf.fileid
where r.recorddate > '2015-05-26'
 group by  r.recordid, recorddate, i.ReceivedDate,
 r.ReferenceNumber, i.InventionTitle



select recordid, recorddate [min date]
into #temp2
from #temp1

update #temp2
set [min date] = ReceivedDate 
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.ReceivedDate < [min date] and  t1.ReceivedDate > '2001-01-01'

update #temp2 
set [min date] = t1.[Min File Upload]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.[Min File Upload] < [min date] and  t1.[Min File Upload] > '2001-01-01'

update #temp2
set [min date] = t1.[Min File Correspondence]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.[Min File Correspondence] < [min date] and t1.[Min File Correspondence] > '2001-01-01'


select t1.*, t2.[min date] [LOWEST DATE]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
order by t1.recordid
1
user3438020

ユニオンクエリのちょっとした工夫:

DECLARE @Foo TABLE (ID INT, Col1 INT, Col2 INT, Col3 INT)

INSERT @Foo (ID, Col1, Col2, Col3)
VALUES
(1, 3, 34, 76),
(2, 32, 976, 24),
(3, 7, 235, 3),
(4, 245, 1, 792)

SELECT
    ID,
    Col1,
    Col2,
    Col3,
    (
        SELECT MIN(T.Col)
        FROM
        (
            SELECT Foo.Col1 AS Col UNION ALL
            SELECT Foo.Col2 AS Col UNION ALL
            SELECT Foo.Col3 AS Col 
        ) AS T
    ) AS TheMin
FROM
    @Foo AS Foo
1
Lamprey

探している値、通常はステータスコードがわかっている場合は、次の情報が役立ちます。

select case when 0 in (PAGE1STATUS ,PAGE2STATUS ,PAGE3STATUS,
PAGE4STATUS,PAGE5STATUS ,PAGE6STATUS) then 0 else 1 end
FROM CUSTOMERS_FORMS
0

私はその質問が古いことを知っていますが、私はまだ答えを必要としており、他の答えに満足していなかったので、@ paxdiablo´s answer をひねった独自のものを考案しなければなりませんでした。


私はSAP ASE 16.0の土地から来ましたが、単一行の異なる列に有効に保存されている特定のデータの統計情報を覗くだけでしたアクションが開始され、最終的に実際の時間でした)。したがって、私は列を一時テーブルの行に転置し、これに対して通常どおりクエリを実行しました。

N.B. ワンサイズのすべてに適合するソリューションではありません!

CREATE TABLE #tempTable (ID int, columnName varchar(20), dataValue int)

INSERT INTO #tempTable 
  SELECT ID, 'Col1', Col1
    FROM sourceTable
   WHERE Col1 IS NOT NULL
INSERT INTO #tempTable 
  SELECT ID, 'Col2', Col2
    FROM sourceTable
   WHERE Col2 IS NOT NULL
INSERT INTO #tempTable 
  SELECT ID, 'Col3', Col3
    FROM sourceTable
   WHERE Col3 IS NOT NULL

SELECT ID
     , min(dataValue) AS 'Min'
     , max(dataValue) AS 'Max'
     , max(dataValue) - min(dataValue) AS 'Diff' 
  FROM #tempTable 
  GROUP BY ID

これには630000行のソースセットで約30秒かかり、インデックスデータのみを使用したため、タイムクリティカルなプロセスで実行するのではなく、1回限りのデータ検査や1日の終わりのレポートなどに使用できます(ただし、これを同僚や上司に確認してください!)このスタイルの主なボーナスfor meは、特にデータがコピーされると、より多く/少ない列を使用し、グループ化、フィルタリングなどを簡単に変更できることでした。

追加のデータ(columnNamemaxes、...)は検索を支援するためのものであるため、必要ない場合があります。多分sparkいくつかのアイデア:-)にそれらを残しました。

0
Rao