Display Customer and all of their order dates on single row - sql

I have a customers table and an orders table. I want to display the customer and all of his/her order dates on one row, rather than multiple rows. Here is what I have and what I'm looking for:
Basic code to get results:
select customerid, name, orderdate
from customer_table c inner join
order_table o
on c.customerid = o.customerid

this will work at the most you cant show it on different columns having nulls:
select customer_id,name,LISTAGG(orderdate, ', ') WITHIN GROUP (ORDER BY orderdate)
from(select customerid, name, orderdate
from customer_table c inner join
order_table o
on c.customerid = o.customerid );

You can use the following because you're bound by the 12 order limit. If you expand to an unknown number of orders with no upper limit, then you would need to use dynamic SQL and even then it would be tricky, because you would also need to dynamically create unique column names.
This query batches by customer and sets the order values. They will be in dated order and set as NULL if there's no more orders. It kinda assumes that at least one customer has 12 orders. You'll get a column of all NULLS if that's not the case
IF OBJECT_ID('tempdb..#Results') IS NOT NULL DROP TABLE #Results
IF OBJECT_ID('tempdb..#sortedRows') IS NOT NULL DROP TABLE #SortedRows
DECLARE #CustomerList TABLE(CustomerID INT, RowNo INT);
INSERT INTO #CustomerList SELECT DISTINCT CustomerID, ROW_NUMBER() OVER(ORDER BY CustomerID) RowNo FROM Customer_Table (NOLOCK)
DECLARE #Count INT = (SELECT COUNT(DISTINCT CustomerID) RowNumber FROM #CustomerList)
DECLARE #Counter INT = 0
DECLARE #CustToProcess INT
CREATE TABLE #Results(CustomerID INT, [Name] VARCHAR(50), OrderDate1 DATETIME,
OrderDate2 DATETIME, OrderDate3 DATETIME, OrderDate4 DATETIME, OrderDate5 DATETIME,
OrderDate6 DATETIME, OrderDate7 DATETIME, OrderDate8 DATETIME, OrderDate9 DATETIME,
OrderDate10 DATETIME, OrderDate11 DATETIME, OrderDate12 DATETIME)
INSERT INTO #Results(CustomerID, Name) SELECT DISTINCT CustomerID, Name FROM Customer_Table
SELECT ROW_NUMBER() OVER(PARTITION BY c.CustomerID ORDER BY OrderDate) RowNo,
c.CustomerID, c.Name, t.OrderDate INTO #SortedRows
FROM Customer_Table c (NOLOCK) JOIN Order_Table t ON c.CustomerID = t.CustomerID
WHILE #Counter < #Count
BEGIN
SET #Counter += 1
SET #CustToProcess = (SELECT CustomerID FROM #CustomerList WHERE RowNo = #Counter)
PRINT #CustToProcess
SELECT * INTO #RowsForProcessing FROM #SortedRows WHERE CustomerID = #CustToProcess
UPDATE #Results SET OrderDate1 = (SELECT OrderDate FROM #RowsForProcessing WHERE Rowno = 1) WHERE CustomerID = #CustToProcess
UPDATE #Results SET OrderDate2 = (SELECT OrderDate FROM #RowsForProcessing WHERE Rowno = 2) WHERE CustomerID = #CustToProcess
UPDATE #Results SET OrderDate3 = (SELECT OrderDate FROM #RowsForProcessing WHERE Rowno = 3) WHERE CustomerID = #CustToProcess
UPDATE #Results SET OrderDate4 = (SELECT OrderDate FROM #RowsForProcessing WHERE Rowno = 4) WHERE CustomerID = #CustToProcess
UPDATE #Results SET OrderDate5 = (SELECT OrderDate FROM #RowsForProcessing WHERE Rowno = 5) WHERE CustomerID = #CustToProcess
UPDATE #Results SET OrderDate6 = (SELECT OrderDate FROM #RowsForProcessing WHERE Rowno = 6) WHERE CustomerID = #CustToProcess
UPDATE #Results SET OrderDate7 = (SELECT OrderDate FROM #RowsForProcessing WHERE Rowno = 7) WHERE CustomerID = #CustToProcess
UPDATE #Results SET OrderDate8 = (SELECT OrderDate FROM #RowsForProcessing WHERE Rowno = 8) WHERE CustomerID = #CustToProcess
UPDATE #Results SET OrderDate9 = (SELECT OrderDate FROM #RowsForProcessing WHERE Rowno = 9) WHERE CustomerID = #CustToProcess
UPDATE #Results SET OrderDate10 = (SELECT OrderDate FROM #RowsForProcessing WHERE Rowno = 10) WHERE CustomerID = #CustToProcess
UPDATE #Results SET OrderDate11 = (SELECT OrderDate FROM #RowsForProcessing WHERE Rowno = 11) WHERE CustomerID = #CustToProcess
UPDATE #Results SET OrderDate12 = (SELECT OrderDate FROM #RowsForProcessing WHERE Rowno = 12) WHERE CustomerID = #CustToProcess
DROP Table #RowsForProcessing
END
SELECT * FROM #Results

Try this if you use MS SQL Server:
-- 1st, get the number of columns
declare #columnnumber int = 1
select #columnnumber = max(a.count)
from (
select c.cid,c.name, count(o.orderdate) as count
from
customer_table c
join order_table o
on c.cid = o.cid
group by c.cid,c.name)a
print #columnnumber
-- Compose the column names for Pivot
declare #columnname varchar(max) = ''
declare #int int = 1
while #int <= #columnnumber
begin
set #columnname = #columnname + '[date' + cast(#int as varchar(10))+ '],'
set #int = #int + 1
end
set #columnname = '('+left(#columnname,len(#columnname)-1)+')'
print #columnname
--Pivot !!! + Dynamic SQL
declare #str varchar(max)
set #str =
'SELECT *
FROM
(SELECT c.cid,c.name, o.orderdate,concat(''date'',row_number() over (partition by c.cid,c.name order by o.orderdate)) rnk
FROM customer_table c
join order_table o
on c.cid = o.cid) AS s
PIVOT
(
min(s.orderdate)
FOR s.rnk IN '+ #columnname+
' ) AS PivotTable'
print #str
execute (#str)
Please change the column name. I used cid as your customerid.
Output:
cid name date1 date2 date3
12 John 2017-03-04 2017-05-26 2017-12-01
4 Nancy 2017-02-01 NULL NULL

Like others said, it's in principle impossible (in pure SQL) to generate this not knowing how many orders you have for a single customer.
I like #nikhil-sugandh's answer, it works great if you are OK with having all orders comma-separated in a single column.
If you insist on having multiple columns, you can build on that answer, by replacing LISTAGG with ARRAY_AGG and postprocessing it. It will be MUCH more efficient than e.g. the proposed solution with multiple joins. You can also use ARRAY_SLICE to handle cases when there are more orders than you are prepared for.
Example (note, I added an extra order to demonstrate handling more-than-expected orders
create or replace table customer_table(customerId int, name varchar)
as select * from values
(12,'John'),(4,'Nancy');
create or replace table order_table(orderId int, customerId int, orderDate date)
as select * from values
(1,12,'3/4/2017'),(2,12,'5/26/2017'),(3,12,'12/1/2017'),(4,4,'2/1/2017'),(5,12,'1/1/2019');
with subq as (
select c.customerid, name,
array_agg(orderdate) within group (order by orderdate) as orders
from customer_table c
inner join order_table o on c.customerid = o.customerid
group by c.customerid, c.name
)
select customerid, name,
orders[0]::date AS order1, orders[1]::date AS order2,
array_to_string(array_slice(orders, 2, 999), ' , ') AS overflow
from subq;
------------+-------+------------+------------+-------------------------+
CUSTOMERID | NAME | ORDER1 | ORDER2 | OVERFLOW |
------------+-------+------------+------------+-------------------------+
4 | Nancy | 2017-02-01 | [NULL] | |
12 | John | 2017-03-04 | 2017-05-26 | 2017-12-01 , 2019-01-01 |
------------+-------+------------+------------+-------------------------+

first create a view like this:
create view order_view as
select
count(*) over (partition by customerId order by orderDate) as ord,
CustomerId,
orderdate
from order_table
then you can use this query:
select c.customerid,
o1.orderdate,
o2.orderdate
o3.orderdate
.
.
.
o12.orderdate
from customer_table c
left join order_view o1
on c.customerid = o1.customerid and ord = 1
left join order_view o2
on c.customerid = o2.customerid and ord = 2
left join order_view o3
on c.customerid = o3.customerid and ord = 3
.
.
.
left join order_view o12
on c.customerid = o12.customerid and ord = 12

Related

Exclude specific rows from table in Stored Procedure

I have a table named Blacklist and table named Order.
Both have CustomerId column.
Stored Procedure ExecOrder manipulates Order table.
My goal is to exclude Orders that have Blacklisted CustomerId (meaning : Order's CustomerId is in Blacklist table).
I edited ExecOrder SP like this:
DECLARE #Temp TABLE
(
CustomerId UNIQUEIDENTIFIER
);
INSERT INTO #Temp
SELECT RightSide
FROM Blacklist
WHERE LeftSide = #CustomerId;
BEGIN
DECLARE db_cursor CURSOR
FOR SELECT OrderId
FROM dbo.[Order] ord
LEFT OUTER JOIN #Temp t ON t.CustomerId <> ord.CustomerId AND ord.CustomerId <> #CustomerId
WHERE AssetId = #BaseAsset
AND CurrencyId = #QuoteAsset
AND OrderTypeId <> #OrderType
AND [QuotePrice] <= #QuotePrice
AND OrderStatusId = 10
AND Amount > ISNULL(AmountFilled, 0)
AND OrderId < #OrderId
AND OrderBookId = #OrderBookId
AND DeliveryStart = #DeliveryPeriodStart
AND DeliveryEnd = #DeliveryPeriodEnd
AND #MinAmount <= Amount - ISNULL(AmountFilled, 0)
ORDER BY OrderDate;
END;
#Temp table returns correct list of CustomerIds.
Problem is that blacklisted orders are not excluded.
Your #Temp table is holding blacklisted customers.
-- contains blacklisted customer
INSERT INTO #Temp
SELECT RightSide
FROM Blacklist
WHERE LeftSide = #CustomerId;
Now, you need to select orders of customers not existing in #Temp table.
-- You need to select orders, where customerId not exists in #Temp table
SELECT OrderId
FROM dbo.[Order] ord
WHERE NOT EXISTS(SELECT 1 from #Temp WHERE customerId = ord.CustomerId) ...
You should connect with blacklist and take only where connected blacklist not existst:
LEFT OUTER JOIN #Temp t ON t.CustomerId <> ord.CustomerId AND ord.CustomerId <> #CustomerId
Change to:
LEFT OUTER JOIN #Temp t ON t.CustomerId = ord.CustomerId OR ord.CustomerId = #CustomerId
Next you should check if it is empty:
WHERE ... AND t.CustomerId IS NULL

Invoice creation in sql - create a stored procedure

I want to create a procedure in which the following details should be displayed:-
I have created this query but I am not getting the right results. I have attached the table schema.
TABLES WE ARE USING:- InvoiceData, CustomerDetails and InvoiceItems
Table Schema ->
1. InvoiceItems TABLE1
2. CustomerDetails Table2
3. InvoiceDetails enter image description here
There are 2 sections for invoice:-
In the first section of the Invoice, Below details should be displayed.
Invoice Information section
In the second section of the invoice, the below details should be displayed:-
Invoice Items description section
I am attaching the query below:-
alter Procedure SaveInvoiceDetails
(
#CustomerId varchar(50),
#InvoiceNumber varchar(50),
#InvoiceDate date,
#InvoiceMonth int,
#FromDate date,
#ToDate date,
#Rate int,
#Quantity int,
#ActualAmount int,
#ZoneId int
)
as
set nocount on;
begin
Declare #TotalRows int
Declare #NumPages int
set #TotalRows = 0
Select ROW_NUMBER() over (order by C.CustomerId) as InvoiceRow,
C.CustomerId, I.InvoiceNumber, I.InvoiceDate, I.FromDate, I.ToDate,
I.InvoiceMonth, I.Rate, I.ActualAmount, I.Quantity, C.ZoneId,
C.BillingAmount
into #tempInvoice
from ConsumerMST_LKO C
inner join InvoiceDetails I
on C.CustomerId = I.CustomerId
inner join INVOICEITEMS II
on I.InvoiceNumber = II.INVOICEID
where InvoiceNumber = #InvoiceNumber AND InvoiceDate = #InvoiceDate AND
InvoiceMonth = #InvoiceMonth
AND FromDate =#FromDate AND ToDate = #ToDate AND ActualAmount =
#ActualAmount
set #TotalRows = ##ROWCOUNT
If #TotalRows = 0
Begin
set #TotalRows = #TotalRows + 1
Insert #tempInvoice
(
InvoiceNumber,
InvoiceDate,
InvoiceMonth,
ZoneId,
Rate,
Quantity,
BillingAmount,
FromDate,
ToDate
)
VALUES
(#TotalRows
, ''
,''
,''
,0
,0
,0
,0
,''
,0)
End
End
SELECT * FROM #tempInvoice ORDER BY InvoiceRow asc
return
So I am expecting that you got all the records in your #tempInvoice from your procedure. And face problem in only generating invoice number with the format you provided.
For each region you already have a specific code generated at your end ( I am guessing).
#regioncode ---- this table contain record of zoneid and zoneocde for each area
If you don't have any table for region then prepare one before going forward as this needs you everywhere in your code section.
At the end pf your procedure need to update this to return your required result.
; with cte as (
select row_number() over (partition by ZoneId order by InvoiceDate) as Slno, * from #tempInvoice as t inner join #regioncode as r on t.zoneid=r.zoneid
)
select zonecode + '_' + cast(slno as varchar(10)) as Uniquecode, * from cte

Select columns without group by in aggregate

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;

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

SQL statement to select group containing all of a set of values

In SQL Server 2005, I have an order details table with an order id and a product id. I want to write a sql statement that finds all orders that have all the items within a particular order. So, if order 5 has items 1, 2, and 3, I would want all other orders that also have 1, 2, and 3. Also, if order 5 had 2 twice and 3 once, I'd want all other orders with two 2s and a 3.
My preference is that it return orders that match exactly, but orders that are a superset are acceptable if that's much easier / performs much better.
I tried a self-join like the following, but that found orders with any of the items rather than all of the items.
SELECT * FROM Order O1
JOIN Order O2 ON (O1.ProductId = O2.ProductId)
WHERE O2.OrderId = 5
This also gave me duplicates if order 5 contained the same item twice.
If the OrderDetails table contains a unique constraint on OrderId and ProductId, then you can do something like this:
Select ...
From Orders As O
Where Exists (
Select 1
From OrderDetails As OD1
Where OD1.ProductId In(1,2,3)
And OD1.OrderId = O.Id
Group By OD1.OrderId
Having Count(*) = 3
)
If it is possible to have the same ProductId on the same Order multiple times, then you could change the Having clause to Count(Distinct ProductId) = 3
Now, given the above, if you want the situation where each order has the same signature with duplicate product entries, that is trickier. To do that you would need the signature of order in question over the products in question and then query for that signature:
With OrderSignatures As
(
Select O1.Id
, (
Select '|' + Cast(OD1.ProductId As varchar(10))
From OrderDetails As OD1
Where OD1.OrderId = O1.Id
Order By OD1.ProductId
For Xml Path('')
) As Signature
From Orders As O1
)
Select ...
From OrderSignatures As O
Join OrderSignatures As O2
On O2.Signature = O.Signature
And O2.Id <> O.Id
Where O.Id = 5
This sort of thing is very difficult to do in SQL, as SQL is designed to generate its result set by, at the most basic level, comparing a set of column values on a single row each to another value. What you're trying to do is compare a single column value (or set of column values) on multiple rows to another set of multiple rows.
In order to do this, you'll have to create some kind of order signature. Strictly speaking, this isn't possible to do using query syntax alone; you'll have to use some T-SQL.
declare #Orders table
(
idx int identity(1, 1),
OrderID int,
Signature varchar(MAX)
)
declare #Items table
(
idx int identity(1, 1),
ItemID int,
Quantity int
)
insert into #Orders (OrderID) select OrderID from [Order]
declare #i int
declare #cnt int
declare #j int
declare #cnt2 int
select #i = 0, #cnt = max(idx) from #Orders
while #i < #cnt
begin
select #i = #i + 1
declare #temp varchar(MAX)
delete #Items
insert into #Items (ItemID, Quantity)
select
ItemID,
Count(ItemID)
from OrderItem oi
join #Orders o on o.idx = #i and o.OrderID = oi.OrderID
group by oi.ItemID
order by oi.ItemID
select #j = min(idx) - 1, #cnt2 = max(idx) from #Items
while #j < #cnt2
begin
select #j = #j + 1
select #temp = isnull(#temp + ', ','') +
'(' +
convert(varchar,i.ItemID) +
',' +
convert(varchar, i.Quantity) +
')'
from #Items i where idx = #j
end
update #Orders set Signature = #temp where idx = #i
select #temp = null
end
select
o_other.OrderID
from #Orders o
join #Orders o_other on
o_other.Signature = o.Signature
and o_other.OrderID <> o.OrderID
where o.OrderID = #OrderID
This assumes (based on the wording of your question) that ordering multiple of the same item in an order will result in multiple rows, rather than using a Quantity column. If the latter is the case, just remove the group by from the #Items population query and replace Count(ItemID) with Quantity.
I think this should work. I'm using 108 as an example OrderID, so you'll have to replace that twice below or use a variable.
WITH TempProducts(ProductID) AS
(
SELECT DISTINCT ProductID FROM CompMarket
WHERE OrderID = 108
)
SELECT OrderID FROM CompMarket
WHERE ProductID IN (SELECT ProductID FROM TempProducts)
AND OrderID != 108
GROUP BY OrderID
HAVING COUNT(DISTINCT ProductID) >= (SELECT COUNT(ProductID) FROM TempProducts)
This uses a CTE to get a list of an Order's Products, then selects all order IDs that have products that are all in this list. To make sure that the Orders returned have all the products, this compares the Count of the CTE to the Counts of the returned Order's Products.