Question

Étant donné le tableau suivant dans SQL Server 2005:

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

Quelle est la meilleure façon d'écrire la requête qui donne le résultat suivant (par exemple une qui donne la dernière colonne - une colonne contenant les valeurs de minium de Col1, Col2 et Col 3 pour chaque rangée )?

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

Mise à jour:

Pour plus de précisions (comme je l'ai dit dans les coments) dans le scénario réel de la base de données est correctement normalisé . Ces colonnes « tableau » ne sont pas dans une table réelle mais sont dans un jeu de résultats qui est requis dans un rapport. Et la nouvelle exigence est que le rapport doit également cette colonne MinValue. Je ne peux pas changer le résultat sous-jacent donnée et, donc je cherchais à T-SQL pour une pratique « sortir de la carte de prison ».

J'ai essayé l'approche CASE mentionné ci-dessous et cela fonctionne, bien qu'il soit un peu lourd. Il est aussi plus compliqué que déclaré dans les réponses parce que vous devez répondre du fait qu'il ya deux valeurs min dans la même ligne.

Quoi qu'il en soit, je pensais que je poste ma solution actuelle qui, compte tenu de mes contraintes, fonctionne assez bien. Il utilise l'opérateur 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

Je vais dire dès le départ que je ne pense pas que cela offrir les meilleures performances, mais compte tenu des circonstances (je ne peux pas redessiner toutes les requêtes que pour la nouvelle exigence de la colonne MinValue), il est un joli élégant « get sur la carte de prison ».

Était-ce utile?

La solution

Il y a probablement plusieurs façons d'y parvenir. Ma suggestion est d'utiliser Case / Quand le faire. Avec 3 colonnes, il est pas trop mal.

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

Autres conseils

Utilisation 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

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

Vous pouvez utiliser l'approche "force brute" avec une touche:

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

Lorsque la première lorsque la condition ne garantit que Col1 n'est pas la plus petite valeur, vous pouvez donc l'éliminer de repos des conditions. De même pour les conditions suivantes. Pour cinq colonnes de votre requête devient:

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]

Notez que s'il y a une égalité entre deux ou plusieurs colonnes puis veille à ce que nous <= sortons de la déclaration le plus tôt CASE possible.

La meilleure façon de le faire est probablement pas pour le faire - il est étrange que les gens insistent pour stocker leurs données d'une manière qui a besoin de « gymnastique » SQL pour extraire des informations significatives, quand il y a beaucoup plus facile des moyens d'obtenir le résultat souhaité si vous structurez juste votre schéma un peu mieux: -)

droite façon de le faire, à mon avis, est d'avoir le tableau suivant:

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

avec la clé comme ID/Col primaire (et peut-être comme une clé Col supplémentaire, en fonction de vos besoins). Ensuite, votre requête devient simple et vous pouvez select min(val) from tbl encore traiter les différents « vieilles colonnes » séparément à l'aide d'autres dans vos where col = 2 requêtes. Cela permet également de faciliter l'expansion si le nombre de grow »colonnes de vieux.

Cela rend vos requêtes si beaucoup plus facile. La règle générale j'ai tendance à utiliser est, si vous jamais avoir quelque chose qui ressemble à un tableau dans une ligne de base de données, vous êtes probablement faire quelque chose de mal et devrait penser à la restructuration des données.


Cependant, si pour une raison quelconque vous ne peut pas changer ces colonnes, je suggère d'utiliser les déclencheurs d'insertion et de mise à jour et ajouter une autre colonne qui ces déclencheurs mis à la minimum sur Col1/2/3. Cela déplacera le « coût » de l'opération loin de la sélection à la mise à jour / insertion où il appartient - la plupart des tables de base de données dans mon expérience sont lues beaucoup plus souvent que écrite pour encourir le coût en écriture a tendance à être plus efficace au fil du temps.

En d'autres termes, le minimum pour une ligne ne change que lorsque l'une des autres colonnes changent, qui est quand vous devez calculera, pas à chaque fois que vous sélectionnez (qui est gaspillée si les données ne change pas). Vous pouvez ensuite se retrouver avec une table comme:

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

Toute autre option qui doit prendre des décisions à temps est généralement select une mauvaise performance sage idée, car les données ne change à l'insertion / mise à jour - l'ajout d'une autre colonne prend plus de place dans le DB et sera un peu plus lent pour les insertions et mises à jour, mais peut être beaucoup plus rapide pour sélectionner - l'approche privilégiée devrait dépendre de là vos priorités mais, comme indiqué, la plupart des tableaux sont lus far plus souvent qu'ils sont écrits.

Si les colonnes sont des nombres entiers comme dans votre exemple, je créerais une fonction:

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

quand je dois l'utiliser que je ferais:

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

si vous avez 5 colums alors ce qui précède devient

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)

Utilisez ceci:

select least(col1, col2, col3) FROM yourtable

Vous pouvez aussi le faire avec une requête syndicale. Comme le nombre de colonnes augmente, vous devez modifier la requête, mais au moins il serait une modification simple.

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

Ceci est la force brute, mais fonctionne

 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

... parce que min () ne fonctionne que sur une colonne et non sur plusieurs colonnes.

cette question Et cette question essayer de répondre.

Le résumé est que Oracle a une fonction intégrée pour cela, avec Sql Server, vous êtes coincé, soit la définition d'une fonction définie par l'utilisateur ou à l'aide des déclarations de cas.

Si vous êtes en mesure de faire une procédure stockée, il pourrait prendre un tableau de valeurs, et vous pouvez simplement appeler.

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

Une petite torsion sur la requête syndicale:

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

Si vous utilisez, vous pouvez faire quelque chose comme propre SQL 2005 ceci:

;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

De cette façon, vous ne perdez pas dans tant d'opérateurs:)

Cependant, cela pourrait être plus lent que l'autre choix.

Il est votre choix ...

Ci-dessous j'utilise une table temporaire pour obtenir le minimum de plusieurs dates. La première table temporaire interroge plusieurs tables jointes pour obtenir différentes dates (ainsi que d'autres valeurs pour la requête), la deuxième table temporaire obtient alors les différentes colonnes et la date minimum en utilisant autant de passes que de colonnes de date.

Ceci est essentiellement comme la requête syndicale, le même nombre de passes sont nécessaires, mais il peut être plus efficace (en fonction de l'expérience, mais nécessiterait des tests). L'efficacité n'a pas été un problème dans ce cas (8000 dossiers). On pourrait index etc.

--==================== 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

Pour plusieurs colonnes de son mieux d'utiliser une instruction CASE, mais pour deux colonnes numériques i et j vous pouvez utiliser les mathématiques simples:

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

Cette formule peut être utilisée pour obtenir la valeur minimale de plusieurs colonnes, mais son passé vraiment désordre 2, min (i, j, k) serait min (i, min (j, k))

SELECT [ID],
            (
                SELECT MIN([value].[MinValue])
                FROM
                (
                    VALUES
                        ([Col1]),
                        ([Col1]),
                        ([Col2]),
                        ([Col3])
                ) AS [value] ([MinValue])
           ) AS [MinValue]
FROM Table;

Si vous savez ce que vous cherchez valeurs, généralement un code d'état, ce qui suit peut être utile:

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

Je sais que cette question est vieux, mais j'étais encore dans le besoin de la réponse et n'a pas été heureux avec d'autres réponses, donc je devais concevoir ma propre qui est une torsion sur @ paxdiablo's answer .


Je suis venu de la terre d'ASE SAP 16.0, et je ne avais besoin d'un coup d'oeil sur les statistiques de certaines données qui sont à mon humble avis valablement stockées dans différentes colonnes d'une seule rangée (ils représentent différents moments - quand l'arrivée de quelque chose était prévu, ce qu'il était attendu lorsque l'action a commencé et, enfin, ce qui était le temps réel). Ainsi, je l'avais transposée dans les colonnes rangées de table temporaire et préformé ma requête sur cela comme d'habitude.

N.B.. Pas le one-size-fits-all solution avant!

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

Cela a pris environ 30 secondes jeu source de 630000 lignes et utilisées uniquement de données d'index, afin de ne pas la chose à exécuter dans le temps critiques processus, mais pour des choses comme un temps d'inspection de données ou d'un rapport de fin de la journée vous pourriez être très bien (mais vérifier avec vos pairs ou supérieurs, s'il vous plaît!). Bonus principal de ce style pour moi était que je pouvais facilement utiliser plus / moins colonnes et regroupement de changement, le filtrage, etc., surtout une fois que les données ont été copyied plus.

Les données supplémentaires (columnName es max, ...) devaient me aider à ma recherche, alors vous pourriez ne pas avoir besoin d'eux; Je les ai laissés ici pour susciter peut-être quelques idées: -.)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top