PIVOT with groups and dynamic column names - sql

I have 3 tables that hold items properties, because each group of items can have different number of properties I hold them as key-value.
Table Package
| ID | Name |
| 1 | TVs |
| 2 | Laptops |
Table PackageItem
| ID | PackageId | Description |
| 1 | 1 | Samsung TV |
| 2 | 1 | Sony TV |
| 3 | 2 | Apple laptop |
| 4 | 2 | Lenovo |
Table PackageItemDetail
| ID | PackageItemId | Key | Value | PropertyOrder |
| 1 | 1 | Brand | Samsung | 1 |
| 2 | 1 | Size | 42 inch | 2 |
| 3 | 1 | Power consumption | A+ | 3 |
| 4 | 1 | Remote | Smart | 5 |
| 5 | 1 | Weight | 15kg | 4 |
| 6 | 2 | Brand | Sony | 1 |
| 7 | 2 | Size | 50 inch | 2 |
| 8 | 2 | Power consumption | A+++ | 3 |
| 9 | 2 | Remote | Standard | 5 |
| 10 | 2 | Weight | 20kg | 4 |
| 11 | 3 | Brand | Apple | 1 |
| 12 | 3 | Procesor | Intel | 2 |
| 13 | 4 | Brand | Lenovo | 1 |
| 14 | 4 | Procesor | Intel I7 | 2 |
If I select items and their properties for specific package using those queries:
SELECT PID.[Key], PID.[Value] FROM PackageItemDetail PID (NOLOCK) JOIN PackageItem PI (NOLOCK) ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=1 ORDER BY PID.PackageItemId, PID.PropertyOrder;
SELECT PID.[Key], PID.[Value] FROM PackageItemDetail PID (NOLOCK) JOIN PackageItem PI (NOLOCK) ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=2 ORDER BY PID.PackageItemId, PID.PropertyOrder;
I get those results:
Each Package always have same number or properties, so TVs have 5 properties and Laptops have 2 properties.
I'd like to transform those tables into those:
| Brand | Size | Power consumption | Weight | Remote |
| Samsung | 42 inch | A+ | 15kg | Smart |
| Sony | 50 inch | A+++ | 20kg | Standard |
| Brand | Procesor |
| Apple | Intel |
| Lenovo | Intel I7 |
I was able to create simple Pivot using:
SELECT
[Brand]
,[Procesor]
FROM
( SELECT
PI.[Id]
,PID.[Key]
,PID.[Value]
FROM
PackageItemDetail PID ( NOLOCK )
JOIN PackageItem PI ( NOLOCK ) ON PID.PackageItemId = PI.Id
WHERE
PI.PackageId = 2
) AS SourceTable PIVOT
( MAX(Value) FOR [Key] IN ( [Brand], [Procesor] ) ) AS PivotTable;
but this way I must specify properties by hand, but I'd like to have them dynamic (so same query will work for different packages)
I've created SQL Fiddle with sample data: http://sqlfiddle.com/#!18/e8c51/1

I tried to apply dynamic SQL in for your problem.
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME([Key])
FROM (SELECT DISTINCT [Key] FROM PackageItemDetail PID
JOIN PackageItem PI ON PID.PackageItemId = PI.Id
WHERE PI.PackageId = 2) AS Courses
SET #DynamicPivotQuery =
N'SELECT
'+ #ColumnName +'
FROM
( SELECT
PI.[Id]
,PID.[Key]
,PID.[Value]
FROM
PackageItemDetail PID ( NOLOCK )
JOIN PackageItem PI ( NOLOCK ) ON PID.PackageItemId = PI.Id
WHERE
PI.PackageId = 2
) AS SourceTable PIVOT
( MAX(Value) FOR [Key] IN (' + #ColumnName + ')) AS PivotTable'
EXEC sp_executesql #DynamicPivotQuery
Below is the link to the demo of the query:
http://sqlfiddle.com/#!18/e8c51/44
For explanation, you can go to this link:
http://sqlhints.com/tag/dynamic-pivot-column-names/

DECLARE #COLUMN1 VARCHAR(250),#STATEMENT1 NVARCHAR(MAX),#COLUMN2 VARCHAR(250),#STATEMENT2 NVARCHAR(MAX);
SELECT #COLUMN1 = COALESCE(#COLUMN1+',','')+QUOTENAME([Key]) FROM(
SELECT DISTINCT PID.[Key] FROM PackageItemDetail PID JOIN PackageItem PI ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=1) A
SET #STATEMENT1 = N'
select *from (
SELECT PID.[Key], PID.[Value] FROM PackageItemDetail PID JOIN PackageItem PI ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=1 ) as a
PIVOT (MIN([Value]) FOR [Key] IN ('+#COLUMN1+'))AS P1
UNION ALL
select *from (
SELECT PID.[Key], PID.[Value] FROM PackageItemDetail PID JOIN PackageItem PI ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=1 ) as a
PIVOT (MAX([Value]) FOR [Key] IN ('+#COLUMN1+'))AS P2'
--PRINT #STATEMENT1
EXEC (#STATEMENT1)
SELECT #COLUMN2 = COALESCE(#COLUMN2+',','')+QUOTENAME([Key]) FROM(
SELECT DISTINCT PID.[Key] FROM PackageItemDetail PID JOIN PackageItem PI ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=2) A1
SET #STATEMENT2 = N'
select *from (
SELECT PID.[Key], PID.[Value] FROM PackageItemDetail PID JOIN PackageItem PI ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=2) as a
PIVOT (MIN([Value]) FOR [Key] IN ('+#COLUMN2+'))AS P1
UNION ALL
select *from (
SELECT PID.[Key], PID.[Value] FROM PackageItemDetail PID JOIN PackageItem PI ON PID.PackageItemId=PI.Id
WHERE PI.PackageId=2 ) as a
PIVOT (MAX([Value]) FOR [Key] IN ('+#COLUMN2+'))AS P2'
--PRINT #STATEMENT2
EXEC (#STATEMENT2)
OUTPUT FOR #STATEMENT1
Brand Power consumption Remote Size Weight
Samsung A+ Smart 42 inch 15kg
Sony A+++ Standard 50 inch 20kg
OUTPUT FOR #STATEMENT2
Brand Procesor
Apple Intel
Lenovo Intel I7

Related

How do I create this pivot SQL query?

I have the following tables in our SQL Server 2012 instance:
tblASSETS
------------------------------------
| ASSETID | ASSETTYPE | NAME |
|---------|-----------|------------|
| 1 | A | Printer A |
| 2 | A | Printer B |
| 3 | A | Printer C |
| 4 | B | Laptop A |
------------------------------------
tblASSETTYPES
--------------------------------------
| ASSETTYPE | TYPENAME | ICON |
|-----------|----------|-------------|
| A | Printer | Printer.png |
| B | Laptop | Laptop.png |
--------------------------------------
tblASSETCUSTOM
-------------------------------------------------------------
| CUSTOMID | ASSETID | MAKE | MODEL | PRINTEDPAGES |
|----------|---------|------|----------------|--------------|
| 1 | 1 | HP | Laserjet 4v | 530 |
| 2 | 2 | HP | Laserjet 4v | 10000 |
| 3 | 3 | HP | Officejet 1050 | NULL |
| 4 | 4 | HP | Probook 430 G3 | NULL |
-------------------------------------------------------------
tblOIDDATA
---------------------------------------------
| OIDDATAID | ASSETID | LABEL | DATA |
|-----------|---------|--------------|------|
| 1 | 1 | Black copies | 430 |
| 2 | 1 | Color copies | 110 |
| 3 | 2 | Black copies | 5300 |
| 4 | 2 | Scans | 450 |
---------------------------------------------
I want to build a query which returns all printers and all their details as columns. I already created this QUERY:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(label)
from tblOIDData
group by label
order by label
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'Select * from ( Select Top 1000000 tAT.icon As icon, tat.typename as [ASSET TYPE] ,tA.Name as [ASSET NAME], tac.Model as [DEVICE MODEL], snmp.label as label, TRY_CONVERT(INT, TRY_CONVERT(NVARCHAR(100), snmp.data)) as PageCount, TA.Printepages as [PRINTED PAGES] from tblAssets as tA, tblAssetCustom as tAC, tsysAssetTypes as tAT, tblOIDData as SNMP where tA.AssetType = tAT.AssetType AND tAT.Typename = ''Printer'' AND tAC.AssetID = tA.AssetID AND snmp.AssetID = tA.AssetID ) as s PIVOT ( sum(PageCount) for [LABEL] IN (' + #cols + ') ) AS pvt'
execute(#query);
This almost give the desired result. The only thing I'm facing is that ASSETID 3 (Printer C) is not in the result. Probably because it is not in the tblOIDData table.
How can I include this Asset in my results also?
You could use a LEFT JOIN in your dynamic sql.
set #query = N'SELECT *
FROM
(
SELECT TOP 1000000
tAT.icon AS [icon],
tat.typename AS [ASSET TYPE],
tA.Name AS [ASSET NAME],
tac.Model AS [DEVICE MODEL],
snmp.label AS [label],
TRY_CONVERT(INT, TRY_CONVERT(NVARCHAR(100), snmp.data)) AS [PageCount],
TA.Printepages AS [PRINTED PAGES]
FROM tblAssets AS tA
JOIN tblAssetCustom AS tAC ON tAC.AssetID = tA.AssetID
JOIN tsysAssetTypes AS tAT ON tAT.AssetType = tA.AssetType
LEFT JOIN tblOIDData AS SNMP ON snmp.AssetID = tA.AssetID
WHERE tAT.Typename = ''Printer''
) AS src
PIVOT
(
SUM([PageCount])
FOR [label] IN (' + #cols + N')
) AS pvt';

Create table of sum of events in SQL table

I'm trying to pivot a table from the format
| ID | access date |
--------------
| 1 | 08.10|
| 1 | 08.10|
| 4 | 08.10|
| 2 | 02.09|
To
|ID | 02.09 | 03.09 | 04.09 | ....
| 1 | 4 | 0 | 2 |
| 2 | 1 | 2 | 5 |
| 3 |
.
.
.
I've tried using the PIVOT function but since I have a lot of different dates I don't want to type out the query
SELECT *
FROM (
SELECT [Sequence of events] as ID
,[Submission Date] as access_date
FROM [database_name].[dbo].[Event Logging]
) AS SOURCE_TABLE
PIVOT( SUM(ID) for access_date IN ("08.01", "09.01", "10.01"....)
) as pvt_table
I'm very new to SQL so I'd appreciate some insight into how to solve this problem.
This is not answer about solving problem in your way but it is about solving it another way.
What i would do is create 2 tables. First one would be called DATE_DB where i would store DATEID and DATE and it would look like this:
| DATEID | DATE |
| 1 | 01.01|
| 2 | 02.02|
....
Then in second table I store data like this:
| ID | DATEID | VALUE |
| 1 | 2 | 10 |
| 2 | 2 | 3 |
| 3 | 3 | 4 |
| 4 | 2 | 5 |
So in second table column ID is used only for primary key and has nothing to do but with tables like this and JOIN command you can use it like this:
SELECT DATE_DB.DATE, SECONDTABLE.VALUE
FROM SECONDTABLE
LEFT JOIN DATE_DB ON SECONDTABLE.DATEID = DATE_DB.DATE
ORDER BY DATE_DB.DATE
which will display result like this:
| DATE | VALUE |
| 02.01 | 10 |
| 02.01 | 3 |
| 02.01 | 5 |
| 03.01 | 4 |
Try it out like this, you need dynamic sql, note script isn't tested out, also when you naming your columns try not to have space, ether use CamelCase or underscore to separate words
And last thing, this is for SQL-Server, as you didn't tag anything and your code looks like sql-server
declare #cols nvarchar(max)
select #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME([Submission Date])
from [database_name].[dbo].[Event Logging]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
declare #sql nvarchar(max);
set #sql = '
SELECT *
FROM (
SELECT [Sequence of events] as ID
,[Submission Date] as access_date
FROM [database_name].[dbo].[Event Logging]
) AS SOURCE_TABLE
PIVOT( SUM(ID) for access_date IN (' + #cols + ')
) as pvt_table';
-- print (#sql)
execute (#sql)

SQL Server Count records from parent category and all subcategories

Currently I have a stored procedure where I create a table and query the table to get my desired result, the result being an infinitely tiered child/parent table that allows me to display the data on my ASP Classic based webpage.
This procedure is:
SET NOCOUNT ON;
DECLARE #Categories TABLE(
CatID INT NOT NULL,
CatName VARCHAR(200) NOT NULL,
ParentID INT
)
INSERT INTO #Categories
SELECT CatID, CatName, ParentID = NULL FROM Categories
WHERE CatName NOT IN (
SELECT CatName FROM Categories c
INNER JOIN CategoriesRel r ON c.CatID = r.ChildID)
UNION
SELECT CatID, CatName, cr.ParentID FROM Categories
INNER JOIN CategoriesRel cr ON cr.ChildID = Categories.CatID
ORDER BY CatID;
WITH r AS (
SELECT CatID, CatName, ParentID, depth=0 ,Sort=CAST(CatName As VARCHAR(MAX))
FROM #Categories WHERE ParentID IS NULL
UNION ALL
SELECT c.CatID, c.CatName, c.ParentID, Depth=r.Depth+1 ,Sort=r.Sort+CAST(c.CatName AS VARCHAR(200))
FROM r INNER JOIN #Categories c ON r.CatID=c.ParentID WHERE r.Depth<32767
)
SELECT CatID, CatName=replicate('-',r.Depth*3)+r.CatName,
(SELECT COUNT(BsnID) FROM Businesses WHERE Businesses.CatID = r.CatID) AS CatCount
FROM r ORDER BY Sort OPTION(maxrecursion 32767);
The problem with this is the count query at the bottom.
(SELECT COUNT(BsnID) FROM Businesses WHERE Businesses.CatID = r.CatID) AS CatCount
If I use this specific piece of code, I only get the count of rows returned for a specific Category ID. For example this is the current result:
CatID | CatName | CatCount
______|______________________|_________
1016 | Antiques | 1
1021 | Automotive | 1
1024 | ---Repair | 1
1026 | ------Engine Repair | 1
1035 | ---Tyres | 1
1002 | Building | 0
I need the result to be something like this:
CatID | CatName | CatCount
______|______________________|_________
1016 | Antiques | 1
1021 | Automotive | 4
1024 | ---Repair | 2
1026 | ------Engine Repair | 1
1035 | ---Tyres | 1
1002 | Building | 0
Any help would be greatly appreciated! Thanks
EDIT: Included is some SQL for testing purposes
CREATE TABLE Categories(CatID int NOT NULL,CatName nvarchar(100) NOT NULL,PRIMARY KEY (CatID));
CREATE TABLE CategoriesRel(CatLinkID int NOT NULL,ParentID int NOT NULL,ChildID int NOT NULL,PRIMARY KEY (CatLinkID),FOREIGN KEY (ParentID) REFERENCES Categories(CatID),FOREIGN KEY (ChildID) REFERENCES Categories(CatID);
CREATE TABLE Businesses(BsnID int NOT NULL,BsnName nvarchar(100) NOT NULL,CatID int NOT NULL,PRIMARY KEY (BsnID),FOREIGN KEY (CatID) REFERENCES Categories(CatID);
INSERT INTO Categories VALUES ('1','Antique'),('2','Automotive'),('3','Building'),('4','Tyres'),('5','Repair'),('6','Engine Repairs');
INSERT INTO CategoriesRel VALUES ('1', '2','4'),('1','2','5'),('1','5','6');
INSERT INTO Businesses VALUES ('1','Test1','2'),('2','Test2','4'),('3','Test3','5'),('4','Test4','6');
Take out the WHERE ParentID IS NULL part of the CTE, and add a RootId field. This will let you find the count of the children for each level of parent.
;WITH r AS (
SELECT CatID, CatName, ParentID, CatID RootId, depth=0 ,Sort=CAST(CatName As VARCHAR(MAX))
FROM #Categories
--WHERE ParentID IS NULL
UNION ALL
SELECT c.CatID, c.CatName, c.ParentID, RootId, Depth=r.Depth+1 ,Sort=r.Sort+CAST(c.CatName AS VARCHAR(200))
FROM r
INNER JOIN #Categories c ON r.CatID=c.ParentID
WHERE r.Depth<32767
)
This gives you a row for each level of the hierarchy. Parent categories appear multiple times, but with different RootIds. This will let us get a count at each level of the hierarcy:
+-------+---------------+----------+--------+-------+-------------------------------+
| CatID | CatName | ParentID | RootId | depth | Sort |
+-------+---------------+----------+--------+-------+-------------------------------+
| 1002 | Building | NULL | 1002 | 0 | Building |
| 1016 | Antiques | NULL | 1016 | 0 | Antiques |
| 1021 | Automotive | NULL | 1021 | 0 | Automotive |
| 1024 | Repair | 1021 | 1024 | 0 | Repair |
| 1026 | Engine Repair | 1024 | 1026 | 0 | Engine Repair |
| 1035 | Tyres | 1021 | 1035 | 0 | Tyres |
| 1026 | Engine Repair | 1024 | 1024 | 1 | RepairEngine Repair |
| 1024 | Repair | 1021 | 1021 | 1 | AutomotiveRepair |
| 1035 | Tyres | 1021 | 1021 | 1 | AutomotiveTyres |
| 1026 | Engine Repair | 1024 | 1021 | 2 | AutomotiveRepairEngine Repair |
+-------+---------------+----------+--------+-------+-------------------------------+
If you group by RootId and get the COUNT(), it gives you the numbers you're looking for:
select RootId, count(b.CatId) catCount
from r
left outer join Businesses b on r.CatID = b.CatId
group by rootid
+--------+----------+
| RootId | CatCount |
+--------+----------+
| 1002 | 0 |
| 1016 | 1 |
| 1021 | 4 |
| 1024 | 2 |
| 1026 | 1 |
| 1035 | 1 |
+--------+----------+
The rest of the query is just getting the Sort and indented CatName. You want to get the deepest child for each category:
select r.CatId, CatName, max(depth) MaxDepth
from r
group by r.catId, CatName
The final query is:
SELECT y.CatID,
replicate('-',y.MaxDepth*3)+y.CatName CatName,
x.CatCount
FROM (select RootId, count(b.CatId) catCount
from r
left outer join Businesses b on r.CatID = b.CatId
group by rootid) x
join (select r.CatId, CatName, max(depth) MaxDepth
from r
group by r.catId, CatName) y on y.CatID = x.RootId
order by (select Sort from r where r.CatID = x.RootId and r.depth = y.MaxDepth)
OPTION(maxrecursion 32767);

SQL server: Transpose Rows to Columns (n:m relationship)

After trying it myself for some hours now I need to ask for help. I only did some basic SQL until now.
I want to solve the following:
(I have translated a couple of things for you to understand the context)
I have three tables:
Workers (Mitarbeiter in German - mitID)
| mitID | Name | FamName | DOB | abtIDref |
|-------|--------|---------|------------|----------|
| 1 | Frank | Sinatra | 12.12.1915 | 1 |
| 2 | Robert | Downey | 4.4.1965 | 2 |
INFO: abtIDref is an 1:n relation for the Workplace, but not involved here
Skills (Faehigkeiten in German - faeID)
| faeID | Descr | time | cost |
|-------|-------|------|------|
| 1 | HV | 2 | 0 |
| 2 | PEV | 1 | 0 |
| 3 | Drive | 8 | 250 |
| 4 | Nex | 20 | 1200 |
Link-List
| linkID | mitIDref | feaIDref | when |
|--------|----------|----------|------------|
| 1 | 2 | 1 | 27.07.2014 |
| 2 | 2 | 2 | 01.01.2016 |
| 3 | 2 | 3 | 20.01.2016 |
| 4 | 1 | 3 | 05.06.2015 |
| 5 | 1 | 4 | 02.11.2015 |
The desired result is:
| mitID | Name | FamName | DOB | abtIDref | HV | PEV | Drive | Nex |
|-------|--------|---------|------------|----------|-----------|------------|------------|------------|
| 1 | Frank | Sinatra | 12.12.1915 | 1 | | | 05.06.2015 | 02.11.2015 |
| 2 | Robert | Downey | 4.4.1965 | 2 | 27.7.2014 | 01.01.2016 | 20.01.2015 | |
Alternative it could be:
| mitID | Name | FamName | DOB | abtIDref | HV | PEV | Drive | Nex |
|-------|--------|---------|------------|----------|----|-----|-------|-----|
| 1 | Frank | Sinatra | 12.12.1915 | 1 | | | x | x |
| 2 | Robert | Downey | 4.4.1965 | 2 | x | x | x | |
The goal is that users/admins can add up new skills and someone can see on this resultlist, if a person has this skill.
What did i try:
I've come across multiple examples of dynamic SQL and the pivot function, but I don't know how to use it in my case, because I don't run a function like AVG() or MIN().
I tried it like this:
DECLARE #columns AS VARCHAR(MAX);
DECLARE #sql AS VARCHAR(MAX);
select #columns = substring((Select DISTINCT ',' + QUOTENAME(faeID) FROM mdb_Fähigkeiten FOR XML PATH ('')),2, 1000);
SELECT #sql = 'SELECT * FROM mdb_Mitarbeiter
PIVOT
(
MAX(Value)
FOR mitID IN( ' + #columns + ' )
);';
execute(#sql);
And a second approach was:
declare #collist nvarchar(max)
SET #collist = stuff((select distinct ',' + QUOTENAME(Question)
FROM #t1 -- your table here
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
select #collist
declare #q nvarchar(max)
set #q = '
select *
from (
select
Vorname, Bezeichnung, faeIDref
from (
select #t1.*, #t2.Answer, #t2.parent
from #t1
inner join #t2 on #t1.QID = #t2.QID
) as x
) as source
pivot (
max(Answer)
for Question in (' + #collist + ')
) as pvt
'
exec (#q)
But TBH I don't get the functions found.
I hope you can provide me with some guidance what I have to change (or even if I can) achieve this.
I believe the query below is what you are looking for. Adjust the column and table names as needed to fit your database.
DECLARE #sql AS NVARCHAR(MAX)
DECLARE #cols AS NVARCHAR(MAX)
SELECT #cols= ISNULL(#cols + ',','') + QUOTENAME(Descr)
FROM Faehigkeiten ORDER BY faeID
SET #sql = N'
SELECT mitID, Name, FamName, DOB, abtIDref, ' + #cols + '
FROM (
SELECT mitID, Name, FamName, DOB, abtIDref, [when], descr
FROM Mitarbeiter m
JOIN [Link-List] l ON m.mitID = l.mitIDref
JOIN Faehigkeiten f ON f.faeID = l.feaIDref
) a
PIVOT(MAX([when]) FOR descr IN (' + #cols + ')) p'
EXEC sp_executesql #sql

Sub-sub-selects and grouping: Get name column from the row containing the max value of a group

I have two tables: States, and Items.
States:
+----+------+-------+----------+
| id | name | state | priority |
+----+------+-------+----------+
| 1 | AA | 10 | 1 |
| 2 | AB | 10 | 2 |
| 3 | AC | 10 | 3 |
| 4 | BA | 20 | 1 |
| 5 | BB | 20 | 5 |
| 6 | BC | 20 | 10 |
| 7 | BD | 20 | 50 |
+----+------+-------+----------+
Items:
+----+--------+-------+
| id | item | state |
+----+--------+-------+
| 1 | Blue | 10 |
| 2 | Red | 20 |
| 3 | Green | 20 |
| 4 | Yellow | 10 |
| 5 | Brown | 10 |
+----+--------+-------+
The priority column is not used in the Items table, but complicates getting the data I need, as shown below.
What I want is a list of the rows in the Items table, replacing the state.id value in each row with the name of the highest priority state.
Results would look like this:
+----+--------+-------+
| id | item | state |
+----+--------+-------+
| 1 | Blue | AC |
| 2 | Red | BD |
| 3 | Green | BD |
| 4 | Yellow | AC |
| 5 | Brown | AC |
+----+--------+-------+
Here's the tiny monster I've come up with. Is this the best way, or can I be more efficient / less verbose? (Sub-sub-selects make my palms itch. :-P )
SELECT *
FROM
Items AS itm
INNER JOIN (SELECT sta.name, sta.state
FROM (SELECT state, MAX(priority) [highest]
FROM States
GROUP BY state) AS pri
INNER JOIN States AS sta
ON sta.state = pri.state
AND sta.priority = pri.highest) AS nam
ON item.state = name.state
Update: I'm using MS-SQL 2005 and MS-SQL 2008R2
You did not post your version of SQL-Server. Assuming you are on 2005 or later you can use the ROW_NUMBER() function together with a cross apply like this:
CREATE TABLE dbo.States(id INT, name NVARCHAR(25), state INT, priority INT);
INSERT INTO dbo.States
VALUES
( 1 ,'AA', 10 , 1 ),
( 2 ,'AB', 10 , 2 ),
( 3 ,'AC', 10 , 3 ),
( 4 ,'BA', 20 , 1 ),
( 5 ,'BB', 20 , 5 ),
( 6 ,'BC', 20 , 10 ),
( 7 ,'BD', 20 , 50 );
CREATE TABLE dbo.Items( id INT ,item NVARCHAR(25), state INT );
INSERT INTO dbo.Items
VALUES
( 1 ,'Blue', 10 ),
( 2 ,'Red', 20 ),
( 3 ,'Green', 20 ),
( 4 ,'Yellow', 10 ),
( 5 ,'Brown', 10 );
SELECT i.id,
i.item,
s.name,
s.priority
FROM dbo.Items i
CROSS APPLY (
SELECT *,ROW_NUMBER()OVER(ORDER BY priority DESC) rn FROM dbo.States si WHERE si.state = i.state
)s
WHERE s.rn = 1;
The cross apply works like a join but allows to reference columns on the left side in the right side as you can see in the where clause. The ROW_NUMBER() function numbers all rows in the states table that match the current state value in reverse priority order so that the row with the highest priority always gets the number 1. The final where clause is filtering out just those rows.
EDIT:
I just started a blog series about joins: A Join A Day
The Cross Apply will be topic of day 8 (12/8/2012).