Using t-sql to lookup value based on dates in other table - sql

I have the following challenge. I have 2 tables. First table contains changes in values of bikes, at a certain moment (i.e. price catalogue). This means a certain price for a product is valid untl there is a new price within the table.
Product | RowNr | Year | Month | Value
------------------------------------------
Bike1 | 1 | 2009 | 8 | 100
Bike1 | 2 | 2010 | 2 | 400
Bike1 | 3 | 2011 | 4 | 300
Bike1 | 4 | 2012 | 9 | 100
Bike1 | 5 | 2013 | 2 | 500
Bike1 | 6 | 2013 | 5 | 200
Bike2 | 1 | 2013 | 1 | 5000
Bike2 | 2 | 2013 | 2 | 4000
Bike2 | 3 | 2014 | 6 | 2000
Bike2 | 4 | 2014 | 10 | 4000
The second table contains dates for which I would like to determine the value of a bike (based on the information in table 1).
Product | Date | Value
-------------------------
Bike1 | 3/01/2008 | ?
Bike1 | 04/30/2011 | ?
Bike1 | 5/08/2009 | ?
Bike1 | 10/10/2012 | ?
Bike1 | 7/01/2014 | ?
So line 1 and 3 should get value "400", line 2 "300", line 4 "100" and line 5 "200" etc.
Does anyone know how this can be achieved in T-SQL? I've already partitioned the first table, but could use some advice on the next steps.
Many thanks,

You could do something like this, which will retrieve the most recent price catalogue value for the product, using the price that is less than or equal to the product table date.
SELECT p.product
, p.date
, valueAsOfDate =
( SELECT TOP 1 c.value
FROM priceCatalogue c
WHERE c.product = p.product
AND convert(date,
convert(varchar(4), c.year) + '-'
+ convert(varchar(2), c.month)
+ '-1'
) <= p.date
--this order by will ensure that the most recent price is used
ORDER BY c.year desc, c.month desc
)
FROM product p
This table structure is not ideal... you would be better off with an "AsOfDate" column in your priceCatalogue table, so that you do not have to cast the values in the priceCatalogue table as a date in order to compare. If this is new development, change the priceCatalogue table to have an asOfDate column that is a date data type. If this is an existing table that is populated from another data source, then you could look at adding a persisted computed column to the table. http://msdn.microsoft.com/en-us/library/ms188300.aspx
With asOfDate column on the productCatalogue table, you have a SARG-able query (What makes a SQL statement sargable? ) that can take advantage of indexes.
SELECT p.product
, p.date
, valueAsOfDate =
( SELECT TOP 1 c.value
FROM priceCatalogue c
WHERE c.product = p.product
AND c.asOfDate <= p.date
--this order by will ensure that the most recent price is used
ORDER BY c.year desc, c.month desc
)
FROM product p

just use the YEAR() and MONTH() functions to take those parts of the date, and join them on your versioned table.
select
from product p
inner join productVersion pv
on p.product = pv.product
and Year(p.Date) = pv.Year
and Month(p.Date) = pv.Month

Xivan,
I think for both your line 1 and 3 it should get value "100" as 3/1/2008 and 5/8/2009 is less then 8/xx/2009.
As your table structure is not ideal, you have to create some computed columns for calculation.Hope the below query will work for you.
WITH cte
AS (
SELECT p.*
,(
SELECT min(p1.rownr) rownr
FROM product p1
WHERE p1.rownr > p.rownr
AND p.product = p1.product
GROUP BY p1.product
) AS nrownr
,(
SELECT max(p1.rownr) rownr
FROM product p1
WHERE p1.rownr < p.rownr
AND p.product = p1.product
GROUP BY p1.product
) AS prownr
FROM product p
)
SELECT pd.*
,c.value
FROM product_date pd
LEFT JOIN cte c ON pd.product = c.product
LEFT JOIN product p ON c.product = p.product
AND c.nrownr = p.rownr
LEFT JOIN product p1 ON c.product = p1.product
AND c.prownr = p1.rownr
WHERE (pd.DATE !> convert(DATE, convert(VARCHAR(4), (
CASE WHEN p.year IS NOT NULL THEN p.year ELSE 9999 END)) + '-' + convert(VARCHAR(2), (
CASE WHEN p.month IS NOT NULL THEN p.month ELSE 12 END)) + '-' + '1')
AND
pd.DATE !< convert(DATE, convert(VARCHAR(4), c.year) + '-' + convert(VARCHAR(2), c.month) + '-' + '1'))
OR
(pd.DATE !> convert(DATE, convert(VARCHAR(4), (
CASE WHEN p1.year IS NOT NULL THEN NULL ELSE 2009 END)) + '-' +
convert(VARCHAR(2), ( CASE WHEN p1.month IS NOT NULL THEN NULL ELSE 8 END)) + '-' +'1')
)
http://sqlfiddle.com/#!3/22c1d/2

Related

Sum Group By Column

I have a column (PL.UNITS) that I need to Total at the bottom of the results of a query, is it possible to sum PL.UNITS that is already summed?
Please see query below.
SELECT ID.DUEDATE AS [DUE DATE], CD.RENEWALDATE, CD.RENEWALSTATUS, CD.CONTRACTNUMBER, L.LOCNAME, L.LOCADDRESS1, L.LOCADDRESS2, L.LOCADDRESS3, L.LOCADDRESS4, L.POSTCODE, SUM(PL.UNITS) AS UNITS from CLIENTDETAILS CD
INNER JOIN LOCATIONS L ON CD.CLIENTNUMBER = L.CLIENTNUMBER
INNER JOIN ITEMDETAILS ID ON L.LOCNUMBER = ID.LOCNUMBER
INNER JOIN PLANT PL ON ID.CODE = PL.CODE
WHERE L.OWNER = 210 and L.STATUSLIVE = 1 and ID.DUEDATE > '01/01/2017'
GROUP BY ID.DUEDATE, CD.RENEWALDATE, CD.RENEWALSTATUS, CD.CONTRACTNUMBER, L.LOCNAME, L.LOCADDRESS1, L.LOCADDRESS2, L.LOCADDRESS3, L.LOCADDRESS4, L.POSTCODE
It's probably best to do this sort of thing in front end development. Nevertheless, here is an example (quick and dirty, but shows the idea) for sql-server:
SELECT COALESCE(a.id, 'total') AS id
, SUM(a.thing) AS thing_summed
FROM (
SELECT '1' id
, 1 thing
UNION
SELECT '2'
, 2 thing
UNION
SELECT '1'
, 3 thing
) AS a
GROUP BY ROLLUP(a.id)
Result:
+-------+--------------+
| id | thing_summed |
+-------+--------------+
| 1 | 4 |
| 2 | 2 |
| total | 6 |
+-------+--------------+

Make single record to multiple records in sql server

I have a records look like below
From two rows, I want to split ShiftPattern values and create multiple records and StartWeek will be created sequentially.
Final Query:
Split ShiftPattern Column and Create multiple records
Increase StartWeek like as 20, 21 to rotation.
Output result
This is what you need. Tested in fiddle.
SQLFiddle Demo
select q.locationid,q.employeeid,
case
when (lag(employeeid,1,null) over (partition by employeeid order by weekshiftpatternid)) is null
then startweek
else startweek + 1
end as rotation ,
q.weekshiftpatternid,
q.shiftyear
from
(
select locationid,employeeid, left(d, charindex(',', d + ',')-1) as weekshiftpatternid ,
startweek,shiftyear
from (
select *, substring(shiftpattern, number, 200) as d from MyTable locationid left join
(select distinct number from master.dbo.spt_values where number between 1 and 200) col2
on substring(',' + shiftpattern, number, 1) = ','
) t
) q
Output
+------------+------------+----------+--------------------+-----------+
| locationid | employeeid | rotation | weekshiftpatternid | shiftyear |
+------------+------------+----------+--------------------+-----------+
| 1 | 10000064 | 20 | 1006 | 2016 |
| 1 | 10000064 | 21 | 1008 | 2016 |
| 1 | 10000065 | 20 | 1006 | 2016 |
| 1 | 10000065 | 21 | 1008 | 2016 |
+------------+------------+----------+--------------------+-----------+
Similar:
In my test table my ID is your EmployeeID or however you want to work it.
SELECT
*,
LEFT(shiftBits, CHARINDEX(',', shiftBits + ',')-1) newShiftPattern,
StartWeek+ROW_NUMBER() OVER(PARTITION BY ID ORDER BY shiftBits ) as newStartWeek
FROM
(
SELECT
SUBSTRING(shiftPattern, number, LEN(shiftPattern)) AS shiftBits,
test2.*
FROM
test2,master.dbo.spt_values
WHERE
TYPE='P' AND number<LEN(shiftPattern)
AND SUBSTRING(',' + shiftPattern, number, 1) = ','
) AS x

SQL Server - Insert lines with null values when month doesn't exist

I have a table like this one:
Yr | Mnth | W_ID | X_ID | Y_ID | Z_ID | Purchases | Sales | Returns |
2015 | 10 | 1 | 5210 | 1402 | 2 | 1000.00 | etc | etc |
2015 | 12 | 1 | 5210 | 1402 | 2 | 12000.00 | etc | etc |
2016 | 1 | 1 | 5210 | 1402 | 2 | 1000.00 | etc | etc |
2016 | 3 | 1 | 5210 | 1402 | 2 | etc | etc | etc |
2014 | 3 | 9 | 880 | 2 | 7 | etc | etc | etc |
2014 | 12 | 9 | 880 | 2 | 7 | etc | etc | etc |
2015 | 5 | 9 | 880 | 2 | 7 | etc | etc | etc |
2015 | 7 | 9 | 880 | 2 | 7 | etc | etc | etc |
For each combination of (W, X, Y, Z) I would like to insert the months that don't appear in the table and are between the first and last month.
In this example, for combination (W=1, X=5210, Y=1402, Z=2), I would like to have additional rows for 2015/11 and 2016/02, where Purchases, Sales and Returns are NULL. For combination (W=9, X=880, Y=2, Z=7) I would like to have additional rows for months between 2014/4 and 2014/11, 2015/01 and 2015/04, 2016/06.
I hope I have explained myself correctly.
Thank you in advance for any help you can provide.
The process is rather cumbersome in this case, but quite possible. One method uses a recursive CTE. Another uses a numbers table. I'm going to use the latter.
The idea is:
Find the minimum and maximum values for the year/month combination for each set of ids. For this, the values will be turned into months since time 0 using the formula year*12 + month.
Generate a bunch of numbers.
Generate all rows between the two values for each combination of ids.
For each generated row, use arithmetic to re-extract the year and month.
Use left join to bring in the original data.
The query looks like:
with n as (
select row_number() over (order by (select null)) - 1 as n -- start at 0
from master.spt_values
),
minmax as (
select w_id, x_id, y_id, z_id, min(yr*12 + mnth) as minyyyymm,
max(yr*12 + mnth) as maxyyyymm
from t
group by w_id, x_id, y_id, z_id
),
wxyz as (
select minmax.*, minmax.minyyyymm + n.n,
(minmax.minyyyymm + n.n) / 12 as yyyy,
((minmax.minyyyymm + n.n) % 12) + 1 as mm
from minmax join
n
on minmax.minyyyymm + n.n <= minmax.maxyyyymm
)
select wxyz.yyyy, wxyz.mm, wxyz.w_id, wxyz.x_id, wxyz.y_id, wxyz.z_id,
<columns from t here>
from wxyz left join
t
on wxyz.w_id = t.w_id and wxyz.x_id = t.x_id and wxyz.y_id = t.y_id and
wxyz.z_id = t.z_id and wxyz.yyyy = t.yr and wxyz.mm = t.mnth;
Thank you for your help.
Your solution works, but I noticed it is not very good in terms of performance, but meanwhile I have managed to get a solution for my problem.
DECLARE #start_date DATE, #end_date DATE;
SET #start_date = (SELECT MIN(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);
SET #end_date = (SELECT MAX(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);
DECLARE #tdates TABLE (Period DATE, Yr INT, Mnth INT);
WHILE #start_date <= #end_date
BEGIN
INSERT INTO #tdates(PEriod, Yr, Mnth) VALUES(#start_date, YEAR(#start_date), MONTH(#start_date));
SET #start_date = EOMONTH(DATEADD(mm,1,DATEFROMPARTS(YEAR(#start_date), MONTH(#start_date), 1)));
END
DECLARE #pks TABLE (W_ID NVARCHAR(50), X_ID NVARCHAR(50)
, Y_ID NVARCHAR(50), Z_ID NVARCHAR(50)
, PerMin DATE, PerMax DATE);
INSERT INTO #pks (W_ID, X_ID, Y_ID, Z_ID, PerMin, PerMax)
SELECT W_ID, X_ID, Y_ID, Z_ID
, MIN(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMin
, MAX(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMax
FROM Table1
GROUP BY W_ID, X_ID, Y_ID, Z_ID;
INSERT INTO Table_Output(W_ID, X_ID, Y_ID, Z_ID
, ComprasLiquidas, RTV, DevManuais, ComprasBrutas, Vendas, Stock, ReceitasComerciais)
SELECT TP.DB, TP.Ano, TP.Mes, TP.Supplier_Code, TP.Depart_Code, TP.BizUnit_Code
, TA.ComprasLiquidas, TA.RTV, TA.DevManuais, TA.ComprasBrutas, TA.Vendas, TA.Stock, TA.ReceitasComerciais
FROM
(
SELECT W_ID, X_ID, Y_ID, Z_ID
FROM #tdatas CROSS JOIN #pks
WHERE Period BETWEEN PerMin And PerMax
) AS TP
LEFT JOIN Table_Input AS TA
ON TP.W_ID = TA.W_ID AND TP.X_ID = TA.X_ID AND TP.Y_ID = TA.Y_ID
AND TP.Z_ID = TA.Z_ID
AND TP.Yr = TA.Yr
AND TP.Mnth = TA.Mnth
ORDER BY TP.W_ID, TP.X_ID, TP.Y_ID, TP.Z_ID, TP.Yr, TP.Mnth;
I do the following:
Get the Min and Max date of the entire table - #start_date and #end_date variables;
Create an auxiliary table with all dates between Min and Max - #tdates table;
Get all the combinations of (W_ID, X_ID, Y_ID, Z_ID) along with the min and max dates of that combination - #pks table;
Create the cartesian product between #tdates and #pks, and in the WHERE clause I filter the results between the Min and Max of the combination;
Compute a LEFT JOIN of the cartesian product table with the input data table.

Multiple sql query or Cursor?

I need help on something that seems to be complex to me.
I made a query to create a tbl1 which is the Cartesian product of the tables Item and Warehouse. It give’s me back all items in all warehouses:
SELECT i.ItemID, w.WarehouseID
FROM Item i, Warehouse w
I made a second query (tbl2) where I check the date of the last document previous or equal to a variable date (#datevar) and whose quantity rule is 1 (PhysicalQtyRule = 1), this by Item and Warehouse, obtained from StockHistory table
SELECT MAX(CreateDate) AS [DATE1], ItemID, Quantity, WarehouseID
FROM StockHistory
WHERE PhysicalQtyRule = 1 AND CreateDate <= #datevar
GROUP BY ItemID, Quantity, WarehouseID
Now, I need more three steps:
Build a third table containing per item and warehouse the sum of quantity, but the quantity rule is 2 (PhysicalQtyRule = 2) and date between tbl2.date (if exists) and the date of the variable #datevar, obtained from the table StockHistory. Something like that:
SELECT ItemID, WarehouseID, SUM(Quantity)
FROM StockHistory
WHERE PhysicalQtyRule = 2 AND CreateDate > tbl2.DATE1 --If exists
AND CreateDate <= #datevar
GROUP BY ItemID, WarehouseID
Build a fourth table containing per item and warehouse the sum of quantity, but the quantity rule is 3 (PhysicalQtyRule = 3) and date between tbl2.date (if any) and the date of the variable #datevar, obtained from the table StockHistory. Something like that:
SELECT ItemID, WarehouseID, SUM(Quantity)
FROM StockHistory
WHERE PhysicalQtyRule = 3 AND CreateDate > tbl2.DATE1 --If exists
AND CreateDate <= #datevar
GROUP BY ItemID, WarehouseID
Create a final table based on the first one, with an sum quantity column, something like that:
SELECT i.ItemID, w.WarehouseID, tbl2.Quantity + tbl3.Quantity – tbl4.Quantity AS [Qty]
FROM Item i, Warehouse w
I don't know if need cursors (something new for me) or multiple querys, but it's important the best performance because my StockHistory table have millions of records.
Can anyone help-me please? Thank you!
Some sample data, only for one Item and one warehouse:
+--------+-------------+------------+-----------------+----------+
| ItemID | WarehouseID | CreateDate | PhysicalQtyRule | Quantity | Balance | comments
+--------+-------------+------------+-----------------+----------+
| 1234 | 11 | 2013-03-25 | 2 | 35 | 35 | Rule 2 = In
| 1234 | 11 | 2013-03-28 | 3 | 30 | 5 | Rule 3 = Out
| 1234 | 11 | 2013-04-01 | 1 | 3 | 3 | Rule 1 = Reset
| 1234 | 11 | 2013-07-12 | 2 | 40 | 43 | Rule 2 = In
| 1234 | 11 | 2013-09-05 | 3 | 20 | 23 | Rule 3 = Out
| 1234 | 11 | 2013-12-31 | 1 | 25 | 25 | Rule 1 = Reset
| 1234 | 11 | 2014-01-09 | 3 | 11 | 14 | Rule 3 = Out
| 1234 | 11 | 2014-01-16 | 3 | 6 | 8 | Rule 3 = Out
I want to know the balance on any variable date.
Without your data, I can't test this but I believe this should be your solution.
SELECT i.ItemID
,w.WarehouseID
,[Qty] = tbl2.Quantity + tbl3.Quantity – tbl4.Quantity
FROM Item i
CROSS JOIN Warehouse w
OUTER APPLY (
SELECT [DATE1] = MAX(sh.CreateDate)
,sh.ItemID
,sh.Quantity
,sh.WarehouseID
FROM StockHistory sh
WHERE sh.PhysicalQtyRule = 1 AND sh.CreateDate <= #datevar
AND i.ItemID = sh.ItemID
AND w.WarehouseID = sh.WarehouseID
GROUP BY sh.ItemID, sh.Quantity, sh.WarehouseID ) tbl2
OUTER APPLY (
SELECT sh.ItemID
,sh.WarehouseID
,[Quantity] = SUM(sh.Quantity)
FROM StockHistory sh
WHERE sh.PhysicalQtyRule = 2 AND sh.CreateDate > tbl2.DATE1 --If exists
AND sh.CreateDate <= #datevar AND i.ItemID = sh.ItemID
AND w.WarehouseID = sh.WarehouseID
GROUP BY sh.ItemID, sh.WarehouseID ) tbl3
OUTER APPLY (
SELECT sh.ItemID
,sh.WarehouseID
,[Quantity] = SUM(sh.Quantity)
FROM StockHistory sh
WHERE sh.PhysicalQtyRule = 3 AND sh.CreateDate > tbl2.DATE1 --If exists
AND sh.CreateDate <= #datevar AND i.ItemID = sh.ItemID
AND w.WarehouseID = sh.WarehouseID
GROUP BY sh.ItemID, sh.WarehouseID ) tbl4

Join table on itself for unique row combinations for calculations

I have a table that I need to use to build a result set from where certain rows from the table are columns in the result set. I started to chain LEFT JOINs together on the table multiple times but I need to eliminate results that are a different combination of another result already in the set:
For example, if I get 1, 21, 25 as result columns, I can't have ANY other combination of those numbers in the results.
My table definition is:
Table tblKPIDetails
Column Month int
Column Year int
Column Division varchar(3)
Column KPI int
Column Value decimal(18,4)
My current query is:
SELECT *
FROM tblKPIDetails J1
LEFT JOIN tblKPIDetails J2 ON J2.Month = J1.Month AND J2.Year = J1.Year AND J2.Division = J1.Division AND NOT(J2.KPI = J1.KPI ) AND (J2.KPI = 1 OR J2.KPI = 21 OR J2.KPI = 25)
LEFT JOIN tblKPIDetails J3 ON J3.Month = J1.Month AND J3.Year = J1.Year AND J3.Division = J1.Division AND NOT(J3.KPI = J1.KPI ) AND (J3.KPI = 1 OR J3.KPI = 21 OR J3.KPI = 25)
WHERE J1.KPI = 1 OR J1.KPI = 21 OR J1.KPI = 25
I know this is wrong, but it's a super-set of what I need. In the results from the query above, I can get J1.KPI, J2.KPI, J3.KPI or J1.KPI, J3.KPI, J2.KPI, or any other combination.
My expected result would be:
Division | Month | Year | KPIA | KPIAValue | KPIB | KPIBValue | KPIC | KPICValue
for each division, month, and year
where KPIA, KPIB, or KPIC = 1, 21, or 25 but only 1 combination of 1,21,25 exists per division|month|year
EDIT
To clarify the expected results a little more, using the above query, I'm getting the following results:
Division | Month | Year | KPIA | KPIAValue | KPIB | KPIBValue | KPIC | KPICValue
--------------------------------------------------------------------------------
000 1 2012 1 1000 21 2000 25 3000
000 1 2012 21 2000 1 1000 25 3000
000 1 2012 25 3000 21 2000 1 1000
111 1 2012 1 555 21 10000 25 5000
I need to make it so my results would only be ANY 1 of the first 3 results and then the last one...for example:
Division | Month | Year | KPIA | KPIAValue | KPIB | KPIBValue | KPIC | KPICValue
--------------------------------------------------------------------------------
000 1 2012 25 3000 21 2000 1 1000
111 1 2012 1 555 21 10000 25 5000
I think you are looking for the PIVOT table operator like so:
SELECT
Devision,
Month,
Year,
[1] AS KPIAValue,
[21] AS KPIBValue,
[25] AS KPICValue
FROM
(
SELECT t1.*
FROM tblKPIDetails t1
INNER JOIN
(
SELECT Month, Year, Devision
FROM tblKPIDetails
WHERE KPI IN(1, 21, 25)
GROUP BY Month, Year, Devision
HAVING COUNT(DISTINCT KPI) = 3
) t2 ON t1.Month = t2.Month AND t1.Year = t2.Year
AND t1.Devision = t2.Devision
) t
PIVOT
(
MAX(Value)
FOR KPI IN([1], [21], [25])) p;
SQL Fiddle Demo
This will give you the data in the form:
| DEVISION | MONTH | YEAR | KPIAVALUE | KPIBVALUE | KPICVALUE |
---------------------------------------------------------------
| A | 2 | 2012 | 16 | 16 | 16 |
| B | 10 | 2012 | 16 | 18 | 20 |
Note that: This will give you the only combination of the Year, Month, DEVISION that have all the values 1, 21 and 25, and that what this query do:
SELECT Month, Year, Devision
FROM tblKPIDetails
WHERE KPI IN(1, 21, 25)
GROUP BY Month, Year, Devision
HAVING COUNT(DISTINCT KPI) = 3
Update: If you are looking for those that had at least one of 1, 21 or 25, just remove the HAVING COUNT(DISTINCT KPI) = 3, but this will make you expect more values than these three, in this case it will ignore other values and return only those three. Also it will return NULL for any of the missing values of them like so:
SELECT
Devision,
Month,
Year,
[1] AS KPIAValue,
[21] AS KPIBValue,
[25] AS KPICValue
FROM
(
SELECT t1.*
FROM tblKPIDetails t1
INNER JOIN
(
SELECT Month, Year, Devision
FROM tblKPIDetails
WHERE KPI IN(1, 21, 25)
GROUP BY Month, Year, Devision
) t2 ON t1.Month = t2.Month AND t1.Year = t2.Year
AND t1.Devision = t2.Devision
) t
PIVOT
(
MAX(Value)
FOR KPI IN([1], [21], [25])) p;
Updated SQL Fiddle Demo
| DIVISION | MONTH | YEAR | KPIAVALUE | KPIBVALUE | KPICVALUE |
---------------------------------------------------------------
| A | 2 | 2012 | 15.5 | 15.5 | 15.5 |
| B | 10 | 2012 | 15.5 | 17.5 | 20.24 |
| C | 12 | 2012 | 15.5 | (null) | 20.24 |
If you don't have a large number of "IDs", you could just transpose the values like this:
select
[Month],
[Year],
Division,
sum(case when KPI = 1 then Value else null end) as KPI1,
sum(case when KPI = 21 then Value else null end) as KPI21,
sum(case when KPI = 25 then Value else null end) as KPI25
from tblKPIDetails
group by
[Month],
[Year],
Division
order by
[Month],
[Year],
Division
Or same thing by using the "OVER" clause.
I think you want a conditional aggregation. But it is still not clear to me how the results are being defined. This might help you on your way:
SELECT Division, Month, Year,
1, max(case when kpi = 1 then value end) as kpi1value,
21, max(case when kpi = 21 then value end) as kpi21value,
25, max(case when kpi = 25 then value end) as kpi25value,
FROM tblKPIDetails J1
maybe you can try the following:
SELECT DISTINCT
t.Division,
t.Month,
t.Year,
KA.Value AS KPIAValue,
KB.Value AS KPIBValue,
KC.Value AS KPICValue
FROM
tblKPIDetails t
LEFT JOIN tblKPIDetails KA ON t.Division = KA.Division and t.Month = KA.month and .year = KA.year and KA.KPI = 1
LEFT JOIN tblKPIDetails KB ON t.Division = KB.Division and t.Month = KB.month and t.year = KB.year and KB.KPI = 21
LEFT JOIN tblKPIDetails KC ON t.Division = KC.Division and t.Month = KC.month and t.year = KC.year and KC.KPI = 25
Then is one LEFT JOIN for each possible KPI value you want.