Related
I'm using SQL Server 2014. I have a structure like this:
Id BIGINT,
ItemName NVARCHAR(4000),
RecordDate DATETIME2,
Supplier NVARCHAR(450),
Quantity DECIMAL(18, 2),
ItemUnit NVARCHAR(2000),
EntityUnit NVARCHAR(2000),
ItemSize DECIMAL(18, 2),
PackageSize DECIMAL(18, 2),
FamilyCode NVARCHAR(20),
Family NVARCHAR(500),
CategoryCode NVARCHAR(20),
Category NVARCHAR(500),
SubCategoryCode NVARCHAR(20),
SubCategory NVARCHAR(500),
ItemGroupCode NVARCHAR(20),
ItemGroup NVARCHAR(500),
PurchaseValue DECIMAL(18, 2),
UnitPurchaseValue DECIMAL(18, 2),
PackagePurchaseValue DECIMAL(18, 2),
FacilityCode NVARCHAR(450),
CurrencyCode NVARCHAR(5)
I'd like to select distinct ItemNames from BatchRecords table paired with the max Id among the items with the same ItemName as well as Supplier, Quantity and other values of the item with the max Id for each ItemName. So far, I came up with the following SP, definitely it doesn't work yet as GROUP BY throws an error.
I could probably use a subquery, but then how do I satisfy the condition with max Ids for each unique ItemName? Also, any input to the stored procedure quality/obvious bottleneck is highly appreciated as it has to be somewhat quick.
CREATE PROCEDURE dbo.GetRecordsPageFlat
(#BatchIds dbo.GenericIntArray READONLY,
#FileRequestId INT,
#PageSize INT,
#PageCount INT,
#LastId BIGINT,
#NameMaskValue NVARCHAR(128) = NULL,
#NameMaskType INT = NULL,
#FamilyCodeMaskValue NVARCHAR(128),
#CategoryCodeMaskValue NVARCHAR(128),
#SubCategoryCodeMaskValue NVARCHAR(128)
)
AS
SET NOCOUNT ON;
DECLARE #Temp dbo.RecordImportStructure
DECLARE #ErrorCode INT
DECLARE #Step NVARCHAR(200)
DECLARE #Rows INT
--OUTPUT ##ROWCOUNT
--OUTPUT INSERTED.Id
INSERT INTO #Temp (
Id,
ItemName,
Supplier,
Quantity,
ItemUnit,
EntityUnit,
ItemSize,
PackageSize,
PurchaseValue,
UnitPurchaseValue,
PackagePurchaseValue,
CurrencyCode
)
SELECT
BR.Id,
BR.ItemName,
BR.Supplier,
BR.Quantity,
BR.ItemUnit,
BR.EntityUnit,
BR.ItemSize,
BR.PackageSize,
BR.ItemGroup,
BR.UnitPurchaseValue,
BR.PackagePurchaseValue,
C.IsoCode
FROM
dbo.BatchRecords BR
LEFT OUTER JOIN
dbo.FacilityInstances F ON F.Id = BR.FacilityInstanceId
LEFT OUTER JOIN
dbo.Currencies C ON C.Id = BR.CurrencyId
--OPTION(RECOMPILE)
WHERE
BR.DataBatchId IN (SELECT * FROM #BatchIds)
AND BR.Id > #LastId
AND (#FamilyCodeMaskValue IS NULL OR BR.FamilyCode = #FamilyCodeMaskValue)
AND (#CategoryCodeMaskValue IS NULL OR BR.CategoryCode = #CategoryCodeMaskValue)
AND (#SubCategoryCodeMaskValue IS NULL OR BR.SubCategoryCode = #SubCategoryCodeMaskValue)
AND (#NameMaskType IS NULL AND #NameMaskValue IS NULL
OR ((#NameMaskType = 1 AND BR.ItemName LIKE #NameMaskValue + '%')
OR (#NameMaskType = 2 AND BR.ItemName LIKE '%' + #NameMaskValue)
OR (#NameMaskType = 3 AND BR.ItemName LIKE '%' + #NameMaskValue + '%')
))
GROUP BY
BR.ItemName
ORDER BY
BR.Id
OFFSET #PageCount * #PageSize ROWS
FETCH NEXT #PageSize ROWS ONLY;
UPDATE dbo.BatchActionRequests
SET PageNumber = #PageCount+1,
LatestItemId = (SELECT MAX(Id) FROM #Temp)
WHERE Id = #FileRequestId
It looks like a top-n-per-group problem.
There are two common approaches to it: using ROW_NUMBER and CROSS APPLY. Here is the ROW_NUMBER variant. See Get top 1 row of each group for details.
WITH
CTE
AS
(
SELECT
BR.Id,
BR.ItemName,
BR.Supplier,
BR.Quantity,
BR.ItemUnit,
BR.EntityUnit,
BR.ItemSize,
BR.PackageSize,
-- BR.ItemGroup,???
BR.UnitPurchaseValue,
BR.PackagePurchaseValue,
C.IsoCode AS CurrencyCode,
ROW_NUMBER() OVER (PARTITION BY BR.ItemName ORDER BY BR.Id DESC) AS rn
FROM
dbo.BatchRecords BR
LEFT OUTER JOIN dbo.FacilityInstances F ON F.Id = BR.FacilityInstanceId
LEFT OUTER JOIN dbo.Currencies C ON C.Id = BR.CurrencyId
WHERE
BR.DataBatchId IN (SELECT * FROM #BatchIds)
AND BR.Id > #LastId
AND (#FamilyCodeMaskValue IS NULL OR BR.FamilyCode = #FamilyCodeMaskValue)
AND (#CategoryCodeMaskValue IS NULL OR BR.CategoryCode = #CategoryCodeMaskValue)
AND (#SubCategoryCodeMaskValue IS NULL OR BR.SubCategoryCode = #SubCategoryCodeMaskValue)
AND (#NameMaskType IS NULL AND #NameMaskValue IS NULL
OR ((#NameMaskType = 1 AND BR.ItemName LIKE #NameMaskValue + '%')
OR (#NameMaskType = 2 AND BR.ItemName LIKE '%' + #NameMaskValue)
OR (#NameMaskType = 3 AND BR.ItemName LIKE '%' + #NameMaskValue + '%')
))
)
INSERT INTO #Temp (
Id,
ItemName,
Supplier,
Quantity,
ItemUnit,
EntityUnit,
ItemSize,
PackageSize,
-- PurchaseValue,???
UnitPurchaseValue,
PackagePurchaseValue,
CurrencyCode
)
SELECT
Id,
ItemName,
Supplier,
Quantity,
ItemUnit,
EntityUnit,
ItemSize,
PackageSize,
-- PurchaseValue,???
UnitPurchaseValue,
PackagePurchaseValue,
CurrencyCode
FROM CTE
WHERE rn = 1
ORDER BY
Id
OFFSET #PageCount * #PageSize ROWS
FETCH NEXT #PageSize ROWS ONLY
OPTION(RECOMPILE);
For each ItemName the query will pick the row with the largest Id.
;WITH CTC
AS
(
SELECT MAX(BR.ID) AS Id, BR.ItemName
FROM dbo.BatchRecords BR
LEFT OUTER JOIN dbo.FacilityInstances F ON F.Id = BR.FacilityInstanceId
WHERE BR.DataBatchId IN (SELECT * FROM #BatchIds)
AND BR.Id > #LastId
AND (#FamilyCodeMaskValue IS NULL OR BR.FamilyCode = #FamilyCodeMaskValue)
AND (#CategoryCodeMaskValue IS NULL OR BR.CategoryCode = #CategoryCodeMaskValue)
AND (#SubCategoryCodeMaskValue IS NULL OR BR.SubCategoryCode = #SubCategoryCodeMaskValue)
AND (#NameMaskType IS NULL AND #NameMaskValue IS NULL
OR ((#NameMaskType = 1 AND BR.ItemName LIKE #NameMaskValue + '%')
OR (#NameMaskType = 2 AND BR.ItemName LIKE '%' + #NameMaskValue)
OR (#NameMaskType = 3 AND BR.ItemName LIKE '%' + #NameMaskValue + '%')
))
GROUP BY
BR.ItemName
)
INSERT INTO #Temp (
Id,
ItemName,
Supplier,
Quantity,
ItemUnit,
EntityUnit,
ItemSize,
PackageSize,
PurchaseValue,
UnitPurchaseValue,
PackagePurchaseValue,
CurrencyCode
)
SELECT BR.Id,
BR.ItemName,
BR.Supplier,
BR.Quantity,
BR.ItemUnit,
BR.EntityUnit,
BR.ItemSize,
BR.PackageSize,
BR.ItemGroup,
BR.UnitPurchaseValue,
BR.PackagePurchaseValue,
C.IsoCode
FROM CTC t
JOIN dbo.BatchRecords BR ON t.Id = BR.Id
LEFT OUTER JOIN dbo.Currencies C ON C.Id = BR.CurrencyId
ORDER BY BR.Id
OFFSET #PageCount * #PageSize ROWS
FETCH NEXT #PageSize ROWS ONLY;
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
I want to store the top 2 results in 2 variables.
create table t(id int);
insert into t (id) values (1),(2),(3),(4);
declare #id1 int
declare #id2 int
select top 2 #id1 = first id,
#id2 = next id
from t
SQLFiddle
Can I do it in one query without using a loop?
declare #id1 int,#id2 int
;with cte as (
select top (2) id
from t
order by id
)
select #id1 = min(id), #id2 = max(id)
from cte
select #id1,#id2
Fiddle demo
with cte as (
select top 2 id, row_number() over(order by id) as rn
from t
order by id
)
select
#id1 = (select id from cte where rn = 1),
#id2 = (select id from cte where rn = 2)
or
with cte as (
select top 2 id, row_number() over(order by id) as rn
from t
order by id
)
select
#id1 = max(case when rn = 1 then id end),
#id2 = max(case when rn = 2 then id end)
from cte
sql fiddle demo
You can use LEAD() for SQL Server 2012.
SELECT TOP 1 #id1 = ID, #id2 = LEAD(ID) OVER (ORDER BY ID) FROM t
SQLFiddle Demo
With two SELECT it's easy...
DECLARE #id1 INT
DECLARE #id2 INT
SELECT TOP 1 #id1 = x.id
FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY id) RN FROM t) x
WHERE x.RN = 1
SELECT TOP 1 #id2 = x.id
FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY id) RN FROM t) x
WHERE x.RN = 2
SELECT #id1, #id2
With SQL 2012 you clearly could
SELECT #id1 = id
FROM t ORDER BY id OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
SELECT #id2 = id
FROM t ORDER BY id OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY
Or evein in 2008 you could
; WITH Base AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY id) RN FROM t
)
SELECT #id1 = b1.id, #id2 = b2.id
FROM Base b1, Base b2
WHERE b1.RN = 1 AND B2.RN = 2
declare #id1 int
declare #id2 int
declare #table table(id int,rownum int)
insert into #table
select top 2 id,row_number() over( order by id) as rn from t
select #id1=case rownum when 1 then id else #id1 end,
#id2=case rownum when 2 then id end from #table
select #id1,#id2
SQL FIDDLE
More easy way with 2 selects:
declare #id1 int
declare #id2 int
select top 1 #id1 = id from t
select top 2 #id2 = id from t
select #id1, #id2
SQL Fiddle
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.
What is the simplest way of doing a recursive self-join in SQL Server? I have a table like this:
PersonID | Initials | ParentID
1 CJ NULL
2 EB 1
3 MB 1
4 SW 2
5 YT NULL
6 IS 5
And I want to be able to get the records only related to a hierarchy starting with a specific person. So If I requested CJ's hierarchy by PersonID=1 I would get:
PersonID | Initials | ParentID
1 CJ NULL
2 EB 1
3 MB 1
4 SW 2
And for EB's I'd get:
PersonID | Initials | ParentID
2 EB 1
4 SW 2
I'm a bit stuck on this can can't think how to do it apart from a fixed-depth response based on a bunch of joins. This would do as it happens because we won't have many levels but I would like to do it properly.
Thanks! Chris.
WITH q AS
(
SELECT *
FROM mytable
WHERE ParentID IS NULL -- this condition defines the ultimate ancestors in your chain, change it as appropriate
UNION ALL
SELECT m.*
FROM mytable m
JOIN q
ON m.parentID = q.PersonID
)
SELECT *
FROM q
By adding the ordering condition, you can preserve the tree order:
WITH q AS
(
SELECT m.*, CAST(ROW_NUMBER() OVER (ORDER BY m.PersonId) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN AS bc
FROM mytable m
WHERE ParentID IS NULL
UNION ALL
SELECT m.*, q.bc + '.' + CAST(ROW_NUMBER() OVER (PARTITION BY m.ParentID ORDER BY m.PersonID) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN
FROM mytable m
JOIN q
ON m.parentID = q.PersonID
)
SELECT *
FROM q
ORDER BY
bc
By changing the ORDER BY condition you can change the ordering of the siblings.
Using CTEs you can do it this way
DECLARE #Table TABLE(
PersonID INT,
Initials VARCHAR(20),
ParentID INT
)
INSERT INTO #Table SELECT 1,'CJ',NULL
INSERT INTO #Table SELECT 2,'EB',1
INSERT INTO #Table SELECT 3,'MB',1
INSERT INTO #Table SELECT 4,'SW',2
INSERT INTO #Table SELECT 5,'YT',NULL
INSERT INTO #Table SELECT 6,'IS',5
DECLARE #PersonID INT
SELECT #PersonID = 1
;WITH Selects AS (
SELECT *
FROM #Table
WHERE PersonID = #PersonID
UNION ALL
SELECT t.*
FROM #Table t INNER JOIN
Selects s ON t.ParentID = s.PersonID
)
SELECT *
FROm Selects
The Quassnoi query with a change for large table. Parents with more childs then 10: Formating as str(5) the row_number()
WITH q AS
(
SELECT m.*, CAST(str(ROW_NUMBER() OVER (ORDER BY m.ordernum),5) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN AS bc
FROM #t m
WHERE ParentID =0
UNION ALL
SELECT m.*, q.bc + '.' + str(ROW_NUMBER() OVER (PARTITION BY m.ParentID ORDER BY m.ordernum),5) COLLATE Latin1_General_BIN
FROM #t m
JOIN q
ON m.parentID = q.DBID
)
SELECT *
FROM q
ORDER BY
bc
SQL 2005 or later, CTEs are the standard way to go as per the examples shown.
SQL 2000, you can do it using UDFs -
CREATE FUNCTION udfPersonAndChildren
(
#PersonID int
)
RETURNS #t TABLE (personid int, initials nchar(10), parentid int null)
AS
begin
insert into #t
select * from people p
where personID=#PersonID
while ##rowcount > 0
begin
insert into #t
select p.*
from people p
inner join #t o on p.parentid=o.personid
left join #t o2 on p.personid=o2.personid
where o2.personid is null
end
return
end
(which will work in 2005, it's just not the standard way of doing it. That said, if you find that the easier way to work, run with it)
If you really need to do this in SQL7, you can do roughly the above in a sproc but couldn't select from it - SQL7 doesn't support UDFs.
Check following to help the understand the concept of CTE recursion
DECLARE
#startDate DATETIME,
#endDate DATETIME
SET #startDate = '11/10/2011'
SET #endDate = '03/25/2012'
; WITH CTE AS (
SELECT
YEAR(#startDate) AS 'yr',
MONTH(#startDate) AS 'mm',
DATENAME(mm, #startDate) AS 'mon',
DATEPART(d,#startDate) AS 'dd',
#startDate 'new_date'
UNION ALL
SELECT
YEAR(new_date) AS 'yr',
MONTH(new_date) AS 'mm',
DATENAME(mm, new_date) AS 'mon',
DATEPART(d,#startDate) AS 'dd',
DATEADD(d,1,new_date) 'new_date'
FROM CTE
WHERE new_date < #endDate
)
SELECT yr AS 'Year', mon AS 'Month', count(dd) AS 'Days'
FROM CTE
GROUP BY mon, yr, mm
ORDER BY yr, mm
OPTION (MAXRECURSION 1000)
DELIMITER $$
DROP PROCEDURE IF EXISTS `myprocDURENAME`$$
CREATE DEFINER=`root`#`%` PROCEDURE `myprocDURENAME`( IN grp_id VARCHAR(300))
BEGIN
SELECT h.ID AS state_id,UPPER(CONCAT( `ACCNAME`,' [',b.`GRPNAME`,']')) AS state_name,h.ISACTIVE FROM accgroup b JOIN (SELECT get_group_chield (grp_id) a) s ON FIND_IN_SET(b.ID,s.a) LEFT OUTER JOIN acc_head h ON b.ID=h.GRPID WHERE h.ID IS NOT NULL AND H.ISACTIVE=1;
END$$
DELIMITER ;
////////////////////////
DELIMITER $$
DROP FUNCTION IF EXISTS `get_group_chield`$$
CREATE DEFINER=`root`#`%` FUNCTION `get_group_chield`(get_id VARCHAR(999)) RETURNS VARCHAR(9999) CHARSET utf8
BEGIN
DECLARE idd VARCHAR(300);
DECLARE get_val VARCHAR(300);
DECLARE get_count INT;
SET idd=get_id;
SELECT GROUP_CONCAT(id)AS t,COUNT(*) t1 INTO get_val,get_count FROM accgroup ag JOIN (SELECT idd AS n1) d ON FIND_IN_SET(ag.PRNTID,d.n1);
SELECT COUNT(*) INTO get_count FROM accgroup WHERE PRNTID IN (idd);
WHILE get_count >0 DO
SET idd=CONCAT(idd,',', get_val);
SELECT GROUP_CONCAT(CONCAT('', id ,'' ))AS t,COUNT(*) t1 INTO get_val,get_count FROM accgroup ag JOIN (SELECT get_val AS n1) d ON FIND_IN_SET(ag.PRNTID,d.n1);
END WHILE;
RETURN idd;
-- SELECT id FROM acc_head WHERE GRPID IN (idd);
END$$
DELIMITER ;