need sql, a tricky one - sql

suppose I have three relations
part(partno,partname,color) partno is P.K
supplier(supplierno,sname) supplierno is P.K
part_supplier(supplierno,partno) supplierno, partno is P.K.
Now i want to get the name of the suppliers who supply parts of only one color.

The having clause is your freind.
select field1, etc, count(*) records
from yourTables
where whatever
group by field1, etc
having count(*) = 1

SELECT s.supplierno
FROM supplier AS s
JOIN part_supplier AS ps
ON s.supplierno = ps.supplierno
JOIN part AS p
ON ps.partno = p.partno
GROUP BY s.supplierno
HAVING COUNT(DISTINCT p.color) = 1 -- only one color for all parts

Select sup.sname,count(*) from part_supplier Map
inner join supplier sup
on map.supplierno=sup.supplierno
inner join part par
on Map.partno=par.partno
group by sup.sname
having count(*)=1

Query:
SELECT S.sname
FROM part P, supplier S, part_supplier PS
WHERE S.supplierno = ps.supplierno AND
PS.partno = P.partno
GROUP BY P.partno
HAVING count(P.color) = 1
Tables :
CREATE TABLE supplier (
supplierno INT PRIMARY KEY,
suppliername VARCHAR(10) NOT NULL
);
CREATE TABLE part (
partno INT PRIMARY KEY,
partname VARCHAR(10) NOT NULL,
color INT NOT NULL
);
CREATE TABLE part_supplier (
supplierno INT NOT NULL REFERENCES supplier(supplierno),
partno INT NOT NULL REFERENCES part(partno),
PRIMARY KEY (supplierno, partno)
);
Some data for testing :
INSERT INTO part VALUES (1, 'a',1), (2, 'b',2), (3, 'c',3),
(4, 'd',1), (5, 'e',2), (6, 'c',3),
(7, 'd',1), (8, 'e',2), (9, 'e',3);
INSERT INTO supplier VALUES (1,'a'), (2,'b'), (3,'c');
INSERT INTO part_supplier VALUES (1,1), (2,1), (3,3),
(4,1), (5,1), (6,1),
(7,2), (8,2), (9,3);

Related

SQL inner join condition by actual date

I need to join actual document to people. Documents has date issued (passport, for example).
SQL Fiddle: http://sqlfiddle.com/#!9/3a8118/2/0
Table structure:
CREATE TABLE people
(
p_id INT NOT NULL AUTO_INCREMENT,
p_name VARCHAR(50) NOT NULL,
PRIMARY KEY(p_id)
);
INSERT INTO people (p_id, p_name)
VALUES (1, 'Name_1'),
(2, 'Name_2');
CREATE TABLE documents
(
d_id INT NOT NULL AUTO_INCREMENT,
d_people INT(10) NOT NULL,
d_date VARCHAR(10) NOT NULL,
PRIMARY KEY(d_id)
);
INSERT INTO documents (d_id, d_people, d_date)
VALUES (1, 1, '01.01.2022'),
(2, 2, '01.12.2021'),
(3, 1, '05.02.2022'),
(4, 1, '10.02.2022'),
(5, 2, '04.01.2022'),
(6, 1, '20.01.2022');
Query: condition is select actual document when date is 21.01.2022, it must return d_id = 6:
SELECT *
FROM people
INNER JOIN documents ON d_people = p_id
WHERE p_id = 1 AND ??? d_date 21.01.2022 ???
;
I need to do an inner join to return only this row:
use this query:
Fiddle
SELECT * FROM people
INNER JOIN documents ON d_people = p_id
WHERE p_id = 1 and d_date='20.01.2022';

How to select rows in a many-to-many relationship? (SQL)

I have a Students table and a Courses table.
They have a many to many relationship between them and the StudentCourses table is the intermediary.
Now, I have a list of Course ids and want to select the Students that follow all Courses in my list.
How??
--CREATE TYPE CourseListType AS TABLE
--(
-- CourseID INT
--)
DECLARE
#CourseList CourseListType
CREATE TABLE #Students
(
ID INT
,Name CHAR(10)
)
CREATE TABLE #Courses
(
ID INT
,Name CHAR(10)
)
CREATE TABLE #StudentCourses
(
StudentID INT
,CourseID INT
)
INSERT INTO #CourseList (CourseID)
VALUES
(1) --English
,(2) --Math
INSERT INTO #Students (ID, Name)
VALUES
(1, 'John')
,(2, 'Jane')
,(3, 'Donald')
INSERT INTO #Courses (ID, Name)
VALUES
(1, 'English')
,(2, 'Math')
,(3, 'Geography')
INSERT INTO #StudentCourses (StudentID, CourseID)
VALUES
(1, 1)
,(1, 2)
,(2, 1)
,(2, 2)
,(3, 1)
,(3, 3)
In this example, I only want the result to be John and Jane, because they both have the two courses in my CourseList.
I dont want Donald, because he only has one of them.
Have tried this JOIN, construction, but it does not eliminate students that only have some of my desired courses.
SELECT
*
FROM
#CourseList CRL
INNER JOIN #Courses CRS ON CRS.ID = CRL.CourseID
INNER JOIN #StudentCourses STC ON STC.CourseID = CRS.ID
INNER JOIN #Students STD ON STD.ID = STC.StudentID
If you want students with all your required courses, you can use aggregation and having:
SELECT sc.StudentId
FROM #StudentCourses sc JOIN
#CourseList cl
ON sc.CourseID = cl.id
GROUP BY sc.StudentId
HAVING COUNT(DISTINCT sc.CourseId) = (SELECT COUNT(*) FROM #DcourseList);
If you want additional information about students, you can join in the Students table (or use a IN or a similar construct).
Note that this only needs the StudentCourses table. It has the matching ids. There is no need to join in the reference tables.

Join with recursive query

Setup
I have the following tables (simplyfied):
CREATE TABLE Category(
CategoryId int NOT NULL PRIMARY KEY,
ParentCategoryId int NULL,
Name nvarchar(255) NOT NULL,
FOREIGN KEY (ParentCategoryId) REFERENCES Category(CategoryId) ON UPDATE NO ACTION ON DELETE NO ACTION);
CREATE TABLE TimeSlot(
TimeSlotId int NOT NULL PRIMARY KEY,
CategoryId int NOT NULL,
FOREIGN KEY (CategoryId) REFERENCES Category(CategoryId) ON UPDATE NO ACTION ON DELETE NO ACTION);
CREATE TABLE PersonTimeSlotAssignment(
PersonId int NOT NULL,
TimeSlotId int NOT NULL,
PRIMARY KEY (PersonId, TimeSlotId),
FOREIGN KEY (TimeSlotId) REFERENCES TimeSlot(TimeSlotId) ON UPDATE NO ACTION ON DELETE NO ACTION);
and here is some test data:
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (100, NULL, 'cat 1');
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (110, 100, 'cat 1.1');
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (111, 110, 'cat 1.1.1');
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (120, 100, 'cat 1.2');
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (200, NULL, 'cat 2');
INSERT INTO TimeSlot(TimeSlotId, CategoryId) VALUES (301, 111);
INSERT INTO TimeSlot(TimeSlotId, CategoryId) VALUES (302, 120);
INSERT INTO TimeSlot(TimeSlotId, CategoryId) VALUES (303, 200);
INSERT INTO PersonTimeSlotAssignment(PersonId, TimeSlotId) VALUES (401, 301);
INSERT INTO PersonTimeSlotAssignment(PersonId, TimeSlotId) VALUES (401, 302);
INSERT INTO PersonTimeSlotAssignment(PersonId, TimeSlotId) VALUES (402, 302);
INSERT INTO PersonTimeSlotAssignment(PersonId, TimeSlotId) VALUES (402, 303);
What I can do
SELECT ts.TimeSlotId, pc.Name
FROM PersonTimeSlotAssignment
JOIN TimeSlot AS ts ON PersonTimeSlotAssignment.TimeSlotId = ts.TimeSlotId
JOIN Category AS pc ON ts.CategoryId = pc.CategoryId
WHERE PersonTimeSlotAssignment.PersonId = #PERSON_ID;
This gives me for some person a list of all TimeSlots to which this person is assigned and the name of the leaf category which the TimeSlot belongs to. For example for person with ID 401 it gives:
TimeSlotId Name
---------------------
301 cat 1.1.1
302 cat 1.2
With the following recursive query I can also get from some category all the ancestors up to the root category:
;WITH Parents AS (
SELECT * FROM Category
WHERE CategoryId=#CATEGORY_ID
UNION ALL SELECT c.* FROM Category c JOIN Parents p ON p.ParentCategoryId=c.CategoryId
)
SELECT Name FROM Parents;
For example for category with ID 111 I get:
Name
---------
cat 1.1.1
cat 1.1
cat 1
What I want to do
What I need is a list of TimeSlots a person is assigned with, joined with the category names for that TimeSlot up to the root category. So for person with ID 401 the result should look like this:
TimeSlotId Name
---------------------
301 cat 1.1.1
301 cat 1.1
301 cat 1
302 cat 1.2
302 cat 1
I was not able to figure out how to combine the above two queries so that I get the expected result.
What I tried
I was hoping that something along these lines could work:
;WITH Parents AS (
SELECT * FROM Category
WHERE CategoryId=<<'How to get CategoryId for each assigned TimeSlot here?'>>
UNION ALL SELECT c.* FROM Category c JOIN Parents p ON p.ParentCategoryId=c.CategoryId
)
SELECT ts.TimeSlotId, pc.Name
FROM PersonTimeSlotAssignment
JOIN TimeSlot AS ts ON PersonTimeSlotAssignment.TimeSlotId = ts.TimeSlotId
JOIN Parents AS pc ON <<'How should this look like?'>>
WHERE PersonTimeSlotAssignment.PersonId = #PERSON_ID;
User defined function and cross apply is very useful in this case.
--1. Create function
create function fn_Category(#id int)
returns table
as
return
with tbl as (
--anckor query
select CategoryId, ParentCategoryId,Name, 1 lvl
from Category where CategoryId = #id
union all
--recursive query
select c.CategoryId, c.ParentCategoryId,c.Name, lvl+1
from Category c
inner join tbl on tbl.ParentCategoryId=c.CategoryId--go up the tree
)
select * from tbl
go
--end of function
--2. and now we can use it
declare #PERSON_ID int = 401
SELECT ts.TimeSlotId, pc.Name
FROM PersonTimeSlotAssignment
JOIN TimeSlot AS ts ON PersonTimeSlotAssignment.TimeSlotId = ts.TimeSlotId
--JOIN Category AS pc ON ts.CategoryId = pc.CategoryId
--use cross apply instead
cross apply fn_Category(ts.CategoryId) pc
WHERE PersonTimeSlotAssignment.PersonId = #PERSON_ID;
This will handle recursing the categories and providing all the data based on a PersonId or TimeSlotId:
WITH Categories (PersonId, CategoryId, ParentCategoryId, TimeSlotId, Name, BASE)
AS
(
SELECT PersonId, c.CategoryId, c.ParentCategoryId, pts.TimeSlotId, c.Name, 0 AS BASE
FROM Category c
INNER JOIN TimeSlot ts ON c.CategoryId = ts.CategoryId
INNER JOIN PersonTimeSlotAssignment pts ON ts.TimeSlotId = pts.TimeSlotId
UNION ALL
SELECT PersonId, pc.CategoryId, pc.ParentCategoryId, TimeSlotId, pc.Name, BASE + 1
FROM Category pc
INNER JOIN Categories cs ON cs.ParentCategoryId = pc.CategoryId
)
SELECT * FROM Categories
WHERE PersonId = 401
--WHERE TimeSlotId = 301
There may be a better way to write this, but does what you've asked and should get you where you need to go. The 'BASE' does not serve its original purpose, but does still show the correlation between your Person and Category, e.g. BASE 0 means the category from that record is assigned directly to the person. So, I left it for that. Thanks.
Hope this helps you :)
DECLARE #PersonId INT= 401;
WITH CTE AS
(
SELECT
t.*,
c.CategoryId AS CategoryId_c,
c.ParentCategoryId as ParentCategoryId_c,
c.Name AS Name_c,
c1.CategoryId AS CategoryId_c2,
c1.ParentCategoryId AS ParentCategoryId_c2,
c1.Name AS Name_c2,
c2.CategoryId as CategoryId_c3,
c2.ParentCategoryId AS ParentCategoryId_c3,
c2.Name AS Name_c3
FROM
PersonTimeSlotAssignment p
INNER JOIN TimeSlot t ON t.TimeSlotId=p.TimeSlotId
INNER JOIN Category c ON t.CategoryId=c.CategoryId
LEFT JOIN Category c1 ON c1.CategoryId=c.ParentCategoryId
LEFT JOIN Category c2 ON c2.CategoryId=c1.ParentCategoryId
WHERE p.PersonId=#PersonId
)
SELECT * FROM (
SELECT TimeSlotId,Name_c FROM CTE
UNION
SELECT TimeSlotId,Name_c2 FROM CTE
UNION
SELECT TimeSlotId,Name_c3 FROM CTE
)a WHERE Name_c IS NOT NULL
DECLARE #PersonId int =401;
IF OBJECT_ID('tempdb.dbo.#TimeSlot') is not null
DROP TABLE #TimeSlot
SELECT DISTINCT DENSE_RANK() OVER(ORDER BY t.TimeSlotId ) ID, t.TimeSlotId,c.CategoryId
INTO #TimeSlot
FROM PersonTimeSlotAssignment p
INNER JOIN TimeSlot t
ON p.TimeSlotId=t.TimeSlotId
INNER JOIN Category c
ON c.CategoryId=t.CategoryId
WHERE PersonId=#PersonId
IF OBJECT_ID('tempdb.dbo.#Output') is not null
DROP TABLE #Output
CREATE TABLE #Output(timeSlotId INT,CategoryId INT)
DECLARE #id INT=1 DECLARE #timeSlotId AS INT DECLARE #CategoryId INT
WHILE( SELECT id FROM #TimeSlot WHERE id=#id) IS NOT NULL
BEGIN
SELECT #timeSlotId=TimeSlotId,#CategoryId=CategoryId FROM #TimeSlot WHERE ID=#id
INSERT INTO #Output SELECT #timeSlotId,#CategoryId
WHILE( SELECT ParentCategoryId FROM Category WHERE CategoryId=#CategoryId) is not null
BEGIN
SELECT #CategoryId=ParentCategoryId FROM Category WHERE CategoryId=#CategoryId
INSERT INTO #Output SELECT #timeSlotId,#CategoryId
END
SET #id=#id+1
END
SELECT a.timeSlotId,c.Name FROM #Output a INNER JOIN Category c ON a.CategoryId=c.CategoryId

SQL Count returns wrong value in my INNER JOIN Statement

I am quite new to SQL and I am having some difficulty with my COUNT() feature.
It keeps returning the wrong COUNT() value as I am trying to calculate the TOTAL number of specific cars sold by users. My tables and results are here:
http://sqlfiddle.com/#!9/d2ef0/5
My Schema:
CREATE TABLE IF NOT EXISTS Users(
userID INT NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
forename VARCHAR(50) NOT NULL,
surname VARCHAR(50) NOT NULL,
PRIMARY KEY (userID)
);
CREATE TABLE IF NOT EXISTS CarType(
carTypeID INT NOT NULL AUTO_INCREMENT,
description VARCHAR(80),
PRIMARY KEY (carTypeID)
);
CREATE TABLE IF NOT EXISTS Country(
countryID INT NOT NULL AUTO_INCREMENT,
name VARCHAR(100),
PRIMARY KEY (countryID)
);
CREATE TABLE IF NOT EXISTS Cars(
carID INT NOT NULL AUTO_INCREMENT,
carTypeID INT NOT NULL,
countryID INT NOT NULL,
description VARCHAR(100) NOT NULL,
make VARCHAR(100) NOT NULL,
model VARCHAR(100),
FOREIGN KEY (carTypeID) REFERENCES CarType(carTypeID),
FOREIGN KEY (countryID) REFERENCES Country(countryID),
PRIMARY KEY (carID)
);
CREATE TABLE IF NOT EXISTS Likes(
userID INT NOT NULL,
carID INT NOT NULL,
likes DOUBLE NOT NULL,
FOREIGN KEY (userID) REFERENCES Users(userID),
FOREIGN KEY (carID) REFERENCES Cars(carID)
);
CREATE TABLE IF NOT EXISTS Sold(
userID INT NOT NULL,
carID INT NOT NULL,
FOREIGN KEY (userID) REFERENCES Users(userID),
FOREIGN KEY (carID) REFERENCES Cars(carID)
);
INSERT INTO Users VALUES
(NULL, "micheal", "Micheal", "Sco"),
(NULL, "bensco", "Ben", "Sco"),
(NULL, "shanemill", "Shane", "Miller");
INSERT INTO CarType VALUES
(NULL, "Saloon"),
(NULL, "HatchBack"),
(NULL, "Low Rider");
INSERT INTO Country VALUES
(NULL, "UK"),
(NULL, "USA"),
(NULL, "JAPAN"),
(NULL, "GERMANY");
INSERT INTO Cars VALUES
(NULL, 1, 2, "Ford Mustang lovers", "Mustang", "Ford"),
(NULL, 2, 3, "Drift Kings", "Skyline", "Nissan"),
(NULL, 3, 1, "British classic", "Cooper", "Mini");
INSERT INTO Likes VALUES
(1, 1, 3),
(1, 2, 2),
(2, 3, 5),
(2, 3, 7),
(2, 3, 1),
(2, 3, 2);
INSERT INTO Sold VALUES
(1, 2),
(1, 3),
(1, 1),
(2, 2),
(2, 3),
(3, 1),
(3, 3);
This is the Sold table:
userID carID
1 2
1 3
1 1
2 2
2 3
3 1
3 3
This is my complex query:
SELECT DISTINCT Cars.carID, Cars.description, Cars.model, Country.name,
CarType.description, ROUND(AVG(Likes.likes)), COUNT(*)
FROM Cars
INNER JOIN Sold ON
Cars.carID = Sold.carID
INNER JOIN Country ON
Cars.countryID = Country.countryID
INNER JOIN CarType ON
Cars.carTypeID = CarType.carTypeID
INNER JOIN Likes ON
Cars.carID = Likes.carID
GROUP BY Cars.carID
The actual result from this complex SQL Query:
carID description model name description ROUND(AVG(Likes.likes)) COUNT(*)
1 Ford Mustang lovers Ford USA Saloon 3 2
2 Drift Kings Nissan JAPAN HatchBack 2 2
3 British classic Mini UK Low Rider 4 12
For example, the result for the last one is incorrect - it should not be 12
Would be nice if someone could tell me where I went wrong
Thanks
You are attempting to aggregate across two different dimensions -- Sold and Likes. The result is a Cartesian product of rows for each car, and this throws off the aggregations.
The solution is the pre-aggregate the results along each dimension:
SELECT c.carID, c.description, c.model, cy.name, ct.description,
l.avgLikes, s.NumSold
FROM Cars c INNER JOIN
(SELECT s.CarId, COUNT(*) as NumSold
FROM Sold s
GROUP BY s.CarId
) s
ON c.carID = s.carID INNER JOIN
Country cy
ON c.countryID = cy.countryID INNER JOIN
CarType ct
ON c.carTypeID = ct.carTypeID LEFT JOIN
(SELECT l.carId, AVG(Likes) as avgLikes
FROM Likes l
GROUP BY CarId
) l
ON c.carID = l.carID;
Here is the SQL Fiddle.
If all you want is the total number of specific cars sold by user then all your info is in the sold table. This query will give you what you want by carID. You can use that as a subquery if you want to join on other tables to get more info.
SELECT userID, carID, count(*) as totalSold FROM Sold GROUP BY userID, carID;

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,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)