2012-01-08 37 views
5

Tengo una tabla Person. Cada persona puede tener muchos nombres en la tabla PersonNames. Me gustaría obtener para cada persona, 2 de sus nombres y 2 de sus apellidos en una sola fila. así:sql. Devolver filas como columnas

PersonId | FName1 | Fname2 | Lname1 | Lname2 
1   David Daniekl Bekernman Stivens 

persona tabla:

PersonId 
    BirthDate 

PersonNames mesa:

PersonId 
    NameId 
    Name 
    NameType (e.g; first, last...) 

, gracias.

+0

¿Cómo se relacionan 'Person' y' PersonNames'? ¿Hay un 'PersonId' en' PersonNames' que no has mostrado? –

+0

Sí, hay un PersonId en personNames. –

+0

¿Puede darnos el ejemplo de la tabla PersonNames para su Persona? –

Respuesta

2

Usted puede hacer esto con un pequeño desvío a través de una columna XML.

;with P(PersonID, Names) as 
(
    select PersonID, 
     (select NameType as '@NameType', 
       Name as '*' 
      from PersonNames as PN 
      where PN.PersonId = P.PersonId 
      for xml path('Name'), type) 
    from Person as P 
) 
select P.PersonID, 
     P.Names.value('(/Name[@NameType = "first"])[1]', 'varchar(100)') as FName1, 
     P.Names.value('(/Name[@NameType = "last"])[1]', 'varchar(100)') as LName1, 
     P.Names.value('(/Name[@NameType = "first"])[2]', 'varchar(100)') as FName2, 
     P.Names.value('(/Name[@NameType = "last"])[2]', 'varchar(100)') as LName2 
from P 

probar aquí: http://data.stackexchange.com/stackoverflow/q/123608/

+0

¿Por qué el XML, y no solo un MAX recto() * [mi respuesta] * o un PIVOT * [la respuesta de AndriyM] *? ¿Hay diferencias características de rendimiento? – MatBailie

+0

@Dems - Solo quería mostrar una forma diferente de hacer el trabajo. No he probado esto para el rendimiento, así que realmente no lo sé. Realmente depende de la cantidad de filas devueltas. La cláusula where para filtrar filas se debe aplicar en el CTE para obtener el mejor rendimiento. No recomendaría hacer esta consulta en una vista y usarla en combinaciones, etc. Probablemente generaría todo el CTE para cada pregunta independientemente de si se genera una fila o todas las filas. Si el rendimiento es un problema, recomendaría hacer una consulta directa y el pivote en el cliente para la presentación. –

+0

@Dems BTW. Esta solución es muy similar al popular truco 'for xml path' para concatenar filas en cadenas separadas por comas. –

1

Prueba de esto, la solución de SQL Server,

Suponiendo también hay PERSONID columna de la tabla de nombres y no hay más de 2 nombres para cada persona, de lo que necesitamos subconsultas aquí:

CREATE TABLE [dbo].[Person] 
(
[PersonId] [int] PRIMARY KEY 
) ON [PRIMARY] 
GO 

CREATE TABLE [dbo].[PersonNames] 
(
[PersonId] [int] NOT NULL, 
[nameId] [int] NOT NULL, 
[NAME] [varchar] (100) NOT NULL, 
[NameType] [varchar] (10) NOT NULL 
) ON [PRIMARY] 
GO 

INSERT Person VALUES(1),(2) 

INSERT dbo.PersonNames(PersonId, NameId, Name, NameType) 
SELECT 1,1,'John', 'first' UNION ALL 
SELECT 1,1,'Doe', 'last' UNION ALL 
SELECT 1,2,'Ioann', 'first' UNION ALL 
SELECT 1,2,'Doeman', 'last' UNION ALL 
SELECT 1,3,'Yonh', 'first' UNION ALL 
SELECT 1,3,'Doesson', 'last' UNION ALL 

SELECT 2,1,'John2', 'first' UNION ALL 
SELECT 2,1,'Doe2', 'last' UNION ALL 
SELECT 2,2,'Ioann2', 'first' UNION ALL 
SELECT 2,2,'Doeman2', 'last' UNION ALL 
SELECT 2,3,'Yonh2', 'first' UNION ALL 
SELECT 2,3,'Doesson2', 'last' 


SELECT 
    Person.PersonId, 
    FName1.NAME FName1, 
    LName1.NAME LName1, 
    FName2.NAME FName2, 
    LName2.NAME LName2 
FROM Person 
JOIN (
     SELECT 
     ROW_NUMBER() OVER (PARTITION BY PersonId ORDER BY NameId) Ordinal, 
     PersonId, Name, NameId 
     FROM PersonNames 
     WHERE NameType = 'first' 
    ) FName1 
    ON FName1.PersonId = Person.PersonId AND FName1.Ordinal=1 
JOIN PersonNames LName1 
    ON LName1.PersonId = FName1.PersonId AND LName1.NameType = 'last' AND FName1.NameId = LName1.NameId 
LEFT JOIN 
    (
     SELECT 
     ROW_NUMBER() OVER (PARTITION BY PersonId ORDER BY NameId) Ordinal, 
     PersonId, Name, NameId 
     FROM PersonNames 
     WHERE NameType = 'first' 
    ) FName2 
    ON FName2.PersonId = Person.PersonId AND FName2.NameId <> FName1.NameId AND FName2.Ordinal = 2 
LEFT JOIN PersonNames LName2 
    ON FName2.PersonId = LName2.PersonId AND LName2.NameType = 'last' AND LName2.NameId <> LName1.NameId AND FName2.NameId = LName2.NameId 

DROP TABLE Person 
DROP TABLE dbo.PersonNames 
+0

Muy elegante. Ojalá hubiera pensado en eso :) – amccausl

+0

Si la persona tiene dos nombres en la tabla, la primera combinación los encontrará a ambos. Esto conducirá a dos registros con diferentes nombres en diferentes columnas. Además, si la persona tiene más de dos nombres, la primera combinación a la izquierda los seleccionará a todos excepto a uno, lo que nuevamente generará duplicación. – MatBailie

+1

Debido al 'TOP 1 ', ahora solo funcionará para una persona en la tabla Person, y no hay garantía de que el segundo' TOP 1' sea para la misma persona. – MatBailie

0

Puede usar group_concat en mysql para ponerlos en una sola fila, luego su aplicación puede determinar cuál mostrar.

SELECT Person.PersonId 
    , GROUP_CONCAT(DISTINCT first_name.Name SEPARATOR ',') AS first_names 
    , GROUP_CONCAT(DISTINCT last_name.Name SEPARATOR ',') AS last_names 
FROM Person 
LEFT JOIN PersonName first_name ON Person.PersonId = first_name.PersonId AND first_name.Type = 'first' 
LEFT JOIN PersonName last_name ON Person.PersonId = last_name.PersonId AND last_name.Type = 'last' 
GROUP BY Person.PersonId 

También puede utilizar SUBSECUENCIA y localizar en SQL para agarrar la primera y segunda entrada en la primera y la última, pero el método es bastante frágil/complicado.

+0

NOTA: esto * asume * MySQL. Además, tiene un problema ... Si hay 2 nombres y 2 apellidos, los dos enlaces perdidos darán 4 registros con las diferentes combinaciones ... [(f1, l1) (f1, l2) (f2, l1) (f2, l2)] y causar duplicación en GROUP_CONCAT. – MatBailie

+0

La duplicación se elimina con la palabra clave 'DISTINCT' – amccausl

4

Si esto es específicamente por sólo dos nombres, esto va a recoger los nombres con el MIN y MAX NameID por Persona ...

SELECT 
    Person.PersonId, 
    MAX(CASE WHEN Name.nameId = map.minNameId AND Name.nameType = 'first' THEN Name.Name ELSE NULL END) AS fname1, 
    MAX(CASE WHEN Name.nameId = map.minNameId AND Name.nameType = 'last' THEN Name.Name ELSE NULL END) AS lname1, 
    MAX(CASE WHEN Name.nameId = map.maxNameId AND Name.nameType = 'first' THEN Name.Name ELSE NULL END) AS fname2, 
    MAX(CASE WHEN Name.nameId = map.maxNameId AND Name.nameType = 'last' THEN Name.Name ELSE NULL END) AS lname2 
FROM 
    Person 
LEFT JOIN 
    (SELECT personId, MIN(nameId) AS minNameId, MAX(nameId) as maxNameId FROM PersonNames GROUP BY PersonId) AS map 
    ON map.PersonId = Person.PersonId 
LEFT JOIN 
    PersonNames AS Name 
    On Name.PersonId = Person.PersonId 
GROUP BY 
    Person.PersonId 


EDITAR

Ahora que Puedo ver que esto es MS SQL Server, hay otra opción. Al igual que en otros aquí, pero posiblemente un poco más simple ...

WITH 
    sequenced_names AS 
(
    SELECT 
    DENSE_RANK() OVER (PARTITION BY PersonId ORDER BY NameID) AS NameOrdinal, 
    * 
    FROM 
    PersonNames 
) 
SELECT 
    PersonID, 
    MAX(CASE WHEN nameOrdinal = 1 AND nameType = 'first' THEN Name END) AS fname1, 
    MAX(CASE WHEN nameOrdinal = 1 AND nameType = 'last' THEN Name END) AS lname1, 
    MAX(CASE WHEN nameOrdinal = 2 AND nameType = 'first' THEN Name END) AS fname2, 
    MAX(CASE WHEN nameOrdinal = 2 AND nameType = 'last' THEN Name END) AS lname2 
FROM 
    sequenced_names 
GROUP BY 
    PersonID 
+0

Tengo dificultades para comprender esto. Usted dice que es solo para dos nombres: para * dos nombres por persona * o * para dos nombres de cada tipo por persona * (es decir, para cuatro nombres). El motivo de mi pregunta es el hecho de que está sacando * cuatro * nombres en la selección final, pero solo está mapeando exactamente * dos * 'nameId's por' personId', independientemente de 'nameType'. O me estoy perdiendo algo o hay un descuido de tu parte. –

+0

@AndriyM - Dos nombres (identificados por un NameID) compuestos de dos partes (un nombre y un apellido, identificados por NameType). – MatBailie

+0

@Dems ¡Hermoso! –

2

Una forma sencilla de resolver esto como yo lo veo, es para clasificar los nombres de cada tipo de cada persona independientemente con diferentes señales de clasificación, es decir, por ejemplo, los nombres de pila con positivo clasificaciones, apellidos con resultados negativos. Luego selecciona y pivota los nombres que tienen clasificaciones de 1, 2, -1, -2. Esto es lo que la consulta podría ser:

WITH ranked AS (
    SELECT 
    *, 
    rnk = 
     CASE NameType WHEN 'first' THEN 1 ELSE -1 END * 
     ROW_NUMBER() OVER (
     PARTITION BY PersonId, NameType 
     ORDER BY NameId 
    ) 
    FROM PersonNames 
    WHERE NameType IN ('first', 'last') 
) 
SELECT 
    PersonId, 
    FName1 = [1], 
    FName2 = [2], 
    LName1 = [-1], 
    LName2 = [-2] 
FROM (
    SELECT 
    PersonId, 
    Name, 
    rnk 
    FROM ranked 
) s 
PIVOT (
    MAX(Name) FOR rnk IN ([-2], [-1], [1], [2]) 
) p 

Por otra parte, la clasificación apellidos del final podría parecer más apropiado (por lo menos, creo, me podrían preferir lo mejor de esta manera).Así que aquí hay una alternativa a la secuencia de comandos que ocupa apellidos desde el final:

WITH ranked AS (
    SELECT 
    *, 
    rnk = 
     CASE NameType WHEN 'first' THEN 1 ELSE -1 END * 
     ROW_NUMBER() OVER (
     PARTITION BY PersonId, NameType 
     ORDER BY CASE NameType WHEN 'first' THEN 1 ELSE -1 END * NameId 
    ) 
    FROM PersonNames 
    WHERE NameType IN ('first', 'last') 
), 
SELECT 
    PersonId, 
    FName1 = [1], 
    FName2 = [2], 
    LName1 = ISNULL([-2], [-1]), 
    LName2 = CASE WHEN [-2] IS NULL THEN NULL ELSE [-1] END 
FROM (
    SELECT 
    PersonId, 
    Name, 
    rnk 
    FROM ranked 
) s 
PIVOT (
    MAX(Name) FOR rnk IN ([-2], [-1], [1], [2]) 
) p 

se puede ver que esta versión es un poco más complicado. Por un lado, un cambio de signo tuvo que ser aplicado a NameId para asegurar su clasificación en diferentes direcciones para diferentes tipos.

Otra cosa es tirar del conjunto de resultados final. Verá, si una persona tiene solo dos o más apellidos, la secuencia de comandos los mostrará en el orden tal como están en la tabla: el elemento anterior al último va al LName1 y el último elemento va al LName2. Pero en el caso de exactamente un apellido, mi intención era mostrarlo como LName1, y LName2 para estar vacío (al igual que la primera consulta). Por lo tanto, como puede ver, se tomaron medidas adicionales para garantizar el orden de visualización.