How to merge two particular rows into single row and reaming rows are same using stored procedure SQL Server 2012 - sql

I have data like this. first row of Id 1 from particular time period and second row of id 1 is another time period. so now want to combined id and name which are same in the two time periods reaming are same.if there is no orders from that time period its should be display 0 or null.
Id Name Qty Price
----------------------
1 Rose 4 540
1 Rose 1 640
2 Lilly 5 550
2 Lilly 18 360
3 Grand 2 460
3 Grand 10 360
4 lotus 0 0
4 Lotus 9 580
now I want data like this..
Id Name Qty Price
4 540
1 rose
1 640
5 550
2 Lilly
18 360
2 460
3 Grand
10 360
0 0
4 Lotus
9 580
This is my procedure
create PROCEDURE [dbo].[Sp_Orders]
(
#Startdate varchar(30),
#Enddate varchar(30),
#Startdate1 varchar(30),
#Enddate1 varchar(30)
)
--[Sp_Orders] '03/01/2016','03/15/2016','02/01/2016','02/28/2016'
AS
BEGIN
---First Duration----
SELECT DISTINCT
op.ProductId as id, op.Price as Prc,
sc.SubCategoryName as ScName,
COUNT(op.ProductId) AS Qty,
ROUND(SUM(op.Price * op.Quantity), 0) AS Revenue,
FROM
orderdetails od
INNER JOIN
(SELECT DISTINCT
Orderid, Productid, ProductFeatures, Price, Quantity
FROM
OrderProducts) op ON od.Orderid = op.Orderid
INNER JOIN
products p ON p.productid = op.productid
INNER JOIN
subcategory sc ON sc.subcategoryid = p.subcategoryid
WHERE
CONVERT(datetime, CONVERT(varchar(50), od.DeliveryDate, 101)) BETWEEN #Startdate AND #Enddate
GROUP BY
op.ProductID, op.Price, sc.SubCategoryName
---Second Duration----
SELECT DISTINCT
op.ProductID AS id, op.Price AS Prc,
sc.SubCategoryName AS ScName,
COUNT(op.ProductId) AS Qty,
ROUND(SUM(op.Price * op.Quantity), 0) AS Revenue,
FROM
orderdetails od
INNER JOIN
(SELECT DISTINCT
Orderid, Productid, ProductFeatures, Price, Quantity
FROM
OrderProducts) op ON od.Orderid = op.Orderid
INNER JOIN
products p ON p.productid = op.productid
INNER JOIN
subcategory sc ON sc.subcategoryid = p.subcategoryid
WHERE
CONVERT(datetime, CONVERT(varchar(50),od.DeliveryDate,101)) BETWEEN #Startdate1 AND #Enddate1
GROUP BY
op.ProductID, op.Price, sc.SubCategoryName
END

From what I understood from your Question and Comments:
Schema for your case
SELECT * INTO #TAB FROM(
SELECT 1 ID, 'ROSE' NAME, 4 QTY, 540 PRICE
UNION ALL
SELECT 1 , 'ROSE' , 1 , 640
UNION ALL
SELECT 2 , 'LILLY' , 5 , 550
UNION ALL
SELECT 2 , 'LILLY' , 18 ,360
UNION ALL
SELECT 3 , 'GRAND' , 2 , 460
UNION ALL
SELECT 3 , 'GRAND' , 10 ,360
UNION ALL
SELECT 4 , NULL,NULL,NULL
UNION ALL
SELECT 4 , 'LOTUS' , 9 , 580
) AS A
And the Logic to display is as below
SELECT CASE WHEN SNO=1 THEN CAST(ID AS VARCHAR(250)) ELSE '' END ID,
CASE WHEN SNO=1 THEN ISNULL(NAME,'') ELSE '' END NAME,ISNULL(Qty,0)Qty
,ISNuLL(Price,0)Price FROM (
SELECT ROW_NUMBER() Over(partition by Name, Id ORDER BY (SELECT 1)) SNO
,ID, NAME , Qty, Price, ID AS ID2 FROM #TAB
)AS A
ORDER BY ID2, NAME DESC
Try this from your Procedure. And may need to do type cast based on your actual datatypes
CREATE PROCEDURE [DBO].[SP_ORDERS]
(
#STARTDATE VARCHAR(30),
#ENDDATE VARCHAR(30),
#STARTDATE1 VARCHAR(30),
#ENDDATE1 VARCHAR(30)
)
--[SP_ORDERS] '03/01/2016','03/15/2016','02/01/2016','02/28/2016'
AS
BEGIN
SELECT CASE WHEN SNO=1 THEN CAST(ID AS VARCHAR(250)) ELSE '' END ID,CASE WHEN SNO=1 THEN ISNULL(SCNAME,'') ELSE '' END NAME,ISNULL(QTY,0)QTY,ISNULL(REVENUE,0)PRICE FROM (
SELECT ROW_NUMBER() OVER(PARTITION BY SCNAME, ID ORDER BY (SELECT 1)) SNO, ID, SCNAME , QTY, REVENUE, ID AS ID2 FROM (
SELECT DISTINCT OP.PRODUCTID AS ID,OP.PRICE AS PRC,SC.SUBCATEGORYNAME AS SCNAME,COUNT(OP.PRODUCTID) AS QTY, ROUND(SUM(OP.PRICE * OP.QUANTITY), 0) AS REVENUE
FROM ORDERDETAILS OD INNER JOIN
(SELECT DISTINCT ORDERID,PRODUCTID,PRODUCTFEATURES,PRICE,QUANTITY FROM ORDERPRODUCTS ) OP ON OD.ORDERID=OP.ORDERID
INNER JOIN PRODUCTS P ON P.PRODUCTID=OP.PRODUCTID
INNER JOIN SUBCATEGORY SC ON SC.SUBCATEGORYID=P.SUBCATEGORYID
WHERE CONVERT(DATETIME,CONVERT(VARCHAR(50),OD.DELIVERYDATE,101)) BETWEEN #STARTDATE AND #ENDDATE
GROUP BY OP.PRODUCTID,OP.PRICE,SC.SUBCATEGORYNAME
---SECOND DURATION----
UNION ALL --ADDED NOW
SELECT DISTINCT OP.PRODUCTID AS ID,OP.PRICE AS PRC,SC.SUBCATEGORYNAME AS SCNAME,COUNT(OP.PRODUCTID) AS QTY, ROUND(SUM(OP.PRICE * OP.QUANTITY), 0) AS REVENUE
FROM ORDERDETAILS OD INNER JOIN
(SELECT DISTINCT ORDERID,PRODUCTID,PRODUCTFEATURES,PRICE,QUANTITY FROM ORDERPRODUCTS ) OP ON OD.ORDERID=OP.ORDERID
INNER JOIN PRODUCTS P ON P.PRODUCTID=OP.PRODUCTID
INNER JOIN SUBCATEGORY SC ON SC.SUBCATEGORYID=P.SUBCATEGORYID
WHERE CONVERT(DATETIME,CONVERT(VARCHAR(50),OD.DELIVERYDATE,101)) BETWEEN #STARTDATE1 AND #ENDDATE1
GROUP BY OP.PRODUCTID,OP.PRICE,SC.SUBCATEGORYNAME
)
AS A
)B
ORDER BY ID2, NAME
END

Based on your sample data i have given this Out put but if the data is inconsistent it may not give accurate results if you see the Expected Output it gives exact same
Declare #Table1 TABLE
(Id VARCHAR(10), Name varchar(5),Qty VARCHAR(10), Price varchar(10))
;
INSERT INTO #Table1
(Id, Name,Qty, Price)
VALUES
(1, 'Rose',4, 540),
(1, 'Rose',1, 640),
(2, 'Lilly',5, 550),
(2, 'Lilly',18, 360),
(3, 'Grand',2, 460),
(3, 'Grand',10, 360),
(4,'Lotus',0,0),
(4, 'Lotus',9, 580)
;
SCRIPT
;WITH CTE AS (
Select
CASE WHEN RN = 1 THEN ID ELSE NULL END ID,
CASE WHEN RN = 1 THEN Name ELSE NULL END NAME,
Qty,
Price
from (
select
Id,
Name,
Qty,
Price,
ROW_NUMBER()OVER(PARTITION BY ID,NAME ORDER BY NAME)RN
FROM
#Table1)T)
Select CASE WHEN RN = 2 THEN T.Id ELSE '' END ID,
CASE WHEN RN = 2 THEN T.Name ELSE '' END Name,
CASE WHEN RN IN (1,3) THEN ISNULL(T.Qty,0) ELSE '' END qty,
CASE WHEN RN IN (1,3) THEN ISNULL(T.Price,0) ELSE '' END qty from (
Select
T.ID,
T.NAME,
c.Qty,
C.Price,
ROW_NUMBER()OVER(PARTITION BY T.ID,T.NAME ORDER BY T.NAME)RN
from #Table1 T
INNER JOIN CTE C
ON T.Id = C.ID
AND T.Name = C.NAME
OR (T.Qty = C.Qty OR T.Price = C.Price ))T
WHERE T.RN <> 4

Related

SQL Server - Get the Total Count with the TOP 1 product

I need to be able to find the total count of orders placed by a customer, but also find the top product in one query. For example in the following structure,
CREATE TABLE #Cust (CustId INT, CustName VARCHAR(50))
CREATE TABLE #Product (ProductId INT, ProductName VARCHAR(10) )
CREATE TABLE #Orders (CustId INT, ProductId INT, OrderTaken BIT)
INSERT #Cust
( CustId, CustName )
VALUES ( 1, 'Paul' ),
( 2, 'F' ),
( 3, 'Francis' )
INSERT #Product
( ProductId, ProductName )
VALUES ( 1, 'Table' ),
( 2, 'Chair' )
INSERT #Orders
( CustId, ProductId, OrderTaken )
VALUES ( 1, 1, 1 ),
( 1, 1, 1 ),
( 1, 2, 1 ),
( 2, 1, 1 )
I have come up with a query,
SELECT * FROM #Cust AS C OUTER APPLY
(
SELECT TOP 1 SQ.ProductId, SUM(SQ.TotalCount) AS TotalQty FROM
(
SELECT O.ProductId, COUNT(*) TotalCount
FROM #Orders AS O WHERE O.CustId = C.CustId
GROUP BY O.CustId , O.ProductId
) SQ
GROUP BY SQ.ProductId
) X
But, that is not giving me the result I am looking for, for Paul it is giving me the correct ProductId, but a count of that product alone.
I want the a Query to return,
CustId | CustName | ProductId | TotalQty
--------+---------------+---------------+------------
1 | Paul | 1 | 3
2 | F | 1 | 1
3 | Francis | NULL | NULL
One option is with the WITH TiES clause
Select Top 1 with ties
CustID
,CustName
,ProductId
,TotalQty
From (
Select C.CustID
,C.CustName
,O.ProductId
,TotalQty = count(O.CustId) over (Partition By O.CustID)
,ProdCount = count(O.CustId) over (Partition By O.CustID,O.ProductID)
From #Cust C
Left Join #Orders O on C.CustID=O.CustId
) A
Order by Row_Number() over (Partition By CustID Order by ProdCount Desc)
Returns
CustID CustName ProductId TotalQty
1 Paul 1 3
2 F 1 1
3 Francis NULL 0
Try
SELECT c.*, ProductId, CustProdTotal, CustTotal
FROM #Cust AS C
OUTER APPLY (
select top(1) with ties ProductId, CustProdTotal, CustTotal
from (
select *, count(OrderTaken) over() as CustTotal
, count(OrderTaken) over(partition by ProductId) as CustProdTotal
from #Orders o
where O.CustId = C.CustId) x
order by row_number() over(order by CustProdTotal desc)
) z
Similar question has been answered with a nice explanation here
(Benefit here : common concept of join used).
(disadvantage : query may not be efficient for large records)
I have modified the solution for your scenario
SELECT s.CustId, s.CustName, s.ProductId, m.TotalOrderTaken
FROM (SELECT p.CustId, p.CustName, t.ProductId, COUNT(*) AS ProductIdCount
FROM #Cust AS p
JOIN #Orders AS t
ON p.CustId = t.CustId
GROUP BY p.CustId, p.CustName, t.ProductId
) AS s
JOIN (SELECT s.CustId, MAX(s.ProductIdCount) AS MaxProductIdCount, sum(s.ProductIdCount) TotalOrderTaken
FROM (
SELECT p.CustId,p.CustName, t.ProductId, COUNT(*) AS ProductIdCount
FROM #Cust AS p
JOIN #Orders AS t
ON p.CustId = t.CustId
GROUP BY p.CustId, p.CustName, t.ProductId
) AS s
GROUP BY s.CustId
) AS m
ON s.CustId = m.CustId AND s.ProductIdCount = m.MaxProductIdCount
Works on SQL Server 2005 onwards.
;with cte1 as (select c.custid, c.custname, o.productid, count(*) as TotalQty
from #cust c
left join #orders o on c.custid=o.custid
left join #product p on p.productid=o.productid
group by c.custid, c.custname, o.productid)
,cte2 as (select custid, max(TotalQty) as TopQty
from cte1
group by custid)
Select cte1.*
from cte1
inner join cte2 on cte1.custid=cte2.custid and cte1.TotalQty=cte2.Topqty
If you can't use the over clause, this would work (obviously, a lot more work compared to an over clause):
SELECT custOrderAll.CustId
, custOrderAll.CustName
, MaxOrder.ProductId
, MAX(custOrderAll.cntAll) TotalQty
FROM (
SELECT c.CustId
, c.CustName
, COUNT(O.ProductId) cntAll
FROM #Cust AS C
LEFT JOIN #Orders AS O
ON O.CustId = C.CustId
GROUP BY c.CustId
, c.CustName
) custOrderAll
LEFT JOIN (
SELECT custOrderMAX.CustId
, custOrderMAX.CustName
, custOrderMAX.ProductId
FROM (
SELECT c.CustId
, c.CustName
, O.ProductId
, COUNT(O.ProductId) cntMax
FROM #Cust AS C
LEFT JOIN #Orders AS O
ON O.CustId = C.CustId
GROUP BY c.CustId
, c.CustName
, O.ProductId
) custOrderMAX
INNER JOIN (
SELECT mxCnt.CustId
, mxCnt.CustName
, MAX(mxCnt.cntMax) mxCnt
FROM (
SELECT c.CustId
, c.CustName
, O.ProductId
, COUNT(O.ProductId) cntMax
FROM #Cust AS C
LEFT JOIN #Orders AS O
ON O.CustId = C.CustId
GROUP BY c.CustId
, c.CustName
, O.ProductId
) mxCnt
GROUP BY mxCnt.CustId
, mxCnt.CustName
) custOrderMAXCnt
ON custOrderMAXCnt.CustId = custOrderMAX.CustId
AND custOrderMAXCnt.mxCnt = custOrderMAX.cntMax
) MaxOrder
ON MaxOrder.CustId = custOrderAll.CustId
AND MaxOrder.CustName = custOrderAll.CustName
GROUP BY custOrderAll.CustId
, custOrderAll.CustName
, MaxOrder.ProductId
Result:
+--------+----------+-----------+----------+
| CustId | CustName | ProductId | TotalQty |
+--------+----------+-----------+----------+
| 1 | Paul | 1 | 3 |
| 2 | F | 1 | 1 |
| 3 | Francis | NULL | 0 |
+--------+----------+-----------+----------+

How to select data rows into columns w/out knowing all values

Below is a table I have with some order details.
OrderID TypeID Amount
11148 1 900
11148 7 30
11148 6 75
12506 3 100
12506 4 60
16845 1 30
Is it possible to return something like this:
OrderID TypeID1 Amount1 TypeID2 Amount2 TypeID3 Amount3
11148 1 900 7 30 6 75
12506 3 100 4 60 null null
16845 1 30 null null null null
I want to get results like this so I can join into another result set with other order information but I can only have one row per ID. I've been trying with a Pivot but it seems I need to know all the possible results for TYPEID and have that many columns instead of just the 3 for TYPEID and 3 for AMOUNT that I'm looking for.
Below is the Pivot table I know how to get to work but this is not desired because then I have columns for each ID type but I would rather have those in the rows too.
SELECT OrderID
,[1] as Amount1
,[2] as Amount2
,[3] as Amount3
,[4] as Amount4
,[5] as Amount5
,[6] as Amount6
,[7] as Amount7
FROM (
SELECT ORDERID, TYPEID, AMOUNT
FROM Order_Details
)x
PIVOT
(
MAX(AMOUNT)
FOR TYPEID in ([1],[2],[3],[4],[5],[6],[7])
)P
OrderID Amount1 Amount2 Amount3 Amount4 Amount5 Amount6 Amount7
11148 900 null null null null 75 30
12506 null null 100 60 null null null
16845 30 null null null null null null
the solution is to make a self outer join combined with row_number over partition like:
select t1.OrderId as OrderID1, t1.TypeID as TypeID1, t1.Amount as Amount1,
t2.TypeID as TypeID2, t2.Amount as Amount2,
t3.TypeID as TypeID3, t3.Amount as Amount3 from
(
SELECT OrderID, TypeID, Amount
FROM (
select OrderID , TypeID, Amount ,ROW_NUMBER() OVER (PARTITION BY OrderID order by OrderID) AS RN
from tt
)Sub
WHERE rn = 1) t1 left join
(
SELECT OrderID, TypeID, Amount
FROM (
select OrderID , TypeID, Amount ,ROW_NUMBER() OVER (PARTITION BY OrderID order by OrderID) AS RN
from tt
)Sub
WHERE rn = 2) t2 on t1.OrderID=t2.OrderID left join
(
SELECT OrderID, TypeID, Amount
FROM (
select OrderID , TypeID, Amount ,ROW_NUMBER() OVER (PARTITION BY OrderID order by OrderID) AS RN
from tt
)Sub
WHERE rn = 3) t3 on t1.OrderID=t3.OrderID;
live example
by using cross apply and Row_number we can achieve same result
declare #Table1 TABLE
(OrderID int, TypeID int, Amount int)
;
INSERT INTO #Table1
(OrderID, TypeID, Amount)
VALUES
(11148, 1, 900),
(11148, 7, 30),
(11148, 6, 75),
(12506, 3, 100),
(12506, 4, 60),
(16845, 1, 30)
;
;with CTE As (
select OrderID,[TypeID1],[TypeID2],[TypeID3] from (
Select OrderID,
val,
COL + CAST(ROW_NUMBER()OVER(PARTITION BY OrderID ORDER BY OrderID) AS VARCHAR(1))RN
FROM #Table1
CROSS APPLY (VALUES ('TypeID',TypeID))CS(Col,val))T
PIVOT (MAX(VAL) FOR RN IN ([TypeID1],[TypeID2],[TypeID3]))P )
,CTE2 AS (select OrderID,[Amount1],[Amount2],[Amount3] from (
Select OrderID,
val,
COL + CAST(ROW_NUMBER()OVER(PARTITION BY OrderID ORDER BY OrderID) AS VARCHAR(1))RN
FROM #Table1
CROSS APPLY (VALUES ('Amount',Amount))CS(Col,val))T
PIVOT (MAX(VAL) FOR RN IN ([Amount1],[Amount2],[Amount3]))P)
select c.OrderID,c.TypeID1,cc.Amount1,c.TypeID2,cc.Amount2,c.TypeID3,cc.Amount3 from CTE C
LEFT JOIN CTE2 CC
ON c.OrderID = cc.OrderID

SQL Inner join not working to get change from previous date

I have a table with the following data :
TradeDate Stock BuySell DayClose
--------------------------------------
10-Dec-12 ABC 1 11
10-Dec-12 ABC 2 12
11-Dec-12 ABC 1 11.5
11-Dec-12 ABC 2 12.5
11-Dec-12 DEF 1 15
11-Dec-12 DEF 2 16
and I want to query on it for a particular date 11-Dec-2012 to get the following output :
Stock Buy Sell Mid Change
--------------------------------------
ABC 11.5 12.5 12.0 0.5
DEF 15 16 15.5
Since DEF does not have data for the previous date, change should be blank for it.
I have created the following query :
Select Stock,
AVG(CASE BuySell WHEN 1 THEN DayClose END) AS 'Buy',
AVG(CASE BuySell WHEN 2 THEN DayClose END) As 'Sell',
Sum(DayClose/2) as 'Mid',
Sum(Change/2) AS Change
FROM (
select t1.Stock, t1.BuySell, t1.DayClose, Sum(t1.DayClose - t2.DayClose) as Change
FROM #myTable as t1 inner join #myTable as t2 on
t1.Stock = t2.Stock
where
t1.TradeDate = '2012-12-11' AND
t2.TradeDate = (SELECT TOP 1 TradeDate FROM #myTable WHERE TradeDate < '2012-12-11' ORDER BY TradeDate DESC)
GROUP BY
t1.Stock, t1.buysell, t1.dayclose ) AS P1 GROUP BY stock
I created a temp table #mytable for this purpose :
drop table #mytable
CREATE TABLE #myTable
(
TradeDate datetime,
stock varchar(20),
buysell int,
dayclose decimal(10,2)
)
insert into #mytable values ('10-dec-2012', 'abc' , 1, 11)
insert into #mytable values ('10-dec-2012', 'abc' , 2, 12)
insert into #mytable values ('11-dec-2012', 'abc' , 1, 11.5)
insert into #mytable values ('11-dec-2012', 'abc' , 2, 12.5)
insert into #mytable values ('11-dec-2012', 'def' , 1, 15)
insert into #mytable values ('11-dec-2012', 'def' , 2, 16)
But I am not able to get the required output, rather getting
Stock Buy Sell Mid Change
--------------------------------------------------------------
abc 11.500000 12.500000 12.00000 1.00
Can someone tell me where am I going wrong. I seem to be lost in here.
Thanks,
Monika
Please try:
;WITH T1 as(
SELECT a.TradeDate
,a.stock
,SUM(CASE WHEN a.BuySell = 1 THEN a.DayClose ELSE 0 END) Buy
,SUM(CASE WHEN a.BuySell = 2 THEN a.DayClose ELSE 0 END) Sell
,SUM(a.DayClose) / 2 AS Mid
FROM #mytable a
GROUP BY a.TradeDate, a.stock
)SELECT t.*,
t.Mid - PR.Mid AS Change
FROM T1 t
LEFT JOIN
T1 PR ON
PR.TradeDate = DATEADD(DAY, -1, t.TradeDate)
AND PR.stock = t.stock
Try this:
SELECT a.TradeDate
,a.stock
,SUM(CASE WHEN a.BuySell = 1 THEN a.DayClose ELSE 0 END) Buy
,SUM(CASE WHEN a.BuySell = 2 THEN a.DayClose ELSE 0 END) Sell
,SUM(a.DayClose) / 2 AS Mid
INTO #temp
FROM #mytable a
GROUP BY a.TradeDate, a.stock
SELECT t.*,
t.Mid - previousRecord.Mid AS Change
FROM #temp t
LEFT JOIN
#temp previousRecord ON
previousRecord.TradeDate = DATEADD(DAY, -1, t.TradeDate)
AND previousRecord.stock = t.stock
DROP TABLE #temp
All you have to do now is to select the data for a date.
Select Stock,
AVG(CASE BuySell WHEN 1 THEN DayClose END) AS 'Buy',
AVG(CASE BuySell WHEN 2 THEN DayClose END) As 'Sell',
Sum(DayClose/2) as 'Mid',
Sum(Change/2) AS Change
FROM (
select t1.Stock, t1.BuySell, t1.DayClose, Sum( t1.DayClose - t2.DayClose ) as Change
FROM #myTable as t1 left join #myTable as t2 on t2.TradeDate = (SELECT TOP 1 TradeDate FROM #myTable WHERE TradeDate < t1.TradeDate ORDER BY TradeDate DESC)
and t1.Stock = t2.Stock and t1.buysell=t2.buysell
where
t1.TradeDate = '11-12-2012'

SQL Update query product cart

ID ProductId MemberId SessionId CreatedOn Quantity
62 1 2 2012-09-11 1
63 2 2 2012-09-11 1
64 1 0 agzdeoqubwokfme05wwmne55 2012-09-11 1
65 3 0 agzdeoqubwokfme05wwmne55 2012-09-11 1
I have above table and I need to make one procedure.
My scenario is without login add item to cart and when you login merge all items.
I will pass two parameter in procedure #MemberId (Login Member Id), #SessionID
And I want out put table like below.
ID ProductId MemberId SessionId CreatedOn Quantity
62 1 2 2012-09-11 2
63 2 2 2012-09-11 1
65 3 2 2012-09-11 1
One product merges with another product and makes quantity 2. And that row should be gone.
Assuming SQL SERVER 2005 or above
CREATE TABLE #T (ID INT , ProductId INT, MemberId INT, SessionId VARCHAR(100), CreatedOn DATETIME, Quantity INT)
INSERT INTO #T SELECT 62,1 ,2 ,' ','2012-09-11',1
INSERT INTO #T SELECT 63, 2,2, ' ','2012-09-11',1
INSERT INTO #T SELECT 64, 1,0,'agzdeoqubwokfme05wwmne55','2012-09-11',1
INSERT INTO #T SELECT 65, 3,0,'agzdeoqubwokfme05wwmne55','2012-09-11',1
;With CTE AS
(SELECT
ID
,ProductId
,MemberId = MAX(MemberId) OVER(PARTITION BY ProductID ORDER bY(SELECT 1))
,SessionID
,CreatedOn = CONVERT(VARCHAR(10),MAX(CreatedOn) OVER(PARTITION BY ProductID ORDER bY(SELECT 1)),20)
,Quantity = SUM(Quantity) OVER(PARTITION BY ProductID ORDER bY(SELECT 1))
,Rn = ROW_NUMBER() OVER(PARTITION BY ProductID ORDER bY(SELECT 1))
FROM #T)
SELECT ID,ProductId,MemberId,SessionID,CreatedOn,Quantity FROM CTE WHERE Rn = 1
DROP TABLE #T
will give the result as
ID ProductId MemberId SessionID CreatedOn Quantity
62 1 2 2012-09-11 2
63 2 2 2012-09-11 1
65 3 0 agzdeoqubwokfme05wwmne55 2012-09-11 1
Finally if you want to update the table, the here you go
UPDATE #T
SET
#T.ID = c.ID
,#T.ProductId =c.ProductId
,#T.MemberId = c.MemberId
,#T.SessionID = c.SessionID
,#T.CreatedOn = c.CreatedOn
,#T.Quantity = c.Quantity
FROM #T
INNER JOIN CTE c
ON #T.ID = c.ID
WHERE c.Rn = 1
The result is
ID ProductId MemberId SessionId CreatedOn Quantity
62 1 2 2012-09-11 2
63 2 2 2012-09-11 1
64 1 0 agzdeoqubwokfme05wwmne5 2012-09-11 1
65 3 0 agzdeoqubwokfme05wwmne55 2012-09-11 1
The complete query is
;With CTE AS
(SELECT
ID
,ProductId
,MemberId = MAX(MemberId) OVER(PARTITION BY ProductID ORDER bY(SELECT 1))
,SessionID
,CreatedOn = CONVERT(VARCHAR(10),MAX(CreatedOn) OVER(PARTITION BY ProductID ORDER bY(SELECT 1)),20)
,Quantity = SUM(Quantity) OVER(PARTITION BY ProductID ORDER bY(SELECT 1))
,Rn = ROW_NUMBER() OVER(PARTITION BY ProductID ORDER bY(SELECT 1))
FROM #T)
UPDATE #T
SET
#T.ID = c.ID
,#T.ProductId =c.ProductId
,#T.MemberId = c.MemberId
,#T.SessionID = c.SessionID
,#T.CreatedOn = CONVERT(VARCHAR(10),c.CreatedOn,20)
,#T.Quantity = c.Quantity
FROM #T
INNER JOIN CTE c
ON #T.ID = c.ID
WHERE c.Rn = 1
SELECT * FROM #T
DROP TABLE #T
For Any Version of SQL
UPDATE #T
SET
#T.ID = c.ID
,#T.ProductId =c.ProductId
,#T.MemberId = c.MemberId
,#T.SessionID = c.SessionID
,#T.CreatedOn = c.CreatedOn
,#T.Quantity = c.Quantity
FROM #T
INNER JOIN (SELECT t.ID,t.ProductID,t.MemberId,t.SessionId,t.CreatedOn,x.Quantity
FROM #T t
JOIN (
SELECT
ID = MIN(ID)
,ProductID
,MemberId = MAX(MemberId)
,CreatedOn = MAX(CreatedOn)
,Quantity = SUM(Quantity)
FROM #T
GROUP BY ProductID
)X
ON t.ID =X.ID) c
ON #T.ID = c.ID
SELECT * FROM #T
DROP TABLE #T
SELECT ProductID,
max(MemberId),
max(CreatedOn),
sum(Quantity)
FROM theTable
WHERE SessionID=<id> or MemberID=<id>
GROUP BY ProductID
Updating may not be the optimum solution, it may be better to delete all the records and insert new ones.
Anyhow you need to do something like this if you want to update and then delete the extra ones.
Assuming ID is the unique key here. Here is complete logic, read carefully
DECLARE #Temp1 TABLE
(
--datafields same AS your table
)
DECLARE #Temp2 TABLE
(
--datafields same AS your table
)
INSERT INTO #Temp1
SELECT ID,
ProductID,
MemberId,
SessionId,
CreatedOn,
Quantity
FROM theTable
WHERE SessionID= #SessionID or MemberID= #MemberID
UPDATE a
SET SessionID = #SessionID , MemberID= #MemberID
FROM #Temp1 AS a
--WHERE SessionID= #SessionID OR MemberID= #MemberID
INSERT INTO #Temp2
SELECT MAX(ID),
ProductID,
max(MemberId),
MAX(SessionId),
max(CreatedOn),
sum(Quantity)
FROM #Temp1
GROUP BY ProductID
UPDATE a
SET a.MemberId = b.MemberId ,a.SessionId=b.SessionId ,a.CreatedOn=b.CreatedOn,a.Quantity=b.Quantity
FROM YourTable AS a
INNER JOIN #Temp2 AS b
ON a.ID=b.ID
DELETE FROM YourTable
WHERE ID IN ( SELECT ID
FROM #Temp1 AS ta
WHERE NOT EXISTS ( SELECT 1
FROM #Temp2 AS tb
WHERE ta.ID = tb.ID ) )

TSQL Finding Order that occurred in 3 consecutive months

Please help me to generate the following query. Say I have customer table and order table.
Customer Table
CustID CustName
1 AA
2 BB
3 CC
4 DD
Order Table
OrderID OrderDate CustID
100 01-JAN-2000 1
101 05-FEB-2000 1
102 10-MAR-2000 1
103 01-NOV-2000 2
104 05-APR-2001 2
105 07-MAR-2002 2
106 01-JUL-2003 1
107 01-SEP-2004 4
108 01-APR-2005 4
109 01-MAY-2006 3
110 05-MAY-2007 1
111 07-JUN-2007 1
112 06-JUL-2007 1
I want to find out the customers who have made orders on three successive months. (Query using SQL server 2005 and 2008 is allowed).
The desired output is:
CustName Year OrderDate
AA 2000 01-JAN-2000
AA 2000 05-FEB-2000
AA 2000 10-MAR-2000
AA 2007 05-MAY-2007
AA 2007 07-JUN-2007
AA 2007 06-JUL-2007
Edit: Got rid or the MAX() OVER (PARTITION BY ...) as that seemed to kill performance.
;WITH cte AS (
SELECT CustID ,
OrderDate,
DATEPART(YEAR, OrderDate)*12 + DATEPART(MONTH, OrderDate) AS YM
FROM Orders
),
cte1 AS (
SELECT CustID ,
OrderDate,
YM,
YM - DENSE_RANK() OVER (PARTITION BY CustID ORDER BY YM) AS G
FROM cte
),
cte2 As
(
SELECT CustID ,
MIN(OrderDate) AS Mn,
MAX(OrderDate) AS Mx
FROM cte1
GROUP BY CustID, G
HAVING MAX(YM)-MIN(YM) >=2
)
SELECT c.CustName, o.OrderDate, YEAR(o.OrderDate) AS YEAR
FROM Customers AS c INNER JOIN
Orders AS o ON c.CustID = o.CustID
INNER JOIN cte2 c2 ON c2.CustID = o.CustID and o.OrderDate between Mn and Mx
order by c.CustName, o.OrderDate
Here is my version. I really was presenting this as a mere curiosity, to show another way of thinking about the problem. It turned out to be more useful than that because it performed better than even Martin Smith's cool "grouped islands" solution. Though, once he got rid of some overly expensive aggregate windowing functions and did real aggregates instead, his query started kicking butt.
Solution 1: Runs of 3 months or more, done by checking 1 month ahead and behind and using a semi-join against that.
WITH Months AS (
SELECT DISTINCT
O.CustID,
Grp = DateDiff(Month, '20000101', O.OrderDate)
FROM
CustOrder O
), Anchors AS (
SELECT
M.CustID,
Ind = M.Grp + X.Offset
FROM
Months M
CROSS JOIN (
SELECT -1 UNION ALL SELECT 0 UNION ALL SELECT 1
) X (Offset)
GROUP BY
M.CustID,
M.Grp + X.Offset
HAVING
Count(*) = 3
)
SELECT
C.CustName,
[Year] = Year(OrderDate),
O.OrderDate
FROM
Cust C
INNER JOIN CustOrder O ON C.CustID = O.CustID
WHERE
EXISTS (
SELECT 1
FROM
Anchors A
WHERE
O.CustID = A.CustID
AND O.OrderDate >= DateAdd(Month, A.Ind, '19991201')
AND O.OrderDate < DateAdd(Month, A.Ind, '20000301')
)
ORDER BY
C.CustName,
OrderDate;
Solution 2: Exact 3-month patterns. If it is a 4-month or greater run, the values are excluded. This is done by checking 2 months ahead and two months behind (essentially looking for the pattern N, Y, Y, Y, N).
WITH Months AS (
SELECT DISTINCT
O.CustID,
Grp = DateDiff(Month, '20000101', O.OrderDate)
FROM
CustOrder O
), Anchors AS (
SELECT
M.CustID,
Ind = M.Grp + X.Offset
FROM
Months M
CROSS JOIN (
SELECT -2 UNION ALL SELECT -1 UNION ALL SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2
) X (Offset)
GROUP BY
M.CustID,
M.Grp + X.Offset
HAVING
Count(*) = 3
AND Min(X.Offset) = -1
AND Max(X.Offset) = 1
)
SELECT
C.CustName,
[Year] = Year(OrderDate),
O.OrderDate
FROM
Cust C
INNER JOIN CustOrder O ON C.CustID = O.CustID
INNER JOIN Anchors A
ON O.CustID = A.CustID
AND O.OrderDate >= DateAdd(Month, A.Ind, '19991201')
AND O.OrderDate < DateAdd(Month, A.Ind, '20000301')
ORDER BY
C.CustName,
OrderDate;
Here's my table-loading script if anyone else wants to play:
IF Object_ID('CustOrder', 'U') IS NOT NULL DROP TABLE CustOrder
IF Object_ID('Cust', 'U') IS NOT NULL DROP TABLE Cust
GO
SET NOCOUNT ON
CREATE TABLE Cust (
CustID int identity(1,1) NOT NULL PRIMARY KEY CLUSTERED,
CustName varchar(100) UNIQUE
)
CREATE TABLE CustOrder (
OrderID int identity(100, 1) NOT NULL PRIMARY KEY CLUSTERED,
CustID int NOT NULL FOREIGN KEY REFERENCES Cust (CustID),
OrderDate smalldatetime NOT NULL
)
DECLARE #i int
SET #i = 1000
WHILE #i > 0 BEGIN
WITH N AS (
SELECT
Nm =
Char(Abs(Checksum(NewID())) % 26 + 65)
+ Char(Abs(Checksum(NewID())) % 26 + 97)
+ Char(Abs(Checksum(NewID())) % 26 + 97)
+ Char(Abs(Checksum(NewID())) % 26 + 97)
+ Char(Abs(Checksum(NewID())) % 26 + 97)
+ Char(Abs(Checksum(NewID())) % 26 + 97)
)
INSERT Cust
SELECT N.Nm
FROM N
WHERE NOT EXISTS (
SELECT 1
FROM Cust C
WHERE
N.Nm = C.CustName
)
SET #i = #i - ##RowCount
END
WHILE #i < 50000 BEGIN
INSERT CustOrder
SELECT TOP (50000 - #i)
Abs(Checksum(NewID())) % 1000 + 1,
DateAdd(Day, Abs(Checksum(NewID())) % 10000, '19900101')
FROM master.dbo.spt_values
SET #i = #i + ##RowCount
END
Performance
Here are some performance testing results for the 3-month-or-more queries:
Query CPU Reads Duration
Martin 1 2297 299412 2348
Martin 2 625 285 809
Denis 3641 401 3855
Erik 1855 94727 2077
This is only one run of each, but the numbers are fairly representative. It turns out that your query wasn't so badly-performing, Denis, after all. Martin's query beats the others hands down, but at first was using some overly-expensive windowing functions strategies that he fixed.
Of course, as I noted, Denis's query isn't pulling the right rows when a customer has two orders on the same day, so his query is out of contention unless he fixed is.
Also, different indexes could possibly shake things up. I don't know.
Here you go:
select distinct
CustName
,year(OrderDate) [Year]
,OrderDate
from
(
select
o2.OrderDate [prev]
,o1.OrderDate [curr]
,o3.OrderDate [next]
,c.CustName
from [order] o1
join [order] o2 on o1.CustId = o2.CustId and datediff(mm, o2.OrderDate, o1.OrderDate) = 1
join [order] o3 on o1.CustId = o3.CustId and o2.OrderId <> o3.OrderId and datediff(mm, o3.OrderDate, o1.OrderDate) = -1
join Customer c on c.CustId = o1.CustId
) t
unpivot
(
OrderDate for [DateName] in ([prev], [curr], [next])
)
unpvt
order by CustName, OrderDate
Here is my take.
select 100 as OrderID,convert(datetime,'01-JAN-2000') OrderDate, 1 as CustID into #tmp union
select 101,convert(datetime,'05-FEB-2000'), 1 union
select 102,convert(datetime,'10-MAR-2000'), 1 union
select 103,convert(datetime,'01-NOV-2000'), 2 union
select 104,convert(datetime,'05-APR-2001'), 2 union
select 105,convert(datetime,'07-MAR-2002'), 2 union
select 106,convert(datetime,'01-JUL-2003'), 1 union
select 107,convert(datetime,'01-SEP-2004'), 4 union
select 108,convert(datetime,'01-APR-2005'), 4 union
select 109,convert(datetime,'01-MAY-2006'), 3 union
select 110,convert(datetime,'05-MAY-2007'), 1 union
select 111,convert(datetime,'07-JUN-2007'), 1 union
select 112,convert(datetime,'06-JUL-2007'), 1
;with cte as
(
select
*
,convert(int,convert(char(6),orderdate,112)) - dense_rank() over(partition by custid order by orderdate) as g
from #tmp
),
cte2 as
(
select
CustID
,g
from cte a
group by CustID, g
having count(g)>=3
)
select
a.CustID
,Yr=Year(OrderDate)
,OrderDate
from cte2 a join cte b
on a.CustID=b.CustID and a.g=b.g