Pagination for a SQL Query Template - sql-server

For my application I created a SQL query builder which has got Where and Order By clauses. I would like to know how to paginate through the results i.e. I would like to get a template on how to paginate through the results of a SQL query. This description may be a bit confusing, so it may be easier with an example:
Consider the Test Table
CREATE TABLE [dbo].[TestTable](
[RecordID] [int] NOT NULL,
[ID] [nvarchar](1000) NULL,
[Name] [nvarchar](1000) NULL,
[Dept] [nvarchar](1000) NULL
)
INSERT [dbo].[TestTable]
SELECT 1, N'1', N'Andy', N'IT'
UNION ALL
SELECT 2, N'2', N'Bob', N'IT'
UNION ALL
SELECT 3, N'3', N'Camila', N'Sales'
UNION ALL
SELECT 4, N'4', N'Drew', N'IT'
UNION ALL
SELECT 5, N'5', N'Elsie', N'Sales'
UNION ALL
SELECT 6, N'6', N'Frank', N'IT'
UNION ALL
SELECT 7, N'7', N'Gaby', N'Sales'
UNION ALL
SELECT 8, N'8', N'Hank', N'IT'
UNION ALL
SELECT 9, N'9', N'Iris', N'Sales'
UNION ALL
SELECT 10, N'8', N'John', N'IT'
Let us say that I have a Where Clause as:
WHERE ([Dept] = 'IT')
And an Order By Clause as:
ORDER BY [Name] DESC
I am attempting to do the pagination by using something like:
SELECT [RECORDID], [ID], [Name], [Dept], RowNum
FROM (
SELECT [RECORDID], [ID], [Name], [Dept],
ROW_NUMBER() OVER (ORDER BY [RecordID]) AS RowNum
FROM [TestTable] WHERE ([Dept] = 'IT')
) AS [TestTable_DerivedTable]
WHERE [TestTable_DerivedTable].RowNum BETWEEN 3 AND 6 ORDER BY [Name] DESC
This does not work because I cannot get the ORDER BY [Name] DESC into [TestTable_DerivedTable].
If I just had the WHERE clause, it would return the names:
Andy, Bob, Drew, Frank, Hank, and John.
If I put in the pagination i.e. BETWEEN 3 AND 6, I correctly get:
Drew, Frank, Hank, and John
How do I add the ORDER BY [Name] DESC so that I get (first the reversal, then the pagination):
Frank, Drew, Bob, and Andy

If you move the ORDER BY [Name] DESC into the Window function, you will get what you want:
SELECT [RECORDID], [ID], [Name], [Dept], RowNum
FROM
(
SELECT [RECORDID], [ID], [Name], [Dept]
, ROW_NUMBER() OVER (ORDER BY [Name] DESC) AS RowNum
FROM [TestTable] WHERE ([Dept] = 'IT')
) AS [TestTable_DerivedTable]
WHERE [TestTable_DerivedTable].RowNum BETWEEN 3 AND 6

Related

SQL Select Only the Most Common Results

I have a table with IDs and Items where sometimes the associated Item has a variation from the other Items associated with the same ID. I need a query that selects the most common Item and assigns it to that ID.
The below query works, but I'm hoping to optimize it to avoid having to join two separate CTEs at the end, and rather have one slick SELECT statement:
IF OBJECT_ID('tempdb..#Test') IS NOT NULL
DROP TABLE #Test
CREATE TABLE #Test
(
[ID] INT
,[Item] VARCHAR(20)
)
INSERT #Test
VALUES
(100, 'Apple'),
(100, 'Apple'),
(100, 'Apples'),
(200, 'Orange'),
(200, 'Orange'),
(200, 'Orange'),
(200, 'Oranges'),
(300, 'Grape');
WITH cteOne AS (SELECT
[ID]
,[Item]
,COUNT(*) [Count]
FROM #Test
GROUP BY [ID]
,[Item]
),
cteTwo AS (SELECT
[ID]
,MAX([Count]) [Max]
FROM cteOne
GROUP BY [ID])
SELECT
C1.[ID]
,C1.[Item]
FROM cteOne C1
INNER JOIN cteTwo C2 ON C2.[ID] = C1.[ID]
AND C2.[Max] = C1.[Count]
ORDER BY [ID]
Any help is appreciated!
You can try top 1 with ties with row_number
select
top 1 with ties [ID], [Item]
from (
SELECT
[ID], [Item], COUNT(*) [Count]
FROM #Test
GROUP BY [ID], [Item]
) t
order by row_number() over (partition by [ID] order by [Count] desc)
This is even better:
;WITH
cteOne AS (
SELECT [ID],[Item] ,COUNT(*) [Count]
FROM #Test
GROUP BY [ID],[Item]
),
cteTwoo as (
select *, ROW_NUMBER() over (partition by id order by count) idx
from cteOne
)
select ID, Item
from cteTwoo
where idx = 1

SQL Server : adding complex rownumber

I was wondering if someone knows how I can get the missing results from the 2nd column ("As-IS-Rownumber", which are yellow). As example which shows the desired outcome, I added the 1st column("To-Be - Desired").
BTW I work with MS SQL.
I want a rownumber based on the columns KlantID and Repeat. However, when I use partition by, rank or dense_rank, I don't get the desired outcome, because of my last column.
I hope some1 can help me out.
DDL
create table tbl (
ToBeSubRow INT,
AsIsSubrow INT,
Rownumber INT,
KlantID INT,
Repeat CHAR(3)
)
insert tbl (ToBeSubRow, AsIsSubrow, Rownumber, KlantID, Repeat)
values (1,1,1,1,'NO'),
(2,null,2,1,'YES'),
(3,null,3,1,'YES'),
(1,1,4,1,'NO'),
(2,null,5,1,'YES'),
(1,1,5,2,'NO'),
(2,null,6,2,'YES'),
(3,null,7,2,'YES')
Thanks
Martijn
enter image description here
This keys on Repeat only
If a new KlantID does not start on no it breaks
declare #T table (Rownumber INT, Subrow INT, Unq INT, KlantID INT, Repeat CHAR(3));
insert #T (Rownumber, Subrow , Unq , KlantID , Repeat)
values (1, null, 1 , 1, 'NO'),
(2, null, null, 1, 'YES'),
(3, null, null, 1, 'YES'),
(4, null, 2, 1, 'NO'),
(5, null, null, 1, 'YES'),
(6, null, 1, 2, 'NO'),
(7, null, null, 2, 'YES'),
(8, null, null, 2, 'YES');
with CTEno as
( select T.Rownumber
, ROW_NUMBER() over (order by T.Rownumber) as r4
from #T T
where T.Repeat = 'No'
)
select t.*
, ISNULL(n.r4, (select top 1 n.r4 from CTEno n where n.Rownumber < t.Rownumber order by n.r4 desc)) as grp
, ROW_NUMBER() over (partition by (ISNULL(n.r4, (select top 1 n.r4 from CTEno n where n.Rownumber < t.Rownumber order by n.r4 desc)))
order by T.Rownumber) grpRow
from #T T
left join CTEno N
on T.Rownumber = N.Rownumber
order by T.Rownumber;
You need to add a column to use as a Group ID, like this:
--Add a column called Group_ID
ALTER TABLE ASIS ADD GROUP_ID INT NULL
--Populate Group_ID
UPDATE ASIS
SET Group_ID = CASE Rownumber WHEN 1 THEN 1
WHEN 2 THEN 1
WHEN 3 THEN 1
WHEN 4 THEN 2
WHEN 5 THEN 2
WHEN 6 THEN 3
WHEN 7 THEN 3
WHEN 8 THEN 3
END
--Use Row_Number() to get the values you desire
SELECT *, ROW_NUMBER() OVER(PARTITION BY Group_ID ORDER BY Rownumber) SubRow2
FROM ASIS

Find MAX value from a JOIN query

I have the following two tables:
Table #USER
SELECT *
INTO #USER
FROM (
SELECT 'A.2017.JAN' AS [KSCEN], 'John' AS [Name], 'Doe' AS [Surname] UNION ALL
SELECT 'A.2017.JAN' AS [KSCEN], 'Paul' AS [Name], 'Red' AS [Surname] UNION ALL
SELECT 'A.2017.FEB' AS [KSCEN], 'John' AS [Name], 'Doe' AS [Surname] UNION ALL
SELECT 'A.2017.FEB' AS [KSCEN], 'Paul' AS [Name], 'Red' AS [Surname] UNION ALL
SELECT 'A.2017.MAR' AS [KSCEN], 'John' AS [Name], 'Doe' AS [Surname] UNION ALL
SELECT 'A.2017.MAR' AS [KSCEN], 'Paul' AS [Name], 'Red' AS [Surname] UNION ALL
SELECT 'A.2017.MAR' AS [KSCEN], 'Kate' AS [Name], 'Blue' AS [Surname]
) A
Table #KSCEN
SELECT *
INTO #KSCEN
FROM (
SELECT 'A.2017.JAN' AS [ID], 6 AS [SEQ] UNION ALL
SELECT 'A.2017.FEB' AS [ID], 7 AS [SEQ] UNION ALL
SELECT 'A.2017.MAR' AS [ID], 8 AS [SEQ] UNION ALL
SELECT 'A.2017.APR' AS [ID], 9 AS [SEQ] UNION ALL
SELECT 'A.2017.MAY' AS [ID], 10 AS [SEQ]
) A
My goal is to find the element of #KSCEN with the MAX SEQ that is used at least one times inside table #USER.
I got it with the following subquery and LEFT JOIN:
SELECT [ID]
FROM #KSCEN
WHERE [SEQ] = (SELECT MAX(B.[SEQ]) FROM #USER A LEFT JOIN #KSCEN B ON A.[KSCEN]=B.[ID])
This works but consider that table #USER, in my case, contains more than 30,000,000 rows so it is not very fast to solve the query as the system need to join every rows and then find the MAX.
Is there a more efficent way to solve my problem?
I would avoid that correlated subquery you have used in the where clause. It produces a "row by agonizing row" effect as each row of the from/where clause gets tested against a MAX() calculation, one new query for each row.
Without the benefit of real tables to use, or execution plans, I suggest the following:
You can try it at SQL Fiddle
Query 1:
with tUSER as
(
SELECT 'A.2017.JAN' AS [KSCEN], 'John' AS [Name], 'Doe' AS [Surname] UNION ALL
SELECT 'A.2017.JAN' AS [KSCEN], 'Paul' AS [Name], 'Red' AS [Surname] UNION ALL
SELECT 'A.2017.FEB' AS [KSCEN], 'John' AS [Name], 'Doe' AS [Surname] UNION ALL
SELECT 'A.2017.FEB' AS [KSCEN], 'Paul' AS [Name], 'Red' AS [Surname] UNION ALL
SELECT 'A.2017.MAR' AS [KSCEN], 'John' AS [Name], 'Doe' AS [Surname] UNION ALL
SELECT 'A.2017.MAR' AS [KSCEN], 'Paul' AS [Name], 'Red' AS [Surname] UNION ALL
SELECT 'A.2017.MAR' AS [KSCEN], 'Kate' AS [Name], 'Blue' AS [Surname]
)
, tKSCEN as (
SELECT 'A.2017.JAN' AS [ID], 6 AS [SEQ] UNION ALL
SELECT 'A.2017.FEB' AS [ID], 7 AS [SEQ] UNION ALL
SELECT 'A.2017.MAR' AS [ID], 8 AS [SEQ] UNION ALL
SELECT 'A.2017.APR' AS [ID], 9 AS [SEQ] UNION ALL
SELECT 'A.2017.MAY' AS [ID], 10 AS [SEQ]
)
SELECT U.[KSCEN], MAX(K.[SEQ]) max_seq
FROM tUSER U
LEFT JOIN tKSCEN K ON U.[KSCEN]=K.[ID]
GROUP BY U.[KSCEN]
;
Results:
| KSCEN | max_seq |
|------------|---------|
| A.2017.FEB | 7 |
| A.2017.JAN | 6 |
| A.2017.MAR | 8 |

Update column with 4 consecutive purchases

I need to update my Result column values for the entire user to yes if the user did make 4 consecutive purchases without receiving a bonus in between. How can this be done. Please see my code below.....
-- drop table #Test
CREATE TABLE #Test (UserID int, TheType VARCHAR(10), TheDate DATETIME, Result VARCHAR(10))
INSERT INTO #Test
SELECT 1234, 'Bonus', GETDATE(), NULL
UNION
SELECT 1234, 'Purchase', GETDATE()-1, NULL
UNION
SELECT 1234, 'Purchase', GETDATE()-2, NULL
UNION
SELECT 1234, 'Purchase', GETDATE()-3, NULL
UNION
SELECT 1234, 'Purchase', GETDATE()-4, NULL
UNION
SELECT 1234, 'Bonus', GETDATE()-5, NULL
UNION
SELECT 1234, 'Purchase', GETDATE()-6, NULL
UNION
SELECT 1234, 'Bonus', GETDATE()-7, NULL
SELECT * FROM #Test ORDER BY TheDate
Again, please note that the purchases need to be consecutive (By TheDate)
You can as the below:
;WITH CTE1
AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY TheDate) RowId,
ROW_NUMBER() OVER (PARTITION BY UserID,TheType ORDER BY TheDate) PurchaseRowId,
*
FROM #Test
), CTE2
AS
(
SELECT
MIN(A.RowId) MinId,
MAX(A.RowId) MaxId
FROM
CTE1 A
GROUP BY
A.TheType,
A.RowId - A.PurchaseRowId
)
SELECT
A.UserID ,
A.TheType ,
A.TheDate ,
CASE WHEN B.MinId IS NULL THEN NULL ELSE 'YES' END Result
FROM
CTE1 A LEFT JOIN
CTE2 B ON A.RowId >= B.MinId AND A.RowId <= B.MaxId AND (B.MaxId - B.MinId) > 2
--AND A.TheType = 'Purchase'
ORDER BY A.TheDate
Result:
UserID TheType TheDate Result
----------- ---------- ----------------------- - ------
1234 Bonus 2017-06-06 11:06:03.130 NULL
1234 Purchase 2017-06-07 11:06:03.130 NULL
1234 Bonus 2017-06-08 11:06:03.130 NULL
1234 Purchase 2017-06-09 11:06:03.130 YES
1234 Purchase 2017-06-10 11:06:03.130 YES
1234 Purchase 2017-06-11 11:06:03.130 YES
1234 Purchase 2017-06-12 11:06:03.130 YES
1234 Bonus 2017-06-13 11:06:03.130 NULL
First you have to derive the column group and then group by that (having = 4) and inner join with the original table.
drop table if exists #Test;
create table #Test
(
UserID int
, TheType varchar(10)
, TheDate date
, Result varchar(10)
);
insert into #Test
select 1234, 'Bonus', getdate(), null
union
select 1234, 'Purchase', getdate() - 1, null
union
select 1234, 'Purchase', getdate() - 2, null
union
select 1234, 'Purchase', getdate() - 3, null
union
select 1234, 'Purchase', getdate() - 4, null
union
select 1234, 'Bonus', getdate() - 5, null
union
select 1234, 'Purchase', getdate() - 6, null
union
select 1234, 'Bonus', getdate() - 7, null;
drop table if exists #temp;
select
*
, lag(t.TheDate, 1) over ( order by t.TheDate ) as Lag01
, lag(t.TheType, 1) over ( order by t.TheDate ) as LagType
into
#temp
from #Test t;
with cteHierarchy
as
(
select
UserID
, TheType
, TheDate
, Result
, Lag01
, t.TheDate as Root
from #temp t
where t.LagType <> t.TheType
union all
select
t.UserID
, t.TheType
, t.TheDate
, t.Result
, t.Lag01
, cte.Root as Root
from #temp t
inner join cteHierarchy cte on t.Lag01 = cte.TheDate
and t.TheType = cte.TheType
)
update test
set
Result = 4
from (
select
t.Root
, count(t.UserID) as Cnt
, t.UserID
from cteHierarchy t
group by t.UserID, t.Root
having count(t.UserID) = 4
) tt
inner join #Test test on tt.UserID = test.UserID
select * from #Test t
order by t.TheDate;

Repeat the first date withing a group

I Would like the first date of each group to repeat for the rest of the rows withing each group
You could use window expressions and grouping;
FIRST_VALUE (Transact-SQL)
You would need to partition by your first column. to get the split of A and B.
For example;
with cteTempData
(
[Code]
, [Date]
)
as
(
select 'A',cast('2015-9-4' as date)
union all select 'A','2015-9-4'
union all select 'A','2015-9-4'
union all select 'A','2015-9-16'
union all select 'B','2015-9-16'
union all select 'B','2015-9-22'
union all select 'B','2015-9-22'
union all select 'B','2015-10-26'
union all select 'B','2015-10-30'
)
select
[Code]
, [Date]
, FIRST_VALUE([Date]) over (partition by [Code] order by [Date]) as [First_Date]
from cteTempData
Using the first_value syntax also allows you to work with other columns in that ordered record....
with cteTempData
(
[Code]
, [Date]
, [Comment]
)
as
(
select 'A',cast('2015-9-4' as date),'One'
union all select 'A','2015-9-4','Two'
union all select 'A','2015-9-4','Three'
union all select 'A','2015-9-16','Four'
union all select 'B','2015-9-16','Five'
union all select 'B','2015-9-22','Six'
union all select 'B','2015-9-22','Seven'
union all select 'B','2015-10-26','Eight'
union all select 'B','2015-10-30','Nine'
)
select
[Code]
, [Date]
, FIRST_VALUE([Date]) over (partition by [Code] order by [Date]) as [First_Date]
, FIRST_VALUE([Comment]) over (partition by [Code] order by [Date]) as [First_Comment]
from cteTempData
Use MIN() Over ()
Declare #Table table (Grp varchar(25),Date date)
Insert into #Table values
('A','2015-09-04'),
('A','2015-09-05'),
('A','2015-09-10'),
('B','2015-10-04'),
('B','2015-10-05'),
('B','2015-10-10')
Select *
,GrpDate = min(Date) over (Partition By Grp)
From #Table
Returns
Grp Date GrpDate
A 2015-09-04 2015-09-04
A 2015-09-05 2015-09-04
A 2015-09-10 2015-09-04
B 2015-10-04 2015-10-04
B 2015-10-05 2015-10-04
B 2015-10-10 2015-10-04
You could use MIN with the OVER-clause
SELECT t.ColumnA,
DateCol = MIN( t.DateCol ) OVER ( PARTITION BY t.ColumnA ),
OtherColumns
FROM dbo.TableName t
you can go with a CROSS JOIN or FIRST_VALUE.
Declare #Yourtable table (groupCol varchar(25),firstDate date)
Insert into #Yourtable values
('A','2015-09-04'),
('A','2015-09-05'),
('A','2015-09-10'),
('B','2015-10-04'),
('B','2015-10-05'),
('B','2015-10-10')
SELECT a.*,b.firstDate
FROM #Yourtable a
CROSS JOIN (SELECT groupCol,MIN(firstDate) firstDate
FROM #Yourtable b
GROUP BY groupCol)b
WHERE a.groupCol =b.groupCol
OR
SELECT a.*,FIRST_VALUE(a.firstDate) OVER (PARTITION BY groupCol ORDER BY groupCol ASC) AS firstDate
FROM #Yourtable a

Resources