How to flatten tree leaves sequence in SQL - sql-server

I have a table S which has an 2 columns: Name for ID, and an XML column.
This table represents a tree data.
S
-----+----------------
Name | XmlCol
-----+----------------
A | <E><B Id='b1' Type=0 /><B Id='D' Type=1 /><B Id='b2' Type=0 /></E>
D | <E><B Id='b3' Type=0 /><B Id='G' Type=1 /></E>
F | <E><B Id='b4' Type=0 /></E>
G | <E><B Id='b5' Type=0 /></E>
The data appears in given order. Order matters here.
Notice the XML structure.
Type = 0 means that the entry is of type leaf.
Type = 1 means that there is a row in the table with same Name, hence a node and not a leaf.
There are 5 leaves, b1, b2, b3, b4, b5.
What I want is to get a table of all leaves in the sequence presented, like this:
Leaves
--------
b1
b3
b5
b2
b4
when the start node is 'A'
This is the XML parsing snippet, but it is just the begining.
SELECT [Id] = xTree.b.value('#Id', 'varchar(10)')
FROM [S]
CROSS APPLY [XmlCol].nodes('/E/B') AS xTree(b)
Can anyone suggest how to do this in SQL?

Try something like this:
DECLARE #Source TABLE (
Name VARCHAR(10) PRIMARY KEY,
XmlCol XML NOT NULL
)
DECLARE #Root VARCHAR(10)='A'
INSERT INTO #Source VALUES
('A',CONVERT(XML,'<E><B Id="b1" Type="0" /><B Id="D" Type="1" /><B Id="b2" Type="0" /></E>')),
('D',N'<E><B Id="b3" Type="0" /><B Id="G" Type="1" /></E>'),
('F',N'<E><B Id="b4" Type="0" /></E>'),
('G',N'<E><B Id="b5" Type="0" /></E>')
DECLARE #Temp1 TABLE (
ID INT IDENTITY PRIMARY KEY,
Name VARCHAR(10) NOT NULL,
Type INT NOT NULL,
Value VARCHAR(10) NOT NULL
)
INSERT INTO #Temp1
SELECT Name, xTree.b.value('#Type', 'int') AS Type, xTree.b.value('#Id', 'varchar(10)') AS Value
FROM #Source
CROSS APPLY [XmlCol].nodes('/E/B') AS xTree(b)
DECLARE #Temp2 TABLE (
ID INT PRIMARY KEY,
Name VARCHAR(10) NOT NULL,
Type INT NOT NULL,
Value VARCHAR(10) NOT NULL,
Position INT NOT NULL
)
INSERT INTO #Temp2
SELECT *, ROW_NUMBER() OVER (PARTITION BY Name ORDER BY ID) AS Position
FROM #Temp1
DECLARE #Temp3 TABLE (
Name VARCHAR(10) NOT NULL,
Type INT NOT NULL,
Value VARCHAR(10) NOT NULL,
Position FLOAT NOT NULL,
Level INT NOT NULL,
PRIMARY KEY (Name, Position)
)
;WITH CTE AS (
SELECT Name, Type, Value, CONVERT(FLOAT,Position) AS Position, 0 AS Level
FROM #Temp2 WHERE Type=0
UNION ALL
SELECT t1.Name, t2.Type, t2.Value,
t1.Position+t2.Position*POWER(CONVERT(FLOAT,0.1),1+t2.Level),
t2.Level+1 AS Level
FROM #Temp2 t1
INNER JOIN CTE t2 ON t2.Name=t1.Value
WHERE t1.Type=1
)
INSERT INTO #Temp3
SELECT * FROM CTE
SELECT Value
FROM (
SELECT Value, 0 AS Extra, Position
FROM #Temp3 WHERE Name=#Root
UNION ALL
SELECT Value, 1 AS Extra, Position
FROM #Temp3 WHERE Value NOT IN (
SELECT Value
FROM #Temp3 WHERE Name=#Root
)
) u
ORDER BY Extra, Position
A few observations:
in XML, the value of the attributes should always be quoted
it's not very clear in which order you want the values that are outside the tree of the given root (in this example, only b4 is not part of the tree starting from A; if there were multiple values like this, in multiple other trees, it's not clear which is the desired order)
the use of table variables may be avoided by using a more complex CTE, but I think they are helpful for performance
I assumed a maximum of 10 sub-nodes per level; if there are more sub-nodes, you can change 0.1 to 0.01, for example

Related

How to combine two FOR XML AUTO into 1 XML?

We are using SQL Server 2012.
Table myTbl has a one to many relationship to table myAllocation
Table ABC_myTbl has a one to many relationship to table ABC_myAllocation
The below query combined 2 FOR XML AUTO into 1 XML, but the problem is ID, SystemSource, Manager are included in element TradeTicket instead of on their own, and accountManager, unitPrice are included in element allocationRow instead of on their own.
Thank you
SELECT '<?xml version="1.0"?>'+
(SELECT
( SELECT trTicket.[id],trTicket.[manager],'PFM' as SystemSource
,allocationRow.accountNumber,allocationRow.unitPrice
FROM myTbl AS trTicket
LEFT JOIN myAllocation AS allocationRow ON allocationRow.trade_ticket_id=trTicket.id
WHERE trTicket.ID = 8779631
ORDER BY trTicket.id,allocationRow.AccountNumber
FOR XML AUTO, type)
,
(
SELECT trTicket.[id],trTicket.[manager],'CRD' as SystemSource
,allocationRow.accountNumber,allocationRow.unitPrice
FROM ABC_myTbl AS trTicket
LEFT JOIN ABC_myAllocation AS allocationRow ON allocationRow.trade_ticket_id=trTicket.id
WHERE trTicket.ID = 8
ORDER BY trTicket.id,allocationRow.AccountNumber
FOR XML AUTO, type)
FOR XML PATH('trTickets'), ELEMENTS) AS XMLResult
This is the current result:
<?xml version="1.0"?>
<trTickets>
<trTicket id="8779631" SystemSource="PFM" manager="MCM">
<allocationRow accountNumber="292 " unit_Price="300"/>
</trTicket>
<trTicket id="8" SystemSource="CRD" manager="DOYLE">
<allocationRow unitPrice="100" accountNumber="F11 "/>
<allocationRow unitPrice="200" accountNumber="F22 "/>
</trTicket>
</trTickets>
This is the desired result that I am looking for:
<?xml version="1.0"?>
<trTickets>
<trTicket>
<id>8</id>
<manager>DOYLE</manager>
<SystemSource>CRD</SystemSource>
<allocationRow>
<accountNumber>F11</accountNumber>
<unitPrice>100</unitPrice>
</allocationRow>
<allocationRow>
<accountNumber>F22</accountNumber>
<unitPrice>200</unitPrice>
</allocationRow>
</trTicket>
<trTicket>
<id>8779631</id>
<manager>MCM</manager>
<SystemSource>PFM</SystemSource>
<allocationRow>
<accountNumber>292</accountNumber>
<unitPrice>300</unitPrice>
</allocationRow>
</trTicket>
</trTickets>
Data sample:
Table ABC_myTbl:
ID Manager
-----------
8 DOYLE
Table ABC_myAllocation:
accountNumber unitPrice
-------------------------
F11 100
F22 200
Table myTbl:
ID Manager
---------------
8779631 MCM
Table myAllocation:
accountNumber unitPrice
--------------------------
292 300
DDL for the tables and their data:
CREATE TABLE dbo.ABC_myTbl
(
ID INT NOT NULL,
MANAGER VARCHAR(10) NOT NULL
)
CREATE TABLE dbo.myTbl
(
ID INT NOT NULL,
MANAGER VARCHAR(10) NOT NULL
)
CREATE TABLE dbo.ABC_myAllocation
(
accountNumber VARCHAR(10) NOT NULL,
unitprice NUMERIC(10, 3) NOT NULL
)
CREATE TABLE dbo.myAllocation
(
accountNumber VARCHAR(10) NOT NULL,
unitprice NUMERIC(10, 3) NOT NULL
)
INSERT INTO dbo.ABC_myTbl VALUES (8,'DOYLE')
INSERT INTO dbo.ABC_myAllocation VALUES ('F11',100)
INSERT INTO dbo.ABC_myAllocation VALUES ('F22',200)
INSERT INTO dbo.myTbl VALUES (8779631,'MCM')
INSERT INTO dbo.myAllocation VALUES ('292',300)
I didn't wait for your DDL and sample data population. So I created a conceptual sample for you. Please pay attention that the tables have implied relationships and they are used in the WHERE clauses.
SQL
-- DDL and sample data population, start
DECLARE #tbl1 TABLE (ID INT PRIMARY KEY, Manager VARCHAR(20));
INSERT INTO #tbl1 (ID, Manager) VALUES
(8, 'DOYLE'),
(9, 'XYZ');
DECLARE #tbl1Child TABLE (accountNumber CHAR(3) PRIMARY KEY, ParentID INT, unitPrice DECIMAL(10,2));
INSERT INTO #tbl1Child (accountNumber, ParentID, unitPrice) VALUES
('F11', 8, 100)
,('F22', 8, 200)
,('F70', 9, 770);
DECLARE #tbl2 TABLE (ID INT PRIMARY KEY, Manager VARCHAR(20));
INSERT INTO #tbl2 (ID, Manager) VALUES
(8779631, 'MCM')
,(8779555, 'TTT');
DECLARE #tbl2Child TABLE (accountNumber CHAR(3) PRIMARY KEY, ParentID INT, unitPrice DECIMAL(10,2));
INSERT INTO #tbl2Child (accountNumber, ParentID, unitPrice) VALUES
('292', 8779631, 300)
,('255', 8779555, 500);
-- DDL and sample data population, end
SELECT TOP(1) NULL
, (
SELECT *
, (
SELECT * FROM #tbl1Child AS c
WHERE p.ID = c.ParentID
FOR XML PATH('allocation_row'), TYPE
)
FROM #tbl1 AS p
FOR XML PATH('tradeTicket'), TYPE
)
, (
SELECT *
, (
SELECT * FROM #tbl2Child AS c
WHERE p.ID = c.ParentID
FOR XML PATH('allocation_row'), TYPE
)
FROM #tbl2 AS p
FOR XML PATH('tradeTicket'), TYPE
)
FROM #tbl1
FOR XML PATH(''), TYPE, ROOT('tradeTickets');
Output
<tradeTickets>
<tradeTicket>
<ID>8</ID>
<Manager>DOYLE</Manager>
<allocation_row>
<accountNumber>F11</accountNumber>
<ParentID>8</ParentID>
<unitPrice>100.00</unitPrice>
</allocation_row>
<allocation_row>
<accountNumber>F22</accountNumber>
<ParentID>8</ParentID>
<unitPrice>200.00</unitPrice>
</allocation_row>
</tradeTicket>
<tradeTicket>
<ID>9</ID>
<Manager>XYZ</Manager>
<allocation_row>
<accountNumber>F70</accountNumber>
<ParentID>9</ParentID>
<unitPrice>770.00</unitPrice>
</allocation_row>
</tradeTicket>
<tradeTicket>
<ID>8779555</ID>
<Manager>TTT</Manager>
<allocation_row>
<accountNumber>255</accountNumber>
<ParentID>8779555</ParentID>
<unitPrice>500.00</unitPrice>
</allocation_row>
</tradeTicket>
<tradeTicket>
<ID>8779631</ID>
<Manager>MCM</Manager>
<allocation_row>
<accountNumber>292</accountNumber>
<ParentID>8779631</ParentID>
<unitPrice>300.00</unitPrice>
</allocation_row>
</tradeTicket>
</tradeTickets>

How to get records which has more than one entries on another table

An example scenario for my question would be:
How to get all persons who has multiple address types?
Now here's my sample data:
CREATE TABLE #tmp_1 (
ID uniqueidentifier PRIMARY KEY
, FirstName nvarchar(max)
, LastName nvarchar(max)
)
CREATE TABLE #tmp_2 (
SeedID uniqueidentifier PRIMARY KEY
, SomeIrrelevantCol nvarchar(max)
)
CREATE TABLE #tmp_3 (
KeyID uniqueidentifier PRIMARY KEY
, ID uniqueidentifier REFERENCES #tmp_1(ID)
, SeedID uniqueidentifier REFERENCES #tmp_2(SeedID)
, SomeIrrelevantCol nvarchar(max)
)
INSERT INTO #tmp_1
VALUES
('08781F73-A06B-4316-B6A5-802ED58E54BE', 'AAAAAAA', 'aaaaaaa'),
('4EC71FCE-997C-46AA-B119-6C5A2545DDC2', 'BBBBBBB', 'bbbbbbb'),
('B0726ABF-738E-48BC-95CB-091C9D731A0E', 'CCCCCCC', 'ccccccc'),
('6C6CE284-A63C-49D2-B2CC-F25C9CBC8FB8', 'DDDDDDD', 'ddddddd')
INSERT INTO #tmp_2
VALUES
('4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'Value1'),
('4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'Value2'),
('6F6EFED6-8EA0-4F70-A63F-6A103D0A71BD', 'Value3')
INSERT INTO #tmp_3
VALUES
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'sdfsdgdfbgcv'),
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'asdfadsas'),
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'xxxxxeeeeee'),
(NEWID(), '4EC71FCE-997C-46AA-B119-6C5A2545DDC2', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'sdfsdfsd'),
(NEWID(), 'B0726ABF-738E-48BC-95CB-091C9D731A0E', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'zxczxcz'),
(NEWID(), 'B0726ABF-738E-48BC-95CB-091C9D731A0E', '6F6EFED6-8EA0-4F70-A63F-6A103D0A71BD', 'eerwerwe'),
(NEWID(), '6C6CE284-A63C-49D2-B2CC-F25C9CBC8FB8', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'vbcvbcvbcv')
Which gives you:
This is my attempt:
SELECT
t1.*
, Cnt -- not really needed. Just added for visual purposes
FROM #tmp_1 t1
LEFT JOIN (
SELECT
xt.ID
, COUNT(1) Cnt
FROM (
SELECT
#tmp_3.ID
, COUNT(1) as Cnt
FROM #tmp_3
GROUP BY ID, SeedID
) xt
GROUP BY ID
) t2
ON t1.ID = t2.ID
WHERE t2.Cnt > 1
Which gives:
ID FirstName LastName Cnt
B0726ABF-738E-48BC-95CB-091C9D731A0E CCCCCCC ccccccc 2
08781F73-A06B-4316-B6A5-802ED58E54BE AAAAAAA aaaaaaa 2
Although this gives me the correct results, I'm afraid that this query is not the right way to do this performance-wise because of the inner queries. Any input is very much appreciated.
NOTE:
A person can have multiple address of the same address types.
"Person-Address" is not the exact use-case. This is just an example.
The Cnt column is not really needed in the result set.
The way you have named your sample tables and data help little in understanding the problem.
I think you want all IDs which have 2 or more SomeIrrelevantCol values in the last table?
This can be done by:
select * from #tmp_1
where ID in
(
select ID
from #tmp_3
group by ID
having count(distinct SomeIrrelevantCol)>=2
)

When inserting a row, can the `ParentId` be set as the `Id` from a previous inserted row within the same query?

Suppose I have a table called #tblTemp like this:
DECLARE #tblTemp TABLE
(
Id INT NOT NULL IDENTITY,
Name VARCHAR(MAX) NOT NULL,
ParentId INT NULL
)
and my XML structure (assigned to #Xml) was:
<Data>
<MyRow Name="I am the Parent"/>
<MyRow Name="I am the child" ParentName="I am the Parent"/>
</Data>
Question: would it be possible to insert into the ParentId column within the same query?
SQL Script
INSERT INTO #tblTemp ([Name], [ParentId])
SELECT
Rw.value('#Name','VARCHAR(MAX)'), -- Name
(SELECT [Id] -- Select ID From Parent Name
FROM #tblTemp AS [TT]
WHERE [TT].[Name] = Rw.value('#ParentName', 'VARCHAR(MAX)'))
FROM
#Xml.nodes('Data/MyRow') AS Data(Rw)
SELECT *
FROM #tblTemp AS [TT]
The script inserts NULL into the ParentId column as I suspect the previous inserts haven't been committed yet so the table will be empty.
Alternative: if it isn't possible to insert into the ParentId column within the same query, then my alternative would be to do the insert then update the table where required.
Try it like this:
DECLARE #tblTemp TABLE
(
Id INT NOT NULL,
Name VARCHAR(MAX) NOT NULL,
ParentId INT NULL
)
DECLARE #xml XML=
N'<Data>
<MyRow Name="I am the Parent"/>
<MyRow Name="I am the child" ParentName="I am the Parent"/>
<MyRow Name="another child" ParentName="I am the Parent"/>
<MyRow Name="baby" ParentName="I am the child"/>
</Data>';
WITH DerivedTable AS
(
SELECT r.value(N'#Name',N'nvarchar(max)') AS [Name]
,r.value(N'#ParentName',N'nvarchar(max)') AS [ParentName]
,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RowNmbr
FROM #xml.nodes(N'/Data/MyRow') AS A(r)
)
,recCTE AS
(
SELECT 1 AS Lvl
,[Name]
,[ParentName]
,RowNmbr
,CAST(NULL AS BIGINT) AS ParentRowNmbr
,CAST(N'' AS NVARCHAR(MAX)) AS [ParentPath]
FROM DerivedTable
WHERE ParentName IS NULL
UNION ALL
SELECT r.Lvl+1
,t.[Name]
,t.[ParentName]
,t.RowNmbr
,r.RowNmbr
,r.[ParentPath]+t.[ParentName]+N'|'
FROM DerivedTable AS t
INNER JOIN recCTE AS r ON r.[Name]=t.[ParentName]
)
--Use this SELECT to see all columns returned by the recursive CTE
--SELECT * FROM recCTE
INSERT INTO #tblTemp(ID,[Name],ParentId)
SELECT RowNmbr, [Name],ParentRowNmbr
FROM recCTE;
SELECT * FROM #tblTemp;
The result
Id Name ParentId
1 I am the Parent NULL
2 I am the child 1
3 another child 1
4 baby 2
Short explanation:
The first CTE will read the values as derived table and use ROW_NUMBER() to give a running number to each row as ID.
The second CTE is recursively travelling down the road.
The result can be inserted directly into your table.
Attention
I changed your table from ID is IDENTITY to a normal INT column. You can use SELECT MAX(ID) first to get the highest existing ID and add this to ROW_NUMBER() in the first CTE. Otherwise it might happen, that the IDs given by ROW_NUMBER() are not the same as the ID given by IDENTITY.

Can't put CTE result to temporary table

I got a SP where I need to put CTE result to a temporary table so I can use the temporary table later on in SP. Since I am new so I am experiencing difficulty. Please help me out.
CREATE TABLE #TempTable1 (
tmp_id INT NULL,
tmp_parent_id INT NULL,
temp_level VARCHAR(50) NULL,
temp_order VARCHAR(50) NULL,
temp_promoter_ID INT NULL
);
DECLARE #promoterid INT = (
SELECT
p.promoterid
FROM dbo.Promoters p
INNER JOIN dbo.UserProfile u
ON u.UserId = p.UserProfileId
WHERE u.UserName = #Username
);
;WITH Empl_Tab (Id, ParentId, LEVEL, [Order], promoterid) AS (
SELECT
promoters.UserProfileId AS ID,
promoters.Level1 AS ParentID,
0 AS LEVEL,
CONVERT([VARCHAR](MAX), promoters.PromoterId) AS [Order],
promoters.PromoterId
FROM promoters
WHERE Promoters.PromoterId = #promoterid
UNION ALL
SELECT
p.UserProfileId AS ID,
p.Level1 AS ParentID,
Empl_Tab.LEVEL + 1 AS LEVEL,
Empl_Tab.[Order] + CONVERT([VARCHAR](30), p.PromoterId) AS [Order],
p.PromoterId
FROM Promoters p
INNER JOIN Empl_Tab
--inner join dbo.UserProfile u on u.UserId= Promoters.UserProfileId
ON Empl_Tab.promoterid = p.Level1
--where p.Active!=2
)
--select Id, ParentId, LEVEL,[Order],promoterid from Empl_Tab
INSERT INTO #TempTable1 --(tmp_id, tmp_parent_id, temp_level, temp_order, temp_promoter_ID )
SELECT *
FROM Empl_Tab;
Now I like to put the Emp1_Tab result to the temporary table and like to use the temporary table data later on in this same SP.
Procedure TeamCustomersListNew, Line 42 String or binary data would be truncated.
The above error message states that one of the values you're inserting exceeds the max length. The temp_order column of the temp table only allows for 50 characters. You may want to increase it or use VARCHAR(MAX) instead:
CREATE TABLE #TempTable1 (
tmp_id INT NULL,
tmp_parent_id INT NULL,
temp_level INT NULL,
temp_order VARCHAR(MAX) NULL,
temp_promoter_ID INT NULL
);
Additionally, temp_level should be INT.

Selecting rows within group based on field that can have a value or is null

From the table below for each IssID FieldID group, I'd like to select the row that Contains the lowest ChgGrpID value, if there is a row in the group that this field IS NULL, then still select the lowest non null value. If only a row with NULL exists then select that row for the group.
create table #Projects
(ProjectID int, IssID int, PtID int, PTY varchar(10), TypeID int, TypeName varchar(20), FieldID int, FieldName varchar(20), STRINGVALUE varchar(50), NUMBERVALUE int,ChgGrpID int,ChgGrpIssID int,ChgItemID int,ChgItemGrpID int,FIELD varchar(20), NEWVALUE varchar(20), NEWSTRING varchar(20))
insert into #Projects values
(10879,107930,3,'Super',22,'A',10648,'ADH',NULL,666,501040,107930,852895,501040,'ADH',NULL,'666')
,(10879,107930,3,'Super',22,'A',10571,'DLV','No',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107930,3,'Super',22,'A',10541,'CMPLX','Large',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107930,3,'Super',22,'A',10542,'EWF','Orange',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107930,3,'Super',22,'A',10654,'WKFL','UAT',NULL,501034,107930,852889,501034,'WKFL','DVP','CRV')
,(10879,107930,3,'Super',22,'A',10654,'WKFL','UAT',NULL,501037,107930,852892,501037,'WKFL','CRV','UAT')
,(10879,107930,3,'Super',22,'A',10654,'WKFL','UAT',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107971,3,'Super',103,'B',10648,'ADH',NULL,999,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107971,3,'Super',103,'B',10571,'DLV','No',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107971,3,'Super',103,'B',10541,'CMPLX','Large',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107971,3,'Super',103,'B',10542,'EWF','Orange',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107971,3,'Super',103,'B',10654,'WKFL','UAT',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
select * from #Projects
order by IssID, FieldID
Here's the result I'd like to see:
insert into #Projects values
(10879,107930,3,'Super',22,'A',10648,'ADH',NULL,666,501040,107930,852895,501040,'ADH',NULL,'666')
,(10879,107930,3,'Super',22,'A',10571,'DLV','No',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107930,3,'Super',22,'A',10541,'CMPLX','Large',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107930,3,'Super',22,'A',10542,'EWF','Orange',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107930,3,'Super',22,'A',10654,'WKFL','UAT',NULL,501034,107930,852889,501034,'WKFL','DVP','CRV')
,(10879,107971,3,'Super',103,'B',10648,'ADH',NULL,999,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107971,3,'Super',103,'B',10571,'DLV','No',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107971,3,'Super',103,'B',10541,'CMPLX','Large',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107971,3,'Super',103,'B',10542,'EWF','Orange',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
,(10879,107971,3,'Super',103,'B',10654,'WKFL','UAT',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL)
The first trick is that nulls are the last values picked by min - they are larger than any number for min(), and smaller than any number for max(). This means your desired behavior is the default.
The second trick is that you cannot join nulls, so you need to use isnull(). (If it is possible to have an ID of -1, you must pick a different replacement value.)
So we find our minimum values, and the other items in the key, then use this as a subquery to pick the rows we want:
Select p.* from #Projects p
join
(Select isnull(MIN(ChgGrpID),-1) as ChgGrpID, IssID, FieldID
from #Projects
group by IssID, FieldID) X
on isnull(P.ChgGrpID,-1)=X.ChgGrpID and P.IssID=X.IssID and P.FieldID=X.FieldID

Resources