SQL JOIN with TOP ID from WHERE clause - sql-server

I have the following tables:
-- t_products
prod_id ... name ... price
--------------------------
1 Prod1 23.2
2 Prod2 11.5
3 Prod3 10.0
4 Prod4 4.43
-- t_products_lists
list_id ... prod_id ... date_created
1 1 2016-02-01
2 1 2015-31-12
3 3 2015-01-01
4 3 2015-12-01
5 4 2014-02-05
6 4 2012-24-06
7 2 2016-11-10
What I need is to get from t_products_lists all the rows that have date_created < 2016-01-01 (2,3,4,5,6).
Now, from those results, I only need the prod_id with the latest date_created, for all groups of duplicated prod_id. It will be required to get name and price from t_products and return along with date_created.
The result should be:
prod_id ... name ... price ... date_created
-------------------------------------------
1 Prod1 23.2 2015-31-12 (the only one before 2016-01-01)
3 Prod3 10.0 2015-12-01 (the latest date)
4 Prod4 4.43 2014-02-05 (the latest date)
I am trying to join the tables like this so far:
SELECT COUNT(prod.prod_id), prod.name, prod.price, prodlist.date_created
FROM t_products_lists prodlist
INNER JOIN t_products prod ON prodlist.prod_id = prod.prod_id
WHERE prodlist.date_created < CONVERT(DATETIME,'01.01.2016 23:59:59.997',0)
GROUP BY prod.prod_id, art.name, art.price, prodlist.date_created
but now I don't know how could I only get the prod_id with the latest date, because with this query it just returns all IDs before 01.01.2016

USE MAX( prodlist.date_created)
SELECT prod.prod_id, prod.name, prod.price, MAX( prodlist.date_created)
FROM t_products_lists prodlist
INNER JOIN t_products prod ON prodlist.prod_id = prod.prod_id
WHERE prodlist.date_created < CONVERT(DATETIME,'01.01.2016 23:59:59.997',0)
GROUP BY prod.prod_id, prod.name, prod.price

You will require something similar to the below. Instead of grouping by the date created, put a MAX around it in your select clause.
SELECT COUNT(prod.prod_id), prod.name, prod.price, MAX(prodlist.date_created )
FROM t_products_lists prodlist
INNER JOIN t_products prod
ON prodlist.prod_id = prod.prod_id
WHERE prodlist.date_created < CONVERT(DATETIME,'01.01.2016 23:59:59.997',0)
GROUP BY prod.prod_id, art.name, art.price
You can read a bit more about MAX at http://www.w3schools.com/sql/sql_func_max.asp

Related

Sql Server - Get SUM() of values for only Active Users

I have a requirement where i need to get Total Active Employees and Total Sales by RegionId
My query result should be like below.
RegionId | TotalEmployees | TotalSales | Average
1 10 100 10
2 3 15 5
My front end application will pass all the RegionIds as a single string separated by a comma, my query parameter is of type VARCHAR() and the Input paramter will look like '1,2,3,4,7,14,26' and there can be upto 20 Region Ids in a single string separated by a comma.
SELECT E.[RegionId] as RegionId
,COUNT(E.[EmployeeId) AS TotalEmployees
,(SELECT SUM([Sale])
FROM dbo.[Sales]
WHERE RegionId = R.[RegionId]
) AS TotalSales
,TotalSales/TotalEmployees AS Average
FROM dbo.[Employee]
JOIN [dbo].[ufn_StringSplit](#RegionIdCollection, ',') RegionId
ON E.RegionId = CAST(RegionId.[Data] AS Varchar(5000))
WHERE E.[Active] = 1
GROUP BY E.[RegionId]
My Employee table structures look alike below
EmployeeId | Name | RegionId | Active
100 Tom 2 1
101 Jim 4 0
103 Ben 2 1
Sales Table
SaleId | EmployeeId| RegionId | Sale
1 100 2 3500
2 101 4 2000
3 100 2 1500
Now my issue is when i am getting TotalSales the below query gets all the sales by RegionId, but i need to get All the sales done by only current Active employees in the Employee table
(SELECT SUM([Sale])
FROM dbo.[Sales]
WHERE RegionId = R.[RegionId]
) AS TotalSales
There is no reason to use a sub-select to find the sum of sales here, that will result in running that query for each and every row. You want to aproach this in a set based way which means you need to join and group appropriately:
with s as
(
select e.RegionId
,e.EmployeeId
,sum(s.Sale) as EmployeeSales
from dbo.ufn_StringSplit(#RegionIdCollection, ',') as r
join dbo.Employee as e
on r.RegionId = CAST(r.[Data] AS varchar(20)) -- Do you really need 5000 characters here?
left join dbo.Sales as s
on r.RegionId = s.RegionId
and e.EmployeeId = s.EmployeeId
where e.Active = 1
group by e.RegionId
,e.EmployeeId
)
select s.RegionId
,count(s.EmployeeId) as TotalEmployees
,sum(s.EmployeeSales) as TotalSales
,sum(s.EmployeeSales)/count(s.EmployeeId) as Average
from s
group by s.RegionId

SQL Server - select column using in having count()

This is my first question (and sorry for my English)
I have this table in SQL Server:
id_patient | date | id_drug
----------------------------------------------------
1 20200101 A
1 20200102 A
1 20200103 A
1 20200104 A
1 20200105 A
1 20200110 A
2 20200101 A
2 20200105 B
2 20200106 C
2 20200107 D
2 20200108 E
2 20200110 L
3 20200101 A
3 20200102 A
3 20200103 A
3 20200104 A
3 20200105 C
3 20200106 C
4 20200105 A
4 20200106 D
4 20200107 D
5 20200105 A
5 20200106 A
5 20200107 C
5 20200108 D
I would like to extract patient and drug for all patients who have taken at least 3 different drugs in a given period
I have tried:
select id_patient, count(distinct ID_drug)
from table
where date between XXX and YYY
group by id_patient
having count(Distinct ID_drug) > 3
but in this way -YES- I get all patients with 3 or more different id_drug in this date range but I can't get the ID_drug because in the count()
For example, I'd like to obtain:
Who help me ?
Thanks
You can use string_agg() in the most recent versions of SQL Server:
select id_patient, count(distinct ID_drug),
string_agg(id_drug, ',')
from table
where date between XXX and YYY
group by id_patient
having count(Distinct ID_drug) > 3;
If you want the original rows, you can use window functions. Unfortunately, SQL Server does not support count(distinct) as a window function, but there is an easy work-around using dense_rank():
select t.*
from (select t.*,
(dense_rank() over (partition by id_patient order by id_drug) +
dense_rank() over (partition by id_patient order by id_drug desc)
) as num_drugs
from t
where . . .
) t
where num_drugs >= 3;
SELECT id_patient,
ID_drug
FROM table
WHERE id_patient IN (
SELECT id_patient
FROM table
WHERE date
BETWEEN XXX
AND YYY
GROUP BY id_patient
HAVING COUNT(DISTINCT ID_drug) >= 3
)
GROUP BY id_patient,
ID_drug;

SQL Server - Group by day for the top N of the range

What I need to do is get a Cost breakout for each grouping, aggregated by day. Also, only taking the top N per the whole date range. I'm probably not explaining this well so let me give examples. Say my table schema and data looks like this:
SoldDate Product State Cost
----------------------- --------------------- --------- ------
2017-07-11 01:00:00.000 Apple NY 6
2017-07-11 07:00:00.000 Banana NY 1
2017-07-11 07:00:00.000 Banana NY 1
2017-07-12 01:00:00.000 Pear NY 2
2017-07-12 03:00:00.000 Olive TX 1
2017-07-12 16:00:00.000 Banana NY 1
2017-07-13 22:00:00.000 Apple NY 6
2017-07-13 22:00:00.000 Apple NY 6
2017-07-13 23:00:00.000 Banana NY 1
Call this table SoldProduce.
Now what I'm looking for is to group by Day, Product and State but for each day, only take the top two of the group NOT the top of that particular day. Anything else gets lumped under 'other'.
So in this case, our top two groups with the greatest Cost are Apple-NY and Banana-NY. So those are the two that should show up in the output only. Anything else is under 'Other'
So in the end this is the desired output:
SoldDay Product State Total Cost
----------------------- --------------------- --------- ------
2017-07-11 00:00:00.000 Apple NY 6
2017-07-11 00:00:00.000 Banana NY 2
2017-07-11 00:00:00.000 OTHER OTHER 0
2017-07-12 00:00:00.000 OTHER OTHER 3
2017-07-12 00:00:00.000 Banana NY 1
2017-07-13 00:00:00.000 Apple NY 12
2017-07-13 00:00:00.000 Banana NY 1
2017-07-13 00:00:00.000 OTHER OTHER 0
Note how on the 12th Pear and Olive were lumped under other. Even though it outsold Banana on that day. This is because I want the Top N selling groups for the whole range, not just on a day by day basis.
I did a lot of googleing a way to make a query to get this data but I'm not sure if it's the best way:
WITH TopX AS
(
SELECT
b.Product,
b.State,
b.SoldDate,
b.Cost,
DENSE_RANK() OVER (ORDER BY GroupedCost DESC) as [Rank]
FROM
(
SELECT
b.Product,
b.State,
b.SoldDate,
b.Cost,
SUM(b.Cost) OVER (PARTITION BY b.Product, b.State) as GroupedCost
FROM
SoldProduce b WITH (NOLOCK)
) as b
)
SELECT
DATEADD(d,DATEDIFF(d,0,SoldDate),0),
b.Product,
b.State,
SUM(b.Cost)
FROM
TopX b
WHERE
[Rank] <= 2
GROUP BY
DATEADD(d,DATEDIFF(d,0,SoldDate),0),
b.Product,
b.State
UNION ALL
SELECT
DATEADD(d,DATEDIFF(d,0,SoldDate),0),
null,
null,
SUM(b.Cost)
from
TopX b
WHERE
[Rank] > 2
GROUP BY
DATEADD(d,DATEDIFF(d,0,SoldDate),0)
Step 1) Create a common query that first projects the cost that the row would be has we just grouped by Product and State. Then it does a second projection to rank that cost 1-N where 1 has the greatest grouped cost.
Step 2) Call upon the common query, grouping by day and restricting to rows <= 2. This is the Top elements. Then union the other category to this, or anything ranked > 2.
What do you guys think? Is this an efficient solution? Could I do this better?
Edit:
FuzzyTrees suggestion benchmarks better than mine.
Final query used:
WITH TopX AS
(
SELECT
TOP(2)
b.Product,
b.State
FROM
SoldProduce b
GROUP BY
b.Product,
b.State
ORDER BY
SUM(b.Cost)
)
SELECT
DATEADD(d,DATEDIFF(d,0,SoldDate),0),
coalesce(b.Product, 'Other') Product,
coalesce(b.State, 'Other') State,
SUM(b.Cost)
FROM
SoldProduce a
LEFT JOIN TopX b ON
(a.Product = b.Product OR (a.Product IS NULL AND b.Product IS NULL)) AND
(a.State = b.State OR (a.State IS NULL AND b.State IS NULL))
GROUP BY
DATEADD(d,DATEDIFF(d,0,SoldDate),0),
coalesce(b.Product, 'Other') Product,
coalesce(b.State, 'Other') State,
ORDER BY DATEADD(d,DATEDIFF(d,0,SoldDate),0)
-- Order by optional. Just for display purposes.
--More effienct to order in code for the final product.
--Don't use I/O if you don't have to :)
I suggest using a plain group by without window functions for your TopX view:
With TopX AS
(
select top 2 Product, State
from SoldProduce
group by Product, State
order by sum(cost) desc
)
Then you can left join to your TopX view and use coalesce to determine which products fall into the Other group
select
coalesce(TopX.Product, 'Other') Product,
coalesce(TopX.State, 'Other') State,
sum(Cost),
sp.SoldDate
from SoldProduce sp
left join TopX on TopX.Product = sp.Product
and TopX.State = sp.State
group by
coalesce(TopX.Product, 'Other'),
coalesce(TopX.State, 'Other'),
SoldDate
order by SoldDate
Note: This query will not return 0 counts

How can I avoid sum multiple times while using join

I am now using the mssql with its sample database "adventureworks 2014", here I faced some problems with join and sum, here is the two table I used:
PurchaseOrderHeader:
PurchaseOrderID VendorID OrderDate TotalDue
1 1580 2011-04-16 00:00:00.000 222.1492
2 1496 2011-04-16 00:00:00.000 300.6721
3 1494 2011-04-16 00:00:00.000 9776.2665
4 1650 2011-04-16 00:00:00.000 189.0395
5 1654 2011-04-30 00:00:00.000 22539.0165
6 1664 2011-04-30 00:00:00.000 16164.0229
7 1678 2011-04-30 00:00:00.000 64847.5328
PurchaseOrderDetail:
PurchaseOrderID PurchaseOrderDetailID OrderQty ProductID
1 1 4 1
2 2 3 359
2 3 3 360
3 4 550 530
4 5 3 4
5 6 550 512
6 7 550 513
7 8 550 317
7 9 550 318
7 10 550 319
Here is the sql script:
CREATE TABLE PurchaseOrderHeader(
PurchaseOrderID INTEGER NOT NULL PRIMARY KEY
,VendorID INTEGER NOT NULL
,OrderDate VARCHAR(23) NOT NULL
,TotalDue NUMERIC(10,4) NOT NULL
);
INSERT INTO PurchaseOrderHeader(PurchaseOrderID,VendorID,OrderDate,TotalDue) VALUES (1,1580,'2011-04-16 00:00:00.000',222.1492);
INSERT INTO PurchaseOrderHeader(PurchaseOrderID,VendorID,OrderDate,TotalDue) VALUES (2,1496,'2011-04-16 00:00:00.000',300.6721);
INSERT INTO PurchaseOrderHeader(PurchaseOrderID,VendorID,OrderDate,TotalDue) VALUES (3,1494,'2011-04-16 00:00:00.000',9776.2665);
INSERT INTO PurchaseOrderHeader(PurchaseOrderID,VendorID,OrderDate,TotalDue) VALUES (4,1650,'2011-04-16 00:00:00.000',189.0395);
INSERT INTO PurchaseOrderHeader(PurchaseOrderID,VendorID,OrderDate,TotalDue) VALUES (5,1654,'2011-04-30 00:00:00.000',22539.0165);
INSERT INTO PurchaseOrderHeader(PurchaseOrderID,VendorID,OrderDate,TotalDue) VALUES (6,1664,'2011-04-30 00:00:00.000',16164.0229);
INSERT INTO PurchaseOrderHeader(PurchaseOrderID,VendorID,OrderDate,TotalDue) VALUES (7,1678,'2011-04-30 00:00:00.000',64847.5328);
CREATE TABLE PurchaseOrderDetail(
PurchaseOrderID INTEGER NOT NULL
,PurchaseOrderDetailID INTEGER NOT NULL PRIMARY KEY
,OrderQty INTEGER NOT NULL
,ProductID INTEGER NOT NULL
);
INSERT INTO PurchaseOrderDetail(PurchaseOrderID,PurchaseOrderDetailID,OrderQty,ProductID) VALUES (1,1,4,1);
INSERT INTO PurchaseOrderDetail(PurchaseOrderID,PurchaseOrderDetailID,OrderQty,ProductID) VALUES (2,2,3,359);
INSERT INTO PurchaseOrderDetail(PurchaseOrderID,PurchaseOrderDetailID,OrderQty,ProductID) VALUES (2,3,3,360);
INSERT INTO PurchaseOrderDetail(PurchaseOrderID,PurchaseOrderDetailID,OrderQty,ProductID) VALUES (3,4,550,530);
INSERT INTO PurchaseOrderDetail(PurchaseOrderID,PurchaseOrderDetailID,OrderQty,ProductID) VALUES (4,5,3,4);
INSERT INTO PurchaseOrderDetail(PurchaseOrderID,PurchaseOrderDetailID,OrderQty,ProductID) VALUES (5,6,550,512);
INSERT INTO PurchaseOrderDetail(PurchaseOrderID,PurchaseOrderDetailID,OrderQty,ProductID) VALUES (6,7,550,513);
INSERT INTO PurchaseOrderDetail(PurchaseOrderID,PurchaseOrderDetailID,OrderQty,ProductID) VALUES (7,8,550,317);
INSERT INTO PurchaseOrderDetail(PurchaseOrderID,PurchaseOrderDetailID,OrderQty,ProductID) VALUES (7,9,550,318);
INSERT INTO PurchaseOrderDetail(PurchaseOrderID,PurchaseOrderDetailID,OrderQty,ProductID) VALUES (7,10,550,319);
and here is my code:
select PurchaseOrderHeader.VendorID,
SUM(CASE WHEN Datename(year,PurchaseOrderHeader.OrderDate) = 2011 THEN PurchaseOrderHeader.TotalDue else 0 END) as "TotalPay IN 2011",
SUM(CASE WHEN Datename(year,PurchaseOrderHeader.OrderDate) = 2011 THEN PurchaseOrderDetail.OrderQty else 0 END) as "TotalOrder IN 2011"
from PurchaseOrderHeader
left join PurchaseOrderDetail on PurchaseOrderHeader.PurchaseOrderID = PurchaseOrderDetail.PurchaseOrderID
group by PurchaseOrderHeader.VendorID
order by VendorID
Here is what I got:
VendorID TotalPay IN 2011 TotalOrder IN 2011
1494 9776.2665 550
1496 601.3442 6
1580 222.1492 4
1650 189.0395 3
1654 22539.0165 550
1664 16164.0229 550
1678 194542.5984 1650
while I should expect:
VendorID TotalPay IN 2011 TotalOrder IN 2011
1494 9776.2665 550
1496 300.6721 6
1580 222.1492 4
1650 189.0395 3
1654 22539.0165 550
1664 16164.0229 550
1678 64847.5328 1650
This code will join two tables on PurchaseOrderID, and calculate the TotalDue grouped by vendorID. The problem is when I use join, where will be multiple rows from table PurchaseOrderDetail refered to one row in table PurchaseOrderHeader. In this example for vendor 1496 and 1678 there are two or three rows refer to one row in PurchaseDetailHeader. So it will be added two or three times. How should I avoid adding multiple times, thanks!
You can just take your SUM and divide by COUNT. Something like this.
select PurchaseOrderHeader.VendorID,
SUM(CASE WHEN Datename(year,PurchaseOrderHeader.OrderDate) = 2011 THEN PurchaseOrderHeader.TotalDue else 0 END) / COUNT(*) as "TotalPay IN 2011",
SUM(CASE WHEN Datename(year,PurchaseOrderHeader.OrderDate) = 2011 THEN PurchaseOrderDetail.OrderQty else 0 END) / COUNT(*) as "TotalOrder IN 2011"
from Purchasing.PurchaseOrderHeader
left join Purchasing.PurchaseOrderDetail on PurchaseOrderHeader.PurchaseOrderID = PurchaseOrderDetail.PurchaseOrderID
group by PurchaseOrderHeader.VendorID
order by VendorID
select h.VendorID,
SUM(CASE WHEN Datename(year,h.OrderDate) = 2011 THEN h.TotalDue else 0 END) as "TotalPay IN 2011",
SUM(CASE WHEN Datename(year,h.OrderDate) = 2011 THEN d.OrderQty else 0 END) as "TotalOrder IN 2011"
from PurchaseOrderHeader h
left join (
select t.PurchaseOrderID,
sum(t.OrderQty) as OrderQty
from PurchaseOrderDetail t
group by t.PurchaseOrderID
) d on d.PurchaseOrderID = h.PurchaseOrderID
group by h.VendorID
order by VendorID
The default way to avoid double counting is to use SUM(DISTINCT expr).
This does not always work well enough, as you do not want to sum distinct values, but want to sum distinct rows even when those rows share the same values.
The solution is to use a sub-query to sum the details on order number and then join the result. Then you have only one total per order id to join with the order lines:
SELECT PurchaseOrderHeader.VendorID,
SUM(PurchaseOrderHeader.TotalDue) AS "TotalPay IN 2011",
SUM(POD.Qty) AS "TotalOrder IN 2011"
FROM PurchaseOrderHeader
LEFT JOIN (
SELECT PurchaseOrderDetail.PurchaseOrderID, SUM(OrderQty) AS Qty
FROM PurchaseOrderDetail
GROUP BY PurchaseOrderDetail.PurchaseOrderID
) AS POD on PurchaseOrderHeader.PurchaseOrderID = POD.PurchaseOrderID
WHERE Datename(year,PurchaseOrderHeader.OrderDate) = 2011
GROUP BY PurchaseOrderHeader.VendorID
ORDER BY VendorID
Also I took the freedom to remove the CASE WHEN statement from the SUM() to the WHERE part of the query. In this case that should give you the same results with shorter code.
Lots of good answers, but I think they miss the bit where a vendor could have multiple purchase orders, and that throws off how the TotalOrder gets calculated. (Try a sample with multiple vendors with multiple orders with each order having multiple details.) Don't forget to check for possible NULL values!
Here, I use the subquery to calculate the TotalPay for each vendor for the year in question, and then join that back to the list of all vendors. (Threw in table aliases as well, for legibility.)
-- As a subquery
SELECT
hd.VendorID,
,sum(case
when year(hd.OrderDate) = 2011 then hd.TotalDue
else 0
end) as "TotalPay IN 2011"
,isnull(subQuery.TotaOrderIn2011, 0) as "TotalOrder IN 2011"
from PurchaseOrderHeader hd
left join (-- Calculate volume by vendor for 2011
select
hd.VendorID
,sum(OrderQty) TotalOrderIn2011
from PurchaseOrderHeader hd
inner join PurchaseOrderDetail dt
on hd.PurchaseOrderID = dt.PurchaseOrderID
where year(hd.OrderDate) = 2011
group by
hd.VendorID
) subQuery
on subQuery.VendorId = hd.VendorId
group by hd.VendorID
order by hd.VendorID

SQL Server 2005 - Update column where DATEDIFF between two dates is minimum

I have two tables, defined as following:
PTable:
[StartDate], [EndDate], [Type], PValue
.................................................
2011-07-01 2011-07-07 001 5
2011-07-08 2011-07-14 001 10
2011-07-01 2011-07-07 002 15
2011-07-08 2011-07-14 002 20
TTable:
[Date], [Type], [TValue]
..................................
2011-07-01 001 11
2011-07-02 001 4
2011-07-03 001 0
2011-07-08 002 12
2011-07-09 002 12
2011-07-10 002 0
I want to update Tvalue column in TTable with the PValue in PTable, where [Date] in TTable is between [StartDate] and [EndDate] in PTable and DATEDIFF(DAY,TTable.[Date],PTable.[EndDate]) is minimum, AND PTable.Type = TTable.Type
The final TTable should look like this:
[Date], [Type], [TValue]
..................................
2011-07-01 001 11
2011-07-02 001 4
2011-07-03 001 5 --updated
2011-07-08 002 12
2011-07-09 002 12
2011-07-10 002 20 --updated
What I have tried is this:
UPDATE [TTable]
SET
TValue = T1.PValue
FROM TTable
INNER JOIN PTable T1 ON
[Date] BETWEEN T1.StartDate AND T1.EndDate
AND DATEDIFF(DAY,[Date],T1.EndDate) =
(SELECT MIN( DATEDIFF(DAY,TTable.[Date],T.EndDate) )
FROM PTable T WHERE TTable.[Date] BETWEEN T.StartDate AND T.EndDate
)
AND
T1.[Type] = TTable.[Type]
It gives me this error :
"Multiple columns are specified in an aggregated expression containing an outer reference. If an expression being aggregated contains an outer reference, then that outer reference must be the only column referenced in the expression."
Later edit:
Considering TTable AS T and PTable AS P, the condition for update are:
1. T.Type = P.Type
2. T.Date BETWEEN P.StartDate AND P.EndDate
3. DATEDIFF(DAY,T.Date,P.EndDate) = minimum value of all DATEDIFFs WHERE P.Type = T.Type AND T.Date BETWEEN P.StartDate AND P.EndDate
Later Edit 2:
Sorry, because I typed wrong the last row in PTable (2011-08-10 instead 2011-07-14), the final result was wrong.
I also managed to update in a simpler way, which I obviously should have tried from the start:
UPDATE TTABLE
SET
TValue = T1.PValue
FROM TTable
INNER JOIN PTABLE T1 ON
[Date] = (SELECT TOP(1) MAX(Date) FROM [TTABLE] WHERE [Date] BETWEEN T1.StartDate AND T1.EndDate)
AND
T1.Type = [TTABLE].Type
Sorry about this.
So you said something about "DATEDIFF(DAY,TTable.[Date],PTable.[EndDate]) is minimum" which confused me. Itt would seem like if there a weekly entry per Type, then for a particular Date, Type combination it would ever only match one. You might give this a try:
UPDATE TTABLE
SET TValue = T1.PValue
FROM TTable
INNER JOIN PTABLE T1 ON T1.Type = [TTABLE].Type -- find row in PTable that the Date falls between
and [Date] BETWEEN T1.StartDate AND T1.EndDate)
where
TValue = ( select MIN(TValue) -- finds the lowest TValue, 0 in example
from TTable))
...updated...
So it appears I read the problem incorrectly the first time. I had thought we update the TTable entries that have the lowest TValue. Not sure how I got that impression. Still seems like there needs to be a check for if it is 0?
UPDATE TTable
SET TValue = T1.PValue
FROM TTable
INNER JOIN PTable T1 ON T1.Type = TTable.Type
and T1.EndDate = (
SELECT top 1 EndDate
FROM PTable
WHERE Type=TTable.Type
ORDER BY abs(DATEDIFF(day,TTable.Date,PTable.EndDate)) desc)
WHERE
TValue = 0 -- only updating entries that aren't set, have a 0
This only works if there is one is one row in PTable with an EndDate of 7/7 or whatever for a given type. If there are two entries for Type 001 with an end date of 7/7, then it will join to two entries. Also if there is two entries that are equal distant from the date in question, so an EndDate of 7/7 and one of 7/13 are both 3 days from 7/10. If the EndDates are all 7 days apart (weekly) you should be ok.

Resources