SQL语句。将行作为列返回

时间:2012-01-08 08:54:39

标签: sql sql-server

我有一张人员表。 每个人在PersonNames表中都可以有许多名称。 我想为每个人,他的2个名字和2个他的姓氏连续得到。 像这样:

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

表:

   PersonId
   BirthDate

PersonNames 表格:

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

感谢。

5 个答案:

答案 0 :(得分:4)

如果这只是具体只有两个名字,这将选择每人MIN和MAX NameId的名字......

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


修改

现在我可以看到这是MS SQL Server,还有另一种选择。这里和其他人类似,但可能稍微简单......

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

答案 1 :(得分:2)

您可以通过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

在此尝试:http://data.stackexchange.com/stackoverflow/q/123608/

答案 2 :(得分:2)

正如我所看到的,解决这个问题的一种简单方法是用不同的排名标志对每种类型的每个人的名称进行独立排名,例如,具有正排名的名字,具有负排名的姓氏。然后,您选择并转动排名为12-1-2的名称。以下是查询的外观:

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

另一方面,从最后对姓名进行排名似乎更合适(至少,我认为,可能更喜欢这种方式)。所以这里是上面脚本的替代品,它从最后对姓氏进行排名:

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

你可以看到这个版本有点复杂。首先,必须对NameId应用符号更改,以确保其针对不同类型的不同方向进行排序。

另一件事是拉动最终结果集。你看,如果一个人只有两个或多个姓氏,那么脚本会按照表中的顺序显示它们:最后一个项目转到LName1,最后一个项目转到{{1 }}。但是如果只有一个姓氏,我的意图是将其显示为LName2,并将LName1显示为空(就像第一个查询一样)。因此,正如您所看到的,必须采取其他措施来确保显示顺序。

答案 3 :(得分:1)

试试这个,SQL Server解决方案,

假设表OF名称中还有PersonId列,每个人的名称不超过2个,否则我们需要子查询:

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

答案 4 :(得分:0)

您可以在mysql中使用group_concat将它们放在一行中,然后您的应用程序可以确定要显示的内容。

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

您还可以在SQL中使用SUBSTRING和LOCATE来获取第一个和第二个条目,但该方法相当脆弱/复杂。