How can I query only the latest iteration? - sql

I'm wondering how to query the latest iteration of a field in my results.
For example, I write a query that'll return me this list of IDs:
132GBD00
132GBD01
59RTW900
59RTW901
59RTW902
376BH200
376BH201
376BH202
376BH203
5789DD00
I'd like the query to to return this result:
132GBD01
59RTW902
376BH203
5789DD00
Notice that the similar IDs differ in only the last two characters. 00 being the original and 01, 02, etc coming after. If I write a query like:
SELECT memid
FROM MEMBERID
WHERE MEMBERID = ???
The table has dates, but I cannot search for distinct memid and filter by a max(date) because sometimes the latest iteration date is NULL. I'm trying to see if it's possible to look at a list of IDs and filter by the last two characters in the ID to see which is greater and return that.

Apparently, the last two numbers are sequence numbers. You can get the most recent one with a group by:
select max(memid) as memid
from members
group by left(memid, len(memid) - 2);
If you wanted other columns, then you would use row_number() instead.

Try this
WITH cte AS (SELECT Memid
, ROW_NUMBER() OVER (PARTITION BY LEFT(Memid, LEN(Memid) - 2) ORDER BY memid DESC) AS Rownum
FROM MEMBERID
)
SELECT Memid
FROM cte
WHERE Rownum = 1;

you can use row_number as below:
Select top(1) with ties * from Members
Order by Row_Number() over (partition by SUBSTRING(memid, 1, len(memid)-2) order by convert(int,substring(memid, len(memid)-1, 2)) desc)
Or outer query as below:
Select MemId from (
Select *, RowN = Row_Number() over (partition by SUBSTRING(memid, 1, len(memid)-2) order by convert(int,substring(memid, len(memid)-1, 2)) desc)
from Members
) a Where a.RowN = 1
With other columns as well

I created a temp table using your data. Here is a pretty simple way to do it:
CREATE TABLE #Values (
SomeValue varchar(20)
);
INSERT INTO #Values
SELECT '132GBD00';
INSERT INTO #Values
SELECT '132GBD01';
INSERT INTO #Values
SELECT '59RTW900';
INSERT INTO #Values
SELECT '59RTW901';
INSERT INTO #Values
SELECT '59RTW902';
INSERT INTO #Values
SELECT '376BH200';
INSERT INTO #Values
SELECT '376BH201';
INSERT INTO #Values
SELECT '376BH202';
INSERT INTO #Values
SELECT '376BH203';
INSERT INTO #Values
SELECT '5789DD00';
SELECT DISTINCT
LAST_VALUE(SUBSTRING(SomeValue, 1, 6)) OVER (PARTITION BY SUBSTRING(SomeValue, 1, 6) ORDER BY SomeValue) AS LasID
FROM #Values

Related

Finding a random sample of unique data across multiple columns - SQL Server

Given a set of data in a SQL Server database with the following columns
AccountID, UserID_Salesperson, UserID_Servicer1, UserID_Servicer2
All three columns are primary keys from the same users table. I need to find a random sample that will include every UserID available in all three columns no matter the position while guaranteeing the fewest unique AccountID's possible.
--SET UP TEST DATA
CREATE TABLE MY_TABLE
(
AccountID int,
UserID_Salesperson int,
UserID_Servicer1 int,
UserID_Servicer2 int
)
INSERT INTO MY_TABLE (AccountID, UserID_Salesperson, UserID_Servicer1, UserID_Servicer2)
VALUES (12345, 1, 1, 2)
INSERT INTO MY_TABLE (AccountID, UserID_Salesperson, UserID_Servicer1, UserID_Servicer2)
VALUES (12346, 3, 2, 1)
INSERT INTO MY_TABLE (AccountID, UserID_Salesperson, UserID_Servicer1, UserID_Servicer2)
VALUES (12347, 4, 3, 1)
INSERT INTO MY_TABLE (AccountID, UserID_Salesperson, UserID_Servicer1, UserID_Servicer2)
VALUES (12348, 1, 2, 3)
--VIEW THE NEW TABLE
SELECT * FROM MY_TABLE
--NORMALIZE DATA (Unique List of UserID's)
SELECT DISTINCT MyDistinctUserIDList
FROM
(SELECT UserID_Salesperson as MyDistinctUserIDList, 'Sales' as Position
FROM MY_TABLE
UNION
SELECT UserID_Servicer1, 'Service1' as Position
FROM MY_TABLE
UNION
SELECT UserID_Servicer2, 'Service2' as Position
FROM MY_TABLE) MyDerivedTable
--NORMALIZED DATA
SELECT *
FROM
(SELECT AccountID, UserID_Salesperson as MyDistinctUserIDList, 'Sales' as Position
FROM MY_TABLE
UNION
SELECT AccountID, UserID_Servicer1, 'Service1' as Position
FROM MY_TABLE
UNION
SELECT AccountID, UserID_Servicer2, 'Service2' as Position
FROM MY_TABLE) MyDerivedTable
DROP TABLE MY_TABLE
For this example table, I could select AccountID (12347 and 12348) OR (12347 and 12346) to get the least accounts with all users.
My current solution is inefficient and can make mistakes. I currently select a random AccountID, insert the data into a temp table and try to find the next insert from something I have not already put in the temp table. I loop through the records until it finds something not used beforeā€¦ and after a few thousand loops it will give up and select any record.
I don't know how you guarantee the fewest account ids, but you can get one row per user id using:
select t.*
from (select t.*,
row_number() over (partition by UserId order by newid()) as seqnum
from my_table t cross apply
(values (t.UserID_Salesperson), (t.UserID_Servicer1), (t.UserID_Servicer2)
) v(UserID)
) t
where seqnum = 1;
Your original table doesn't have a primary key. Assuming that there is one row per account, you can dedup this so it doesn't have duplicate accounts:
select top (1) with ties t.*
from (select t.*,
row_number() over (partition by UserId order by newid()) as seqnum
from my_table t cross apply
(values (t.UserID_Salesperson), (t.UserID_Servicer1), (t.UserID_Servicer2)
) v(UserID)
) t
where seqnum = 1
order by row_number() over (partition by accountID order by accountID);

How To Create Duplicate Records depending on Column which indicates on Repetition

I've got a table which consisting aggregated records, and i need to Split them according to specific column ('Shares Bought' like in the example below), as Follow:
Original Table:
Requested Table:
Needless to say, that there are more records like that in the table and i need an automated query (not manual insertions),
and also there are some more attributes which i will need to duplicate (like the field 'Date').
You would need to first generate_rows with increasing row_number and then perform a cross join with your table.
Eg:
create table t(rowid int, name varchar(100),shares_bought int, date_val date)
insert into t
select *
from (values (1,'Dan',2,'2018-08-23')
,(2,'Mirko',1,'2018-08-25')
,(3,'Shuli',3,'2018-05-14')
,(4,'Regina',1,'2018-01-19')
)t(x,y,z,a)
with generate_data
as (select top (select max(shares_bought) from t)
row_number() over(order by (select null)) as rnk /* This would generate rows starting from 1,2,3 etc*/
from sys.objects a
cross join sys.objects b
)
select row_number() over(order by t.rowid) as rowid,t.name,1 as shares_bought,t.date_val
from t
join generate_data gd
on gd.rnk <=t.shares_bought /* generate rows up and until the number of shares bought*/
order by 1
Here is a db fiddle link
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=5736255585c3ab2c2964c655bec9e08b
declare #t table (rowid int, name varchar(100), sb int, dt date);
insert into #t values
(1, 'Dan', 2, '20180823'),
(2, 'Mirco', 1, '20180825'),
(3, 'Shuli', 3, '20180514'),
(4, 'Regina', 1, '20180119');
with nums as
(
select n
from (values(1), (2), (3), (4)) v(n)
)
select t.*
from #t t
cross apply (select top (t.sb) *
from nums) a;
Use a table of numbers instead of CTE nums or add there as many values as you can find in Shares Bought column.
Other option is to use recursive cte :
with t as (
select 1 as RowId, Name, ShareBought, Date
from table
union all
select RowId+1, Name, ShareBought, Date
from t
where RowId <= ShareBought
)
select row_number() over (order by name) as RowId,
Name, 1 as ShareBought, Date
from t;
If the sharebought not limited to only 2 or 3 then you would have to use option (maxrecursion 0) query hint as because by default it is limited to only 100 sharebought.

Auto increment the column value by 1 conditionally in select query in sql

I am having input table like below:
and in a select query without alteration of table need an output like:
drop table if exists T;
create table T(id int, nm nvarchar(10))
GO
insert T(id, nm) values (1,'r'),(2,'r'),(3,null),(4,'r')
SELECT * FROM T
GO
-- solution:
select
id, nm,
CASE WHEN nm is not null then count(nm) over (order by id) ELSE NULL END
from T
GO
compare execution plan of all solutions (using SQL 2017) :-)
My solution 21%; LukStorms solution 38%; Ian-Fogelman solution 41%
Choose your solution after you test in your specific server!
You can calculate a row_number partitioned on whether "nm" is null, then only show the calculated row_number when "nm" is not null.
Example snippet:
declare #T table (id int identity(1,1) primary key, nm varchar(8));
insert into #T (nm) values ('R'),('R'),(null),('R');
select *,
iif(nm is null,null,row_number() over (partition by iif(nm is null,1,0) order by id)) as [Count]
from #T
order by id
;WITH CTE AS
(
SELECT ID,
ROW_NUMBER() OVER(PARTITION BY 1 ORDER BY ID) AS [COUNT]
FROM [TABLE] WHERE NM IS NOT NULL
)
SELECT S.ID,
S.NM,
CTE.[COUNT]
FROM [TABLE] AS S LEFT JOIN CTE AS CTE ON S.ID = CTE.ID
You can start with a CTE that adds a ROW_NUMBER() column and filters out rows WHERE 'nm' IS NULL.
Then SELECT from your table and OUTER JOIN to the CTE, using the ROW_NUMBER column to populate your Count column.

Selecting entries with biggest value less than list of values

Suppose my table structure as follows:
id | Word
---|-----
1 | a
2 | aa
. | ..
I have a list of id's like this:
(...,900, 1000, 2000, 3000, 4000,....)
I want to find the biggest id less than each id in the above list.
My table id's is not necessarily consecutive and there are some gaps between two successive id's, for example:
(...,889,900,950,952,997,1000,1001,1010,1920,2000,2990,3000,3500,4000,...)
The expected result according to the above list would be:
(889, 997, 1920, 2990, 3500,...)
How do i achieve desired results?
Use a common table expression and ROW_NUMBER()
;WITH cte AS(
SELECT *, ROW_NUMBER() OVER (ORDER BY ID) rowNum
FROM example)
SELECT ID, word
FROM cte
WHERE rowNum IN (
SELECT (rowNum - 1)
FROM cte
WHERE ID IN ('900','1000','2000','3000','4000'))
--WHERE ID IN (SELECT ID FROM <tableWithIDs>))
If you already have all of the ID you are looking for in another table, you would instead use the commented portion of my answer instead of the hardcoded IN list.
This will work only if the ID you are looking for exists in the table. So, as noted in a comment below if you were searching for 1001 you would not get 997, unless 1001 existed in the table (meaning, if it existed it would get a rowNum value and could be used to decrement in the subquery)
[DEMO HERE]
The following is another way to just see what the previous ID is for each row:
SELECT *, LEAD(ID,1) OVER(ORDER BY ID DESC) PreviousID
FROM example
ORDER BY ID
I would simply do:
select v.val, t.*
from (values (900), (1000), (2000), (3000), (4000) ) v(val) outer apply
(select top 1 t.*
from t
where t.id < v.val
order by t.id desc
) t;
This allows you to see the value on each of the rows. That is probably important because SQL result sets are unordered and it will not be obvious which value goes with which row.
EDIT:
If you know the row numbers are in the table, the most performance solution is probably:
select t.*
from (select t.*, lead(id) over (order by id) as next_id
from t
) t
where next_id in ( . . . );
This should work and I think it will be fairly efficient.
declare #V table (num int primary key);
insert into #V values (800), (889), (900), (997), (1000), (1910), (1920), (2000), (2990), (3000), (3500), (4000);
declare #T table (num int primary key);
insert into #T values (800), (900), (1000), (1200), (2000), (3000), (4000);
select tt.vP
from ( select t.num as t, v.num as v
, LAG(v.num) over (order by v.num) as vP
from #V v
left join #T t
on v.num = t.num
) tt
where tt.t is not null
and tt.vP is not null
order by tt.vP
Not clear how you want it to behave
select t.num
, (select max(v.num) from #V v where v.num < t.num) as prior
from #T t

Getting top 2 rows in each group without row_number() in SQL Server

I am looking for a simple query to get result of 2 rows with latest invoice date in each group. Although this task can be accomplished by a row_number() that you can see in below code ,I need an alternative to this with minimum complexity.
Code :
create table #tt
(
id int,
invoiceDT datetime
)
insert into #tt
values(1,'01-01-2016 00:12'),(1,'01-02-2016 06:16'),(1,'01-01-2016 00:16')
,(2,'01-01-2016 01:12'),(2,'04-02-2016 06:16'),(2,'01-06-2016 00:16')
select *
from (
SELECT id,invoiceDT,row_number() over(partition by id order by invoiceDT desc) as rownum
FROM #tt
)tmp
where rownum <=2
I need same result that is returned by above query
Please suggest an alternative.
Strange request, but here you go:
WITH CTE as
(
SELECT distinct id FROM #tt t1
)
SELECT x.*
FROM CTE
CROSS APPLY
(
SELECT top 2 *
FROM #tt
WHERE CTE.id = id
ORDER BY invoiceDT desc
) x