开发者

SQL Server field calculation based on multiple condition

Here is my scenario:

I have a Person table with following fields.

create table Person(PersonID int primary key identity(1,1),
                     Age int,
                     height decimal(4,2),
                     weight decimal(6,2)
                    );

 insert into Person(Age,height,weight) values (60,6.2,169); -- 1
 insert into Person(Age,height,weight) values (15,5.1,100); -- 2
 insert into Person(Age,height,weight) values (10,4.5,50); -- 3

What I need to do is,

if the person Age >= 18 and height >= 6 then calculationValue = 20
if the person Age >= 18 and height < 6 then calculationValue = 15
if the person Age < 18 and weight >= 60 then calculationValue = 10
if the person Age < 18 and weight < 60 then calculationValue = 5

based on these condition I need to find the calculationValue and do some math.

I tried to make a flexible model so in future it would be easier to add any more conditions and can easily change the constant values (like 18, 6, 60 etc)

I created couple of tables as below:

create table condTable(condTableID int primary key identity(1,1),
                        condCol varchar(20),
                        startValue int,
                        endValue int
                      );

  insert into condTable(condCol,startValue,endValue) values ('Age',18,999) -- 1
  insert into condTable(condCol,startValue,endValue) values ('Height',6,99) -- 2
  insert into condTable(condCol,startValue,endValue) values ('Height',0,5.99) -- 3
  insert into condTable(condCol,startValue,endValue) values ('Age',0,17) -- 4
  insert into condTable(condCol,startValue,endValue) values ('Weight',60,999) -- 5
  insert into condTable(condCol,startValue,endValue) values ('Weight',0,59) -- 6

I join two condition to make it one in the following table as given by the requirement.(ie. if age >=18 and height >=6 then calculationValue = 20. etc)

create table CondJoin(CondJoin int,condTableID int,CalculationValue int)


insert into CondJoin values (1,1,20)
insert into CondJoin values (1,2,20)
insert into CondJoin values (2,1,15)
insert into CondJoin values (2,3,15)
insert into CondJoin values (3,4,10)
insert into CondJoin values (3,5,10)
insert into CondJoin values (4,4开发者_如何学Go,5)
insert into CondJoin values (4,6,5)

I think this model will provide the flexibility of adding more conditions in future. But I am having difficulties on implementing it in SQL Server 2005. Anyone can write a sql that process in set basis and compare the value in Person table with CondJoin table and provide the corresponding calculationvalue. For eg. for person ID 1 it should look at CondJoin table and give the calculationValue 20 since his age is greater than 18 and height is greater than 6.


this looks like you are headed towards dynamic sql generation.

i think maybe you would be better off with a row for each column and cutoff values for the ranges, and a value if true ... maybe something like:

age_condition
-----------------
min_age
max_age
value

this is something that you could populate and then query without some dynamic generation.


The following is extremely rough but it should get the point across. It normalizes the data and moves towards a semi-object oriented (attribute/value/attribute value) structure. I'll leave it up to you to reinforce referential integrity, but the following is flexible and will return the results you want:

CREATE TABLE Person (
    PersonID INT PRIMARY KEY IDENTITY(1,1)
    ,Name NVARCHAR(255)
    );

GO

CREATE TABLE PersonAttribute (
    PersonID INT
    ,CondAttributeID INT
    ,Value NVARCHAR(255)
    );

GO

CREATE TABLE CondAttribute (
AttributeID INT PRIMARY KEY IDENTITY(1,1)
,Attribute NVARCHAR(255));

GO

CREATE TABLE CondTable (
    CondTableID INT PRIMARY KEY IDENTITY(1,1)
    ,CondAttributeID INT
    ,StartValue MONEY
    ,EndValue MONEY
    );

GO

CREATE TABLE CalculationValues (
    CalculationID INT PRIMARY KEY IDENTITY(1,1)
    ,CalculationValue INT
    );

GO

CREATE TABLE CondCalculation (
    CondTableID INT
    ,CalculationID INT
    );


INSERT Person (Name)

VALUES ('Joe')
        ,('Bob')
        ,('Tom');


INSERT PersonAttribute (
    PersonID
    ,CondAttributeID
    ,Value
)

VALUES  (1, 1, '60')
        ,(1, 2, '6.2')
        ,(1, 3, '169')
        ,(2, 1, '15')
        ,(2, 2, '5.1')
        ,(2, 3, '100')
        ,(3, 1, '10')
        ,(3, 2, '4.5')
        ,(3, 3, '50');          

INSERT CondAttribute (Attribute)

VALUES ('Age')
        ,('height')
        ,('weight');

INSERT CondTable (
    CondAttributeID
    ,StartValue
    ,EndValue)

VALUES (1,18,999) --Age
        ,(2,6,99) --Height 
        ,(2,0,5.99) -- Height
        ,(1,0,17) -- Age
        ,(3,60,999) -- Weight
        ,(3,0,59); -- Weight



INSERT CalculationValues (CalculationValue)
VALUES (5)
        ,(10)
        ,(15)
        ,(20);  


INSERT CondCalculation (CondTableID, CalculationID)
VALUES (1,4)
       ,(2,4)
       ,(1,3)
       ,(3,3)
       ,(4,2)
       ,(5,2)
       ,(5,1)
       ,(6,1);

SELECT *
FROM Person AS p
JOIN PersonAttribute AS pa ON p.PersonID = pa.PersonID
JOIN CondAttribute AS ca ON pa.CondAttributeID = ca.AttributeID
JOIN CondTable AS ct ON ca.AttributeID = ct.CondAttributeID
    AND CONVERT(money,pa.Value) BETWEEN ct.StartValue AND ct.EndValue
JOIN CondCalculation AS cc ON cc.CondTableID = ct.CondTableID
JOIN CalculationValues AS c ON cc.CalculationID = c.CalculationID
WHERE p.PersonID = 1


The following solution uses PIVOT (twice) to transform the combination of CondJoin and condTable into a chart, then joins the chart to the Person table to calculate the target value. I believe, a series of CASE expressions could be used instead just as well. Anyway...

All the tables have been turned into table variables, for easier testing. So first, DDL and data preparation:

declare @Person table(PersonID int primary key identity(1,1),
                     Age int,
                     height decimal(4,2),
                     weight decimal(6,2)
                    );
insert into @Person(Age,height,weight) values (60,6.2,169); -- 1
insert into @Person(Age,height,weight) values (15,5.1,100); -- 2
insert into @Person(Age,height,weight) values (10,4.5,50); -- 3

declare @condTable table(condTableID int primary key identity(1,1),
                        condCol varchar(20),
                        startValue int,
                        endValue int
                      );
insert into @condTable(condCol,startValue,endValue) values ('Age',18,999) -- 1
insert into @condTable(condCol,startValue,endValue) values ('Height',6,99) -- 2
insert into @condTable(condCol,startValue,endValue) values ('Height',0,5.99) -- 3
insert into @condTable(condCol,startValue,endValue) values ('Age',0,17) -- 4
insert into @condTable(condCol,startValue,endValue) values ('Weight',60,999) -- 5
insert into @condTable(condCol,startValue,endValue) values ('Weight',0,59) -- 6
declare @CondJoin table(CondJoin int,condTableID int,CalculationValue int);
insert into @CondJoin values (1,1,20)
insert into @CondJoin values (1,2,20)
insert into @CondJoin values (2,1,15)
insert into @CondJoin values (2,3,15)
insert into @CondJoin values (3,4,10)
insert into @CondJoin values (3,5,10)
insert into @CondJoin values (4,4,5)
insert into @CondJoin values (4,6,5)

And now the query:

;with startValues as (
  select
    CondJoin,
    Age,
    Height,
    Weight,
    CalculationValue
  from (
    select
      j.CondJoin,
      j.CalculationValue,
      t.condCol,
      t.startValue
    from @CondJoin j
      inner join @condTable t on j.condTableID = t.condTableID
  ) j
  pivot (
    max(startValue) for condCol in (Age, Height, Weight)
  ) p
),
endValues as (
  select
    CondJoin,
    Age,
    Height,
    Weight,
    CalculationValue
  from (
    select
      j.CondJoin,
      j.CalculationValue,
      t.condCol,
      t.endValue
    from @CondJoin j
      inner join @condTable t on j.condTableID = t.condTableID
  ) j
  pivot (
    max(endValue) for condCol in (Age, Height, Weight)
  ) p
),
combinedChart as (
select
  s.CondJoin,
  AgeFrom    = s.Age,
  AgeTo      = e.Age,
  HeightFrom = s.Height,
  HeightTo   = e.Height,
  WeightFrom = s.Weight,
  WeightTo   = e.Weight,
  s.CalculationValue
from startValues s
  inner join endValues e on s.CondJoin = e.CondJoin
)
select
  p.*,
  c.CalculationValue
from @Person p
  left join combinedChart c
    on (c.AgeFrom    is null or p.Age    between c.AgeFrom    and c.AgeTo)
   and (c.HeightFrom is null or p.Height between c.HeightFrom and c.HeightTo)
   and (c.WeightFrom is null or p.Weight between c.WeightFrom and c.WeightTo)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜