我在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”应根据有多少人重复。如何编写可以实现此目的的查询?简单性优于性能,因为这是我需要提出的一次性报告。
答案 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
根据索引,优化设置和许多其他条件,这甚至可能是一个非常高效的解决方案。