MS SQL SERVER PAGING - sql

I did a query which is :
SELECT DISTINCT m.logID
FROM Monitor_data m
inner join Monitor_object o on (o.objID = m.domainID)
inner join Monitor_event e on (e.mainID = m.logID)
WHERE (o.name = #objName
and m.service = #service
and e.statement = #statement
and m.start >= #start
and m.end <= #end)
That allows me to get some id (VARCHAR(50)). But, now I want to make a pagination so I need to modify that query. Unfortunately, I cannot use LIMIT and OFFSET ... I may use ROW_NUMBER but I don't know how :/ It will be great to get a result corresponding to the rows at line n to m. Thus, I will be able to create a paging process easily.
Can someone help me ?
Thank you.

Try this query:
select logID from (
SELECT DISTINCT m.logID,
ROW_NUMBER() over (order by m.start) rn
FROM Monitor_data m
inner join Monitor_object o on (o.objID = m.domainID)
inner join Monitor_event e on (e.mainID = m.logID)
WHERE (o.name = #objName
and m.service = #service
and e.statement = #statement
and m.start >= #start
and m.end <= #end)
) a where rn between (m, n) --here you provide values for limits for rows to return
Above query is based on ROW_NUMBER function in SQL Server, which requires some ordering, so I assumed that m.start will provide an order (I think it start date or something :) ).

Use #PageIndex and PageSize for paging
declare #PageIndex int=1
declare #PageSize int=10
declare #RecordCount int
SET NOCOUNT ON;
SELECT
ROW_NUMBER() OVER (ORDER BY m.logID ) as RowNumber, DISTINCT m.logID
INTO #Results
FROM Monitor_data m
inner join Monitor_object o on (o.objID = m.domainID)
inner join Monitor_event e on (e.mainID = m.logID)
WHERE (o.name = #objName
and m.service = #service
and e.statement = #statement
and m.start >= #start
and m.end <= #end)
SELECT #RecordCount = COUNT(*) FROM #Results
SELECT
*, #RecordCount as RecordCount FROM #Results
WHERE
RowNumber BETWEEN (#PageIndex -1) * #PageSize + 1 AND (((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
DROP TABLE #Results

I think this is what you want.
Select logId from
(SELECT DISTINCT m.logID, Row_number() over (order by (select null)) as ranking
FROM Monitor_data m
inner join Monitor_object o on (o.objID = m.domainID)
inner join Monitor_event e on (e.mainID = m.logID)
WHERE (o.name = #objName
and m.service = #service
and e.statement = #statement
and m.start >= #start
and m.end <= #end))
where ranking between n and m

ORDER BY ******
OFFSET #ItemsPerPage * (#CurrentPage - 1) ROWS
FETCH NEXT #ItemsPerPage ROWS ONLY
DECLARE #CurrentPage int = 1;
DECLARE #ItemsPerPage int = 10;
SELECT DISTINCT m.logID
FROM Monitor_data m
inner join Monitor_object o on (o.objID = m.domainID)
inner join Monitor_event e on (e.mainID = m.logID)
WHERE (o.name = #objName
and m.service = #service
and e.statement = #statement
and m.start >= #start
and m.end <= #end)
ORDER BY m.logID
OFFSET #ItemsPerPage * (#CurrentPage - 1) ROWS
FETCH NEXT #ItemsPerPage ROWS ONLY
paging Example
DECLARE #myTable TABLE(Id int, Name nvarchar(50),EventDate date);
INSERT INTO #myTable(Id, Name, EventDate)
VALUES (1, 'a', '2018-01-01'),
(2, 'b', '2018-01-02'),
(3, 'c', '2018-01-03'),
(4, 'd', '2018-01-04'),
(5, 'e', '2018-01-05'),
(6, 'f', '2018-01-06');
DECLARE #CurrentPage int = 1;
DECLARE #ItemsPerPage int = 4;
SELECT * FROM #myTable
ORDER BY EventDate DESC
OFFSET #ItemsPerPage * (#CurrentPage - 1) ROWS
FETCH NEXT #ItemsPerPage ROWS ONLY

Starting with 2012, you could use OFFSET and FETCH. Prior to that a solution is to use ROW_NUMBER. However, beware, ROW_NUMBER() approach is very slow. If you don't have a performance problem you can use it.
Whatever the version is, a fast paging is done using TOP N and ordering by your desired columns and also specifying minimum value. ie:
select TOP (#pageSize) * from myTable
where myKeyValue > #minValue
order by myKeyValue;

Related

SQL alternative to using while loop to create calculated measure

I am creating a field by performing some calculations.
Then in the next iteration I use that result to compute a new value. Currently doing this with a WHILE loop. Any other way?
I have tried LAG and Partition and even a recursive CTE but could not achieve the same outcome:
DECLARE #Period INT = 2;
DECLARE #MaxPeriod INT;
SELECT #MaxPeriod = Periods FROM dbo.Engine (NOLOCK);
WHILE (#Period <= #MaxPeriod)
BEGIN
WITH CTE AS (
SELECT
A.ProjectId, A.TypeId, A.Trial, A.Period,
((B.ValueA * (COALESCE(B.FactorCalculated, 0) + 1)) +
(A.ValueA * 0)) / A.ValueA AS FactorCalculated
FROM dbo.PenetrationResults A (NOLOCK)
INNER JOIN dbo.PenetrationResults B (NOLOCK)
ON A.ProjectId = B.ProjectId
AND A.TypeId = B.TypeId
AND A.Trial = B.Trial
AND (A.Period - 1) = B.Period
WHERE A.[Period] = #Period
AND A.ProjectId = #ProjectId
)
UPDATE [target]
SET FactorCalculated = CTE.FactorCalculated
FROM dbo.PenetrationResults AS [target] --(TABLOCK)
INNER JOIN CTE
ON [target].[ProjectId] = CTE.ProjectId AND [target].[TypeId] = CTE.TypeId
AND [target].[Trial] = CTE.Trial
AND [target].[Period] = CTE.Period
SET #Period = #Period + 1
END ;
Well I found a way to make this work using the CTE recursive abilities finally, improved performance 4 x
WITH ncte AS (
-- number the rows sequentially without gaps
SELECT
ProjectId,
TypeId,
Trial,
[Period],
FactorWifiCumulative--,
--FactorConnectedCalc
FROM dbo.PenetrationResults (NOLOCK)
WHERE ProjectId = #ProjectId
), rcte AS (
-- find first row in each group
SELECT *, CAST(0 AS DECIMAL(21,14)) AS Calc
FROM ncte AS base
WHERE Period = 1
UNION ALL
-- find next row for each group from prev rows
SELECT curr.*,
CAST(
(
(
prev.FactorWifiCumulative
*
(
COALESCE(prev.Calc,0)
+ 1
)
)
+
(
curr.FactorWifiCumulative*0
)
)
/
curr.FactorWifiCumulative AS DECIMAL(21,14))
AS Calc
--CAST(prev.Calc * (1 + curr.FactorWifiCumulative / 100) AS DECIMAL(21, 14))
FROM ncte AS curr
INNER JOIN rcte AS prev
ON curr.Period = prev.Period + 1
AND curr.TypeId = prev.TypeId
AND curr.Trial = prev.Trial
)
UPDATE [target]
SET [FactorConnectedCalc] = [Calc]
FROM dbo.PenetrationResults AS [target] (NOLOCK)
INNER JOIN rcte
ON [target].[ProjectId] = rcte.ProjectId
AND [target].[TypeId] = rcte.TypeId
AND [target].[Trial] = rcte.Trial
AND [target].[Period] = rcte.Period
OPTION(MAXRECURSION 0);

How to avoid Recursive CTE repeating the anchor values

I am using following CTE to get hierarchical structure of manager and employees, i have multiple managers for one employee, in this case i do not want CTE to repeat for that employee again and again as it is doing in my code -
getemp() is a simple function returning employeeid, name and managerID
;With hierarchy as
(
select [Level]=1 , * from dbo.getemp() where managerid = 1
union all
select [Level]+1 , e.* from getemp() e
join hierarchy h on h.employeeid = e.managerid
)
Select * from hierarchy
After edit -
Following approach working for me. Is it possible with CTE ?
SET NOCOUNT ON;
DECLARE #Rows int
SELECT [Level] = ISNULL(1,0),
employeeid = ISNULL(employeeid, 0 ),
empname = CAST(empname as varchar(10)),
managerid = ISNULL(managerid,0)
into #Temp1
from dbo.getemp() as a1
where a1.managerid = #Top1
--select * from #Temp1
SELECT #Rows=##ROWCOUNT
DECLARE #I INT = 2;
while #Rows > 0
BEGIN
Insert into #Temp1
select #I as Level, b.employeeid, b.empname, b.managerid from #Temp1 as e
inner join (select [employeeid], [empname], [managerid] from dbo.GetEmp()) as b on b.managerid = e.employeeid
where e.Level = #I - 1
and not exists (
SELECT 1 FROM #Temp1 t
WHERE b.employeeid = t.employeeid
AND b.managerid = t.managerid);
SELECT #Rows=##ROWCOUNT
--SELECT #Rows AS Rows
IF #Rows > 0
BEGIN
SELECT #I = #I + 1;
END
END
select distinct * from #Temp1
END
Since you have several managers, which means that people can also be in several different levels due to having different levels in the manager, you could just take the minimum levels for each branch with something like this:
;With hierarchy as
(
select [Level]=1 , * from dbo.getemp() where managerid = 1
union all
select [Level]+1 , e.* from getemp() e
join hierarchy h on h.employeeid = e.managerid
)
Select min(Level) as Level, employeeid, name, managerid from hierarchy
group by employeeid, name, managerid
Using a function to return all the employees in every recursion might not be the best solution regarding to performance, especially if it's not an inline function. You might want to consider using for example a temp. table if you can't read the tables themselves directly.
Couldnt find the solution using CTE, so I have used the while loop to avoid the repeating anchors, here the code ..
DECLARE #Rows int
SELECT [Level] = ISNULL(1,0),
employeeid = ISNULL(employeeid, 0 ),
empname = CAST(empname as varchar(10)),
managerid = ISNULL(managerid,0)
into #Temp1
from dbo.getemp() as a1
where a1.managerid = #Top1
--select * from #Temp1
SELECT #Rows=##ROWCOUNT
DECLARE #I INT = 2;
while #Rows > 0
BEGIN
Insert into #Temp1
select #I as Level, b.employeeid, b.empname, b.managerid from #Temp1 as e
inner join (select [employeeid], [empname], [managerid] from dbo.GetEmp()) as b on b.managerid = e.employeeid
where e.Level = #I - 1
and not exists (
SELECT 1 FROM #Temp1 t
WHERE b.employeeid = t.employeeid
AND b.managerid = t.managerid);
SELECT #Rows=##ROWCOUNT
--SELECT #Rows AS Rows
IF #Rows > 0
BEGIN
SELECT #I = #I + 1;
END
END
select distinct * from #Temp1
END

How to tune the following query?

This query gives me the desired result but i can't run this query every time.The 2 loops is costing me.So i need to implement something like view.But the logic has temp tables involved which isn't allowed in views as well.so, is there any other way to store this result or change the query so that it will cost me less.
DECLARE #Temp TABLE (
[SiteID] VARCHAR(100)
,[StructureID] INT
,[row] DECIMAL(4, 2)
,[col] DECIMAL(4, 2)
)
DECLARE #siteID VARCHAR(100)
,#structureID INT
,#struct_row INT
,#struct_col INT
,#rows_count INT
,#cols_count INT
,#row INT
,#col INT
DECLARE structure_cursor CURSOR
FOR
SELECT StructureID
,SiteID
,Cols / 8.5 AS Cols
,Rows / 11 AS Rows
FROM Structure
WHERE SellerID = 658 --AND StructureID = 55
OPEN structure_cursor
FETCH NEXT
FROM structure_cursor
INTO #structureID
,#siteID
,#struct_col
,#struct_row
SELECT #rows_count = 1
,#cols_count = 1
,#row = 1
,#col = 1
WHILE ##FETCH_STATUS = 0
BEGIN
WHILE #row <= #struct_row
BEGIN
WHILE #col <= #struct_col
BEGIN
--PRINT 'MEssage';
INSERT INTO #Temp (
SiteID
,StructureID
,row
,col
)
VALUES (
#siteID
,#structureID
,#rows_count
,#cols_count
)
SET #cols_count = #cols_count + 1;
SET #col = #col + 1;
END
SET #cols_count = 1;
SET #col = 1;
SET #rows_count = #rows_count + 1;
SET #row = #row + 1;
END
SET #row = 1;
SET #col = 1;
SET #rows_count = 1;
FETCH NEXT
FROM structure_cursor
INTO #structureID
,#siteID
,#struct_col
,#struct_row
END
CLOSE structure_cursor;
DEALLOCATE structure_cursor;
SELECT * FROM #Temp
Do this with a set-based operation. I think you just want insert . . . select:
INSERT INTO #Temp (SiteID, StructureID, row, col)
SELECT StructureID, SiteID, Cols / 8.5 AS Cols, Rows / 11 AS Rows
FROM Structure
WHERE SellerID = 658;
You should avoid cursors, unless you really need them for some reason (such as calling a stored procedure or using dynamic SQL on each row).
EDIT:
Reading the logic, it looks like you want to insert rows for based on the limits in each row. You still don't want to use a cursor. For that, you need a number generator and master..spt_values is a convenient one, if it has enough rows. So:
with n as (
select row_number() over (order by (select null)) as n
from master..spt_values
)
INSERT INTO #Temp (SiteID, StructureID, row, col)
SELECT StructureID, SiteID, ncol.n / 8.5 AS Cols, nrow.n / 11 AS Rows
FROM Structure s JOIN
n ncol
ON ncol.n <= s.struct_col CROSS JOIN
n nrow
ON nrow <= s.struct_row
WHERE SellerID = 658;
You can generate the number of rows and columns and then CROSS APPLY with those, like below. I've left out your SellerID condition.
;WITH Cols
AS
(
SELECT StructureID, SiteID, CAST(Cols / 8.5 AS INT) AS Col
FROM Structure
UNION ALL
SELECT s.StructureID, s.SiteID, Col - 1
FROM Structure s
INNER JOIN Cols c ON s.StructureID = c.StructureID AND s.SiteID = c.SiteID
WHERE Col > 1
)
, Rows
AS
(
SELECT StructureID, SiteID, CAST(Rows / 11 AS INT) AS Row
FROM Structure
UNION ALL
SELECT s.StructureID, s.SiteID, Row - 1
FROM Structure s
INNER JOIN Rows r ON s.StructureID = r.StructureID AND s.SiteID = r.SiteID
WHERE Row > 1
)
--INSERT INTO #Temp (SiteID, StructureID, row, col)
SELECT s.SiteID, s.StructureID, r.Row, c.Col
FROM Structure s
CROSS APPLY Cols c
CROSS APPLY Rows r
WHERE s.StructureID = c.StructureID AND s.SiteID = c.SiteID
AND s.StructureID = r.StructureID AND s.SiteID = r.SiteID
We can do this by using CROSS APPLY and CTE.
CREATE TABLE Structure(SiteID varchar(20), StructureID int,
Cols decimal(18,2), [Rows] decimal(18,2))
INSERT INTO Structure (SiteID, StructureID, Cols, [Rows])
VALUES
('MN353970', 51,17,22),
('MN272252', 52,17,11)
;WITH RowCTE([Rows]) AS
(
SELECT 1
UNION ALL
SELECT 2
),
ColCTE(Cols) AS
(
SELECT 1
UNION ALL
SELECT 2
)
SELECT SiteID, StructureID, R.Rows, C.Cols
FROM Structure s
CROSS APPLY
(
SELECT Cols FROM ColCTE
) C
CROSS APPLY
(
SELECT [Rows] FROM RowCTE
) R
Sql Fiddle Demo

ambigious column name in sql server

hello i'm having a ambiguous column name in m stored procedure for payment .bid-id can someone help to resolve this issue please?
SET NOCOUNT ON;
SELECT ROW_NUMBER() OVER
(
ORDER BY [PaymentID] ASC
)AS RowNumber
,[PaymentID]
,[Name]
,[WinningPrice]
,[PaymentDate]
,[Payment.BidID]
INTO #Results
FROM Item INNER JOIN
Auction ON Item.ItemID = Auction.ItemID INNER JOIN
BID ON Auction.AuctionID = BID.AuctionID INNER JOIN
Payment ON BID.BidID = Payment.BidID
Where (BID.Status = 'Paid') AND (BID.BuyerID = #buyer)
SELECT #RecordCount = COUNT(*)
FROM #Results
SELECT * FROM #Results
WHERE RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1 AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
DROP TABLE #Results
End
There is a column name you use in the query that is available in multiple tables.
Without the table structure we can't be certain which one it is, but probably one with an alias in your query:
,[PaymentID]
,[Name]
,[WinningPrice]
,[PaymentDate]
Try this, Create a alias for ambiguous column name
SET NOCOUNT ON;
SELECT ROW_NUMBER() OVER
(
ORDER BY [PaymentID] ASC
)AS RowNumber
,[PaymentID]
,[Name]
,[WinningPrice]
,[PaymentDate]
,[Payment.BidID] as PBidID
INTO #Results
FROM Item INNER JOIN
Auction ON Item.ItemID = Auction.ItemID INNER JOIN
BID ON Auction.AuctionID = BID.AuctionID INNER JOIN
Payment ON BID.BidID = Payment.BidID
Where (BID.Status = 'Paid') AND (BID.BuyerID = #buyer)
SELECT #RecordCount = COUNT(*)
FROM #Results
SELECT * FROM #Results
WHERE RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1 AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
DROP TABLE #Results
End
Good practice is using aliases like:
SET NOCOUNT ON;
SELECT ROW_NUMBER() OVER
(
ORDER BY i.[PaymentID] ASC --which table it belongs? put correct alias
)AS RowNumber
,i.[PaymentID]
,i.[Name]
,i.[WinningPrice]
,i.[PaymentDate]
,p.[BidID]
INTO #Results
FROM Item i
INNER JOIN Auction a
ON i.ItemID = a.ItemID
INNER JOIN BID b
ON a.AuctionID = b.AuctionID
INNER JOIN Payment p
ON b.BidID = p.BidID
Where (b.Status = 'Paid')
AND (b.BuyerID = #buyer)
SELECT #RecordCount = COUNT(*)
FROM #Results
SET NOCOUNT ON;
SELECT ROW_NUMBER() OVER
(
ORDER BY [PaymentID] ASC
)AS RowNumber
,[PaymentID]
,[Name]
,[WinningPrice]
,[PaymentDate]
,[Payment.BidID]
INTO #Results
FROM Item INNER JOIN
Auction ON Item.ItemID = Auction.ItemID INNER JOIN
BID ON Auction.AuctionID = BID.AuctionID INNER JOIN
**Payment P1 ON BID.BidID = P1.BidID**
Where (BID.Status = 'Paid') AND (BID.BuyerID = #buyer)
SELECT #RecordCount = COUNT(*)
FROM #Results
SELECT * FROM #Results
WHERE RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1 AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
DROP TABLE #Results
End
Hope this helps..
highlighted the line which i changed

How to set the out parameter when using ROW_NUMBER() & COUNT(*)

How can I set the out #Total parameter of this tsql proc when using the ROW_NUMBER() OVER along with COUNT(*)?
ALTER proc [Generic].[proc_GetPartsForUser_BySupplier_ByCategory]
#UserID UNIQUEIDENTIFIER,
#SupplierID INT,
#CategoryID INT,
#StartIndex INT,
#PageSize INT,
#Total INT out
AS
SET NOCOUNT ON;
SET #StartIndex = #StartIndex + 1
BEGIN
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY ID ASC) AS RowNum, COUNT(*) OVER() AS Total
FROM (
SELECT p.*,
s.Name'SupplierName',s.Email'SupplierEmail',s.Phone'SupplierPhone'
FROM [Generic].[Part] p WITH(NOLOCK) JOIN
Generic.Supplier s WITH(NOLOCK) ON p.SupplierID = s.ID
WHERE p.ID IN(SELECT up.PartID FROM Generic.GenericCatalog gc with(nolock) JOIN
Generic.UserPart up WITH(NOLOCK) ON up.GenericCatID = gc.ID
WHERE gc.UserID = #UserID)
AND
CategoryID = #CategoryID
AND
s.ID = #SupplierID
) AS firstt
) AS final
WHERE RowNum BETWEEN #StartIndex AND (#StartIndex + #pageSize) - 1
ORDER BY final.[Name] ASC;
END;
SET NOCOUNT OFF;
The simple answer is you can't!
The OVER() clause creates a windowed function, meaning it will return the value for every row! The parameter can only store one value!
The question remains, why do you want to do this?
If the point of the entire query is to return the value of COUNT(*), then just use it without the OVER clause like this:
SELECT #Total = COUNT(*)
FROM (SELECT p.*,
s.NAME 'SupplierName',
s.EMAIL'SupplierEmail',
s.PHONE'SupplierPhone'
FROM [Generic].[PART] p WITH(NOLOCK)
JOIN GENERIC.SUPPLIER s WITH(NOLOCK)
ON p.SUPPLIERID = s.ID
WHERE p.ID IN(SELECT up.PARTID
FROM GENERIC.GENERICCATALOG gc WITH(NOLOCK)
JOIN GENERIC.USERPART up WITH(NOLOCK)
ON up.GENERICCATID = gc.ID
WHERE gc.USERID = #UserID)
AND CATEGORYID = #CategoryID
AND s.ID = #SupplierID) AS firstt
If what you need is more than this, edit your question and I'll try to find you a better answer.