Dada la siguiente tabla en SQL Server 2005:
ID Col1 Col2 Col3
-- ---- ---- ----
1 3 34 76
2 32 976 24
3 7 235 3
4 245 1 792
¿Cuál es la mejor manera de escribir la consulta que arroja el siguiente resultado (es decir, una que arroja la columna final, una columna que contiene los valores mínimos de Col1, Col2 y Col 3 para cada fila )?
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
ACTUALIZAR:
Para aclarar (como he dicho en los comentarios) en el escenario real la base de datos está correctamente normalizada . Estas columnas de "matriz" no están en una tabla real, sino en un conjunto de resultados que se requiere en un informe. Y el nuevo requisito es que el informe también necesita esta columna MinValue. No puedo cambiar el conjunto de resultados subyacente y, por lo tanto, estaba buscando en T-SQL una práctica "tarjeta para salir de la cárcel".
Probé el enfoque CASE mencionado a continuación y funciona, aunque es un poco engorroso. También es más complicado de lo que se indica en las respuestas porque debe tener en cuenta el hecho de que hay dos valores mínimos en la misma fila.
De todos modos, pensé en publicar mi solución actual que, dadas mis limitaciones, funciona bastante bien. Utiliza el operador 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
Diré de antemano que no espero que esto ofrezca el mejor rendimiento, pero dadas las circunstancias (no puedo rediseñar todas las consultas solo para el nuevo requisito de la columna MinValue), es una forma bastante elegante de "salir de la cárcel tarjeta".
fuente
Respuestas:
Es probable que haya muchas formas de lograrlo. Mi sugerencia es usar Case / When para hacerlo. Con 3 columnas, no está tan 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
fuente
Usando
CROSS APPLY
:SELECT ID, Col1, Col2, Col3, MinValue FROM YourTable CROSS APPLY (SELECT MIN(d) AS MinValue FROM (VALUES (Col1), (Col2), (Col3)) AS a(d)) A
Violín SQL
fuente
cross apply
. ¿Agrega valor / desempeño? stackoverflow.com/a/14712024/78522where MinValue > 10
, de lo que no podría prescindirCROSS APPLY
SELECT ID, Col1, Col2, Col3, (SELECT MIN(Col) FROM (VALUES (Col1), (Col2), (Col3)) AS X(Col)) AS TheMin FROM Table
fuente
En MySQL, use esto:
select least(col1, col2, col3) FROM yourtable
fuente
LEAST
funciona en la última versión de instancias administradas de Microsoft SQL Server, desde hace unos 12 días. reddit.com/r/SQLServer/comments/k0dj2r/…Puede utilizar el enfoque de "fuerza bruta" con un giro:
SELECT CASE WHEN Col1 <= Col2 AND Col1 <= Col3 THEN Col1 WHEN Col2 <= Col3 THEN Col2 ELSE Col3 END AS [Min Value] FROM [Your Table]
Cuando falla la primera condición when, garantiza que Col1 no es el valor más pequeño, por lo que puede eliminarlo del resto de condiciones. Así mismo para condiciones posteriores. Para cinco columnas, su consulta se convierte en:
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]
Tenga en cuenta que si hay un empate entre dos o más columnas, se
<=
asegura de que salgamos de laCASE
declaración lo antes posible.fuente
<=
lugar, de lo contrario, se usará el último valor mínimo coincidente en lugar del primero.Si las columnas fueran enteros como en su ejemplo, crearía una función:
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
luego, cuando necesite usarlo, haría:
select col1, col2, col3, dbo.f_min_int(dbo.f_min_int(col1,col2),col3)
si tiene 5 columnas, lo anterior se convierte en
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)
fuente
La mejor manera de hacerlo es probablemente no hacerlo; es extraño que la gente insista en almacenar sus datos de una manera que requiera "gimnasia" SQL para extraer información significativa, cuando hay formas mucho más fáciles de lograr el resultado deseado si solo estructura tu esquema un poco mejor :-)
El derecho forma de hacer esto, en mi opinión, es tener la siguiente tabla:
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
con
ID/Col
como clave principal (y posiblementeCol
como clave adicional, según sus necesidades). Luego, su consulta se vuelve simpleselect min(val) from tbl
y aún puede tratar las 'columnas antiguas' individuales por separado al usarlaswhere col = 2
en sus otras consultas. Esto también permite una fácil expansión en caso de que aumente el número de "columnas antiguas".Esto hace que sus consultas de manera mucho más fácil. La pauta general que suelo utilizar es, si alguna vez tiene algo que se parece a una matriz en una fila de la base de datos, probablemente esté haciendo algo mal y debería pensar en reestructurar los datos.
Sin embargo, si por alguna razón no puede cambiar esas columnas, le sugiero que use los activadores de inserción y actualización y agregue otra columna en la que estos activadores se establezcan al mínimo
Col1/2/3
. Esto moverá el 'costo' de la operación de la selección a la actualización / inserción a la que pertenece: la mayoría de las tablas de bases de datos en mi experiencia se leen con mucha más frecuencia que las escritas, por lo que incurrir en el costo de escritura tiende a ser más eficiente con el tiempo.En otras palabras, el mínimo para una fila solo cambia cuando cambia una de las otras columnas, por lo que es entonces cuando debe calcularlo, no cada vez que selecciona (lo cual se desperdicia si los datos no cambian). Luego terminarías con una tabla como:
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
Cualquier otra opción que tenga que tomar decisiones en el
select
momento suele ser una mala idea en cuanto al rendimiento, ya que los datos solo cambian al insertar / actualizar: la adición de otra columna ocupa más espacio en la base de datos y será un poco más lenta para las inserciones y actualizaciones, pero puede ser mucho más rápido para las selecciones: el enfoque preferido debería depender de sus prioridades allí, pero, como se indicó, la mayoría de las tablas se leen con mucha más frecuencia de lo que se escriben.fuente
También puede hacer esto con una consulta de unión. A medida que aumenta el número de columnas, deberá modificar la consulta, pero al menos sería una modificación sencilla.
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
fuente
Esto es fuerza bruta pero funciona
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
... porque min () funciona solo en una columna y no entre columnas.
fuente
Tanto esta pregunta Y esta pregunta trata de responder a esta.
La recapitulación es que Oracle tiene una función incorporada para esto, con Sql Server está atascado ya sea definiendo una función definida por el usuario o usando declaraciones de casos.
fuente
Para varias columnas, es mejor usar una instrucción CASE, sin embargo, para dos columnas numéricas i y j, puede usar matemáticas simples:
min (i, j) = (i + j) / 2 - abs (ij) / 2
Esta fórmula se puede usar para obtener el valor mínimo de varias columnas, pero es realmente complicado después de 2, min (i, j, k) sería min (i, min (j, k))
fuente
Si puede crear un procedimiento almacenado, podría tomar una matriz de valores, y podría simplemente llamar a eso.
fuente
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
fuente
Un pequeño giro en la consulta sindical:
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
fuente
Si usa SQL 2005, puede hacer algo como esto:
;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 esta forma no te perderás en tantos operadores :)
Sin embargo, esto podría ser más lento que la otra opción.
Es tu elección...
fuente
A continuación, utilizo una tabla temporal para obtener el mínimo de varias fechas. La primera tabla temporal consulta varias tablas unidas para obtener varias fechas (así como otros valores para la consulta), la segunda tabla temporal luego obtiene las distintas columnas y la fecha mínima utilizando tantos pases como columnas de fecha.
Esto es esencialmente como la consulta de unión, se requiere la misma cantidad de pases, pero puede ser más eficiente (según la experiencia, pero necesitaría pruebas). La eficiencia no fue un problema en este caso (8.000 registros). Se podría indexar, 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
fuente
SELECT [ID], ( SELECT MIN([value].[MinValue]) FROM ( VALUES ([Col1]), ([Col1]), ([Col2]), ([Col3]) ) AS [value] ([MinValue]) ) AS [MinValue] FROM Table;
fuente
Si sabe qué valores está buscando, generalmente un código de estado, lo siguiente puede ser útil:
select case when 0 in (PAGE1STATUS ,PAGE2STATUS ,PAGE3STATUS, PAGE4STATUS,PAGE5STATUS ,PAGE6STATUS) then 0 else 1 end FROM CUSTOMERS_FORMS
fuente
Sé que esa pregunta es vieja, pero todavía necesitaba la respuesta y no estaba contento con otras respuestas, así que tuve que idear la mía propia, que es un giro en la respuesta de @paxdiablo .
Vengo de la tierra de SAP ASE 16.0, y solo necesitaba echar un vistazo a las estadísticas de ciertos datos que, en mi humilde opinión, están almacenados válidamente en diferentes columnas de una sola fila (representan diferentes momentos: cuando se planeó la llegada de algo, lo que se esperaba cuando la acción comenzó y finalmente cuál fue la hora real). Por lo tanto, había transpuesto columnas a las filas de la tabla temporal y realicé mi consulta sobre esto como de costumbre.
NB ¡ No es la solución única para todos!
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
Esto tomó unos 30 segundos en el conjunto de origen de 630000 filas y usó solo datos de índice, por lo que no es lo que debe ejecutarse en un proceso de tiempo crítico, pero para cosas como la inspección de datos única o el informe al final del día, podría ser bien (pero verifique esto con sus compañeros o superiores, ¡por favor!). La principal ventaja de este estilo para mí. era que podía usar más / menos columnas y cambiar la agrupación, el filtrado, etc., especialmente una vez que se copiaron los datos.
Los datos adicionales (
columnName
,max
es, ...) fueron para ayudarme en mi búsqueda, por lo que es posible que no los necesite; Los dejé aquí para quizás suscitar algunas ideas :-).fuente