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

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.

Related

SQL - More efficient way instead of using a cursor

-- Declare the table we are interested in reverting.
DECLARE #table_name VARCHAR(1000)
SET #table_name = 'tblCustomers' -- change this
-- Declare cursor and use the select statement (the one we want to loop through).
DECLARE customer_cursor CURSOR FOR
SELECT C.CustomerId
FROM tblCustomers C
WHERE ModifiedBy like '%crm%'
ORDER BY C.CustomerId DESC
-- Open the cursor and copy the columns into the original_consumer variable.
DECLARE #customer_id INT
OPEN customer_cursor
FETCH NEXT FROM customer_cursor
INTO #customer_id
-- Now loop through the old consumer id's and update their corresponding purchase and refunds records.
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS ( SELECT TOP 1 UserName
FROM tblAudit
WHERE TableName = #table_name
AND TableId IN (
SELECT CONVERT(VARCHAR, CustomerId)
FROM tblCustomers -- change this
WHERE CustomerId = #customer_id
)
AND UserName != 'crmuser'
ORDER BY TransactionDate DESC)
BEGIN
UPDATE tblCustomers
SET ModifiedBy = (SELECT TOP 1 UserName
FROM tblAudit
WHERE TableName = #table_name
AND TableId IN (
SELECT CONVERT(VARCHAR, CustomerId)
FROM tblCustomers -- change this
WHERE CustomerId = #customer_id
)
AND UserName != 'crmuser'
ORDER BY TransactionDate DESC),
ModifiedDate = (SELECT TOP 1 TransactionDate
FROM tblAudit
WHERE TableName = #table_name
AND TableId IN (
SELECT CONVERT(VARCHAR, CustomerId)
FROM tblCustomers -- change this
WHERE CustomerId = #customer_id
)
AND UserName != 'crmuser'
ORDER BY TransactionDate DESC)
WHERE CustomerId = #customer_id
END
FETCH NEXT FROM customer_cursor INTO #customer_id
END
-- Finally close and deallocate the cursor to stop memory leakage.
CLOSE customer_cursor
DEALLOCATE customer_cursor
This looks like it might work:
UPDATE tblCustomers
SET ModifiedBy = a.UserName,
ModofiedDate = amax.LatestChangeDate
from tblCustomers t
inner join -- get the latest transaction_date for each customer
(
select customerId, max(transaction_date) LatestChangeDate
from tblAudit
where TableName = #table_name
) amax on amax.customerId = t.customer_id
inner join -- get the details of changes for customer and latest date
(
select CustomerId, UserName, transaction_date
from tblAudit
where table_name = #table_name
) a on a.customerId = t.customerId and a.transaction_date = amax.LatestChangeDate
WHERE t.CustomerId = #customer_id
);
(I might have some of the column names wrong.)
BEGIN TRAN
SELECT COUNT(*)
FROM tblInvoices
WHERE ModifiedBy = 'DataImporterUser'
;WITH cte AS
(
SELECT TableId,
TransactionDate,
UserName,
TransactionDetail,
ROW_NUMBER() OVER (PARTITION BY TableId ORDER BY TransactionDate DESC) AS rn
FROM tblAudit
WHERE TableName = 'tblInvoices'
AND UserName <> 'DataImporterUser'
)
UPDATE C
SET C.ModifiedBy = cte.UserName,
C.ModifiedDate = cte.TransactionDate
FROM tblInvoices AS C
INNER JOIN cte
ON cte.TableId = C.InvoiceId
WHERE rn = 1
SELECT COUNT(*)
FROM tblEvents
WHERE ModifiedBy = 'DataImporterUser'
ROLLBACK

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

sql server 2008, cannot use order by in subquery

here's my sql server 2008 stored procedure.
ALTER PROCEDURE [dbo].[GetSharedSmoothies]
#Page INT ,
#Status INT ,
#ItemPerPage INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #X INT
DECLARE #Y INT
SET #X = ( #Page - 1 ) * #ItemPerPage
SET #Y = #Page * #ItemPerPage
SELECT *
FROM ( SELECT S.* ,
U.Avatar ,
U.Displayname ,
( SELECT COUNT(Id)
FROM Vote
WHERE Vote.SmoothieId = S.Id
) AS Votes ,
ROW_NUMBER() OVER ( ORDER BY S.Id ) rownum
FROM dbo.Smoothie AS S
INNER JOIN dbo.[User] AS U ON S.UserId = U.Id
WHERE S.IsPublic = 1
AND S.Status = 3
AND S.UserId > 0
-- ORDER BY S.CreatedDate DESC
) seq
WHERE seq.rownum BETWEEN #X AND #Y
ORDER BY seq.rownum
END
in my code, you will see I comment out the order by
-- ORDER BY S.CreatedDate DESC
because order by will not work in subquery. i need to show the lastest one on the top. is there a way I can use order by in my code?
You may add S.CreatedDate within the Row_NUMBER()
ROW_NUMBER() OVER (PARTITION BY S.Id ORDER BY S.CreatedDate DESC) AS RowNum
That's right. It is not allowed, because it will do nothing.
Having the latest one at the top in the subquery will do nothing to the result set using the subquery.
Add the needed column to the result set ORDER BY:
ORDER BY seq.CreatedDate DESC, seq.rownum
Or:
ORDER BY seq.rownum, seq.CreatedDate DESC

Stored Procedure and output parameter from paging script (SQL Server 2008)

I have the below stored procedure and would like to only have one SQL statement. At the moment you can see there are two statements, one for the actual paging and one for a count of the total records which needs to be return to my app for paging.
However, the below is inefficient as I am getting the total rows from the first query:
COUNT(*) OVER(PARTITION BY 1) as TotalRows
How can I set TotalRows as my output parameter?
ALTER PROCEDURE [dbo].[Nop_LoadAllOptimized]
(
#PageSize int = null,
#PageNumber int = null,
#WarehouseCombinationID int = null,
#CategoryId int = null,
#OrderBy int = null,
#TotalRecords int = null OUTPUT
)
AS
BEGIN
WITH Paging AS (
SELECT rn = (ROW_NUMBER() OVER (
ORDER BY
CASE WHEN #OrderBy = 0 AND #CategoryID IS NOT NULL AND #CategoryID > 0
THEN pcm.DisplayOrder END ASC,
CASE WHEN #OrderBy = 0
THEN p.[Name] END ASC,
CASE WHEN #OrderBy = 5
THEN p.[Name] END ASC,
CASE WHEN #OrderBy = 10
THEN wpv.Price END ASC,
CASE WHEN #OrderBy = 15
THEN wpv.Price END DESC,
CASE WHEN #OrderBy = 20
THEN wpv.Price END DESC,
CASE WHEN #OrderBy = 25
THEN wpv.UnitPrice END ASC
)),COUNT(*) OVER(PARTITION BY 1) as TotalRows, p.*, pcm.DisplayOrder, wpv.Price, wpv.UnitPrice FROM Nop_Product p
INNER JOIN Nop_Product_Category_Mapping pcm ON p.ProductID=pcm.ProductID
INNER JOIN Nop_ProductVariant pv ON p.ProductID = pv.ProductID
INNER JOIN Nop_ProductVariant_Warehouse_Mapping wpv ON pv.ProductVariantID = wpv.ProductVariantID
WHERE pcm.CategoryID = #CategoryId
AND (wpv.Published = 1 AND pv.Published = 1 AND p.Published = 1 AND p.Deleted = 0 AND pv.Deleted = 0 and wpv.Deleted = 0)
AND wpv.WarehouseID IN (select WarehouseID from Nop_WarehouseCombination where UserWarehouseCombinationID = #WarehouseCombinationID)
)
SELECT TOP (#PageSize) * FROM Paging PG
WHERE PG.rn > (#PageNumber * #PageSize) - #PageSize
SELECT #TotalRecords = COUNT(p.ProductId) FROM Nop_Product p
INNER JOIN Nop_Product_Category_Mapping pcm ON p.ProductID=pcm.ProductID
INNER JOIN Nop_ProductVariant pv ON p.ProductID = pv.ProductID
INNER JOIN Nop_ProductVariant_Warehouse_Mapping wpv ON pv.ProductVariantID = wpv.ProductVariantID
WHERE pcm.CategoryID = #CategoryId
AND (wpv.Published = 1 AND pv.Published = 1 AND p.Published = 1 AND p.Deleted = 0 AND pv.Deleted = 0 and wpv.Deleted = 0)
AND wpv.WarehouseID IN (select WarehouseID from Nop_WarehouseCombination where UserWarehouseCombinationID = #WarehouseCombinationID)
END
I think I understand your issue here. Have you considered that the Count could be done BEFORE the CTE
and then passed in as value to the CTE as a variable.
i.e, set the value for #TotalRecords up front, pass it in, and so the CTE will use this count rather than executing the count a second time?
Does this make sense, or have I missed your point here.
no problem friend, highly possible i missed a trick here. However without the schema and data its tricky to test what I am suggesting. In the absence of someone giving a better answer, I've put this test script with data together to demo what I am talking about. If this isn't what you want then no problem. If it is just plain missing the point again, then I'll take that on the chin.
Declare #pagesize as int
Declare #PageNumber as int
Declare #TotalRowsOutputParm as int
SET #pagesize = 3
SET #PageNumber = 2;
--create some test data
DECLARE #SomeData table
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[SomeValue] [nchar](10) NULL
)
INSERT INTO #SomeData VALUES ('TEST1')
INSERT INTO #SomeData VALUES ('TEST2')
INSERT INTO #SomeData VALUES ('TEST3')
INSERT INTO #SomeData VALUES ('TEST4')
INSERT INTO #SomeData VALUES ('TEST5')
INSERT INTO #SomeData VALUES ('TEST6')
INSERT INTO #SomeData VALUES ('TEST7')
INSERT INTO #SomeData VALUES ('TEST8')
INSERT INTO #SomeData VALUES ('TEST9')
INSERT INTO #SomeData VALUES ('TEST10');
--Get total count of all rows
Set #TotalRowsOutputParm = (SELECT COUNT(SomeValue) FROM #SomeData p) ;
WITH Paging AS
(
SELECT rn = (ROW_NUMBER() OVER (ORDER BY SomeValue ASC)),
#TotalRowsOutputParm as TotalRows, p.*
FROM [SomeData] p
)
SELECT TOP (#PageSize) * FROM Paging PG
WHERE PG.rn > (#PageNumber * #PageSize) - #PageSize
PRINT #TotalRowsOutputParm
I don't think you can do it without running the query twice if you want to assign it to a variable
however, can't you just add another column and do something like this instead?
;WITH Paging AS (select *,ROW_NUMBER() OVER(ORDER BY name) AS rn FROM sysobjects)
SELECT (SELECT MAX(rn) FROM Paging) AS TotalRecords,* FROM Paging
WHERE rn < 10
Or in your case
SELECT TOP (#PageSize) *,(SELECT MAX(PG.rn) FROM Paging) AS TotalRecords
FROM Paging PG
WHERE PG.rn > (#PageNumber * #PageSize) - #PageSize
Then from the front end grab that column
In the end I decided just to use two different SQL statements, one for count, one for select.
The "COUNT(*) OVER(PARTITION BY 1) as TotalRows" actually was pretty expensive and it turned out much quicker to just use two different statements.
Thank you everyone who helped with this question.

Sql query - return 2 table

declare #PageIndex int
declare #PageSize int
declare #CategoryID int
declare #FromReleaseDate datetime
declare #TillRelaseDate datetime
set #CategoryID =6
set #FromReleaseDate = '1.01.2000'
set #TillRelaseDate = '1.01.2022'
set #PageIndex =1
set #PageSize=2
begin
with filtered as (
select ArticleList.ID as ID, ArticleList.CategoryID as CategoryID
from (
select a.*, c.ID as cID, c.ParentID as ParentID,
ROW_NUMBER() over(order by ReleaseOn desc) as RowNum
from Article as a
inner join Category as c
on a.CategoryID=c.ID and (#CategoryID is null or a.CategoryID = #CategoryID )
where (a.ReleaseOn>=#FromReleaseDate)
and (a.ReleaseOn <=#TillRelaseDate )
)
as ArticleList
where ArticleList.RowNum between
(#PageIndex - 1) * #PageSize + 1 and #PageIndex*#PageSize
)
select c.* from Article as a
inner join Category as c on a.CategoryID=c.ID
where
c.id in (select CategoryID from filtered)
select a.*
from Article as a
inner join Category as c on a.CategoryID=c.ID
where a.id in (select id from filtered)
end
I have to return 2 tables. But I can't do it, because filtered in the second query is not accessible. Is there any way to fix this error?
Using table variable...
declare #filtered as table (
ID int
, CategoryID int
)
insert into #filtered
select ArticleList.ID as ID, ArticleList.CategoryID as CategoryID
from (
select a.*, c.ID as cID, c.ParentID as ParentID,
ROW_NUMBER() over(order by ReleaseOn desc) as RowNum
from Article as a
inner join Category as c
on a.CategoryID=c.ID and (#CategoryID is null or a.CategoryID = #CategoryID )
where (a.ReleaseOn>=#FromReleaseDate)
and (a.ReleaseOn <=#TillRelaseDate )
)
as ArticleList
where ArticleList.RowNum between
(#PageIndex - 1) * #PageSize + 1 and #PageIndex*#PageSize
with filtered as (
select * from #filtered
)
... Rest of the query
select * from #filtered
Use table variables or create a view (if you have permissions) that represents the query you have in your CTE currently.