Cómo unirse a la primera fila

773

Usaré un ejemplo concreto pero hipotético.

Cada pedido normalmente tiene solo una línea de pedido :

Pedidos:

OrderGUID   OrderNumber
=========   ============
{FFB2...}   STL-7442-1      
{3EC6...}   MPT-9931-8A

Artículos de línea:

LineItemGUID   Order ID Quantity   Description
============   ======== ========   =================================
{098FBE3...}   1        7          prefabulated amulite
{1609B09...}   2        32         spurving bearing

Pero ocasionalmente habrá un pedido con dos líneas de pedido:

LineItemID   Order ID    Quantity   Description
==========   ========    ========   =================================
{A58A1...}   6,784,329   5          pentametric fan
{0E9BC...}   6,784,329   5          differential girdlespring 

Normalmente al mostrar los pedidos al usuario:

SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
    INNER JOIN LineItems 
    ON Orders.OrderID = LineItems.OrderID

Quiero mostrar el artículo único en el pedido. Sin embargo, con este orden de vez en cuando, con dos (o más) elementos, las órdenes se parecen ser duplicada :

OrderNumber   Quantity   Description
===========   ========   ====================
STL-7442-1    7          prefabulated amulite
MPT-9931-8A   32         spurving bearing
KSG-0619-81   5          panametric fan
KSG-0619-81   5          differential girdlespring

Lo que realmente quiero es que SQL Server solo elija uno , ya que será lo suficientemente bueno :

OrderNumber   Quantity   Description
===========   ========   ====================
STL-7442-1    7          prefabulated amulite
MPT-9931-8A   32         differential girdlespring
KSG-0619-81   5          panametric fan

Si me vuelvo aventurero, podría mostrarle al usuario una elipsis para indicar que hay más de uno:

OrderNumber   Quantity   Description
===========   ========   ====================
STL-7442-1    7          prefabulated amulite
MPT-9931-8A   32         differential girdlespring
KSG-0619-81   5          panametric fan, ...

Entonces la pregunta es cómo

  • eliminar filas "duplicadas"
  • solo unirse a una de las filas, para evitar duplicaciones

Primer intento

Mi primer intento ingenuo fue unirme solo a las líneas de pedido " TOP 1 ":

SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
    INNER JOIN (
       SELECT TOP 1 LineItems.Quantity, LineItems.Description
       FROM LineItems
       WHERE LineItems.OrderID = Orders.OrderID) LineItems2
    ON 1=1

Pero eso da el error:

La columna o prefijo 'Pedidos' no
coincide con un nombre de tabla o nombre de alias
utilizado en la consulta.

Presumiblemente porque la selección interna no ve la tabla externa.

Ian Boyd
fuente
3
¿No puedes usar group by?
Dariush Jafari
2
Creo que (y corríjame si me equivoco) group byrequeriría enumerar todas las otras columnas, excluyendo la que no desea duplicados. Fuente
Joshua Nelson

Respuestas:

1213
SELECT   Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM     Orders
JOIN     LineItems
ON       LineItems.LineItemGUID =
         (
         SELECT  TOP 1 LineItemGUID 
         FROM    LineItems
         WHERE   OrderID = Orders.OrderID
         )

En SQL Server 2005 y versiones posteriores, simplemente puede reemplazar INNER JOINcon CROSS APPLY:

SELECT  Orders.OrderNumber, LineItems2.Quantity, LineItems2.Description
FROM    Orders
CROSS APPLY
        (
        SELECT  TOP 1 LineItems.Quantity, LineItems.Description
        FROM    LineItems
        WHERE   LineItems.OrderID = Orders.OrderID
        ) LineItems2

Tenga en cuenta que TOP 1sin ORDER BYno es determinista: esta consulta le dará una línea de pedido por pedido, pero no está definido cuál será.

Las invocaciones múltiples de la consulta pueden proporcionarle diferentes líneas de pedido para el mismo pedido, incluso si el subyacente no cambió.

Si desea un orden determinista, debe agregar una ORDER BYcláusula a la consulta más interna.

Quassnoi
fuente
3
Excelente, eso funciona; mover TOP 1 de la cláusula de tabla derivada a la cláusula de unión.
Ian Boyd
107
y el equivalente de "OUTER JOIN" sería "OUTER APPLY"
Alex
99
¿Qué tal para IZQUIERDA EXTERIOR UNIRSE?
Alex Nolasco
8
¿Cómo se hace esto si la unión se realiza mediante una clave compuesta / tiene varias columnas?
Brett Ryan el
77
CROSS APPLYen su lugar INNER JOINy OUTER APPLYen su lugar LEFT JOIN(lo mismo que LEFT OUTER JOIN).
hastrb
117

Sé que esta pregunta fue respondida hace un tiempo, pero cuando se trata de grandes conjuntos de datos, las consultas anidadas pueden ser costosas. Aquí hay una solución diferente donde la consulta anidada solo se ejecutará una vez, en lugar de por cada fila devuelta.

SELECT 
  Orders.OrderNumber,
  LineItems.Quantity, 
  LineItems.Description
FROM 
  Orders
  INNER JOIN (
    SELECT
      Orders.OrderNumber,
      Max(LineItem.LineItemID) AS LineItemID
    FROM
      Orders INNER JOIN LineItems
      ON Orders.OrderNumber = LineItems.OrderNumber
    GROUP BY Orders.OrderNumber
  ) AS Items ON Orders.OrderNumber = Items.OrderNumber
  INNER JOIN LineItems 
  ON Items.LineItemID = LineItems.LineItemID
Justin Fisher
fuente
2
Esto también es mucho más rápido si su columna 'LineItemId' no está indexada correctamente. En comparación con la respuesta aceptada.
GER
3
Pero, ¿cómo haría esto si Max no es utilizable, ya que necesita ordenar por una columna diferente a la que desea devolver?
NickG
2
puede ordenar la tabla derivada de la forma que desee y utilizar TOP 1 en SQL Server o LIMIT 1 en MySQL
stifin
28

Podrías hacerlo:

SELECT 
  Orders.OrderNumber, 
  LineItems.Quantity, 
  LineItems.Description
FROM 
  Orders INNER JOIN LineItems 
  ON Orders.OrderID = LineItems.OrderID
WHERE
  LineItems.LineItemID = (
    SELECT MIN(LineItemID) 
    FROM   LineItems
    WHERE  OrderID = Orders.OrderID
  )

Esto requiere un índice (o clave principal) activado LineItems.LineItemIDy un índice activado LineItems.OrderIDo será lento.

Tomalak
fuente
2
Esto no funciona si un pedido no tiene LineItems. La subexpresión luego evalúa LineItems.LineItemID = nully elimina completamente los pedidos de la entidad izquierda del resultado.
leo
66
Ese también es el efecto de la unión interna, así que ... sí.
Tomalak
1
Solución que se puede adaptar para IZQUIERDA EXTERIOR IZQUIERDA: stackoverflow.com/a/20576200/510583
leo
3
@leo Sí, pero el OP usó una combinación interna, así que no entiendo tu objeción.
Tomalak
27

La respuesta de @Quassnoi es buena, en algunos casos (especialmente si la tabla externa es grande), una consulta más eficiente podría ser el uso de funciones de ventana, como esta:

SELECT  Orders.OrderNumber, LineItems2.Quantity, LineItems2.Description
FROM    Orders
LEFT JOIN 
        (
        SELECT  LineItems.Quantity, LineItems.Description, OrderId, ROW_NUMBER()
                OVER (PARTITION BY OrderId ORDER BY (SELECT NULL)) AS RowNum
        FROM    LineItems

        ) LineItems2 ON LineItems2.OrderId = Orders.OrderID And RowNum = 1

A veces solo necesita probar qué consulta proporciona un mejor rendimiento.

BornToCode
fuente
3
Esta es la única respuesta que encontré que hace una unión real "Izquierda", lo que significa que no agrega más líneas que en la tabla "Izquierda". Solo necesita ingresar una subconsulta y agregar "donde RowNum no es nulo"
user890332
1
De acuerdo, esta es la mejor solución. Esta solución tampoco requiere que tenga una identificación única en la tabla a la que se está uniendo, y es mucho más rápida que la respuesta más votada. También puede agregar criterios para qué fila prefiere devolver, en lugar de simplemente tomar una fila aleatoria, mediante el uso de una cláusula ORDER BY en la subconsulta.
Geoff Griswald
Esta es una buena solución. Tenga en cuenta: cuando lo use para su propia situación, tenga mucho cuidado de cómo PARTIDA POR (por lo general, probablemente desee una columna de ID allí) y ORDENAR POR (lo que podría hacer casi cualquier cosa, dependiendo de la fila que desee mantener, por ejemplo DateCreated desc sería una opción para algunas tablas, pero dependería de muchas cosas)
JosephDoggie
14

Otro enfoque que utiliza la expresión de tabla común:

with firstOnly as (
    select Orders.OrderNumber, LineItems.Quantity, LineItems.Description, ROW_NUMBER() over (partiton by Orders.OrderID order by Orders.OrderID) lp
    FROM Orders
        join LineItems on Orders.OrderID = LineItems.OrderID
) select *
  from firstOnly
  where lp = 1

o, al final, tal vez le gustaría mostrar todas las filas unidas?

versión separada por comas aquí:

  select *
  from Orders o
    cross apply (
        select CAST((select l.Description + ','
        from LineItems l
        where l.OrderID = s.OrderID
        for xml path('')) as nvarchar(max)) l
    ) lines
avb
fuente
13

Desde SQL Server 2012 y en adelante, creo que esto funcionará:

SELECT DISTINCT
    o.OrderNumber ,
    FIRST_VALUE(li.Quantity) OVER ( PARTITION BY o.OrderNumber ORDER BY li.Description ) AS Quantity ,
    FIRST_VALUE(li.Description) OVER ( PARTITION BY o.OrderNumber ORDER BY li.Description ) AS Description
FROM    Orders AS o
    INNER JOIN LineItems AS li ON o.OrderID = li.OrderID
P. Olesen
fuente
2
La mejor respuesta si me preguntas.
Thomas
11

Las subconsultas correlacionadas son subconsultas que dependen de la consulta externa. Es como un bucle for en SQL. La subconsulta se ejecutará una vez para cada fila en la consulta externa:

select * from users join widgets on widgets.id = (
    select id from widgets
    where widgets.user_id = users.id
    order by created_at desc
    limit 1
)
Abdullah Yousuf
fuente
5

EDITAR: no importa, Quassnoi tiene una mejor respuesta.

Para SQL2K, algo como esto:

SELECT 
  Orders.OrderNumber
, LineItems.Quantity
, LineItems.Description
FROM (  
  SELECT 
    Orders.OrderID
  , Orders.OrderNumber
  , FirstLineItemID = (
      SELECT TOP 1 LineItemID
      FROM LineItems
      WHERE LineItems.OrderID = Orders.OrderID
      ORDER BY LineItemID -- or whatever else
      )
  FROM Orders
  ) Orders
JOIN LineItems 
  ON LineItems.OrderID = Orders.OrderID 
 AND LineItems.LineItemID = Orders.FirstLineItemID
Peter Radocchia
fuente
4

Mi forma favorita de ejecutar esta consulta es con una cláusula no existe. Creo que esta es la forma más eficiente de ejecutar este tipo de consulta:

select o.OrderNumber,
       li.Quantity,
       li.Description
from Orders as o
inner join LineItems as li
on li.OrderID = o.OrderID
where not exists (
    select 1
    from LineItems as li_later
    where li_later.OrderID = o.OrderID
    and li_later.LineItemGUID > li.LineItemGUID
    )

Pero no he probado este método contra otros métodos sugeridos aquí.

Anand
fuente
2

Probé la cruz, funciona bien, pero lleva un poco más de tiempo. Columnas de línea ajustadas para tener un grupo máximo y agregado que mantuvo la velocidad y eliminó el registro adicional.

Aquí está la consulta ajustada:

SELECT Orders.OrderNumber, max(LineItems.Quantity), max(LineItems.Description)
FROM Orders
    INNER JOIN LineItems 
    ON Orders.OrderID = LineItems.OrderID
Group by Orders.OrderNumber
ernst
fuente
10
Pero tener max por separado en dos columnas significa que la cantidad podría no estar relacionada con la descripción. Si el pedido fuera de 2 Widgets y 10 Gadgets, la consulta devolvería 10 Widgets.
Brianorca
1

prueba esto

SELECT
   Orders.OrderNumber,
   LineItems.Quantity, 
   LineItems.Description
FROM Orders
   INNER JOIN (
      SELECT
         Orders.OrderNumber,
         Max(LineItem.LineItemID) AS LineItemID
       FROM Orders 
          INNER JOIN LineItems
          ON Orders.OrderNumber = LineItems.OrderNumber
       GROUP BY Orders.OrderNumber
   ) AS Items ON Orders.OrderNumber = Items.OrderNumber
   INNER JOIN LineItems 
   ON Items.LineItemID = LineItems.LineItemID
Bane Neba
fuente
2
Considere explicar qué hace su consulta para resolver el problema del OP
Simas Joneliunas