在SQL Server中展平父子关系

时间:2014-08-04 19:45:38

标签: sql sql-server

我在SQL Server中有两个表:Household和People。家庭代表一个家庭,人们代表住在家里的人们:

家用

Id       Address        City        State          Zip
------------------------------------------------------
1        123 Main       Anytown     CA           90121

Id        HouseholdId       Name        Age
-------------------------------------------
1         1                 John         32
2         1                 Jane         29

我想查询两个表并最终得到如下结果集,但我不确定如何最好地解决这个问题:

Id        Address        City        State        Zip        Person1Name        Person1Age        Person2Name     Person2Age
----------------------------------------------------------------------------------------------------------------------------
1         123 Main       Anytown     CA           90121      John                       32        Jane                    29

当然,“PersonXName和PersonXAge”应根据有多少人重复。如何编写可以实现此目的的查询?简单性优于性能,因为这是我需要提出的一次性报告。

5 个答案:

答案 0 :(得分:3)

这是使用动态交叉表完成的。供参考:http://www.sqlservercentral.com/articles/Crosstab/65048/

CREATE TABLE HouseHold(
    ID      INT,
    Address VARCHAR(20),
    City    VARCHAR(20),
    State   CHAR(2),
    Zip     VARCHAR(10)
)
CREATE TABLE People(
    ID          INT,
    HouseHoldID INT,
    Name        VARCHAR(20),
    Age         INT
)
INSERT INTO HouseHold VALUES
(1, '123 Main', 'Anytown', 'CA', '90121');
INSERT INTO People VALUES
(1, 1, 'John', 32),
(2, 1, 'Jane', 29);

DECLARE @sql1 VARCHAR(4000) = ''
DECLARE @sql2 VARCHAR(4000) = ''
DECLARE @sql3 VARCHAR(4000) = ''

SELECT @sql1 =
'SELECT
     ID
    ,Address
    ,City
    ,State
    ,Zip'
+ CHAR(10)

SELECT @sql2 = @sql2 +
'   ,MAX(CASE WHEN RN = ' + CONVERT(VARCHAR(10), RN) + ' THEN Name END) AS [Person' + CONVERT(VARCHAR(10), RN) + 'Name]
    ,MAX(CASE WHEN RN = ' + CONVERT(VARCHAR(10), RN) + ' THEN Age END) AS [Person' + CONVERT(VARCHAR(10), RN) + 'Age]
'
FROM(
    SELECT DISTINCT RN = ROW_NUMBER() OVER(PARTITION BY p.HouseHoldID ORDER BY p.ID)
    FROM People p   
)t

SELECT @sql3 =
'FROM(
    SELECT
        h.*
        ,p.Name
        ,p.Age
        ,RN = ROW_NUMBER() OVER(PARTITION BY h.ID ORDER BY p.ID)
    FROM Household h
    INNER JOIN People p ON p.HouseHoldId = h.ID
)t
GROUP BY ID, Address, City, State, Zip
ORDER BY ID'

PRINT(@sql1 + @sql2 + @sql3)
EXEC (@sql1 + @sql2 + @sql3)

DROP TABLE HouseHold
DROP TABLE People

<强> RESULT

ID          Address              City                 State Zip        Person1Name          Person1Age  Person2Name          Person2Age
----------- -------------------- -------------------- ----- ---------- -------------------- ----------- -------------------- -----------
1           123 Main             Anytown              CA    90121      John                 32          Jane                 29

答案 1 :(得分:1)

这是根据我使用的类似要求的脚本改编的。如果People表有一百万行,可能不想使用,但对于我的大约20000行的用例来说效果还不错:

DECLARE @id int, @householdid int, @name varchar(50), @age int, @currentid   int, @peoplecount int;
DECLARE @colsql nvarchar(1000), @datasql nvarchar(1000), @RunSql nvarchar(1000);

CREATE TABLE #ReturnTable (HouseholdId int, Address varchar(50))

INSERT #ReturnTable
SELECT Id, Address
FROM Household;

-- these are split into two dynamic queries
-- so that columns exist when we try the insert
SET @colsql = 'IF (SELECT COUNT(*)
  FROM TempDB.INFORMATION_SCHEMA.COLUMNS 
  WHERE COLUMN_NAME = ''Person{Number}Name'' 
    AND TABLE_NAME LIKE ''#ReturnTable'') = 0
BEGIN
  ALTER TABLE #ReturnTable
  ADD Person{Number}Name VARCHAR(50)
END

IF (SELECT COUNT(*)
  FROM TempDB.INFORMATION_SCHEMA.COLUMNS 
  WHERE COLUMN_NAME = ''Person{Number}Age'' 
    AND TABLE_NAME LIKE ''#ReturnTable'') = 0
BEGIN
  ALTER TABLE #ReturnTable
  ADD Person{Number}Age INT
END'

set @datasql =
'UPDATE #ReturnTable
SET Person{Number}Name = @name,
  Person{Number}Age = @age
WHERE HouseholdId = @householdid'

DECLARE PeopleCursor CURSOR FOR
SELECT p.Id, p.HouseholdId, p.Name, p.Age
FROM People p
ORDER BY p.HouseholdId, p.Age

OPEN PeopleCursor;

FETCH NEXT FROM PeopleCursor
INTO @id, @householdid, @name, @age

SET @currentid = @id

SET @peoplecount = 1;

WHILE @@FETCH_STATUS = 0
BEGIN
    IF @currentid <> @id
    BEGIN
       SET @peoplecount = 1
       SET @currentid = @id
    END
    ELSE SET @peoplecount = @peoplecount + 1;

    SET @RunSql = REPLACE(@colsql, '{Number}', CAST(@peoplecount AS VARCHAR(3)));

    EXEC dbo.sp_ExecuteSql @RunSql

    SET @RunSql = REPLACE(@datasql, '{Number}', CAST(@peoplecount AS VARCHAR(3)));

    EXEC dbo.sp_ExecuteSql @RunSql, N'@householdid int, @name varchar(50), @age int', @householdid = @householdid, @name = @name, @age = @age;

    FETCH NEXT FROM PeopleCursor
    INTO @id, @householdid, @name, @age
END

CLOSE PeopleCursor
DEALLOCATE PeopleCursor

SELECT *
FROM #ReturnTable

drop table #ReturnTable

答案 2 :(得分:0)

很少需要更改,如果你们帮我解决这个问题,那就太棒了......

 DECLARE @col1 nvarchar(max) = '', @col2 nvarchar(max) = ''
 declare @colname nvarchar(max),@query nvarchar(max), @cols nvarchar(max)=''

 DECLARE openall CURSOR for
 SELECT ROW_NUMBER() OVER(ORDER BY NAME) rowno FROM People 

    OPEN openall
  fetch next from openall into @colname

while @@FETCH_STATUS = 0
    begin

        set @col1 += 'Person'+ @colname +'Name,'
        set @col2 += 'Person'+ @colname+'Age,'

        fetch next from openall into @colname
     end     

  set @col1 = LEFT(@col1,LEN(@col1)-1)
  set @col2 = LEFT(@col2,LEN(@col2)-1)

 set @query = 'SELECT ID, Address, City, State, Zip, ' + @col1 + ', ' + @col2 + ' 
       FROM ( 
        SELECT h.ID, Address, City, State, Zip,p.name,p.age from Household h 
         inner join people p on h.id = p.householdid
           ) x
        pivot
       (
      sum(age) for
      name in (' + @col1 + ', ' + @col2 + ')
      ) p '

     execute(@query)
     close openall
     deallocate openall

答案 3 :(得分:0)

这&#34;展平&#34;操作称为多列动态枢轴。它是动态的,因为在设计时不知道枢轴列值,而且它是多色的,因为你有&#34; age&#34;和&#34;名称&#34;枢轴列值。

要在SQL中执行多列动态数据透视,您需要使用动态sql并使用case表达式。 SQL Pivot运算符不适用于多列数据透视。这很难实现。

我不认为SQL是执行多列动态数据透视的最佳语言。我认为最好在客户端完成。

以下c#方法返回一个包含您要求的结果集的数据表:

public DataTable GetPivotedPeople()
{
    using (var ds = new MyDataService())
    {
        return ds.PersonRepository
            .Query("Household")
            .OrderBy(PersonFields.HouseHoldId, PersonFields.Address, PersonFields.City, PersonFields.State, PersonFields.Zip)
            .Pivot(
                new PivotTransform
                {
                    PivotColumnName = PersonFields.PersonId,
                    ValueColumnName = PersonFields.Name,
                    GetPivotedColumnName = (personId) => "Person" + personId.ToString() + "Name"
                },
                new PivotTransform
                {
                    PivotColumnName = PersonFields.PersonId,
                    ValueColumnName = PersonFields.Age,
                    GetPivotedColumnName = (personId) => "Person" + personId.ToString() + "Age"
                }
            );
    }
}

这是返回数据表的内容:

+-------------+----------+---------+-------+-------+-------------+-------------+------------+------------+
| HouseHoldId | Address  |   Ciy   | State |  Zip  | Person1Name | Person2Name | Person1Age | Person2Age |
+-------------+----------+---------+-------+-------+-------------+-------------+------------+------------+
|           1 | 123 Main | Anytown | CA    | 90121 | John        | Jane        |         32 |         29 |
+-------------+----------+---------+-------+-------+-------------+-------------+------------+------------+

它使用EntityLite,我开发的微型ORM。它查询Person_HouseHold视图并在客户端旋转行。这里有sql脚本:

CREATE DATABASE DynamicPivot
GO
USE DynamicPivot
GO
CREATE TABLE Households
(
    HouseholdId int IDENTITY(1,1) PRIMARY KEY,
    [Address] nvarchar(128) NOT NULL,
    City nvarchar(128) NOT NULL,
    [State] nvarchar(128) NOT NULL,
    Zip nvarchar(128) NOT NULL
);
INSERT INTO Households ([Address], City, [State], Zip) 
VALUES (N'123 Main', N'Anytown', N'CA', N'90121');
GO

CREATE TABLE People
(
    PersonId int IDENTITY(1,1) PRIMARY KEY,
    HouseHoldId int NOT NULL CONSTRAINT FK_People_Households REFERENCES HouseHolds(HouseholdId),
    Name nvarchar(128) NOT NULL,
    Age int NOT NULL

);
INSERT INTO People(HouseHoldId, Name, Age) VALUES
(1, N'John', 32), (1, 'Jane', 29)

GO

CREATE VIEW Person_Household
AS
    SELECT 
        P.PersonId, P.HouseHoldId, P.Name, P.Age,
        H.[Address], H.City, H.[State], H.Zip
    FROM
        dbo.People P INNER JOIN dbo.Households H
            ON P.HouseHoldId = H.HouseholdId

我在CodeProject上写了this article。它解释了如何使用sql进行数据透视以及如何使用EntityLite或使用原始ADO.NET在客户端执行数据透视表。因此,要在客户端进行调整,您不需要使用EntityLite。

答案 4 :(得分:-1)

考虑到这是一次性的,你可以考虑这个:

住在同一栋房子里的人数最多(选择计数)

将每个人的人员表加入家庭表。

SELECT * 
FROM Household 
LEFT JOIN People p1 
ON p1.HouseHoldId = Household.Id
LEFT JOIN People p2 
ON p2.HouseHoldId = Household.Id

根据索引,优化设置和许多其他条件,这甚至可能是一个非常高效的解决方案。