TSQL Finding Order that occurred in 3 consecutive months - sql

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

Related

Find customers with at least 5 transactions in At most 3 consecutive days

I have a table in SQL Server that contains customers' transactions From 2022-02-10 to 2022-03-10.
I want to find customers that have at least 5 transactions on At most three consecutive days
For example, output of below table should be CustomerId = 2 and customerid=3
Id
CustomerId
Transactiondate
1
1
2022-03-01
2
1
2022_03_01
3
1
2022_03_05
4
1
2022_03_07
5
1
2022_03_07
6
2
2022_03_05
7
2
2022_03_05
8
2
2022_03_06
9
2
2022_03_06
10
2
2022_03_07
1
3
2022-03-01
2
3
2022_03_01
3
3
2022_03_01
4
3
2022_03_03
5
3
2022_03_03
I tried this query but it doesn't have good performance for a large table:
select distinct p1.customerid
from trntbl p1
join trntbl p2 on p2.id <> p1.id
and p2.customerid = p1.customerid
and p2.TransactionDate >= p1.TransactionDate
and p2.TransactionDate < date_add(day, 3, p1.prchasedate)
group by p1.customerid, p1.id
having count(*) >= 4
If customers must have done transactions in three consecutive days (meaning that 5 transactions in a day then nothing in the next two days wouldn't count), then this can be done with two self joins:
with cte as
(select CustomerId, Transactiondate, count(*) ct
from table_name
group by CustomerId, Transactiondate)
select distinct t1.CustomerId
from cte t1 inner join cte t2
on t1.Transactiondate = dateadd(day, 1, t2.Transactiondate)
and t1.CustomerId = t2.CustomerId
inner join cte t3
on t2.Transactiondate = dateadd(day, 1, t3.Transactiondate)
and t3.CustomerId = t2.CustomerId
;
Fiddle
Although this is a gaps-and-islands problem, there are shortcuts you can take.
You can group it up by date, then get the row 2 previous, and filter by only rows where the 2 previous row is exactly two days apart.
SELECT DISTINCT
CustomerId
FROM (
SELECT
t.CustomerId,
v.Date,
Prev2 = LAG(v.Date, 2) OVER (PARTITION BY t.CustomerId ORDER BY v.Date)
FROM YourTable t
CROSS APPLY (VALUES( CAST(Transactiondate AS date) )) v(Date)
GROUP BY
t.CustomerId,
v.Date
) t
WHERE DATEDIFF(day, t.Prev2, t.Date) = 2
db<>fiddle
If the base table only has a maximum of one row per date then you can forgo the GROUP BY.
This is actually a gaps and islands problem, you can solve by using analytic window functions to subtract sequential row_number from consecutive days and then grouping, after first "plugging" any gaps with the help of a numbers table.
with numbers as (select top(20) Row_Number() over(order by (select null))-1 n from master.dbo.spt_values),
dRanges as (
select customerId,
Min(Transactiondate) CustStartDate,
Max(Transactiondate) CustEndDate
from t
group by CustomerId
), dates as (
select *
from dranges r
outer apply (
select DateAdd(day,n,r.CustStartDate) SeqDate
from numbers n
where DateAdd(day,n,r.CustStartDate) < = r.CustEndDate
)d
), q as (
select customerId, transactiondate, Count(*) qty
from t
group by CustomerId, Transactiondate
), g as (
select d.CustomerId, d.SeqDate, IsNull(q.qty,0)Qty,
DateAdd(day, - row_number() over (partition by d.customerid order by d.SeqDate), d.SeqDate) as dGrp
from dates d
left join q on q.Transactiondate = d.SeqDate and q.CustomerId = d.CustomerId
)
select customerId
from g
group by CustomerId, dGrp
having Count(*) <= 3 and Sum(qty) >= 5
DB<>Fiddle
You could make use of datediff function and verify if the sum of the date differences are between 3 and 5 (provided the max of the differences is just 1) since the dates might be unique (for example customerid 2 can have transaction dates as 5,6,7,8,9 of March 2022) and this should be taken into account too.
declare #tbl table(id int identity,customerid int,transactiondate date)
insert into #tbl(customerid,transactiondate)
values(1,'2022-03-01')
,(1,'2022-03-01')
,(1,'2022-03-05')
,(1,'2022-03-07')
,(1,'2022-03-07')
,(2,'2022-03-05')
,(2,'2022-03-05')
,(2,'2022-03-06')
,(2,'2022-03-06')
,(2,'2022-03-07')
select customerid from (
select *
,SUM(datediff)over(partition by customerid order by transactiondate)[sum]
,max(datediff)over(partition by customerid order by transactiondate)[max]
from(
select customerid , transactiondate,
DATEDIFF(DAY
,
case when LEAD(transactiondate,1)over(partition by customerid order by transactiondate)
is null then
LAG(transactiondate,1,transactiondate)
over(partition by customerid order by transactiondate)
else
transactiondate end
, case when LEAD(transactiondate,1)over(partition by customerid order by transactiondate)
is null then
transactiondate
else
LEAD(transactiondate,1,transactiondate)
over(partition by customerid order by transactiondate)end) as [datediff]
,ROW_NUMBER()over(partition by customerid order by transactiondate)rownum
from #tbl
)t
)t1
where t1.rownum = 5
and t1.max = 1
and t1.sum between 3 and 5

Ms Access query count first

I have the following table:
tblOrder
----------------------------
OrderID ==>Primary Key
CustomerID ==>Number
OrderDate ==>Date
Order ==>Number
Price ==>Currency
[Order] field contain a number (like 11, 15, 17) which is a code.
I am trying to find a way, and if it is possible of course, to count [Order] per [CustomerID] that:
==> I) Placed only [Order]=15
==> ii) Made first [Order]=15 and then placed another order which IS NOT 15.
Example Table:
OrderID CustomerID OrderDate Order Price
--------------------------------------------
1 3 1/1/2018 15 100
2 2 3/2/2018 15 300
3 2 7/3/2018 11 400
4 3 2/6/2018 15 200
5 1 5/2/2018 17 300
6 1 11/7/2018 15 600
7 2 1/4/2018 11 200
OutPut Query:
CustomerID OnlyOrder=15 FirstOrder=15
---------------------------------------
1 Null Null
2 1 3
3 2 Null
Thank you in advance.
This is much easier using aggregation and having. The following gets the customers:
select customerId
from tblOrders
group by customerId
having min(iif([order] = 15, orderdate, null)) < max(iif([order] <> 15, orderdate, null)) or
(min([order]) = 15 and max(order = 15));
To count them, use a subquery:
select count(*)
from (select customerId
from tblOrders
group by customerId
having min(iif([order] = 15, orderdate, null)) < max(iif([order] <> 15, orderdate, null)) or
(min([order]) = 15 and max([order]) = 15)
) as c;
Edit:
If you want the first order to be 15 --which is not how I interpret the description -- that is also easy:
select customerId
from tblOrders
group by customerId
having min(iif([order] = 15, orderdate, null)) = min(orderdate) or
(min([order]) = 15 and max(order = 15));
You can use this to count the customer ids.
EDIT:
For the revised question, you want something like this:
select customerId,
iif(min([order]) = 15 and max(order = 15), count(*), 0) as all_first_15,
iif(min(iif([order] = 15, orderdate, null)) = min(orderdate) and min([order]) <> max([order]), count(*), 0) as first_order_15
from tblOrders
group by customerId
having min(iif([order] = 15, orderdate, null)) = min(orderdate) or
(min([order]) = 15 and max(order = 15));
Yup, that's quite a set of rules, but certainly possible.
Let's start with a subquery:
Select all customers where the first order (the order with the lowest (minimal) OrderID) is 15:
SELECT t.CustomerID
FROM tblOrder t
WHERE t.Order = 15
AND EXISTS (
SELECT 1
FROM tblOrder i
WHERE t.CustomerID = i.CustomerID
HAVING Min(i.OrderID) = t.OrderID
)
Then, we can use that to count all orders for these users:
SELECT Count(OrderID)
FROM tblOrder c
WHERE EXISTS (
SELECT 1
FROM tblOrder t
WHERE t.Order = 15
AND EXISTS (
SELECT 1
FROM tblOrder i
WHERE t.CustomerID = i.CustomerID
HAVING Min(i.OrderID) = t.OrderID
)
AND t.CustomerID = c.CustomerID
)
GROUP BY c.CustomerID
Or, if you want to split out counts by 15 and not 15:
SELECT DISTINCT o.CustomerID, (SELECT COUNT(s2.OrderID) FROM tblOrder s2 WHERE s2.CustomerID = o.CustomerID AND s2.Order = 15) As Only15Count, (SELECT COUNT(s1.OrderID) FROM tblOrder s1 WHERE s1.CustomerID = o.CustomerID AND EXISTS (SELECT 1 FROM tblOrder s3 WHERE s3.CustomerID = s1.CustomerID AND s3.Order <> 15)) As TotalCount
FROM tblOrders o
WHERE EXISTS (
SELECT 1
FROM tblOrder t
WHERE t.Order = 15
AND EXISTS (
SELECT 1
FROM tblOrder i
WHERE t.CustomerID = i.CustomerID
HAVING Min(i.OrderID) = t.OrderID
)
AND t.CustomerID = o.CustomerID
)
Note that, in contrast to your expected output, nothing will get returned if the first order isn't 15.

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

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

Joining values with DateDim, where null'd dates' value will take the last non-null value in the table

I need to do a join but I'm not sure which type. I have a table like this:
Date Amount | FOO
------------------
2012-01-12 x
2012-03-14 y
2012-05-06 z
2012-05-14 aa
2012-09-02 bb
I am joining this with DateDim (Google here: DATE DIM, which is a table of dates (historical and future).
I need a query that would display data like this:
datedim.Date foo.Amount | FOO x DATEDIM
------------------------------------------
2012-01-12 x
2012-01-13 x
2012-01-14 x
... etc...
2012-03-14 y
2012-03-15 y
2012-03-16 y
2012-03-17 y
... etc...
2012-05-06 z
... etc...
Basically, I need the values to persist (were it a left join, it would be NULLs) until the next non-null value. That will persist too... etc..
What I have so far...
SELECT datedim.Date
,CASE
WHEN Amount IS NULL
THEN (SELECT TOP 1 Amount
FROM FOO WHERE foo.Date <= datedim.Date
ORDER BY Date DESC)
ELSE Amount END AS Amount
FROM DATEDIM datedim
LEFT JOIN FOO foo
ON foo.Date = datedim.Date
I need to create a view out of this. I'm getting an error saying ORDER BY is invalid for views, unless specified by TOP??? I do have a TOP in the subquery...
In SQLServer2005+ use recursive CTE
;WITH cte (id, [Date], Amount) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY [Date] ASC) AS id,
[Date], Amount
FROM dbo.your_table t1
), cte2 (id, [Date], [LevelDate], Amount) AS
(
SELECT c1.id, c1.[Date], DATEDIFF(day, c1.[Date], c2.[Date]) AS [LevelDate], c1.Amount
FROM cte c1 LEFT JOIN cte c2 ON c1.id = c2.id - 1
), cte3 (id, [Date], Amount, [Level]) AS
(
SELECT id, [Date], Amount, 1 AS [Level]
FROM cte2 c
UNION ALL
SELECT c.id, DATEADD(day, 1, ct.[Date]) AS [Date], c.Amount, ct.[Level] + 1
FROM cte2 c JOIN cte3 ct ON c.id = ct.id
WHERE c.[LevelDate] > ct.[Level]
)
SELECT [Date], Amount
FROM cte3
ORDER BY Date
OPTION (maxrecursion 0)
Demo on SQLFiddle
I'm sure there are more efficient ways using a CTE, or window functions, but something along these lines should work:
Select
d.Date,
d.FooDate,
f.Amount
From
Foo f
Inner Join (
Select
d.[Date],
Max(f.[Date]) as FooDate
From
Foo f
Inner Join
DateDim d
On f.[Date] < d.[Date]
Group By
d.[Date]
) d
On d.[FooDate] = f.[Date]
http://sqlfiddle.com/#!3/3c7d5/10

SQL amount consumed query?

How do you do a query to calculate how much of an item has been consumed (used up)?
We can find the qty of each item that we purchased in a purchases table with columns Id, ProductId, Qty (decimal), Date
Id, ProductId, Qty, Date
1, 1, 10, 1/1/11
2, 1, 5, 2/2/11
3, 1, 8, 3/3/11
And how do you then add a count of how many of each row in the purchase table have been consumed - assuming strict FIFO? So in the above example if we know that 14 have been consumed the output would be:
Id, ProductId, Qty, Date, Consumed
1, 1, 10, 1/1/11, 10
2, 1, 5, 2/2/11, 4
3, 1, 8, 3/3/11, 0
Hopefully that explains what I mean by an amount consumed query - we know 14 were consumed and that the first purchase was for 10, so all 10 have been consumed. The next purchase was for 5 so we know that 4 of those have been consumed.
Theres two places I can get the consumed data from - the ConsumedItems table: columns Id, ProductId, QtyUsed, Date), or from the ConsumedSummaryView with columns ProductId, QtyUsed (this is the sum of ConsumedItems.QtyUsed)
Sample table and view
create table purchases (Id int, ProductId int, Qty int, Date datetime)
insert purchases select 1, 1, 10, '1/1/11'
insert purchases select 2, 1, 5, '2/2/11'
insert purchases select 3, 1, 8, '3/3/11'
create view ConsumedSummaryView as select ProductID = 1, QtyUsed = 14
The query
;with p as (
select *, rn=ROW_NUMBER() over (partition by productid order by date, id)
from purchases)
, tmp(Id, ProductId, Qty, Date, rn, ToGo, Consumed) as (
select p.Id, p.ProductId, p.Qty, p.Date, cast(1 as bigint),
CAST(ISNULL(v.qtyused,0) - p.Qty as decimal(20,10)),
cast(case
when v.qtyused >= p.Qty Then p.Qty
when v.qtyused > 0 then v.qtyused
else 0 end as decimal(20,10))
from p
left join ConsumedSummaryView v on p.ProductId = v.productId
where rn=1
union all
select p.Id, p.ProductId, p.Qty, p.Date, cast(p.rn as bigint),
cast(ISNULL(tmp.toGo,0) - p.Qty as decimal(20,10)),
cast(case
when tmp.toGo >= p.Qty Then p.Qty
when tmp.toGo > 0 then tmp.toGo
else 0 end as decimal(20,10))
from tmp
--inner join p on p.rn=tmp.rn+1
inner join p on p.rn=tmp.rn+1 and p.productid = tmp.ProductId
)
select Id, ProductId, Qty, Date, Consumed
from tmp
order by rn
Output
Id ProductId Qty Date Consumed
----------- ----------- ----------- ----------------------- -----------
1 1 10 2011-01-01 00:00:00.000 10
2 1 5 2011-02-02 00:00:00.000 4
3 1 8 2011-03-03 00:00:00.000 0
A little different approach than Richard's, but I'm not sure which will perform better:
SELECT
Purchases.Id,
Purchases.ProductId,
Purchases.Qty,
Purchases.Date,
CASE
WHEN COALESCE (PreviousPurchases.PreviousUsed, 0) + Qty < ConsumedSummaryView.QtyUsed THEN Qty
ELSE
CASE
WHEN ConsumedSummaryView.QtyUsed - COALESCE (PreviousPurchases.PreviousUsed, 0) < 0 THEN 0
ELSE ConsumedSummaryView.QtyUsed - COALESCE (PreviousPurchases.PreviousUsed, 0)
END
END AS Used
FROM
Purchases
INNER JOIN ConsumedSummaryView ON Purchases.ProductId = ConsumedSummaryView.ProductId
LEFT OUTER JOIN (
SELECT
SUM(Purchases_2.Qty) AS PreviousUsed,
Purchases_1.Id
FROM
Purchases AS Purchases_2
INNER JOIN Purchases AS Purchases_1 ON Purchases_2.Id < Purchases_1.Id
AND Purchases_2.ProductId = Purchases_1.ProductId
GROUP BY
Purchases_1.Id
) AS PreviousPurchases ON Purchases.Id = PreviousPurchases.Id