Find count of transactions and customer where they have purchased one item along with something else SQL - sql

I have a table which has the transaction details of the customer:
Customernum sls unit txn no SKU txn_date
1 10 30 567 903633 2019-02-01 yes
1 20 30 567 123767 2019-02-01 yes
1 50 40 567 126658 2019-03-01 yes
1 10 40 345 773633 2019-02-10 yes
1 12 30 345 965322 2019-02-10
1 10 50 678 838364 2019-02-15 yes
1 10 70 975 983636 2019-02-28 yes
2 11 80 910 903633 2019-02-11
2 11 90 910 566373 2019-02-12 yes
3 11 62 855 678364 2019-02-12
I have another table which has the SKU:
sku Desc
123767 APP
903633 CD
773633 APP
838364 APP
983636 APP
566373 APP
126658 APP
I need to find the transactions from the above transactions which has 903633 in the transaction along with the other SKU from the SKU table and transactions which have more than one of the SKU' from the above table.
So the two questions I am trying to anwer is :
How many transactions included only 1 apparel item along with CD
How many transactions included more than one apparel items with CD.
I have tried the below query but I have had no luck:
select a.Customernum, a.txnno, td.sku,
case when ps.sku is not null
then 'Yes'
else 'No'
end as is_sku,
case when count(b.sku) over (partition by a.txnno) > 0
then 'Yes'
else 'No'
end as has_sku
from table a
left join sku b on b.sku = a.sku;
I am expecting the below result:
Result 1- How many transactions included only 1 apparel item along with CD
customernum txnno unit sls
2 910 80 11
2 910 90 11
1 678 50 10
1 975 70 10
Result 2- How many transactions included more than one apparel items with CD.
customernum txnno unit sls
1 567 30 10
1 567 30 20
1 567 40 50

Use two levels of aggregation. The subquery counts the number of "app"s and "cd"s. The outer filter to just one "cd" (you can use >= if you want at least one) and aggregates by the number of "app"s:
select num_app, count(*) as num_customers
from (select t.customernum,
sum(case when s.desc = 'APP' then 1 else 0 end) as num_app,
sum(case when s.desc = 'CD' then 1 else 0 end) as num_cd
from transactions t join
skus s
on t.sku = s.sku
) c
where num_cd = 1
group by num_app
order by num_app;

You can filter with window functions.
The following query would give you all transactions that involve sku 903633 with exactly one other sku:
select *
from (
select
t.*,
count(*) over(partition by txnno) cnt,
max(case when sku = 903633 then 1 end) has_cd
from mytable t
) t
where has_cd = 1 and cnt = 2
To get the transactions that involve sku 903633 and at least two other skus, you can just change cnt = 2 to cnt > 2.

Related

T SQL Cumulative Subtraction

I'm using MS SQL Server.
I have the below table:
SKU Shop WeekNum ShopPrioirty Replen OpeningStock
111 100 1 1 10 5000
111 200 1 2 10 NULL
111 300 1 3 5 NULL
111 400 1 4 8 NULL
222 100 2 1 20 6000
222 200 2 2 15 NULL
222 300 2 3 12 NULL
222 400 2 4 10 NULL
This is the desired result:
SKU Shop WeekNum ShopPrioirty Replen OpeningStock
111 100 1 1 10 5000
111 200 1 2 10 4990
111 300 1 3 5 4980
111 400 1 4 8 4975
222 100 2 1 20 6000
222 200 2 2 15 5980
222 300 2 3 12 5965
222 400 2 4 10 5953
For a given week, a SKU exists in multiple shops and is assigned a priority. At Priority 1 the opening stock is assigned.
However, I need to update the Opening Stock (where it is currently NULL) to equal the previous Opening Stock Minus the Previous Replen.
Before I attempt the update, I tried to just do a SELECT
SELECT SKU
,Shop
,WeekNum
,StorePriority
,Replen
,OpeningStock
,OpeningStock - Replen OVER (ORDER BY SKU,Shop,WeekNum ROWS UNBOUNDED PRECEDING) AS Opening
FROM [table1] t
But I receive the error:Incorrect syntax near the keyword 'OVER'.
Is a running sum the correct way to go?
Would it be best to create a key made up from the SKU\Shop\WeekNum\Priority?
Thanks.
I've made a couple of assumptions here on your PARTITION BY and ORDER BY clauses, but this gets you the result you're after. As you only have a value for OpeningStock in the first row for a SKU, then I use FIRST_VALUE to get the First Value, and then take away all prior values of Replen:
WITH VTE AS(
SELECT *
FROM (VALUES(111,100,1,1,10,5000),
(111,200,1,2,10,NULL),
(111,300,1,3,5 ,NULL),
(111,400,1,4,8 ,NULL),
(222,100,2,1,20,6000),
(222,200,2,2,15,NULL),
(222,300,2,3,12,NULL),
(222,400,2,4,10,NULL))V(SKU,Shop,WeekNum,ShopPrioirty,Replen,OpeningStock))
SELECT V.SKU,
V.Shop,
V.WeekNum,
V.ShopPrioirty,
V.Replen,
V.OpeningStock,
FIRST_VALUE(V.OpeningStock) OVER (PARTITION BY V.SKU ORDER BY V.ShopPrioirty,V.WeekNum ROWS UNBOUNDED PRECEDING) -
ISNULL(SUM(V.Replen) OVER (PARTITION BY V.SKU ORDER BY V.ShopPrioirty,V.WeekNum ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING),0) AS CurrentStock
FROM VTE V;

Can't use case & aggregation correctly

I have the following table
Cash_table
ID Cash Rates Amount
1 50 3 16
2 100 4 25
3 130 10 7
3 130 10 6
4 13 7 1.8
5 30 8 2.5
5 30 10 1
6 10 5 2
What I want as a result is to cumulate all the entries that have a Count(id)>1 like this:
ID New_Cash New_Rates New_Amount
1 50 3 16
2 100 4 25
3 130 10+10 130/(10+10)
4 13 7 1.8
5 30 8+10 30/(8+10)
6 10 5 2
So I only want to change the rows where Count(id)>1 and leave the rest like it was.
For the rows with count(id)>1 I want to sum up the rates and take the cash and divide it by the sum of the rates. The Rates alone aren't a problem since I can sum them up and group by id and get the desired result.
The problem is with the New_Amount column:
I am trying to do it with a case statement but it isn't working:
select id,
cash as new_cash,
sum(rates) as new_rates,
(case count(id)
when 1 then amount
else cash/sum(nvl(rates,null))
end) as new_amount
from Cash_table
group by id
As the cash value is always the same for an ID, you can group by that as well:
select id,
cash as new_cash,
sum(rates) as new_rates,
case count(id)
when 1 then max(amount)
else cash/sum(rates)
end as new_amount
from cash_table
group by id, cash
order by id
ID NEW_CASH NEW_RATES NEW_AMOUNT
---------- ---------- ---------- ----------
1 50 3 16
2 100 4 25
3 130 20 6.5
4 13 7 1.8
5 30 18 1.66666667
6 10 5 2
The first branch of the case expression needs an aggregate because you aren't grouping by amount; and the sum(nvl(rates,null)) can just be sum(rates). If you're expecting any null rates then you need to decide how you want the amount to be handled, but nvl(rates,null) isn't doing anything.
You can do the same thing without a case expression if you prefer, manipulating all the values - which might be more expensive:
select id,
cash as new_cash,
sum(rates) as new_rates,
sum(amount * rates)/sum(rates) as new_amount
from cash_table
group by id, cash
order by id

complex paratition sum in postgresql

I have tables as follow:
A deliveries
delveryid clientid deliverydate
1 10 2015-01-01
2 10 2015-02-02
3 11 2015-04-08
B items in deliveris
itemid deliveryid qty status
70 1 5 1
70 1 8 2
70 2 10 1
72 1 12 1
70 3 100 1
I need to add a column to my query that gives me the qty of each part in other deliveris of the same client.
meaning that for given data of client 10 and delivery id 1 I need to show:
itemid qty status qtyOther
70 5 1 10 //itemid 70 exists in delivery 2
70 8 2 10 //itemid 70 exists in delivery 2
72 12 1 0 //itemid 72 doesn't exists in other delivery of client 11
Since I need to add qtyOther to my existing qry i'm trying to avoid using Group By as it's a huge query and if I use SUM in select I will have to group by all items in select.
This is what I have so far:
Select ....., coalesce( SUM(a.qty) OVER (PARTITION BY a.itemid) ,0) AS qtyOther
FROM B b
LEFT JOIN A a USING
LEFT JOIN (other tables)
WHERE clientid=10 ....
This query gives me the total sum of qty per itemid for specific clientid, regardless of which delivery it is. How do I change it so it will consider the delivryid? I need something like:
coalesce( SUM(a.qty) OVER (PARTITION BY a.itemid) FROM B where deliveryid<>b.deliveryid ,0) AS qtyOther
Any suggestions how to do that?
Note: I can NOT change the condition in WHERE.
I think you just want to subtract out the total for the current delivery:
Select .....,
(coalesce( SUM(a.qty) OVER (PARTITION BY a.itemid), 0) -
coalesce( SUM(a.qty) OVER (PARTITION BY a.itemid, a.deliveryid), 0)
) as qtyOther

Use column from first table as condition for joining second table

Suppose I have
products:
folio price quantity
1 100.00 1
1 450.00 2
3 150.00 1
4 600.00 2
terms:(to know how many payment terms depending on the price of the product)
level term
0.01 12
100.00 14
200.00 16
300.00 18
400.00 20
500.00 22
What can I do to have a resulting table like this:
folio price quantity term
1 100.00 1 14
1 450.00 2 20
I've tried using:
SELECT a.*, b.term
FROM products AS a
JOIN terms AS b ON b.level <= a.price
WHERE a.folio = 1
But I end up getting:
folio price quantity term
1 100.00 1 12
1 100.00 1 14
1 450.00 2 12
1 450.00 2 14
1 450.00 2 16
1 450.00 2 18
1 450.00 2 20
What can I do so I only get the row with the biggest term? Please help!
You are looking for one row from the terms table, not all of them. One way to do this is with a correlated subquery:
SELECT p.*,
(select t.term from terms t where p.price >= t.level order by t.level desc limit 1
) as term
FROM products p
WHERE p.folio = 1;
If you can modify your terms table to have a minimum and maximum price, then that would make it easier to user.
And, you can mimic this with the lead() function:
select p.*, t.term
from products p left outer join
(select t.*, lead(level) over (order by level) as nextlevel
from terms t
) t
on p.price >= t.level and (p.price < t.nextlevel or t.nextlevel is null)
where p.folio = 1;

TSQL - divide rows into groups based on one field

This is modified version of my earlier question: TSQL equally divide resultset to groups and update them
I have my database with 2 tables like so:
Orders table has data like below:
OrderID OperatorID GroupID OrderDesc Status Cash ...
--------------------------------------------------------------------------
1 1 1 small_order 1 300
2 1 1 another_order 1 0
3 1 2 xxxxxxxxxxx 2 1000
5 2 2 yyyyyyyyyyy 2 150
9 5 1 xxxxxxxxxxx 1 50
10 NULL 2 xxxxxxxxxxx 1 150
11 NULL 3 xxxxxxxxxxx 1 -50
12 4 1 xxxxxxxxxxx 1 200
Operators table:
OperatorID Name GroupID Active
---------------------------------------
1 John 1 1
2 Kate 1 1
4 Jack 2 1
5 Will 1 0
6 Sam 3 0
I'm able to equally divide my recordset into equally groups using below query:
SELECT o.*, op.operatorName AS NewOperator, op.operatorID AS NewOperatorId
FROM (SELECT o.*, (ROW_NUMBER() over (ORDER BY newid()) % numoperators) + 1 AS randseqnum
FROM Orders o CROSS JOIN
(SELECT COUNT(*) AS numoperators FROM operators WHERE operators.active=1) op
WHERE o.status in (1,3)
) o JOIN
(SELECT op.*, ROW_NUMBER() over (ORDER BY newid()) AS seqnum
FROM Operators op WHERE op.active=1
) op
ON o.randseqnum = op.seqnum ORDER BY o.orderID
Demo available at: http://sqlfiddle.com/#!3/ff47b/1
Using script from above I can divide Orders to (almost) equal groups but based on number or Orders for Operator, but I need to modify it so that it will assign Operators to Orders based on sum or Cash for orders.
For example:
If I have 6 Orders with Cash values: 300, 0, 50, 150, -50, 200 they sum gives 650.
My script should assign to 3 Operators random 2 Orders with random sum of Cash for Orders.
What I would like to get is to assign for example 300,-50 to operator1, 200, 0 to second and 150, 50 to third.
Hope this sound clear :)
Here is example output that I expect to get:
ORDERID OPERATORID GROUPID DESCRIPTION STATUS CASH NEWOPERATORID
------------------------------------------------------------------------
1 1 1 small_order 1 300 2
2 1 1 another_order 1 0 1
9 5 1 xxxxxxxxxxx 1 50 4
10 (null) 2 xxxxxxxxxxx 1 150 4
11 (null) 3 xxxxxxxxxxx 1 -50 2
12 4 1 xxxxxxxxxxx 1 200 1
How can I (if I can at all) assign Operators to my Orders so that sum or Cash will be closest to average
If I'm understanding this right, could you get the result you want by ordering the Cash column by the biggest, then the smallest, then the next biggest, then the next smallest, etc. Like this:
ROW_NUMBER() over (order by CASE WHEN CashOrder % 2 = 1 then Cash else -Cash END) as splitCash
where you've provided CashOrder lower in the query with
ROW_NUMBER() over (ORDER by CASH) as CashOrder
Then you specify each of your operators depending on this split value, ie (for three operators):
splitCash%3 +1