Problem with text datatype and UDF - user-defined-functions

I have this great function that parses a field into a pivot table consisting of four columns so I can sort my table accordingly. My only problem now is I can't use the function in a query that also calls a "text" datatype to be displayed in the results. Query runs fine as long as I don't include "spName" which is a "text" datatype. I've tried using cast and convert but neither built-in functions work in this query. thanks.
error:
Pivot grouping columns must be comparable. The type of column "spName" is "text", which is not comparable.
query:
SELECT title, recID, spName, [1] AS [Col1], [2] AS [Col2],[3] AS [Col3],[4] AS [Col4]
FROM (select title, recID, spName from TestTable) t CROSS APPLY dbo.GetNumbers(title) PIVOT (MAX(num) FOR idx IN ([1], [2],[3],[4]) ) AS PivotTable ORDER BY Col1
udf:
CREATE FUNCTION GetNumbers
(
#Numbers NVARCHAR(2000)
)
RETURNS #Results TABLE
(
idx INT IDENTITY(1,1),
num INT
)
AS
BEGIN
DECLARE #NonNumericIndex INT, #NumericIndex INT
SET #NumericIndex = PATINDEX('%[0-9]%',#Numbers)
IF (#NumericIndex > 4) --First Column not there
INSERT INTO #Results VALUES (NULL)
WHILE #NumericIndex > 0
BEGIN
SET #Numbers = RIGHT(#Numbers,LEN(#Numbers)-#NumericIndex+1)
SET #NonNumericIndex = PATINDEX('%[^0-9]%',#Numbers)
IF(#NonNumericIndex = 0)
BEGIN
INSERT
INTO #Results VALUES (#Numbers)
RETURN
END
ELSE
INSERT
INTO #Results VALUES
(LEFT(#Numbers,#NonNumericIndex-1))
SET #Numbers = RIGHT(#Numbers,LEN(#Numbers)-#NonNumericIndex+1)
SET #NumericIndex = PATINDEX('%[0-9]%',#Numbers)
END
RETURN
END
sample data
title recid spname
QW 1 RT 309-23-1 1 This is title 1 words
QW 1 RT 29-1 2 this is title 2 desc
QW 1 RT 750-1 3 This is title 3
QW RT 750-1 4 This is title 4 words

The Text datatype is deprecated and sounds a dubious choice for a name field in any event.
This works for me
IF OBJECT_ID('tempdb..#TestTable') IS NULL
BEGIN
CREATE TABLE #TestTable
(
recid INT PRIMARY KEY,
title VARCHAR(50),
spName TEXT
)
INSERT INTO #TestTable
SELECT 1 AS recid, 'QW 1 RT 309-23-1' AS title, CAST('test1' AS TEXT) AS spName UNION ALL
SELECT 2 AS recid, 'QW 1 RT 29-1', CAST('test' AS TEXT) AS spName UNION ALL
SELECT 3 AS recid, 'QW 1 RT 750-1', CAST('test' AS TEXT) AS spName UNION ALL
SELECT 4 AS recid, 'QW RT 750-1' , CAST('test' AS TEXT) AS spName
END
SELECT recid, title, spName, [1] AS [COLUMN 1], [2] AS [COLUMN 2],[3] AS [COLUMN 3],[4] AS [COLUMN 4]
FROM
(SELECT recid, title, CAST(spName AS VARCHAR(MAX)) AS spName FROM #TestTable ) T
CROSS APPLY dbo.GetNumbers(title)
PIVOT
(MAX(num) FOR idx IN ([1], [2],[3],[4])
) AS PivotTable;

Related

How to modify column data-type while column is in used

I have problem while I use SELECT query. Unfortunettly, the ID is store as VARCHAR which is big mistake and right now I have problem in following function
FUNCTION GET_SUB_PROJECTS(p_currentUserId IN VARCHAR,p_projectId IN INT)
RETURN SYS_REFCURSOR IS
rc SYS_REFCURSOR;
-- getSubProject
BEGIN
OPEN rc FOR
SELECT
p.*,
a.number_ AS activityNumber,
a.description AS activityDescription
FROM
projects p
LEFT JOIN
project_users_schedule_dates pusd
ON
pusd.ProjectID = p.ProjectID AND pusd.UserID = p_currentUserId
LEFT JOIN
activities a
ON
a.id = p.activity
LEFT JOIN
responsible_persons rp
ON
rp.ProjectID = p.ProjectID AND rp.UserID = p_currentUserId
LEFT JOIN
users u
ON
u.UserID = p_currentUserId
WHERE
(u.User_roleID = 1 AND
p.CustomName LIKE CONCAT((SELECT CustomName FROM projects pr WHERE pr.ProjectID = p_projectId), '%') AND p.ProjectID <> p_projectId)
OR
((
(p.Responsible_person_id = p_currentUserId OR p.Delivery_contact = p_currentUserId OR rp.UserID = p_currentUserId OR (pusd.UserID = p_currentUserId AND SYSTIMESTAMP BETWEEN TO_DATE(pusd.StartDate,'YYYY-MM-DD') AND TO_DATE(pusd.EndDate,'YYYY-MM-DD') + INTERVAL '1' DAY AND
SYSTIMESTAMP BETWEEN TO_DATE(p.StartDate,'YYYY-MM-DD') AND TO_DATE(p.EndDate,'YYYY-MM-DD') + INTERVAL '1' DAY))
)
AND
p.CustomName LIKE CONCAT((SELECT CustomName FROM projects pr WHERE pr.ProjectID = p_projectId), '%') AND p.ProjectID <> p_projectId)
ORDER BY p.CustomName;
RETURN rc;
END GET_SUB_PROJECTS;
When I call function it needs to return data, but it doesn't. Somehow, here p.Responsible_person_id and p.Delivery_contact needs to be NUMBER but somehow it is VARCHAR
When I call function I use
SELECT PROJECT_PACKAGE.GET_SUB_PROJECTS('199',141) FROM DUAL
I found one solution to modify but It throw me error like
Error report -
ORA-01439: column to be modified must be empty to change datatype
01439. 00000 - "column to be modified must be empty to change datatype"
What to do in this situation ? What is the best method to solve this issue ?
You can change the data type of a column online using dbms_redefinition
create table t (
c1 varchar2(10)
primary key
);
create table t_new (
c1 number(10, 0)
primary key
);
insert into t values ( '1.0000' );
commit;
begin
dbms_redefinition.start_redef_table (
user, 't', 't_new',
col_mapping => 'to_number ( c1 ) c1',
options_flag => dbms_redefinition.cons_use_rowid
);
end;
/
exec dbms_redefinition.sync_interim_table ( user, 't', 't_new' );
exec dbms_redefinition.finish_redef_table ( user, 't', 't_new' );
desc t
Name Null? Type
C1 NOT NULL NUMBER(10)
select * from t;
C1
1
There are also options to copy constraints, triggers, indexes, etc. automatically in this process.

want to get values from 2003 up to today

DECLARE #year VARCHAR(100)
SET #year = '%-2003-%'
SELECT
DISTINCT 'AR Invoice', b.user_transaction_type_code as ar_transaction_type_code, b.name_l as ar_name_l, min(transaction_nr) as ar_min_tran_nr, max(transaction_nr) as ar_max_tran_nr, MIN(transaction_text) as min_ar_transaction_text, MAX(transaction_text) as max_ar_transaction_text
FROM
ar_invoice a, user_transaction_type b
WHERE
a.user_transaction_type_id = b.user_transaction_type_id AND
transaction_text like #year
GROUP BY
b.user_transaction_type_code, b.name_l
I want to get the values from 2003 up to today?
Can someone tell me how?
If you want to get values from 2003 to today, you can use BETWEEN '2003-01-01' AND GETDATE() option in the WHERE clause.
SELECT DISTINCT 'AR Invoice', b.user_transaction_type_code AS ar_transaction_type_code,
b.name_l AS ar_name_l, min(transaction_nr) AS ar_min_tran_nr, max(transaction_nr) AS ar_max_tran_nr,
MIN(transaction_text) AS min_ar_transaction_text, MAX(transaction_text) AS max_ar_transaction_text
FROM ar_invoice a
INNER JOIN user_transaction_type b ON b.user_transaction_type_id = a.user_transaction_type_id
WHERE transaction_text BETWEEN '2003-01-01' AND GETDATE()
GROUP BY b.user_transaction_type_code, b.name_l
DECLARE #year VARCHAR(100)
SET #year = #FromYear --2003 --today
SELECT DISTINCT 'AR Invoice'
, b.user_transaction_type_code as ar_transaction_type_code
, b.name_l as ar_name_l , min(transaction_nr) as ar_min_tran_nr
, max(transaction_nr) as ar_max_tran_nr
, MIN(transaction_text) as min_ar_transaction_text
, MAX(transaction_text) as max_ar_transaction_text
FROM
ar_invoice a, user_transaction_type b
WHERE
a.user_transaction_type_id = b.user_transaction_type_id
AND
transaction_text like '%'+'-'+ #year +'-' + '%'
GROUP BY
b.user_transaction_type_code, b.name_l

Looking for whether a row exists in a subquery

Spoiler alert: I am fairly new to Oracle.
I have four tables: enrollments, courses/sections, standards, and grades.
We are running Honor Roll. I have queries on the first three tables that add various constraints needed to meet honor roll requirements. Then we look at the grades table. If they have a valid enrollment, in a valid course, meeting valid standards, then count up their scores. If their score qty meets thresholds, then they get Honors.
This code is not optimized, and likely can be done in a far better/more compact way I'm sure -- however, it only gets run a few times a year, so I'm willing to trade off optimization in order to increase human readability, so that I can continue to learn the fundamentals. So far I have:
WITH validCC (SELECT CC.ID AS CCID,
CC.STUDENTID AS STUDENTID,
CC.SECTIONID AS SECTIONID,
CC.TERMID AS TERMID,
STUDENTS.DCID AS STUDENTSDCID
FROM CC
INNER JOIN STUDENTS ON CC.STUDENTID = STUDENTS.ID
WHERE TERMID in (2700,2701)
AND CC.SCHOOLID = 406;
), --end validCC
validCrsSect (SELECT SECTIONS.ID AS SECTIONID,
SECTIONS.DCID AS SECTIONSDCID,
SECTIONS.EXCLUDEFROMHONORROLL AS SECTHR,
COURSES.COURSE_NUMBER AS COURSE_NUMBER,
COURSES.COURSE_NAME AS COURSE_NAME,
COURSES.EXCLUDEFROMHONORROLL AS CRSHR
FROM SECTIONS
INNER JOIN COURSES ON SECTIONS.COURSE_NUMBER = COURSES.COURSE_NUMBER AND SECTIONS.SCHOOLID = COURSES.SCHOOLID
WHERE SECTIONS.TERMID IN (2700,2701)
AND SECTIONS.SCHOOLID = 406
AND SECTIONS.EXCLUDEFROMHONORROLL = 0
AND COURSES.EXCLUDEFROMHONORROLL = 0
), --end validCrsSect
validStandard (SELECT STANDARDID,
IDENTIFIER,
TRANSIENTCOURSELIST
FROM STANDARD
WHERE isActive = 1
AND YEARID = 27
AND ( instr (STANDARD.identifier, 'MHS.TS', 1 ,1) > 0 --Is a valid standard for this criteria: MHS TS
or STANDARD.identifier = 'MHTC.TS.2' --or MHTC TS
or STANDARD.identifier = 'MHTC.TS.4' )
), --end validStandard
--sgsWithChecks (
SELECT sgs.STANDARDGRADESECTIONID AS SGSID,
sgs.STUDENTSDCID as STUDENTSDCID,
sgs.STANDARDID AS STANDARDID,
sgs.STORECODE AS STORECODE,
sgs.SECTIONSDCID AS SECTIONSDCID,
sgs.YEARID AS YEARID,
sgs.STANDARDGRADE AS STANDARDGRADE,
(select count(CCID) from validCC INNER JOIN STANDARDGRADESECTION sgs ON sgs.STUDENTSDCID = validCC.STUDENTSDCID and sgs.SECTIONSDCID = validCC.SECTIONID) as CC_OK,
(select count(SECTIONID) from validCrsSection INNER JOIN STANDARDGRADESECTION sgs ON sgs.SECTIONSDCID = validCrsSect.SECTIONSDCID) AS CRS_OK,
(select count(STANDARDID) from validStandard INNER JOIN STANDARDGRADESECTION sgs ON sgs.STANDARDID = validStandard.STANDARDID) AS STD_OK
FROM STANDARDGRADESECTION sgs
The purpose of putting the 'OK' columns in the vGrades table is because the final SELECT (not included) goes through and counts up the instances of certain scores filtering by the checks.
Frustratingly, there are two IDs in both the students table and the sections table (and it's not the same data). So when I go to link everything, some tables use ID as the FK, others use DCID as the FK; and I have to pull in an extra table to make that conversion. Makes the joins more fun that way I guess.
Each individual query works on its own, but I can't get the final select count() to work to pull their data. I tried embedding the initial queries as subqueries, but I couldn't pass the studentid into them, and it would run that query for each student, instead of once at the beginning.
My current error is:
Error starting at line : 13 in command -
SECTIONS.DCID AS SECTIONSDCID,
Error report -
Unknown Command
However before it was saying unknown table and referencing the last line of the join statement. All the table names are valid.
Thoughts?
I replaced the INNER JOIN with a simple WHERE condition. This seems to work.
(SELECT COUNT (CCID) FROM validCC WHERE sgs.STUDENTSDCID = validCC.STUDENTSDCID and sgs.SECTIONSDCID = validCC.SECTIONID) as CC_OK,
(SELECT COUNT (SECTIONID) FROM validCrsSect WHERE sgs.SECTIONSDCID = validCrsSect.SECTIONSDCID) AS CRS_OK,
(SELECT COUNT (STANDARDID) FROM validStandard WHERE sgs.STANDARDID = validStandard.STANDARDID) AS STD_OK
I removed the stray comma at the end of validStandard and replaced from validCrsSection with from validCrsSect (assuming it was meant to refer to that WITH clause and there isn't another validCrsSection table). I am also guessing that the counts are meant to be keyed to the current sgs row and not counts of the whole table. I make it this:
with validcc as
( select cc.id as ccid
, cc.studentid
, cc.sectionid
, cc.termid
, st.dcid as studentsdcid
from cc
join students st on st.id = cc.studentid
where cc.termid in (2700, 2701)
and cc.schoolid = 406
)
, validcrssect as
( select s.id as sectionid
, s.dcid as sectionsdcid
, s.excludefromhonorroll as secthr
, c.course_number
, c.course_name
, c.excludefromhonorroll as crshr
from sections s
join courses c
on c.course_number = s.course_number
and c.schoolid = s.schoolid
where s.termid in (2700, 2701)
and s.schoolid = 406
and s.excludefromhonorroll = 0
and c.excludefromhonorroll = 0
)
, validstandard as
( select standardid
, identifier
, transientcourselist
from standard
where isactive = 1
and yearid = 27
and ( instr(standard.identifier, 'MHS.TS', 1, 1) > 0
or standard.identifier in ('MHTC.TS.2','MHTC.TS.4') )
)
select sgs.standardgradesectionid as sgsid
, sgs.studentsdcid
, sgs.standardid
, sgs.storecode
, sgs.sectionsdcid
, sgs.yearid
, sgs.standardgrade
, ( select count(*) from validcc
where validcc.studentsdcid = sgs.studentsdcid
and validcc.sectionid = sgs.sectionsdcid ) as cc_ok
, ( select count(*) from validcrssect
where validcrssect.sectionsdcid = sgs.sectionsdcid ) as crs_ok
, ( select count(*) from validstandard
where validstandard.standardid = sgs.standardid ) as std_ok
from standardgradesection sgs;
This works with the six table definitions reverse-engineered as:
create table students
( id integer not null
, dcid integer );
create table cc
( id integer
, studentid integer
, sectionid integer
, termid integer
, schoolid integer );
create table courses
( course_number integer
, course_name varchar2(30)
, excludefromhonorroll integer
, schoolid integer );
create table sections
( id integer not null
, dcid integer
, excludefromhonorroll integer
, termid integer
, schoolid integer
, course_number integer );
create table standard
( standardid integer
, identifier varchar2(20)
, transientcourselist varchar2(50)
, isactive integer
, yearid integer );
create table standardgradesection
( standardgradesectionid integer
, studentsdcid integer
, standardid integer
, storecode integer
, sectionsdcid integer
, yearid integer
, standardgrade integer );

SSRS How to get the first and last values of a matrix row group?

I basically have the screenshot below as my layout.
My matrix columns are DCG1 and DCG2.
At the end of the Matrix I have a Total Group, which works just find. But I want to find the difference between my first and last value of the group. I've tried everything from ReportItems! to values. I cannot get SSRS to recognize these values.
So basically in the screen shots below. Screen shot 1 is the matrix structure. I have a column group called Test1, I want the first Value of Test1 and the last Value of Test 1 and place that in the Red box.
In screenshot 2, you can see the values i want to compare. The table groupings are named the same as the column + group. So dcs1group/dcs2group
Okay here is the DDL and DML for the datasource
http://pastebin.com/1ySN701D
The pastebin has been removed. Why, not sure so here it is below.
IF EXISTS
(SELECT [name]
FROM tempdb.sys.tables
WHERE [name] LIKE '%tmpHoldingTable%')
BEGIN
DROP TABLE #tmpHoldingTable;
END;
CREATE TABLE #tmpHoldingTable
(
dcs1 NVARCHAR (50),
dcs2 NVARCHAR (50),
Total DECIMAL (10, 2),
Test1 NVARCHAR (50)
)
INSERT INTO #tmpHoldingTable (dcs1,
dcs2,
Total,
Test1)
VALUES ('Contract',
'Breach of Contract',
500.00,
'01/01/2013-12/31/2013'),
('Contract',
'Breach of Contract',
300.00,
'01/01/2014-12/31/2014'),
('Employment',
'Discrimination',
500.00,
'01/01/2013-12/31/2013'),
('Employment',
'Discrimination',
300.00,
'01/01/2014-12/31/2014'),
('Employment',
'Research',
500.00,
'01/01/2013-12/31/2013'),
('Employment',
'Research',
300.00,
'01/01/2014-12/31/2014')
SELECT * FROM #tmpHoldingTable;
Yes, this is possible, but as you can see it is a bit complicated.
To make this a more generic answer, I have created my own DataSet, with simplified columns but more data:
select grp = 1, val = 100, dt = cast('01-jan-2015' as date)
union all select grp = 1, val = 110, dt = cast('01-jan-2015' as date)
union all select grp = 1, val = 200, dt = cast('02-jan-2015' as date)
union all select grp = 1, val = 210, dt = cast('02-jan-2015' as date)
union all select grp = 1, val = 300, dt = cast('03-jan-2015' as date)
union all select grp = 1, val = 310, dt = cast('03-jan-2015' as date)
union all select grp = 1, val = 400, dt = cast('04-jan-2015' as date)
union all select grp = 1, val = 410, dt = cast('04-jan-2015' as date)
union all select grp = 1, val = 500, dt = cast('05-jan-2015' as date)
union all select grp = 1, val = 510, dt = cast('05-jan-2015' as date)
union all select grp = 2, val = 220, dt = cast('02-jan-2015' as date)
union all select grp = 2, val = 230, dt = cast('02-jan-2015' as date)
union all select grp = 2, val = 320, dt = cast('03-jan-2015' as date)
union all select grp = 2, val = 330, dt = cast('03-jan-2015' as date)
union all select grp = 2, val = 420, dt = cast('04-jan-2015' as date)
union all select grp = 2, val = 430, dt = cast('04-jan-2015' as date)
Note that each grp / dt combination has two values, and that grp 1 is over a longer range for dt than grp 2.
I have created a simple Matrix based on this:
Since you are using SQL Server 2012, you can use the LookupSet function to get the First/Last values per row group.
The expression in the First row group TextBox is:
=Code.SumLookup(
LookupSet(
First(Fields!dt.Value, "grp").ToString & Fields!grp.Value.ToString
, Fields!dt.Value.ToString & Fields!grp.Value.ToString
, Fields!val.Value
, "DataSet1"
)
)
Based on my sample data, this is giving my required results:
Note that the second grp row has a narrower range than the first, but its first/last columns are independent for each group so are correct within each grp.
There are quite a few things going on here.
Custom code for aggregation of LookUpSet result
The LookupSet expression is wrapped in a Code.SumLookup custom function:
Function SumLookup(ByVal items As Object()) As Decimal
If items Is Nothing Then
Return Nothing
End If
Dim suma As Decimal = New Decimal()
suma = 0
For Each item As Object In items
suma += Convert.ToDecimal(item)
Next
Return suma
End Function
This is taken from the answer at this SO question.
This assumes that each matrix cell can be the sum of multiple values, so this needs to be summed up. LookupSet returns an array of values, which is aggregated by Code.SumLookup.
Details for LookupSet
Next, the LoopupSet expression itself:
LookupSet(
First(Fields!dt.Value, "grp").ToString & Fields!grp.Value.ToString
, Fields!dt.Value.ToString & Fields!grp.Value.ToString
, Fields!val.Value
, "DataSet1"
)
LookupSet takes the following parameters:
LookupSet(source_expression, destination_expression, result_expression, dataset)
In our expression, we want get all values from DataSet1 that match the first dt in the current grp scope.
For source_expression I use:
First(Fields!dt.Value, "grp").ToString & Fields!grp.Value.ToString
This gets the first dt in the row scope ("grp" is the name of the row group), then appends this to the current grp. This creates an expression to match to a similar expression when seeking in DataSet1.
i.e. destination_expression:
Fields!dt.Value.ToString & Fields!grp.Value.ToString
Finally, we specify we want Fields!val.Value as the result_expression and DataSet1 as the dataset parameter.
All matching Fields!val.Value values in DataSet1 are constructed into an array by LookupSet, then aggregated by Code.SumLookup.
Update expression for Last values
The expression for the Last TextBox is practically the same; just change First to Last:
=Code.SumLookup(
LookupSet(
Last(Fields!dt.Value, "grp").ToString & Fields!grp.Value.ToString
, Fields!dt.Value.ToString & Fields!grp.Value.ToString
, Fields!val.Value
, "DataSet1"
)
)
Get the Difference
Finally, to get the difference of these, simply subtract one expression from the other in the Difference TextBox, or even reference the ReportItems values:
=ReportItems!Last.Value - ReportItems!First.Value
Where Last and First are the names of the TextBoxes.
Conclusion
Obviously you will need to update for your specific case, but you can see that this can be done.
Is it worth doing this in your report? You can see there are many steps involved, and in general would be easier to address when generating the DataSet. But, if that is not an option hopefully this LookupSet approach is useful.
AFAIK this is not possible in SSRS alone. Believe me I've tried. Fortunately you have a SQL datasource so I would resolve this requirement there where you have (almost) unlimited powers to shape and manipulate data.
For example I would replace your final select with:
; WITH CTE_Base AS (
SELECT * FROM #tmpHoldingTable
)
, CTE_Test1 AS (
SELECT Test1
, ROW_NUMBER () OVER ( ORDER BY Test1 ) AS Column_Number_Test1
FROM CTE_Base
GROUP BY Test1
)
SELECT CTE_Base.*
, CTE_Test1.Column_Number_Test1
, CASE WHEN CTE_Test1.Column_Number_Test1 = 1
THEN Total
WHEN CTE_Test1.Column_Number_Test1 =
( SELECT MAX ( Column_Number_Test1 ) FROM CTE_Test1 )
THEN 0 - Total
ELSE 0
END AS [Difference]
FROM CTE_Base
INNER JOIN CTE_Test1
ON CTE_Base.Test1 = CTE_Test1.Test1
This adds a [Difference] column with a copy of [Total] for the 1st column and 0 - [Total] for the last column.
The SQL could probably be made more efficient, but hopefully breaking it into CTEs is easier to follow.
Then in the SSRS Designer you can add a [Difference] column outside the [Test1] column group and let it sum (default).
BTW your test data seems a bit simplistic - it will only produce 2 columns and all cells have values. But its great you posted DDL & DML - it made it easy to extend the data and code and test this.

How to get the last element by date of each "type" in LINQ or TSQL

Imagine to have a table defined as
CREATE TABLE [dbo].[Price](
[ID] [int] NOT NULL,
[StartDate] [datetime] NOT NULL,
[Price] [int] NOT NULL
)
where ID is the identifier of an action having a certain Price. This price can be updated if necessary by adding a new line with the same ID, different Price, and a more recent date.
So with a set of a data like
ID StartDate Price
1 01/01/2009 10
1 01/01/2010 20
2 01/01/2009 10
2 01/01/2010 20
How to obtain a set like the following?
1 01/01/2010 20
2 01/01/2010 20
In SQL, there are several ways to say it. Here's one that uses a subquery:
SELECT *
FROM Price p
WHERE NOT EXISTS (
SELECT *
FROM Price
WHERE ID = p.ID
AND StartDate > p.StartDate
)
This translates fairly trivially to LINQ:
var q = from p in ctx.Price
where !(from pp in ctx.Price
where pp.ID == p.ID
&& pp.StartDate > p.StartDate
select pp
).Any()
select p;
Or should I say, I think it does. I'm not in front VS right now, so I can't verify that this is correct, or that LINQ will be able to convert it to SQL.
Minor quibble: Don't use the name ID to store a non-unique value (the type, in this case). It's confusing.
Assuming ID & StartDate will be unique:
SELECT p.ID, p.StartDate, p.Price
FROM Price p
JOIN
(
SELECT ID, MAX(StartDate) AS LatestDate
FROM Price
GROUP BY ID
) p2 ON p.ID = p2.ID AND p.StartDate = p2.LatestDate
Since you tagged your question with LINQ to SQL, here is an LINQ query to express what you want:
from price in db.Prices
group price by price.Id into group
let maxDateInGroup = group.Max(g => g.StartDate)
let maxDatePrice = group.First(g => g.StartDate == maxDateInGroup)
select
{
Id = group.Key,
StartDate = maxDatePrice.StartDate,
Price = maxDatePrice.Price
};

Resources