SQL Server query to select local maximums - sql

I have this data. I need to get the lowest $ full rows for each person.
Amount Date Name
$123 Jun 1 Peter
$120 Jun 5 Peter
$123 Jun 5 Paul
$100 Jun 1 Paul
$220 Jun 3 Paul
The result of the SQl Server query should be:
$120 Jun 5 Peter
$100 Jun 1 Paul

SQL Server 2005+ Version
;WITH CTE AS
(
SELECT
Amount, [Date], Name,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY [Amount]) AS RowNum
FROM Table
)
SELECT *
FROM CTE
WHERE RowNum = 1
Alternative Version
SELECT t.Amount, t.[Date], t.Name
FROM
(
SELECT Name, MIN(Amount) AS MinAmount
FROM Table
GROUP BY Name
) m
INNER JOIN Table t
ON t.Name = m.Name
AND t.Amount = m.Amount

One way which works on SQL Server 7 and up
select t1.*
from(select min(amount) Minamount,name
from Yourtable
group by name) t2
join Yourtable t1 on t1.name = t2.name
and t1.amount = t2.Minamount
There are a couple of ways to solve this, see here: Including an Aggregated Column's Related Values

SELECT * FROM TableName T1 WHERE NOT EXISTS
(SELECT * FROM TableName T2
WHERE T2.Name = T1.Name AND T2.Amount < T1.Amount)
In the event of ties, both rows will be shown in this scenario.

Group on the person to get the lowest amount for each person, then join the table to get the date for each row:
select y.Amount, y.Date, y.Name
from (
select min(Amount), Name
from TheTable
group by Name
) x
inner join TheTable y on x.Name = y.Name and x.Amount = y.Amount
If the amount can exist on more than one date for a person, pick one of the dates, for example the first:
select y.Amount, min(y.Date), y.Name
from (
select min(Amount), Name
from TheTable
group by Name
) x
inner join TheTable y on x.Name = y.Name and x.Amount = y.Amount
group by y.Amount, y.Name

Not quite the most efficient possible, but simpler to read:
SELECT DISTINCT [Name], [Date], MIN([Amount]) OVER(PARTITION BY [Name])
FROM #Table

Related

Identify duplicates rows based on multiple columns

#SQL Experts,
I am trying to fetch duplicate records from SQL table where 1st Column and 2nd Column values are same but 3rd column values should be different.
Below is my table
ID NAME DEPT
--------------------
1 VRK CSE
1 VRK ECE
2 AME MEC
3 BMS CVL
From the above table , i am trying to fetch first 2 rows, below is the Query, suggest me why isn't give correct results.
SELECT A.ID, A.NAME, A.DEPT
FROM TBL A
INNER JOIN TBL B ON A.ID = B.ID
AND A.NAME = B.NAME
AND A.DEPT <> B.DEPT
Somehow I am not getting the expected results.
Your sample data does not make it completely clear what you want here. Assuming you want to target groups of records having duplicate first/second columns with all third column values being unique, then we may try:
SELECT ID, NAME, DEPT
FROM
(
SELECT ID, NAME, DEPT,
COUNT(*) OVER (PARTITION BY ID, NAME) cnt,
MIN(DEPT) OVER (PARTITION BY ID, NAME) min_dept,
MAX(DEPT) OVER (PARTITION BY ID, NAME) max_dept
FROM yourTable
) t
WHERE cnt > 1 AND min_dept = max_dept;
UPDATE
select *
from
(
select *,
COUNT(*) over (partition by id, [name]) cnt1,
COUNT(*) over (partition by id, [name], dept) cnt2
from dbo.T
) x
where x.cnt1 > 1 and x.cnt2 < x.cnt1;
For find duplicate column
select x.id, x.name, count(*)
from
(select distinct a.id, a.name, a.dept
from tab a) x
group by x.id, x.name
having count(*) > 1
If you want the original rows, I would just go for exists:
select t.*
from tbl t
where exists (select 1
from tbl t
where t2.id = t.id and t2.name = t.name and
t2.dept <> t.dept
);
If you just want the id/name pairs:
select t.id, t.name
from tbl t
group by t.id, t.name
having min(t.dept) <> max(t.dept);

Select rows with the same field values

How can I query only the records that show up twice in my table?
Currently my table looks something like this:
Number Date RecordT ReadLoc
123 08/13/13 1:00pm N Gone
123 08/13/13 2:00pm P Home
123 08/13/13 3:00pm N Away
123 08/13/13 4:00pm N Away
I need a query that will select the records that have the same 'Value' in the RecordT field and the same 'Value' in the ReadLoc field.
So my result for the above would show with the query:
Number Date RecordT ReadLoc
123 08/13/13 3:00pm N Away
123 08/13/13 4:00pm N Away
I was trying to do a subselect like this:
SELECT t.Number, t.Date, n.RecordT, n.ReadLoc
FROM Table1 t join Table2 n ON t.Number = n.Number
WHERE t.Number IN (SELECT t.Number FROM Table1 GROUP BY t.Number HAVING COUNT(t.Number) > 1 )
AND n.ReadLoc IN (SELECT n.ReadLoc FROM Table2 GROUP n.ReadLoc HAVING COUNT(n.ReadLoc) > 1 )
SELECT a.*
FROM Table1 a
JOIN (SELECT RecordT, ReadLoc
FROM Table1
GROUP BY RecordT, ReadLoc
HAVING COUNT(*) > 1
)b
ON a.RecordT = b.RecordT
AND a.ReadLoc = b.ReadLoc
SQL Fiddle
Shouldn't this work:
select *
from table1
where (RecordT, ReadLoc) in
(select RecordT, ReadLoc
from table1
group by RecordT, ReadLoc
having count(*) > 1)
The following can be taken as a base:
;with cte as (
select *, cnt = count(1) over (partition by RecordT, ReadLoc)
from TableName
)
select *
from cte
where cnt > 1
If your TableName is actually a view of two joined tables, try:
;with TableName as (
SELECT t.Number, t.Date, n.RecordT, n.ReadLoc
FROM Table1 t
join Table2 n ON t.Number = n.Number
),
cte as (
select Number, Date, RecordT, ReadLoc,
cnt = count(1) over (partition by RecordT, ReadLoc)
from TableName
)
select Number, Date, RecordT, ReadLoc
from cte
where cnt > 1 /* and RecordT='N' and ReadLoc='AWAY' */

SQL stored procedure to add up values and stop once the maximum has been reached

I would like to write a SQL query (SQL Server) that will return rows (in a given order) but only up to a given total. My client has paid me a given amount, and I want to return only those rows that are <= to that amount.
For example, if the client paid me $370, and the data in the table is
id amount
1 100
2 122
3 134
4 23
5 200
then I would like to return only rows 1, 2 and 3
This needs to be efficient, since there will be thousands of rows, so a for loop would not be ideal, I guess. Or is SQL Server efficient enough to optimise a stored proc with for loops?
Thanks in advance. Jim.
A couple of options are.
1) Triangular Join
SELECT *
FROM YourTable Y1
WHERE (SELECT SUM(amount)
FROM YourTable Y2
WHERE Y1.id >= Y2.id ) <= 370
2) Recursive CTE
WITH RecursiveCTE
AS (
SELECT TOP 1 id, amount, CAST(amount AS BIGINT) AS Total
FROM YourTable
ORDER BY id
UNION ALL
SELECT R.id, R.amount, R.Total
FROM (
SELECT T.*,
T.amount + Total AS Total,
rn = ROW_NUMBER() OVER (ORDER BY T.id)
FROM YourTable T
JOIN RecursiveCTE R
ON R.id < T.id
) R
WHERE R.rn = 1 AND Total <= 370
)
SELECT id, amount, Total
FROM RecursiveCTE
OPTION (MAXRECURSION 0);
The 2nd one will likely perform better.
In SQL Server 2012 you will be able to so something like
;WITH CTE AS
(
SELECT id,
amount,
SUM(amount) OVER(ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AS RunningTotal
FROM YourTable
)
SELECT *
FROM CTE
WHERE RunningTotal <=370
Though there will probably be a more efficient way (to stop the scan as soon as the total is reached)
Straight-forward approach :
SELECT a.id, a.amount
FROM table1 a
INNER JOIN table1 b ON (b.id <=a.id)
GROUP BY a.id, a.amount
HAVING SUM(b.amount) <= 370
Unfortunately, it has N^2 performance issue.
something like this:
select id from
(
select t1.id, t1.amount, sum( t2.amount ) s
from tst t1, tst t2
where t2.id <= t1.id
group by t1.id, t1.amount
)
where s < 370

How to filter out records grouped by date with a large date difference

I have some records, grouped by name and date.
I would like to find any records in a table that have a date difference between them larger than a week, from the most recent record.
Would this be possible to do with a cte?
I am thinking something along these lines (it is difficult to explain)
; with mycte as (
select *
from #GroupedRecords)
select *
from mycte a
join (select *
from #GroupedRecords) b on a.Name = b.Name
where datediff(day, a.DateCreated, b.DateCreated) > 7
For example:
Id Name Date
1 Foo 02/03/2010
2 Bar 23/02/2010
3 Ram 21/01/2010
4 Foo 29/02/2010
5 Foo 22/02/2010
6 Foo 05/12/2009
The results should be:
Id Name Date
1 Foo 02/03/2010
5 Foo 22/02/2010
6 Foo 05/12/2009
You can try:
SELECT id,
name,
DATE
FROM groupedrecords AS gr1
WHERE ( (SELECT MAX(DATE) AS md
FROM groupedrecords gr2
WHERE gr1.name = gr2.name) - gr1.DATE ) > 7;
Or probably better yet:
SELECT id,
name,
DATE
FROM groupedrecords AS gr1
INNER JOIN (SELECT name,
MAX(DATE) AS md
FROM groupedrecords AS gr2
GROUP BY name) AS q1
ON gr1.name = q1.name
WHERE ( q1.md - gr1.DATE ) > 7;
UPDATE: As suggested in the comments, here is a version that uses union to get the id with the max date per group AND the ids of those that are 7 days or older than the max date. I used a CTE for fun, it was not necessary. Note that if there is more than 1 ID that shares the max date in a group, this query will need to be modified-
WITH CTE
AS (SELECT name,
Max(date) AS MD
FROM Records
GROUP BY name)
SELECT R.ID,
R.name,
R.date
FROM CTE
INNER JOIN Records AS R
ON CTE.Name = R.Name
AND CTE.MD = R.date
UNION ALL
SELECT r1.id,
r1.name,
r1.DATE
FROM Records AS R1
INNER JOIN CTE
ON CTE.name = R1.name
WHERE ( CTE.md - R1.DATE ) > 7
ORDER BY name ASC,
date DESC
I wonder if this gets close to a solution:
; with tableWithRow as (
select *, row_number() over (order by name, date) as rowNum
from t
)
select t1.*, t2.id t2id, t2.name t2name, t2.date t2date, t2.rowNum t2rowNum
from tableWithRow t1
join tableWithRow t2
on t1.rowNum = t2.rowNum + 1 and t1.name = t2.name

Find duplicates, display each result in sql

I want to write something like this :
select t.id, t.name, from table t
group by t.name having count(t.name) > 1
To produce the following :
id name count
904834 jim 2
904835 jim 2
90145 Fred 3
90132 Fred 3
90133 Fred 3
For SQL Server 2005+, you can do the following:
SELECT *
FROM (SELECT id, Name, COUNT(*) OVER(PARTITION BY Name) [Count]
FROM table) t
WHERE [Count]>1
If you remove the ID column then you can get all the names that have multiple entries
select t.name
from table t
group by t.name
having count(t.name) > 1
For each name, if you want the minimum or maximum id you can do this
select t.id, t.name, min (t.id) as min_id, max (t.id) as max_id
from table t
group by t.name
having count(t.name) > 1
For each name, if you want all the ids that are duplicates, then you have to use a subquery
select t.id, t.name
from table t
where name in
(
select t1.name
from table t1
group by t1.name
having count(t1.name) > 1
)
Just join the table to a subquery pulling the count for each name
SELECT t.ID, t.Name, d.Count
FROM #MyTable t
JOIN
(
SELECT name, COUNT(*) as Count
FROM #MyTable
GROUP BY Name
HAVING COUNT(*) > 1
) D
ON t.Name = d.Name
Assuming mysql (when I wrote the answer, I do not think the person specified the dbms)
SELECT t.id, t.name, (SELECT COUNT(t2.name) FROM test t2 ) AS t_count
FROM test t
HAVING t_count > 1;
Similar to previous answers with less code. Tested on SQL Server 2008:
SELECT t.id, t.name,COUNT(*)
FROM table t
GROUP BY t.id, t.name
HAVING COUNT(t.id) > 1
Please Check it once .... in SQL Server 2008
SELECT t.id,
t.NAME,
Count(t.id) AS duplicate id,
count(t.NAME) AS duplicate names
FROM t
GROUP BY t.id,
t.NAME
HAVING count(t.NAME) > 1