2012-01-08 42 views
5

Tôi có bảng Person. Mỗi người có thể có nhiều tên trong bảng PersonNames. Tôi muốn nhận được cho mỗi người, 2 của tên đầu tiên của mình và 2 tên cuối cùng của ông trong một hàng. như thế này:Sql. Trả lại các hàng dưới dạng cột

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

Person bảng:

PersonId 
    BirthDate 

PersonNames bảng:

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

Cảm ơn.

+0

Liên kết 'Person' và' PersonNames' có liên quan như thế nào? Có 'PersonId' trong' PersonNames' mà bạn không hiển thị không? –

+0

Có một PersonId trong personNames. –

+0

Bạn có thể cho chúng tôi bảng ví dụ về PersonNames cho Người của bạn không? –

Trả lời

2

Bạn có thể làm điều này với một đường vòng nhỏ qua cột 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 

Hãy thử ở đây: http://data.stackexchange.com/stackoverflow/q/123608/

+0

Tại sao XML chứ không phải chỉ MAX() * [câu trả lời của tôi] * hoặc PIVOT * [câu trả lời của AndriyM] *? Có sự khác biệt về tính chất peformance? – MatBailie

+0

@ Dems - Tôi chỉ muốn thể hiện một cách khác để hoàn thành công việc. Tôi đã không thử nghiệm này cho hiệu suất vì vậy tôi thực sự không biết. Nó thực sự phụ thuộc vào số hàng được trả về. Mệnh đề where nên lọc các hàng nên được áp dụng trong CTE để có hiệu suất tốt nhất. Tôi sẽ không khuyên bạn nên làm cho truy vấn này trong một cái nhìn và sử dụng trong tham gia vv Nó có thể sẽ tạo ra toàn bộ CTE cho mỗi câu hỏi bất kể nếu một hàng là đầu ra hoặc tất cả các hàng. Nếu hiệu suất là một vấn đề tôi sẽ khuyên bạn nên làm một truy vấn thẳng và trục trên máy khách để trình bày. –

+0

@Dems BTW. Giải pháp này rất giống với mẹo 'xml path' phổ biến để nối các hàng vào các chuỗi được phân cách bằng dấu phẩy. –

1

Hãy thử điều này, giải pháp SQL Server,

Giả sử đó cũng là PersonID cột trong bảng tên và không có hơn 2 tên cho mỗi người, nếu không chúng ta cần truy vấn con ở đây:

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

Rất thanh lịch. Ước gì tôi đã nghĩ về điều đó :) – amccausl

+0

Nếu người đó có hai tên đầu tiên trong bảng, lần đầu tiên tham gia sẽ tìm thấy cả hai. Điều này sẽ dẫn đến hai bản ghi với các tên khác nhau trong các cột khác nhau. Ngoài ra, nếu người đó có nhiều hơn hai tên, thì lần đầu tiên bên trái tham gia sẽ chọn tất cả chúng ngoại trừ một tên, một lần nữa gây ra sự trùng lặp. – MatBailie

+1

Do 'TOP 1', điều này sẽ chỉ hoạt động cho một người trong bảng Person, và không có gì đảm bảo rằng' TOP 1' thứ hai sẽ là cùng một người. – MatBailie

0

Bạn có thể sử dụng group_concat trong mysql để đặt chúng trong một hàng duy nhất, sau đó ứng dụng của bạn có thể xác định được hiển thị.

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 

Bạn cũng có thể sử dụng SUBSTRING và VỊ trong SQL để lấy mục đầu tiên và thứ hai trong lần đầu tiên và cuối cùng, nhưng phương pháp này khá giòn/phức tạp.

+0

LƯU Ý: điều này * giả định * MySQL. Ngoài ra, nó có một vấn đề ... Nếu có 2 tên đầu tiên và 2 tên cuối cùng, hai trái JOIN sẽ mang lại 4 hồ sơ với các kết hợp khác nhau ... [(f1, l1) (f1, l2) (f2, l1) (f2, l2)] và do đó gây ra trùng lặp trong GROUP_CONCAT. – MatBailie

+0

Sao chép bị xóa bởi từ khóa 'DISTINCT' – amccausl

4

Nếu đây là đặc biệt chỉ hai tên, điều này sẽ lấy tên với MIN và MAX NameId mỗi Person ...

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 


EDIT

Bây giờ Tôi có thể thấy rằng đây là MS SQL Server, có một tùy chọn khác. Tương tự như những người khác ở đây, nhưng có thể hơi đơn giản hơn ...

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

Tôi đang cố gắng để hiểu điều này. Bạn nói nó chỉ cho hai tên - cho * hai tên cho mỗi người * hoặc * cho hai tên của mỗi loại cho mỗi người * (tức là bốn tên)? Lý do cho câu hỏi của tôi là thực tế rằng bạn đang kéo * bốn * tên trong lựa chọn cuối cùng, nhưng bạn chỉ lập bản đồ chính xác * hai * 'nameId' trên' personId', không phân biệt 'nameType'. Hoặc là tôi đang thiếu một cái gì đó hoặc có một sự giám sát về phía bạn. –

+0

@AndriyM - Hai tên (được xác định bởi một NameID) bao gồm hai phần (tên và họ, được xác định bởi NameType). – MatBailie

+0

@Dems Beautiful! –

2

Một cách đơn giản để giải quyết này như tôi có thể nhìn thấy nó, là để xếp hạng tên của mỗi người của từng loại một cách độc lập với dấu hiệu xếp hạng khác nhau, tức là ví dụ, tên đầu tiên với tích cực bảng xếp hạng, họ với những tiêu cực. Sau đó, bạn chọn và các tên trục có thứ hạng của 1, 2, -1, -2. Dưới đây là những gì các truy vấn có thể trông giống như:

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 

Mặt khác, đứng tên cuối cùng từ cuối cùng có thể xuất hiện thích hợp hơn (ít nhất, tôi nghĩ rằng, tôi có thể thích nó tốt hơn theo cách này).Vì vậy, đây là giải pháp thay thế cho tập lệnh ở trên xếp hạng các tên cuối cùng từ cuối:

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 

Bạn có thể thấy rằng phiên bản này phức tạp hơn một chút. Đối với một điều, một sự thay đổi của dấu hiệu đã được áp dụng cho NameId để đảm bảo phân loại của nó theo các hướng khác nhau cho các loại khác nhau.

Một điều khác là kéo tập kết quả cuối cùng. Bạn thấy đấy, nếu một người chỉ có hai hoặc nhiều tên cuối cùng, tập lệnh sẽ hiển thị chúng theo thứ tự như trong bảng: mục trước khi mục cuối cùng đến LName1 và mục cuối cùng sẽ chuyển đến LName2. Nhưng trong trường hợp chính xác một tên cuối cùng, ý định của tôi là hiển thị nó là LName1LName2 để trống (giống như truy vấn đầu tiên sẽ làm). Do đó, như bạn có thể thấy, các biện pháp bổ sung phải được thực hiện để đảm bảo thứ tự hiển thị đó.

Các vấn đề liên quan