Dynamic Operators in SQL - sql-server

Let's say I have the following table tbl_Rules:
RuleID NameOperator NameValues TypeOperator TypeValue
1 NotIn John In 2
1 NotIn Alex In NULL
1 NotIn Mike In NULL
2 In Mike NotIn 2
And my source table looks like this tbl_Source:
ID Name Type Cost
1 Mike 2 100
2 Cole 2 200
3 Ken 1 300
4 Tara 1 400
5 Mike 1 500
6 Sonya 1 600
7 Ann 2 700
8 Mike 1 800
I want to be able to join these two tables and get the following result tbl_Result:
RuleID Name Type Cost
1 Cole 2 200
1 Ann 2 700
2 Mike 1 500
2 Mike 1 800
If I was writing this query manually my query would look like this:
select 1, Name, Type, Cost
from tbl_Source
Where Name not in ('John', 'Alex', 'Mike') and Type in (2)
union all
select 2, Name, Type, Cost
from tbl_Source
where Name in ('Mike') and Type not in (2)
In my current setup tbl_Rule has 500 records and tbl_Source has 500k records.
Any advice on this can be achieved is greatly appreciated. Limitations:
No CLR functions, No 2017 features (e.g. String_agg)
Update: DDL statements for the above sample can be found here: http://sqlfiddle.com/#!18/9a29f/2/0

Here's one way. I have used cross join to check the rules. There may be better ways with dynamic SQL where rules are implemented in join
declare #tbl_Rules table(
RuleID int
, NameOperator varchar(20)
, NameValues varchar(20)
, TypeOperator varchar(20)
, TypeValue int
)
insert into #tbl_Rules
values
(1, 'NotIn', 'John', 'In', 2)
, (1, 'NotIn', 'Alex', 'In', NULL)
, (1, 'NotIn', 'Mike', 'In', NULL)
, (2, 'In', 'Mike', 'NotIn', 2)
declare #tbl_Source table (
ID int
, Name varchar(20)
, Type int
, Cost int
)
insert into #tbl_Source
values
(1, 'Mike', 2, 100)
, (2, 'Cole', 2, 200)
, (3, 'Ken', 1, 300)
, (4, 'Tara', 1, 400)
, (5, 'Mike', 1, 500)
, (6, 'Sonya', 1, 600)
, (7, 'Ann', 2, 700)
, (8, 'Mike', 1, 800)
;with cte as (
select
distinct Ruleid, a.NameOperator, a.TypeOperator
, NameValues = (
select
'!' + b.NameValues
from
#tbl_Rules b
where
a.RuleID = b.RuleID
and b.NameValues is not null
for xml path('')
) + '!'
, TypeValue = (
select
concat('!', b.TypeValue)
from
#tbl_Rules b
where
a.RuleID = b.RuleID
and b.TypeValue is not null
for xml path('')
) + '!'
from
#tbl_Rules a
)
select
b.RuleID, a.Name, a.Type, a.Cost
from
#tbl_Source a
cross join cte b
where
1 = case
when b.NameOperator = 'In' and charindex('!' + a.Name + '!', b.NameValues) > 0 and b.TypeOperator = 'In' and charindex(concat('!', a.Type, '!'), b.TypeValue) > 0 then 1
when b.NameOperator = 'In' and charindex('!' + a.Name + '!', b.NameValues) > 0 and b.TypeOperator = 'Notin' and charindex(concat('!', a.Type, '!'), b.TypeValue) = 0 then 1
when b.NameOperator = 'NotIn' and charindex('!' + a.Name + '!', b.NameValues) = 0 and b.TypeOperator = 'In' and charindex(concat('!', a.Type, '!'), b.TypeValue) > 0 then 1
when b.NameOperator = 'NotIn' and charindex('!' + a.Name + '!', b.NameValues) = 0 and b.TypeOperator = 'NotIn' and charindex(concat('!', a.Type, '!'), b.TypeValue) = 0 then 1
else 0
end
Output:
RuleID Name Type Cost
---------------------------
1 Cole 2 200
1 Ann 2 700
2 Mike 1 500
2 Mike 1 800

You can try this.
SELECT MAX(R_N.RuleID) RuleID, S.Name, S.Type, S.Cost
FROM tbl_Source S
INNER JOIN tbl_Rules R_N ON
(R_N.NameValues <> S.Name and R_N.NameOperator = 'NotIn' )
OR (R_N.NameValues = S.Name and R_N.NameOperator = 'In' )
INNER JOIN tbl_Rules R_S ON
R_S.RuleID = R_N.RuleID AND
(R_S.TypeValue <> S.Type and R_S.TypeOperator = 'NotIn' )
OR (R_S.TypeValue = S.Type and R_S.TypeOperator = 'In' )
GROUP BY
S.Name, S.Type, S.Cost
HAVING
MAX(R_N.NameOperator) = MIN(R_N.NameOperator)
AND MAX(R_S.TypeOperator) = MIN(R_S.TypeOperator)
Result:
RuleID Name Type Cost
----------- -------------------- ----------- -----------
1 Ann 2 700
1 Cole 2 200
2 Mike 1 500
2 Mike 1 800

Related

Create comma separated value strings using data from different tables in SQL Server

I have the following database model:
criteria table:
criteria_id criteria_name is_range
1 product_category 0
2 product_subcategory 0
3 items 1
4 criteria_4 1
evaluation_grid table:
evaluation_grid_id criteria_id start_value end_value provider property_1 property_2 property_3
1 1 3 NULL internal 1 1 1
2 1 1 NULL internal 1 1 1
3 2 1 NULL internal 1 2 1
4 3 1 100 internal 2 1 1
5 4 1 50 internal 2 2 1
6 1 2 NULL external 2 8 1
7 2 2 NULL external 2 5 1
8 3 1 150 external 2 2 2
9 3 1 100 external 2 3 1
product_category table:
id name
1 test1
2 test2
3 test3
product_subcategory table:
id name
1 producttest1
2 producttest2
3 producttest3
What I am trying to achieve is returning the values like this:
criteria start_value end_value provider property_1 property_2 property_3
product_category test3, test1 NULL internal 1 1 1
product_subcategory producttest1 NULL internal 1 2 1
items 1 100 internal 2 1 1
criteria_4 1 50 internal 2 2 1
product_category test2 NULL external 2 8 1
product_subcategory producttest2 NULL external 2 5 1
items 1 150 external 2 2 2
criteria_4 1 100 external 2 3 1
Basically keeping the order from table evaluation_grid but grouping only the criterias which are not ranges
in comma separated value strings based on start_value, end_value, provier, property_1, property_2 and property_3
I tried like this:
SELECT c.criteria_name AS criteria
,CASE WHEN c.criteria_id = 1
THEN
(IsNull(STUFF((SELECT ', ' + RTRIM(LTRIM(pc.name))
FROM product_category pc
INNER JOIN [evaluation_grid] eg ON eg.start_value=pc.id
WHERE srsg.criteria_id=c.criteria_id
FOR XML PATH('')), 1, 2, ''), ''))
WHEN c.criteria_id = 2
THEN (IsNull(STUFF((SELECT ' , ' + RTRIM(LTRIM(psc.name))
FROM product_subcategory psc
INNER JOIN [evaluation_grid] eg ON eg.start_value=psc.id
WHERE srsg.criteria_id=c.criteria_id
FOR XML PATH('')
), 1, 3, ''), ''))
ELSE
CAST(eg.start_value AS VARCHAR)
END AS start_value
,eg.end_value AS end_value
,eg.provider AS provider
,eg.property_1 AS property_1
,eg.property_2 AS property_2
,eg.property_3 AS property_3
FROM [evaluation_grid] eg
INNER JOIN criteria c ON eg.criteria_id = crs.criteria_id
GROUP BY c.criteria_name,c.criteria_id,c.is_range,eg.start_value,eg.end_value,eg.provider,eg.property_1,eg.property_2,eg.property_3
But it is returning wrong data, like this:
criteria start_value end_value provider property_1 property_2 property_3
product_category test3, test1, test2 NULL internal 1 1 1
product_category test3, test1, test2 NULL external 2 8 1
product_category test3, test1, test2 NULL internal 1 1 1
product_subcategory producttest1,producttest2 NULL internal 1 2 1
product_subcategory producttest1,producttest2 NULL external 2 5 1
items 1 100 internal 1 1 1
items 1 150 external 2 2 2
criteria_4 1 50 internal 2 2 1
criteria_4 1 100 external 2 3 1
I tried some versions with "with cte;" as well but didn't manage to find the solution yet and yes, I checked the similar questions already. :)
PS: I cannot use STRING_AGG because we have below 2017 Sql Server version.
Any suggestion will be highly appreciated, thanks !
As far as I can tell this query returns the exact output you're looking for.
with cte as (
select c.criteria_name,
eg.evaluation_grid_id,
case when c.criteria_id = 1 then pc.[name]
when c.criteria_id = 2 then psc.[name]
else null end pc_cat,
c.criteria_id,c.is_range, eg.start_value, eg.end_value,
eg.[provider], eg.property_1, eg.property_2,eg.property_3
from #evaluation_grid eg
join #criteria c ON eg.criteria_id = c.criteria_id
left join #product_category pc on eg.start_value=pc.id
left join #product_subcategory psc on eg.start_value=psc.id)
select c.criteria_name as criteria,
case when c.is_range=0 then
STUFF((SELECT ', ' + RTRIM(LTRIM(c2.pc_cat))
FROM cte c2
WHERE c2.criteria_id=c.criteria_id
and c2.is_range=c.is_range
and c2.[provider]=c.[provider]
and c2.property_1=c.property_1
and c2.property_2=c.property_2
and c2.property_3=c.property_3
FOR XML PATH('')), 1, 2, '')
else max(cast(c.start_value as varchar(50))) end as start_value,
c.end_value, c.[provider], c.property_1, c.property_2, c.property_3
from cte c
group by c.criteria_name, c.criteria_id, c.is_range, c.end_value,
c.[provider], c.property_1, c.property_2, c.property_3
order by max(c.evaluation_grid_id);
Output
criteria start_value end_value provider property_1 property_2 property_3
product_category test3, test1 NULL internal 1 1 1
product_subcategory producttest1 NULL internal 1 2 1
items 1 100 internal 2 1 1
criteria_4 1 50 internal 2 2 1
product_category test2 NULL external 2 8 1
product_subcategory producttest2 NULL external 2 5 1
items 1 150 external 2 2 2
criteria_4 1 100 external 2 3 1
It's a bit difficult to follow the requirements. Can you review the setup & results below and let us know what the desired resultset should be?
declare #criteria table (criteria_id int, criteria_name varchar(50), is_range bit)
insert into #criteria
values(1, 'product_category', 0), (2, 'product_subcategory', 0), (3, 'items', 1), (4, 'criteria_4', 1);
declare #evaluation_grid table (evaluation_grid_id int, criteria_id int, start_value int, end_value int, [provider] varchar(50), property_1 int, property_2 int, property_3 int);
insert into #evaluation_grid
values
(1, 1, 3, NULL, 'internal', 1, 1, 1),
(2, 1, 1, NULL, 'internal', 1, 1, 1),
(3, 2, 1, NULL, 'internal', 1, 2, 1),
(4, 3, 1, 100, 'internal', 2, 1, 1),
(5, 4, 1, 50, 'internal', 2, 2, 1),
(6, 1, 2, NULL, 'external', 2, 8, 1),
(7, 2, 2, NULL, 'external', 2, 5, 1),
(8, 3, 1, 150, 'external', 2, 2, 2),
(9, 4, 1, 100, 'external', 2, 3, 1)
declare #product_category table (id int, [name] varchar(50))
insert into #product_category
values (1, 'test1'), (2, 'test2'), (3, 'test3'), (4, 'test4');
declare #product_subcategory table (id int, [name] varchar(50))
insert into #product_subcategory
values (1, 'producttest1'), (2, 'producttest2'), (3, 'producttest3');
select c.criteria_name,
stuff(( select ',' + ipc.[name]
from #evaluation_grid ieg
join #product_category ipc on ieg.start_value = ipc.id
where [provider] = eg.[provider] and property_1 = eg.property_1 and property_2 = eg.property_2 and property_3 = eg.property_3
order by ieg.evaluation_grid_id
for xml path('')), 1 ,1, '') as start_value,
end_value,
[provider],
property_1,
property_2,
property_3
from #evaluation_grid eg
join #criteria c on eg.criteria_id = c.criteria_id
where c.is_range = 0
group
by c.criteria_name, end_value, [provider], property_1, property_2, property_3
union all
select c.criteria_name, cast(start_value as varchar(10)), end_value, [provider], property_1, property_2, property_3
from #evaluation_grid eg
join #criteria c on eg.criteria_id = c.criteria_id
where c.is_range = 1;

Count(*) automatically rounds

I have a query where I am trying to determine what percentage of events happen on certain days and I'm getting nothing but zeroes back. I think (but am not sure) that something is causing my query to round. This is happening to me in SQL Server but not MySQL.
/* create the event table */
create table event (id int
, dayOf datetime
, description varchar(32)
);
/* add some events */
insert into event( id, dayOf, description ) values
( 1, '2018-01-01', 'Thing 1'),
( 2, '2018-01-01', 'Thing 2'),
( 3, '2018-01-02', 'Thing 3'),
( 4, '2018-01-02', 'Thing 4'),
( 5, '2018-01-03', 'Thing 5');
/* try to get % of events by day, but actually get zeroes */
select event_daily.dayOf, event_daily.cnt, event_total.cnt,
event_daily.cnt / event_total.cnt as pct_daily /* this is the zero */
from ( select dayOf, count(*) as cnt from event group by dayOf ) event_daily
, ( select count(*) as cnt from event ) event_total;
Anticipated result:
DateOf cnt cnt pct_daily
1/1/2018 2 5 0.40
1/2/2018 2 5 0.40
1/3/2018 1 5 0.20
Actual result:
DateOf cnt cnt pct_daily
1/1/2018 2 5 0
1/2/2018 2 5 0
1/3/2018 1 5 0
Any help would be much appreciated!
That is because SQL Server performs integer division, you can convert it into float first with CAST
select event_daily.dayOf, event_daily.cnt, event_total.cnt,
CAST(event_daily.cnt AS float) / CAST(event_total.cnt AS float) as pct_daily
from ( select dayOf, count(*) as cnt from event group by dayOf ) event_daily
, ( select count(*) as cnt from event ) event_total;
Try the below approach
declare #TotalCount DECIMAL(18, 2)
select #TotalCount = count(*) from #event
select
a.dayOf, a.DailyCount, a.TotalCount, CONVERT(DECIMAL(18, 2), (A.DailyCount/A.TotalCount)) AS pct_daily
FROM
(select
dayOf, Count(Id) AS DailyCount, #TotalCount as TotalCount
from
#event
group by
dayOf ) a

SQL Query Conversation

I have three tables:
__Person__ __Hat__ __Shoe__
ID int ID int ID int
Name nvarchar Name nvarchar Name nvarchar
HatID int
ShoeID int
Here's some sample data of the tables:
________________Person_________________
-ID- -Name- -HatID- -ShoeID-
1 Anna 1 2
2 Nina 2 3
3 Lola 3 NULL
______Hat_______
-ID- -Name-
1 Blue
2 Red
3 Green
______Shoe_______
-ID- -Name-
1 Boot
2 Heels
3 Sport
I have a query like this:
SELECT Person.ID, Person.Name, Hat.Name, Shoe.Name
FROM Person
INNER JOIN Hat ON Person.HatID = Hat.ID
JOIN JOIN Shoe ON Person.Shoe = Shoe.ID
This query returns the following results:
-PersonID- -PersonName- -HatName- -ShoeName-
1 Anna Blue Heels
2 Nina Red Sport
3 Lola Green NULL
I want to give PersonName, HatName and ShoeName order numbers 1, 2, 3. I need results like this:
-PersonID- -OrderNumber- -Value-
1 1 Anna
1 2 Blue
1 3 Heels
2 1 Nina
2 2 Red
2 3 Sport
3 1 Lola
3 2 Green
3 3 NULL
How should I write the query to return this results?
You can use UNPIVOT here:
DECLARE #Persons TABLE
(
ID INT ,
Name NVARCHAR(20) ,
HatID INT ,
ShoeID INT
)
DECLARE #Hats TABLE ( ID INT, Name NVARCHAR(20) )
DECLARE #Shoes TABLE ( ID INT, Name NVARCHAR(20) )
INSERT INTO #Persons
VALUES ( 1, 'Anna', 1, 2 ),
( 2, 'Nina', 2, 3 ),
( 3, 'Lola', 3, NULL )
INSERT INTO #Hats
VALUES ( 1, 'Blue' ),
( 2, 'Red' ),
( 3, 'Green' )
INSERT INTO #Shoes
VALUES ( 1, 'Boot' ),
( 2, 'Heels' ),
( 3, 'Sport' );
WITH cte
AS ( SELECT p.ID ,
p.Name AS PersonName ,
h.Name AS HatName ,
ISNULL(s.Name, '') AS ShoeName
FROM #Persons p
INNER JOIN #Hats h ON p.HatID = h.ID
LEFT JOIN #Shoes s ON p.ShoeID = s.ID
)
SELECT ID AS PersonID ,
ROW_NUMBER() OVER ( PARTITION BY ID ORDER BY ( SELECT
1
) ) AS OrderNumber ,
IIF(Value = '', NULL, Value) AS Value
FROM cte UNPIVOT( Value FOR v IN ( [PersonName], [HatName], [ShoeName] ) ) u;
Output:
PersonID OrderNumber Value
1 1 Anna
1 2 Blue
1 3 Heels
2 1 Nina
2 2 Red
2 3 Sport
3 1 Lola
3 2 Green
3 3 NULL
try this:
SELECT Person.ID, 1 OrderNumber, Person.Name
FROM Person
UNION ALL
SELECT Person.ID, 2 OrderNumber, Hat.Name
FROM Person
LEFT JOIN JOIN Hat ON Person.HatID = Hat.ID
UNION ALL
SELECT Person.ID, 3 OrderNumber, Shoe.Name
FROM Person
LEFT JOIN JOIN Shoe ON Person.ShoeID = Shoe.ID
ORDER BY 1,2

Tsql query for periods by buy / sell?

An agent buys products from factory and sells them to normal people.
e.g.
he buy apple ( buy) (1/1/2010)
next day he buys again apple ( buy) (2/1/2010)
later he buys orange ( buy) (2/1/2010)
later he sells apple ( sell) (3/1/2010)
later he sells apple ( sell) (20/1/2010)
<=here the cycle of buy/sell for apple has finished.
sells the orange (22/1/2010)
i need Query result that tells me for each cycle - this :>
item | numberOfItems | timeFromBeginingToVanish
------------------------------------------------------
apple 2 19 days // ( 20/1 - 1/1)
orange 1 20 days //(22/1 - 2/1)
notice :
he can buy another apple and not to sell him - this WONT be in the list.
only when the stack is FIRST TO BE FINISHED
Edit: I have added another filter (HAVING clause) to display only those cycles that are completed .
This solution uses recursive CTE:
1) to compute a running total (RunningTotal) for every item and
2) to generate a group id (PseudoDenseRank) for every item cycle.
CREATE TABLE [Transaction]
(
TransactionId INT IDENTITY(10,10) PRIMARY KEY
,Item VARCHAR(100) NOT NULL
,TransactionType CHAR(1) NOT NULL
,Qty INT NOT NULL
,TransactionDate DATE NOT NULL
,CHECK( TransactionType IN ('B', 'S') ) --Buy, Sell
);
INSERT [Transaction]
SELECT 'apple', 'B', 1, '2010-01-01'
UNION ALL
SELECT 'apple', 'B', 1, '2010-01-02'
UNION ALL
SELECT 'orange','B', 1, '2010-01-03'
UNION ALL
SELECT 'apple', 'S', 1, '2010-01-03'
UNION ALL
SELECT 'apple', 'S', 1, '2010-01-20'
UNION ALL
SELECT 'orange','S', 1, '2010-01-22'
UNION ALL
SELECT 'apple', 'B', 2, '2010-02-01'
UNION ALL
SELECT 'orange','B', 3, '2010-02-02'
UNION ALL
SELECT 'apple', 'S', 1, '2010-02-03'
UNION ALL
SELECT 'apple', 'S', 1, '2010-02-10'
UNION ALL
SELECT 'orange','S', 1, '2010-02-10'
UNION ALL
SELECT 'orange','S', 1, '2010-02-11';
DECLARE #Results TABLE
(
TransactionId INT NOT NULL
,Item VARCHAR(100) NOT NULL
,TransactionType CHAR(1) NOT NULL
,Qty INT NOT NULL
,TransactionDate DATE NOT NULL
,RowNum INT NOT NULL
,PRIMARY KEY (Item, RowNum)
);
INSERT #Results
SELECT *
,ROW_NUMBER() OVER(PARTITION BY t.Item ORDER BY t.TransactionDate ASC, t.TransactionType ASC, t.TransactionId ASC) RowNum
FROM [Transaction] t;
WITH CteRecursive
AS
(
SELECT q.Item
,q.RowNum
,CASE WHEN q.TransactionType = 'B' THEN q.Qty END QtyBuy
,CASE WHEN q.TransactionType = 'S' THEN q.Qty END QtySell
,q.TransactionDate
,CASE WHEN q.TransactionType = 'B' THEN q.Qty WHEN q.TransactionType = 'S' THEN -q.Qty END AS RunningTotal
,1 AS PseudoDenseRank
FROM #Results q
WHERE q.RowNum = 1
UNION ALL
SELECT prev.Item
,crt.RowNum
,CASE WHEN crt.TransactionType = 'B' THEN crt.Qty END QtyBuy
,CASE WHEN crt.TransactionType = 'S' THEN crt.Qty END QtySell
,crt.TransactionDate
,prev.RunningTotal + CASE WHEN crt.TransactionType = 'B' THEN crt.Qty WHEN crt.TransactionType = 'S' THEN -crt.Qty END
,CASE WHEN prev.RunningTotal = 0 THEN prev.PseudoDenseRank + 1 ELSE prev.PseudoDenseRank END
FROM CteRecursive prev
INNER JOIN #Results crt ON prev.Item = crt.Item
AND prev.RowNum + 1 = crt.RowNum
)
SELECT q.Item
,q.PseudoDenseRank AS CycleNumber
,SUM(q.QtyBuy) AS QtyBuyTotal
,SUM(q.QtySell) AS QtySellTotal
,CASE WHEN ISNULL(SUM(q.QtyBuy), 0) - ISNULL(SUM(q.QtySell),0) = 0 THEN 'Complete' ELSE 'Incomplete' END AS CycleStatus
,MIN(q.TransactionDate) AS CycleStartDate
,MAX(q.TransactionDate) AS CycleEndDate
,CONVERT(VARCHAR(25), MIN(q.TransactionDate), 112) + ' - ' + CONVERT(VARCHAR(25), MAX(q.TransactionDate), 112) AS CycleInterval
FROM CteRecursive q
GROUP BY q.Item, q.PseudoDenseRank
HAVING ISNULL(SUM(q.QtyBuy), 0) - ISNULL(SUM(q.QtySell),0) = 0
ORDER BY q.Item, q.PseudoDenseRank;
DROP TABLE [Transaction];
Results:
Item CycleNumber QtyBuyTotal QtySellTotal CycleStatus CycleStartDate CycleEndDate CycleInterval
------ ----------- ----------- ------------ ----------- -------------- ------------ -----------------------
apple 1 2 2 Complete 2010-01-01 2010-01-20 2010-01-01 - 2010-01-20
apple 2 2 2 Complete 2010-02-01 2010-02-10 2010-02-01 - 2010-02-10
orange 1 1 1 Complete 2010-01-03 2010-01-22 2010-01-03 - 2010-01-22
--The next row is eliminated by the filter from HAVING
orange 2 3 2 Incomplete 2010-02-02 2010-02-11 2010-02-02 - 2010-02-11

Create "Sets" within the same table based on multi-column criteria

For every unique combination of BoxId and Revision with a single UnitTypeId of 1 and a single UnitTypeId of 2 both having a NULL SetNumber, assign a SetNumber of 1.
Table and data setup:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[UnitTypes]') AND type in (N'U'))
Drop Table dbo.UnitTypes
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Tracking]') AND type in (N'U'))
DROP TABLE [dbo].[Tracking]
GO
CREATE TABLE dbo.UnitTypes
(
Id int NOT NULL,
Notes varchar(80)
)
GO
CREATE TABLE dbo.Tracking
(
Id int NOT NULL IDENTITY (1, 1),
BoxId int NOT NULL,
Revision int NOT NULL,
UnitValue int NULL,
UnitTypeId int NULL,
SetNumber int NULL
)
GO
ALTER TABLE dbo.Tracking ADD CONSTRAINT
PK_Tracking PRIMARY KEY CLUSTERED
(
Id
)
GO
Insert Into dbo.UnitTypes (Id, Notes) Values (1, 'X Coord'),
(2, 'Y Coord'),
(3, 'Weight'),
(4, 'Length')
Go
Insert Into dbo.Tracking (BoxId, Revision, UnitValue, UnitTypeId, SetNumber)
Values (1165, 1, 150, 1, NULL),
(1165, 1, 1477, 2, NULL),
(1165, 1, 31, 4, NULL),
(1166, 1, 425, 1, 1),
(1166, 1, 1146, 2, 1),
(1166, 1, 438, 1, NULL),
(1166, 1, 1163, 2, NULL),
(1167, 1, 560, 1, NULL),
(1167, 1, 909, 2, NULL),
(1167, 1, 12763, 3, NULL),
(1168, 1, 21, 1, NULL),
(1168, 1, 13109, 3, NULL)
The ideal results would be:
Id BoxId Revision UnitValue UnitTypeId SetNumber
1 1165 1 150 1 1
2 1165 1 1477 2 1
3 1165 1 31 4 1
4 1166 1 425 1 1
5 1166 1 1146 2 1
6 1166 1 438 1 NULL <--NULL Because there is already an existing Set
7 1166 1 1163 2 NULL <--NULL Because there is already an existing Set
8 1167 1 560 1 1
9 1167 1 909 2 1
10 1167 1 12763 3 1
11 1168 1 21 1 NULL <--NULL Because there is not exactly one UnitTypeId of 1 and exactly one UnitTypeId of 2 for this BoxId\Revision combination.
12 1168 1 13109 3 NULL <--NULL Because there is not exactly one UnitTypeId of 1 and exactly one UnitTypeId of 2 for this BoxId\Revision combination.
EDIT:
The question is how can I update the SetNumber, given the constraints above, using pure TSQL?
If I understand your question correctly, you could do this with a subquery that demands all conditions are met:
update t1
set SetNumber = 1
from dbo.Tracking t1
where SetNumber is null
and 1 =
(
select case
when count(case when t2.UnitTypeId = 1 then 1 end) <> 1 then 0
when count(case when t2.UnitTypeId = 2 then 1 end) <> 1 then 0
when count(t2.SetNumber) <> 0 then 0
else 1
end
from dbo.Tracking t2
where t1.BoxId = t2.BoxId
and t1.Revision = t2.Revision
)
The count(t2.SetNumber) is a bit tricky: this will only count rows where SetNumber is not null. So this meets the criterion that no other set with the same (BoxId, Revision) exists.
Try this out, it returns the same results that you gave. The WITH statement sets up a CTE to query from. The ROW_NUMBER() function is partitioning function that does what you want:
;WITH BoxSets AS (
SELECT
ID
,BoxId
,Revision
,UnitValue
,UnitTypeId
,CASE WHEN UnitTypeId IN (1,2) THEN 1 ELSE 0 END ValidUnit
,ROW_NUMBER() OVER (PARTITION BY BoxID,UnitTypeID ORDER BY BoxID,UnitTypeID,UnitValue ) SetNumber
FROM Tracking
)
SELECT
b.ID
,b.BoxId
,b.Revision
,b.UnitValue
,b.UnitTypeId
,CASE ISNULL(b1.ValidUnits,0) WHEN 0 THEN NULL ELSE CASE b.SetNumber WHEN 1 THEN b.SetNumber ELSE NULL END END
FROM BoxSets AS b
LEFT JOIN (SELECT
BoxID
,SUM(ValidUnit) AS ValidUnits
FROM BoxSets
GROUP BY BoxId
HAVING SUM(ValidUnit) > 1) AS b1 ON b.BoxId = b1.BoxId

Resources