Select Max Date in either case statement or where clause - tsql

I have 2 columns 1) id (int) and 2) Date. I want to select id on max date. Grouping results in returning both id's/more than one id. Instead i only want to retrieve id on max date .

I am sure there is easier way to do this however below should work fine.
-- create sample data
create table #temp(ID int, courseID int, end_date datetime)
go
insert into #temp
select 1 , 11 , getdate()
union
select 1, 12, getdate()-20
union
select 1, 13, getdate()-40
union
select 2, 13, getdate()-70
union
select 2, 14, getdate()-80
-- create temp table to calculate correct date
select id, max(end_date) as correctDate
into #temp2
from #temp
group by id
-- final desired outup
select #temp2.id , #temp.courseID
from #temp2
inner join #temp
on #temp2.id = #temp.id
and #temp2.correctDate = #temp.end_date
-- drop temp tables
drop table #temp
drop table #temp2
give me a shout if you have any questions

Simpler alternative
-- create sample data
create table #temp(id int, courseID int, end_date datetime)
go
insert into #temp
select
1 , 11 , getdate()
union
select
1, 12, getdate()-20
union
select
1, 13, getdate()-40
union
select
2, 13, getdate()-70
union
select
2, 14, getdate()-80
SELECT * FROM(
SELECT DENSE_RANK() OVER(PARTITION BY id ORDER BY end_date DESC ) sira, id,courseID,end_date FROM #temp
) t WHERE sira = 1
-- drop temp tables
drop table #temp
drop table #temp2

Related

Delete duplicate rows with different values in columns

I didn't find my case on the Internet. Tell me how i can delete duplicates if the values are in different columns.
I have a table with a lot of values, for example:
|Id1|Id2|
|89417980|89417978|
|89417980|89417979|
|89417978|89417980|
|89417979|89417980|
I need to exclude duplicates and leave in the answer only:
|Id1|Id2|
|89417980|89417978|
|89417980|89417979|
min/max does not work here, as the values may be different.
I tried to union/join tables on a table/exclude results with temporary tables, but in the end I come to the beginning.
Assuming id1 and id2 are primary keys columns you could try this
DECLARE #tbl table (id1 int, id2 int )
INSERT INTO #tbl
SELECT 89417980, 89417978
UNION SELECT 89417980, 89417979
UNION SELECT 89417978, 89417980
UNION SELECT 89417979, 89417980
SELECT * FROM #tbl
;WITH CTE AS (--Get comparable value as "cs"
SELECT
IIF(id1 > id2, CHECKSUM(id1, id2), CHECKSUM(id2,id1)) as cs
, id1
, id2
, ROW_NUMBER() OVER (order by id1, id2) as rn
FROM #tbl
)
, CTE2 AS ( --Get rows to keep
SELECT MAX (rn) as rn
FROM CTE
GROUP BY cs
HAVING COUNT(*) > 1
)
DELETE tbl -- Delete all except the rows to keep
FROM #tbl tbl
WHERE NOT EXISTS(SELECT 1
FROM CTE2
JOIN CTE ON CTE.rn = CTE2.rn
WHERE CTE.id1 = tbl.id1
AND CTE.id2 = tbl.id2
)
SELECT * FROM #tbl

Create New Rows based on valid to and valid from dates

I have a table that has account number, end of month valid from and end of month valid to columns.
What I need is a table that has account number and a column that has all the end of month dates of when the account was live, inclusive of end of month valid to. The Current Table looks like this
New table will need to be like this
I have tried using a calendar table and an CTE table type query but have had no success.
Any help would be great.
This can be achieved using Using multiple comma separated CTEs in a statement
Query
with t0 (i) AS (select 0 union all select 0 union all select 0 union all select 0 union all select 0 union all select 0),
t1 (i) AS (select 0 from t0 a inner join t0 b on a.i = b.i),
n (i) AS (select row_number()over(order by i) from t1),
Account_details (Account_number,valid_from,valid_to,mth,Live_date)As(
select Account_number,valid_from,valid_to, datediff(month,valid_from,valid_to ) mth, valid_from"Live_date"
from tbl1
union all
select Account_number,valid_from,valid_to, datediff(month,valid_from,valid_to ) mth, EOMONTH (dateadd(month,n.i,valid_from)) "Live_date"
from tbl1
inner join n on 1=1 and n.i between 1 and datediff(month,valid_from,valid_to )
)
select *
from Account_details
where Account_details.Account_number =1
order by Account_details.Account_number
Output
CTE Table t0, t1 and n will generate numbers. This is a best way to generate rows without any data.
Then the CTE table Account_details is used to pull data from the table.
Based on sql on the msdn thread how to get month end date between two dates.
DECLARE #Old AS Table (AccountNumber INT, ValidFrom DATE, ValidTo DATE)
DECLARE #New AS Table (AccountNumber INT, LiveDate DATE)
INSERT INTO #old
SELECT 1, '20130630', '20131130' UNION ALL
SELECT 2, '20130630', '20131231' UNION ALL
SELECT 3, '20120430', '20120531' UNION ALL
SELECT 4, '20170331', '20171130'
SELECT TOP 100 * FROM #old
DECLARE #AccountNumber INT, #ValidFrom DATE, #ValidTo DATE
DECLARE #Cursor CURSOR
SET #Cursor = CURSOR FOR
SELECT AccountNumber, ValidFrom, ValidTo
FROM #old
OPEN #Cursor
FETCH NEXT INTO #Cursor FROM #AccountNumber, #ValidFrom, #ValidTo
WHILE ##FETCH_STATUS = 0
BEGIN
;WITH cteEndMonthDates (MonthEndDate)
AS
(
SELECT eomonth(#ValidFrom) AS MonthEndDate
UNION ALL
SELECT eomonth( dateadd(day, 1, MonthEndDate)) AS MonthEndDate
FROM cteEndMonthDates
WHERE MonthEndDate < eomonth(#ValidTo)
)
INSERT INTO #new (AccountNumber, LiveDate)
SELECT #AccountNumber, MonthEndDate
FROM cteEndMonthDates
FETCH NEXT FROM #Cursor INTO #AccountNumber, #ValidFrom, #ValidTo
END
CLOSE #Cursor
DEALLOCATE #Cursor
SELECT * FROM #New
Edit: Or without the cursor
DECLARE #Old AS Table (AccountNumber INT, ValidFrom DATE, ValidTo DATE)
DECLARE #New AS Table (AccountNumber INT, LiveDate DATE)
INSERT INTO #old
SELECT 1, '20130630', '20131130' UNION ALL
SELECT 2, '20130630', '20131231' UNION ALL
SELECT 3, '20120430', '20120531' UNION ALL
SELECT 4, '20170331', '20171130' UNION ALL
SELECT 5, '20180430', '20190131' UNION ALL
SELECT 6, '20160430', '20180531'
SELECT TOP 100 * FROM #old
;WITH cteEndMonthDates (AccountNumber, MonthEndDate)
AS
(
SELECT AccountNumber, eomonth(ValidFrom) AS MonthEndDate
FROM #Old
UNION ALL
SELECT x.AccountNumber, eomonth( dateadd(day, 1, MonthEndDate)) AS MonthEndDate
FROM cteEndMonthDates x
JOIN #Old o ON o.AccountNumber = x.AccountNumber
WHERE MonthEndDate < eomonth(ValidTo)
)
SELECT AccountNumber, MonthEndDate
FROM cteEndMonthDates
order by AccountNumber, MonthEndDate
This should work.
;WITH Span AS (
SELECT
AccountNumber,
ValidFrom AS Valid
FROM dbo.Input
UNION ALL
SELECT
AccountNumber,
DATEADD(DAY, 1, Span.Valid) AS Valid
FROM Span
WHERE DATEADD(DAY, 1, Span.Valid) <= (SELECT ValidTo FROM dbo.Input WHERE AccountNumber = Span.AccountNumber)
)
SELECT * FROM Span
ORDER BY Span.AccountNumber, Span.Valid
OPTION (MAXRECURSION 0);

T-SQL Average of Table Subset

I have a table (in SQL Server 2005) of daily weather data for a single location which includes these columns:
LogDate DATETIME
HighTemp INT
Temp6MonthHighAverage INT
LogDate and HighTemp have data. HighTemp6MonthAverage will be populated with, as the name suggests, the average high temperature for the 6 months ending in LogDate.
There are similar requirements for LowTemp, as well as humidity and several other items, for data spanning decades.
I find myself thinking in circles. Can I derive this average for each row in an UPDATE statement using set operations, or do I need to implement a solution with cursors? I will appreciate any suggestions.
-- select
select HighTemp, LogDate,(select AVG(HighTemp)
from tbl where
DATEDIFF(MONTH, LogDate, t1.LogDate) between 0 and 6)
from tbl t1
-- update
update t1 set Temp6MonthHighAverage = (select AVG(HighTemp)
from tbl where
DATEDIFF(MONTH, LogDate, t1.LogDate) between 0 and 6)
from tbl t1
You can certainly do this with a simple UPDATE:
UPDATE table SET Temp6MonthHighAverage =
(SELECT AVG(HighTemp) FROM table t2 WHERE
t2.LogDate <= table.LogDate
AND t2.LogDate > DATEADD(m, -6, table.LogDate)
)
To avoid re-calculating constantly (since the past will not change), just add a WHERE Temp6MonthHighAverage IS NULL at the end and the same UPDATE can be run as needed to fill in the gaps as new dates are added.
Have a look at something like this
DECLARE #Table TABLE(
LogDate DATETIME,
HighTemp INT,
Temp6MonthHighAverage INT
)
INSERT INTO #Table SELECT '01 Jan 2000', 15, NULL
INSERT INTO #Table SELECT '01 May 2000', 14, NULL
INSERT INTO #Table SELECT '01 Jun 2000', 13, NULL
INSERT INTO #Table SELECT '01 Jul 2000', 12, NULL
INSERT INTO #Table SELECT '01 Dec 2000', 17, NULL
SELECT *
FROM #Table
;WITH DistinctDates AS (
SELECT DATEADD(month,-6,LogDate) StartDate,
LogDate EndDate,
HighTemp
FROM #Table
)
, Aggregates AS (
SELECT dd.EndDate LogDate,
dd.HighTemp,
MAX(t.HighTemp) Temp6MonthHighAverage
FROM DistinctDates dd LEFT JOIN
#Table t ON t.LogDate BETWEEN dd.StartDate AND dd.EndDate
GROUP BY dd.EndDate,
dd.HighTemp
)
UPDATE #Table
SET Temp6MonthHighAverage = a.Temp6MonthHighAverage
FROM #Table t INNER JOIN
Aggregates a ON t.LogDate = a.LogDate
SELECT *
FROM #Table

one column split to more column sql server 2008?

Table name: Table1
id name
1 1-aaa-14 milan road
2 23-abcde-lsd road
3 2-mnbvcx-welcoome street
I want the result like this:
Id name name1 name2
1 1 aaa 14 milan road
2 23 abcde lsd road
3 2 mnbvcx welcoome street
This function ought to give you what you need.
--Drop Function Dbo.Part
Create Function Dbo.Part
(#Value Varchar(8000)
,#Part Int
,#Sep Char(1)='-'
)Returns Varchar(8000)
As Begin
Declare #Start Int
Declare #Finish Int
Set #Start=1
Set #Finish=CharIndex(#Sep,#Value,#Start)
While (#Part>1 And #Finish>0)Begin
Set #Start=#Finish+1
Set #Finish=CharIndex(#Sep,#Value,#Start)
Set #Part=#Part-1
End
If #Part>1 Set #Start=Len(#Value)+1 -- Not found
If #Finish=0 Set #Finish=Len(#Value)+1 -- Last token on line
Return SubString(#Value,#Start,#Finish-#Start)
End
Usage:
Select ID
,Dbo.Part(Name,1,Default)As Name
,Dbo.Part(Name,2,Default)As Name1
,Dbo.Part(Name,3,Default)As Name2
From Dbo.Table1
It's rather compute-intensive, so if Table1 is very long you ought to write the results to another table, which you could refresh from time to time (perhaps once a day, at night).
Better yet, you could create a trigger, which automatically updates Table2 whenever a change is made to Table1. Assuming that column ID is primary key:
Create Table Dbo.Table2(
ID Int Constraint PK_Table2 Primary Key,
Name Varchar(8000),
Name1 Varchar(8000),
Name2 Varchar(8000))
Create Trigger Trigger_Table1 on Dbo.Table1 After Insert,Update,Delete
As Begin
If (Select Count(*)From Deleted)>0
Delete From Dbo.Table2 Where ID=(Select ID From Deleted)
If (Select Count(*)From Inserted)>0
Insert Dbo.Table2(ID, Name, Name1, Name2)
Select ID
,Dbo.Part(Name,1,Default)
,Dbo.Part(Name,2,Default)
,Dbo.Part(Name,3,Default)
From Inserted
End
Now, do your data manipulation (Insert, Update, Delete) on Table1, but do your Select statements on Table2 instead.
The below solution uses a recursive CTE for splitting the strings, and PIVOT for displaying the parts in their own columns.
WITH Table1 (id, name) AS (
SELECT 1, '1-aaa-14 milan road' UNION ALL
SELECT 2, '23-abcde-lsd road' UNION ALL
SELECT 3, '2-mnbvcx-welcoome street'
),
cutpositions AS (
SELECT
id, name,
rownum = 1,
startpos = 1,
nextdash = CHARINDEX('-', name + '-')
FROM Table1
UNION ALL
SELECT
id, name,
rownum + 1,
nextdash + 1,
CHARINDEX('-', name + '-', nextdash + 1)
FROM cutpositions c
WHERE nextdash < LEN(name)
)
SELECT
id,
[1] AS name,
[2] AS name1,
[3] AS name2
/* add more columns here */
FROM (
SELECT
id, rownum,
part = SUBSTRING(name, startpos, nextdash - startpos)
FROM cutpositions
) s
PIVOT ( MAX(part) FOR rownum IN ([1], [2], [3] /* extend the list here */) ) x
Without additional modifications this query can split names consisting of up to 100 parts (that's the default maximum recursion depth, which can be changed), but can only display no more than 3 of them. You can easily extend it to however many parts you want it to display, just follow the instructions in the comments.
select T.id,
substring(T.Name, 1, D1.Pos-1) as Name,
substring(T.Name, D1.Pos+1, D2.Pos-D1.Pos-1) as Name1,
substring(T.Name, D2.Pos+1, len(T.name)) as Name2
from Table1 as T
cross apply (select charindex('-', T.Name, 1)) as D1(Pos)
cross apply (select charindex('-', T.Name, D1.Pos+1)) as D2(Pos)
Testing performance of suggested solutions
Setup:
create table Table1
(
id int identity primary key,
Name varchar(50)
)
go
insert into Table1
select '1-aaa-14 milan road' union all
select '23-abcde-lsd road' union all
select '2-mnbvcx-welcoome street'
go 10000
Result:
if you always will have 2 dashes, you can do the following by using PARSENAME
--testing table
CREATE TABLE #test(id INT, NAME VARCHAR(1000))
INSERT #test VALUES(1, '1-aaa-14 milan road')
INSERT #test VALUES(2, '23-abcde-lsd road')
INSERT #test VALUES(3, '2-mnbvcx-welcoome street')
SELECT id,PARSENAME(name,3) AS name,
PARSENAME(name,2) AS name1,
PARSENAME(name,1)AS name2
FROM (
SELECT id,REPLACE(NAME,'-','.') NAME
FROM #test)x
if you have dots in the name column you have to first replace them and then replace them back to dots in the end
example, by using a tilde to substitute the dot
INSERT #test VALUES(3, '5-mnbvcx-welcoome street.')
SELECT id,REPLACE(PARSENAME(name,3),'~','.') AS name,
REPLACE(PARSENAME(name,2),'~','.') AS name1,
REPLACE(PARSENAME(name,1),'~','.') AS name2
FROM (
SELECT id,REPLACE(REPLACE(NAME,'.','~'),'-','.') NAME
FROM #test)x

Select count from second table based on initial select

Table 1:
AccountId, ReferenceId, Name, (lots of other columns)
Table 2:
AccountId, ReferenceId, (other columns)
How can I do a select to get the following:
AccountId, ReferenceId, [Count(*) in Table2 where accountId and reference ID match.]
1, AB, 1
1, AC, 0
2, AD, 4
2, EF, 0
etc
Guessing a join, but that gives me values, not a count?
Tried adding a count, but get errors?
SELECT T1.AccountId,
T1.ReferenceId,
COUNT(T2.ReferenceId) AS Cnt
FROM Table1 T1
LEFT JOIN Table2 T2
ON T1.AccountId = T2.AccountId
AND T1.ReferenceId = T2.ReferenceId
GROUP BY T1.AccountId,
T1.ReferenceId
Something like:
SELECT t1.AccountId, t1.ReferenceId, COUNT(t2.AccountId)
FROM Table1 t1
LEFT JOIN Table2 t2 ON t1.AccountId = t2.AccountId AND
t1.ReferenceId = t2.ReferenceId
GROUP BY t1.AccountId, t1.ReferenceId
should work. The trick is to group by both key values so you can aggregate over other values. In this case you want to simply count values from other rows (you could also sum or average values from the grouped-by rows.).
sample data
declare #tbl1 table (AccountId INT, ReferenceId int, Name varchar(20))
declare #tbl2 table (AccountId INT, ReferenceId int)
insert into #tbl1 select 1, 10, 'White'
insert into #tbl1 select 2, 20, 'Green'
insert into #tbl1 select 3, 30, 'Black'
insert into #tbl1 select 3, 40, 'Red'
insert into #tbl2 select 1, 10
insert into #tbl2 select 1, 10
insert into #tbl2 select 2, 20
insert into #tbl2 select 3, 30
Query
select t.AccountId, t.ReferenceId, t.Name
,(select COUNT(*) from #tbl2 t2
where t.AccountId = t2.AccountId
and t.ReferenceId = t.ReferenceId) as countt
from #tbl1 t
SELECT t1.AccountId, t1.ReferenceId, COUNT(t2.AccountId)
FROM Table1 t1 LEFT JOIN Table2 t2
ON (t1.AccountId=t2.AccountId AND t1.ReferenceId=t2.ReferenceId)
GROUP BY Table1.AccountId, Table1.ReferenceId