Select all second highest values only from temp table - sql-server

Using T-SQL (SQL Server 2008 R2), I'm trying to list only the rows with the second highest value in a particular column from a temp table and then place the results into a new temp table. The PK is the ID, which can have increasing version numbers and then unique codes.
Example:
ID | Name| Version | Code
------------------------
1 | A | 1 | 10
1 | A | 2 | 20
1 | A | 3 | NULL
2 | B | 1 | 40
2 | B | 2 | 50
2 | C | 1 | 60
The desired outcome of the query is
ID | Version | Code
------------------------
1 | 2 | 20
2 | 1 | 40
To achieve this I need the below query to be adapted to pull the second highest value as long as the result gives a version number greater than 1. These results come from a temp table and will then be placed into a final results temp table. EDIT: Please note this will be applied over 33000 rows of data so I would prefer something neater than INSERT VALUES. Thanks.
Current query:
SELECT
ID
,Version
,Code
INTO
#table2
FROM
#table1
SELECT *
FROM #table2
WHERE Version > 1
ORDER BY ID asc
DROP TABLE #table1
DROP TABLE #table2
I have tried running the where clause WHERE Version < (SELECT MAX(VERSION) FROM #TABLE 2) but this has no effect, presumably due to the unique code values and in any case wouldn't work where I have more than 3 Versions.
Ideas would be gratefully received.
Thanks in advance.

i HAVE TEST THE BELOW CODE AND IT IS GIVING OUTPUT AS PER The YOUR desired outcome of the query is
SELECT ID,Name,[Version],Code
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY NAME ORDER BY [Version] DESC) AS RNK,*
FROM
(
SELECT 1 ID, 'A' Name ,1 [Version] ,10 Code
UNION ALL
SELECT 1, 'A', 2 ,20
UNION ALL
SELECT 1, 'A', 3 ,30
UNION ALL
SELECT 1, 'A', 4 ,NULL
UNION ALL
SELECT 2, 'B', 1 ,40
UNION ALL
SELECT 2, 'B', 2 ,50
UNION ALL
SELECT 2, 'C', 1 ,60
)B
)BASE
WHERE RNK =2

If your primary key is only ID, you have duplicate rows. So I assume your primary key is something else, for example ID, Version, Name. You have two rows with the same ID and same Version, what kind of rule do you want to apply on this ? Lowest number ?
I made an example that does kind of what you want:
First declare the necessary tables:
declare #table1 table (
Id int,
Name nvarchar(20),
[Version] int,
Code int
)
insert into #table1 values (1,'A',1,10),(1,'A',2,20),(1,'A',3,30),(1,'A',4,NULL)
,(2,'B',1,40),(2,'B',2,50),(2,'C',1,60);
And then the query to get the results:
with HighestVersions (Id, MaxVersion) As
(
select Id, max(version) from #table1 group by Id
)
select
t1.Id,
t1.[Version],
min(t1.Code) as Code
from
#table1 t1
inner join
HighestVersions hv
on
hv.Id = t1.Id
and (hv.MaxVersion-1) = t1.[Version]
group by
t1.Id
,t1.[Version]
I had to do a little dirty trick with the outermost select, this is because of the duplicate 'Id' and 'Version'. Else you would have gotten two rows with ID = 2, Version = 1
If you want to remove the NULL value you can change the WITH part (according to your last edit):
with HighestVersions (Id, MaxVersion) As
(
select Id, max(version) from #table1 where Code is not null group by Id
)

Try this:
DECLARE #List TABLE (ID int, Name char(1), Version int, Code int NULL)
INSERT INTO #List
VALUES
(1, 'A', 1, 10),
(1, 'A', 2, 20),
(1, 'A', 3, 30),
(1, 'A', 4, NULL),
(2, 'B', 1, 40),
(2, 'B', 2, 50),
(2, 'C', 1, 60)
SELECT
ID, Name, Version, Code
FROM
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ID, Name ORDER BY Version DESC) Rn
FROM #List
) a
WHERE
a.Rn = 2

Related

How do I mask certain values and maintain uniqueness while using a case...when statement in MS SQL Server?

Say I have a column in a SQL Server table with the following entries:
+----+-----+
| ids| col1|
+----+-----+
|4 | a |
|4 | b |
|4 | a |
|4 | b |
|5 | a |
+----+-----+
I'd like to mask the ids column given that col1 = a. However, I'd also like to maintain the uniqueness of the ids masking, so the result would look as follows:
+----+-----+
| ids| col1|
+----+-----+
|XX | a |
|4 | b |
|XX | a |
|4 | b |
|YY | a |
+----+-----+
I have used a case...when with SHA2_256 algorithm to maintain uniqueness as in this post:
How do I mask/encrypt data in a view but maintain uniqueness of values?
,but then the resulting mask are 'Chinese-looking' characters that seem machine-unreadable. Is there a better way?
Would numbers be OK?
First, create and populate sample table (Please save us this step in your future questions)
DECLARE #T AS TABLE
(
ids int,
col1 char(1)
)
INSERT INTO #T VALUES
(4, 'a'),
(4, 'b'),
(4, 'a'),
(4, 'b'),
(5, 'a')
The query:
SELECT CASE WHEN col1 = 'a' THEN CHECKSUM(CAST(Ids as varchar(11))) ELSE ids END As ids,
col1
FROM #T
Results:
ids col1
136 a
4 b
136 a
4 b
137 a
Your suggested masked output values of XX and YY are perhaps misleading, because if you have millions of id values in your table, then two letters won't be able to uniquely/randomly cover all data. One option here might be to use NEWID() to generate a unique UUID for each id group:
WITH cte AS (
SELECT DISTINCT id, NEWID() AS mask
FROM yourTable
)
SELECT t2.mask, t1.col
FROM yourTable t1
INNER JOIN cte t2
ON t1.id = t2.id;
If you don't want to show the entire UUID, because it is too long, then you may instead show a substring of it, e.g. for just the first 5 characters:
SELECT LEFT(t2.mask, 5) AS mask, t1.col
FROM yourTable t1
INNER JOIN cte t2
ON t1.id = t2.id;
But keep in mind that the shorter you make the UUID being displayed, the greater the probability that two different id groups would be rendered with the same mask.
Try this query (Replace #test with your actual table name), In future case can come where you need to include other characters too in addition to just 'a'.
Below List table will help you with that.
create table #list
(
col1 varchar(1)
)
insert into #list values ('a')
select case when isnull(b.col1,'0')<>'0' then a.col1+cast ( Dense_rank() OVER(PARTITION BY a.col1 ORDER BY a.col1 ASC) as varchar(max)) else cast(a.ids as varchar(max)) end as ids,
a.col1 from #test a
left join #list b
on a.col1 =b.col1
Out Put
So this is what I ended up doing. Using the example provided by #Zohar Peled, but making the adjustment that the ids column is a varchar, we can make the table as follows:
DECLARE #T AS TABLE
(
ids varchar(150),
col1 char(1)
)
INSERT INTO #T VALUES
(4, 'a'),
(4, 'b'),
(4, 'a'),
(4, 'b'),
(5, 'a')
and then do the following:
SELECT CASE WHEN col1 = 'a' THEN CONVERT(VARCHAR(150),HashBytes('SHA2_256', ids),2) ELSE ids END As ids,
col1
FROM #T
This more closely resembles the initial solution in the link, I believe.
You can hide IDs also by integer numbers (don't know if it's secure enough in your case)
CREATE TABLE #t (ids int, col1 char(1));
INSERT INTO #t VALUES
(4, 'a'),
(4, 'b'),
(4, 'a'),
(4, 'b'),
(5, 'a');
Query
SELECT ISNULL(t2.num, t1.ids) AS ids, t1.col1
FROM
#t t1 LEFT JOIN
(
SELECT
ROW_NUMBER() OVER (ORDER BY ids, col1) + (SELECT MAX(ids) FROM #t) AS num,
ids, col1
FROM #t
WHERE col1 = 'a'
GROUP BY ids, col1) t2
ON t1.ids = t2.ids AND t1.col1 = t2.col1;
Result
ids col1
-------------------- ----
6 a
4 b
6 a
4 b
7 a

Display only the top records according to there stage in SQL Server

I have a scenario where table has
Record_id,Record_Stage,Other_Column
1,A,Text1
1,B,Text2
1,C,Text3
1,D,Text4
2,A,SText1
2,B,SText2
My output should be based on record_id
1)the record with stage D for record_id 1
2)record_id 2 with Stage B is displayed as there are no Stage C and Stage D
O/p
1,D,Text4
2,B,SText2
I am manipulating this case in a SQL Server view,It would be great help If someone can help me in this.
it is easy with row_number()
select *
from (
select *, rn = row_number() over (partition by Record_id
order by Record_Stage desc)
from yourtable
) d
where d.rn = 1
Here is a solution:
CREATE TABLE T(
ID INT,
Stage VARCHAR(10),
Other VARCHAR(45)
);
INSERT INTO T VALUES
(1, 'A', 'Text1'),
(1, 'B', 'Text2'),
(1, 'C', 'Text3'),
(1, 'D', 'Text4'),
(2, 'A', 'SText1'),
(2, 'B', 'SText2');
WITH CTE AS
(
SELECT MAX(T.ID) AS ID,
MAX(T.Stage) AS Stage
FROM T
GROUP BY ID
)
SELECT T.*
FROM T INNER JOIN CTE ON T.ID = CTE.ID AND T.Stage = CTE.Stage;
Results:
+----+----+-------+--------+
| | ID | Stage | Other |
+----+----+-------+--------+
| 1 | 1 | D | Text4 |
| 2 | 2 | B | SText2 |
+----+----+-------+--------+

Select alternate rows from SQL Server table

I am working with SQL Server 2008. I have a table which does not contain any unique columns; how to get alternate rows from it?
SQL Server table:
+-----+--------+
| id | name |
|-----+--------|
| 1 | abc |
| 2 | pqr |
| 2 | pqr |
| 3 | xyz |
| 4 | lmn |
| 5 | efg |
| 5 | efg |
+-----+--------+
As we've to come with at least one working suggestion with the question, I've tried below code; which is not so proper technique when fetching from a huge amount of data.
Trial:
create table #tmp
(
id int, name varchar(10), srNo int
)
insert into #tmp
select
id, name,
ROW_NUMBER() OVER (ORDER BY id) % 2 as srNo --,alternate rows
from
Employee
select *
from #tmp
where srNo = 1 --or srNo = 0
Above query gives out alternate rows i.e. 1st, 3rd, 5th OR 2nd, 4th, 6th etc.
Please help me out with proper way without #tmp to achieve the goal!
You can just use your select statement as an in-line view. You don't need the #tmp table.
select t.id, name
from (select id, name, ROW_NUMBER() over (order by id) as srNo from Employee) t
where (t.srNo % 2) = 1
SqlFiddle
--To fetch ALTERNATE records from a table (EVEN NUMBERED)
Select * from TableName where ColumnName % 2 = 0
For Eg : select * from HumanResources.Employee where BusinessEntityID % 2 = 0
--To fetch ALTERNATE records from a table (ODD NUMBERED)
Select * from TableName where ColumnName % 2 = 1
For Eg : select * from HumanResources.Employee where BusinessEntityID % 2 = 1
I'm taking student as a table name.
Here is my answer ->
For Even Row Number -
> SELECT id from (SELECT rowno, id from student) where mod(rowno,2)=0
For Odd Row Number -
> SELECT id from (SELECT rowno, id from student) where mod(rowno,2)=1
Same also can be achieved using having clause; but it adds group by task:
SELECT id, name
FROM (SELECT id, name, ROW_NUMBER()over(order by id) AS srNo FROM Employee) x
GROUP BY srNo, id, name
HAVING (srNo % 2) = 0
You can just use your select statement as an in-line view. You don't need the #tblCities table.
select tbl1.CityID,tbl1.CityName from (select ROW_NUMBER() over(order by CityID asc) as row_no,CityID,CityName from tblCities) as tbl1 where tbl1.row_no%2=1
declare #t table
(
id int,
name nvarchar(20)
)
insert into #t
Select 1, 'abc'
union all
Select 2, 'pqr'
union all
Select 2, 'pqr'
union all
Select 3, 'xyz'
union all
Select 4, 'lmn'
union all
Select 5, 'efg'
union all
Select 2, 'efg'
Select * from(
Select *, row_number() over(order by id) as rnum from #t ) t where rnum % 2 <> 0
create table t (id bigint NOT NULL, input_1 boolean not null, data_gps timestamp(0) not null);
insert into t (id, input_1,data_gps) values
(1, false , '2022-01-01 15:42:07'),
(2, true , '2022-01-02 15:42:07'),
(3, true , '2022-01-03 15:42:07'),
(4, false , '2022-01-04 15:42:07'),
(5, true , '2022-01-05 15:42:07'),
(6, true , '2022-01-06 15:42:07'),
(7, true , '2022-01-07 15:42:07'),
(8, true , '2022-01-08 15:42:07'),
(9, false , '2022-01-09 15:42:07'),
(10 ,true , '2022-01-10 15:42:07'),
(11, true , '2022-01-11 15:42:07'),
(12, true , '2022-01-12 15:42:07'),
(13, false , '2022-01-13 15:42:07'),
(14, true , '2022-01-14 15:42:07');
you will have
Here is the query that will group by value change
select input_1, min(data_gps) as mind, max(data_gps) as maxd
from (
select input_1, data_gps,
row_number() over (order by data_gps)
- row_number() over (partition by input_1 order by data_gps) as grp
from t
) as tmp
group by input_1, grp
order by min(data_gps);
The results
DEMO
https://dbfiddle.uk/6Ajy3H5O

How can I recursively calculate a value

I have this table.
Bundles
id | parent_id | quantity
1 | 0 | 1
2 | 1 | 4
3 | 2 | 5
I want to get the total quantity of a bundle with id 3, which is 1 * 4 * 5 = 20 items
Can this be done with a single query?
Here's a solution using CTE:
Setup:
CREATE TABLE Table1
(id int, parent_id int, quantity int)
;
INSERT INTO Table1
(id, parent_id, quantity)
VALUES
(1, 0, 1),
(2, 1, 4),
(3, 2, 5),
(4, 0, 7),
(5, 4, 10)
;
CTE to return total of id=3 and it's parent items:
;WITH myCTE AS
(
SELECT id, parent_id, quantity
FROM Table1
WHERE id = 3
UNION ALL
SELECT T.id, T.parent_id, T.quantity
FROM Table1 T
JOIN myCTE C ON T.id = C.parent_id
)
SELECT EXP(sum(log(quantity)))
FROM myCTE
Demo SQL Fiddle
Multiplication method for values in a column, SELECT EXP(sum(log(quantity))), taken from here.

COUNT number of rows in a GROUP on higher aggregate level

I am trying to find out how many rows of a certain item exist in the table, e.g. in the following example for itemID 1 I need the result 5 (not 3, which is what I currently get). I am tempted to add TransactionID into the PARTITION BY clause, but that results in Msg 8120 since the query does not GROUP by TransactionID. Well, if it did then getting that count would be easy, but I do not want to group on Transaction Level. What can I do to get that ItemCount right? It must be so easy but I am banging my head.
DECLARE #t TABLE (TransactionID INT PRIMARY KEY IDENTITY, CustomerID INT, ItemID INT);
INSERT INTO #t (CustomerID, ItemID)
VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 2),
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(1, 1);
SELECT
CustomerID,
ItemID,
Rows = COUNT(*),
ItemRowCount = COUNT(*) OVER (PARTITION BY ItemID)
FROM
#t
GROUP BY
CustomerID,
ItemID
ORDER BY
ItemID,
CustomerID;
EDIT: I was overaggregating, I guess. Sebastian Meine got me on the track and his answer is right so I accepted it. However, this subquery works for my:
SELECT
CustomerID,
ItemID,
Rows = COUNT(*),
ItemRowCount = (SELECT COUNT(*) FROM #t x WHERE t.ItemID = x.ItemID)
FROM
#t t
GROUP BY
CustomerID,
ItemID
ORDER BY
ItemID,
CustomerID;
You need to pull your outer group count out of the actual group by query. The easiest way to do that is like this:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE dbo.tbl (TransactionID INT PRIMARY KEY IDENTITY, CustomerID INT, ItemID INT);
INSERT INTO dbo.tbl (CustomerID, ItemID)
VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 2),
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(1, 1);
Query 1:
SELECT *,SUM(Rows)OVER(PARTITION BY ItemId) ItemCnt
FROM(
SELECT
CustomerID,
ItemID,
Rows = COUNT(*)
FROM
dbo.tbl
GROUP BY
CustomerID,
ItemID
)X
ORDER BY
ItemID,
CustomerID
Results:
| CUSTOMERID | ITEMID | ROWS | ITEMCNT |
----------------------------------------
| 1 | 1 | 3 | 5 |
| 2 | 1 | 1 | 5 |
| 3 | 1 | 1 | 5 |
| 2 | 2 | 1 | 2 |
| 4 | 2 | 1 | 2 |
| 3 | 3 | 1 | 1 |
| 4 | 4 | 1 | 1 |
Notice that I add the inner counts together instead of recounting from scratch.
You can use simple count and group by:
SELECT
ItemID,
ItemRowCount = COUNT(1)
FROM
#t
GROUP BY
ItemID
ORDER BY
ItemID
or if you need attach total rows count and item row count to every row:
SELECT
CustomerID,
ItemID,
Rows = COUNT(1) over (),
ItemRowCount = COUNT(1) OVER (PARTITION BY ItemID)
FROM
#t
ORDER BY
ItemID,
CustomerID;
To find out how many rows of a certain item exist in the table, we may not need CustomerId. Use following query -
DECLARE #t TABLE (TransactionID INT PRIMARY KEY IDENTITY, CustomerID INT, ItemID INT);
INSERT INTO #t (CustomerID, ItemID)
VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 2),
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(1, 1);
;WITH cte AS(
SELECT itemId, ROW_NUMBER() OVER (PARTITION BY itemId ORDER BY itemId DESC) AS row_cnt FROM #t
)
SELECT itemId, MAX(row_cnt) row_count FROM cte GROUP BY itemId
It will return -
itemId row_count
1 5
2 2
3 1
4 1
And if is case you need customerId, the use -
;WITH cte AS(
SELECT customerId, itemId, ROW_NUMBER() OVER (PARTITION BY itemId ORDER BY itemId DESC) AS row_cnt FROM #t
)
SELECT customerId, itemId, MAX(row_cnt) item_count FROM cte GROUP BY CustomerID, itemId
It will return -
customerId itemId item_count
1 1 5
2 1 2
3 1 3
2 2 1
4 2 2
3 3 1
4 4 1
You can use a CTE or join to a sub-table (like this)
SELECT
tbl.CustomerID,
tbl.ItemID,
Rows = COUNT(*),
ItemRowCount
FROM tbl
JOIN (SELECT ItemID, Count(*) as ItemRowCount
FROM tbl
GROUP BY ItemID) t ON tbl.ItemID = t.ItemID
GROUP BY
tbl.CustomerID,
tbl.ItemID,
ItemRowCount
ORDER BY
tbl.ItemID,
CustomerID;
or this
SELECT
tbl.CustomerID,
tbl.ItemID,
Rows = COUNT(*),
MAX(ItemRowCount)
FROM tbl
JOIN (SELECT ItemID, Count(*) as ItemRowCount
FROM tbl
GROUP BY ItemID) t ON tbl.ItemID = t.ItemID
GROUP BY
tbl.CustomerID,
tbl.ItemID
ORDER BY
tbl.ItemID,
CustomerID;

Resources