Related
I have created a Transaction table with columns card_id, amount, created_at. There may be more than 1 row of one user so I want to return the value card_id, sum(amount), first created_at date of all users.
CREATE TABLE Transactions(card_id int, amount money, created_at date)
INSERT INTO Transactions(card_id, amount, created_at)
SELECT 1, 500, '2016-01-01' union all
SELECT 1, 100, '2016-01-01' union all
SELECT 1, 100, '2016-01-01' union all
SELECT 1, 200, '2016-01-02' union all
SELECT 1, 300, '2016-01-03' union all
SELECT 2, 100, '2016-01-04' union all
SELECT 2, 200, '2016-01-05' union all
SELECT 3, 700, '2016-01-06' union all
SELECT 1, 100, '2016-01-07' union all
SELECT 2, 100, '2016-01-07' union all
SELECT 3, 100, '2016-01-07'
I have created function for that but one of my client says I need query not function. Can anyone here suggest what query to use?
CREATE FUNCTION [dbo].[card_id_data]()
RETURNS #t TABLE
(
card_id text,
amount money,
dateOfFirstTransaction date
)
AS
BEGIN
INSERT INTO #t(card_id)
SELECT DISTINCT(card_id) FROM Transactions;
UPDATE #t
SET dateOfFirstTransaction = b.createdat
FROM
(SELECT DISTINCT(card_id) cardid,
MIN(created_at) createdat
FROM Transactions
WHERE amount < 0
GROUP BY card_id) b
WHERE card_id = b.cardid;
UPDATE #t
SET amount = T.AMOUNT
FROM
(SELECT
card_id AS cardid, SUM(MIN(AMOUNT)) AMOUNT, created_at
FROM Transactions
WHERE amount < 0
GROUP BY card_id, created_at) T
WHERE card_id = cardid
AND dateOfFirstTransaction = created_at;
RETURN
END
I want a result as shown in this screenshot:
You can use DENSE_RANK for this. It will number the rows, taking into account tied places (same dates)
SELECT
t.card_id,
SumAmount = SUM(amount),
FirstDate = MIN(t.created_at)
FROM (
SELECT *,
rn = DENSE_RANK() OVER (PARTITION BY t.card_id ORDER BY t.created_at)
FROM dbo.Transactions t
) t
WHERE t.rn = 1
GROUP BY t.card_id;
If the dates are actually dates and times, and you want to sum the whole day, change t.created_at to CAST(t.created_at AS date)
Try this:
/*
CREATE TABLE dbo.Transactions
(
card_id INT,
amount MONEY,
created_at DATE
);
INSERT INTO dbo.Transactions (card_id, amount, created_at)
VALUES (1, 500, '2016-01-01'),
(1, 100, '2016-01-01'),
(1, 100, '2016-01-01'),
(1, 200, '2016-01-02'),
(1, 300, '2016-01-03'),
(2, 100, '2016-01-04'),
(2, 200, '2016-01-05'),
(3, 700, '2016-01-06'),
(1, 100, '2016-01-07'),
(2, 100, '2016-01-07'),
(3, 100, '2016-01-07');
*/
WITH FirstDatePerCard AS
(
SELECT
card_id,
FirstDate = MIN(created_at)
FROM
dbo.Transactions
GROUP BY
card_id
)
SELECT DISTINCT
t.card_id,
SumAmount = SUM(amount) OVER (PARTITION BY t.card_id),
FirstDate = f.FirstDate
FROM
FirstDatePerCard f
INNER JOIN
dbo.Transactions t ON f.card_id = t.card_id AND f.FirstDate = t.created_at
You'll get an output something like this:
card_id SumAmount FirstDate
--------------------------------
1 700.00 2016-01-01
2 100.00 2016-01-04
3 700.00 2016-01-06
Is that what you're looking for??
UPDATE: OK, so you want to sum the amount only for the first_date, for every card_id - is that correct? (wasn't clear from the original question)
Updated my solution accordingly
I have a table prices holding all prices that some products have had:
CREATE TABLE prices (
id INT,
product_id INT, /*Foreign key*/
created_at TIMESTAMP,
price INT
);
The first entity for a product_id is it's initial sales price. If the product is then reduced, a new entity will be added.
I would like to find the mean and total price change per day across all products.
This is some sample data:
INSERT INTO prices (id, product_id, created_at, price) VALUES (1, 1, '2020-01-01', 11000);
INSERT INTO prices (id, product_id, created_at, price) VALUES (2, 2, '2020-01-01', 3999);
INSERT INTO prices (id, product_id, created_at, price) VALUES (3, 3, '2020-01-01', 9999);
INSERT INTO prices (id, product_id, created_at, price) VALUES (4, 4, '2020-01-01', 2000);
INSERT INTO prices (id, product_id, created_at, price) VALUES (5, 1, '2020-01-02', 9999);
INSERT INTO prices (id, product_id, created_at, price) VALUES (6, 2, '2020-01-02', 2999);
INSERT INTO prices (id, product_id, created_at, price) VALUES (7, 5, '2020-01-02', 2999);
INSERT INTO prices (id, product_id, created_at, price) VALUES (8, 1, '2020-01-03', 8999);
INSERT INTO prices (id, product_id, created_at, price) VALUES (9, 1, '2020-01-03 10:00:00', 7000);
INSERT INTO prices (id, product_id, created_at, price) VALUES (10, 5, '2020-01-03', 4000);
INSERT INTO prices (id, product_id, created_at, price) VALUES (11, 6, '2020-01-03', 3999);
INSERT INTO prices (id, product_id, created_at, price) VALUES (12, 3, '2020-01-03', 6999);
The expected result should be:
date mean_price_change total_price_change
2020-01-01 0 0
2020-01-02 1000.5 2001
2020-01-03 1666 4998
Explanation:
Mean price reduction and total on '2020-01-01' was 0 as all products were new on that date.
On '2020-01-02' however the mean price change was: (11000-9999 + 3999-2999)/2 = 1000.5 as both product_id 1 and 2 has been reduced to 9999 and 2999 on that day, and their previous prices were 11000 and 3999 and there total reduction would be: (11000-9999 + 3999-2999) = 2001.
On '2020-01-03' only product_id 1, 3 and 5 were changed. 1 at two different times on the day: 9999 => 8999 => 7000 (last one governing) and 3: going from 9999 => 6999 a then 5: going up from 2999 => 4000. This gives a total of: (9999-7000 + 9999-6999 + 2999-4000) = 4998 and a mean price reduction on that day of: 1666
I have added the data here too: https://www.db-fiddle.com/f/tJgoKFMJxcyg5gLDZMEP77/1
I stated to play around with some DISTINCT ON but that does not seem to do it...
You seem to want lag() and aggregation:
select created_at, avg(prev_price - price), sum(prev_price - price)
from (select p.*, lag(price) over (partition by product_id order by created_at) as prev_price
from prices p
) p
group by created_at
order by created_at;
You have two prices for product 1 on 2020-01-03. Once I fix that, I get the same results as in your question. Here is the db<>fiddle.
EDIT:
To handle multiple prices per day:
select created_at, avg(prev_price - price), sum(prev_price - price)
from (select p.*, lag(price) over (partition by product_id order by created_at) as prev_price
from (select distinct on (product_id, created_at::date) p.*
from prices p
order by product_id, created_at::date
) p
) p
group by created_at
order by created_at;
try this
select
created_at,
avg(change),
sum(change)
from
(
with cte as
(
select
id,
product_id,
created_at,
lag(created_at) over(order by product_id, created_at) as last_date,
price
from prices
)
select
c.id,
c.product_id,
c.created_at,
c.last_date,
p.price as last_price,
c.price,
COALESCE(p.price - c.price,0) as change
from cte c
left join prices p on c.product_id =p.product_id and c.last_date =p.created_at
where p.price != c.price or p.price is null
) tmp
group by created_at
order by created_at
The query below tracks all price changes, notice that we join current and earlier based on
their product being the same
earlier is indeed earlier than current
earlier is the latest item on a date earlier than current
current is the latest item on its own date
select today.product_id, (today.price - coalesce(earlier.price)), today.created_at as difference
from prices current
join prices earlier
on today.product_id = earlier.product_id and earlier.created_at < current.created_at
where not exists (
select 1
from prices later
where later.product_id = today.product_id and
(
((today.created_at = later.created_at) and (today.id < later.id)) or
((earlier.created_at <= later.created_at) and (earlier.id < later.id))
)
);
Now, let's do some aggregation:
select created_at, avg(today.price - coalesce(earlier.price)) as mean, sum(today.price - coalesce(earlier.price)) as total
from prices current
left join prices earlier
on today.product_id = earlier.product_id and earlier.created_at < current.created_at
where not exists (
select 1
from prices later
where later.product_id = today.product_id and
(
((today.created_at = later.created_at) and (today.id < later.id)) or
((earlier.created_at <= later.created_at) and (earlier.id < later.id))
)
)
group by created_at
order by created_at;
I'm having trouble with a simple problem for fifo sql query (to calculate profit for each sales day).
There are two tables Production and Invoice. For each day of sales, I have to output total sales profit by using FIFO method.
Example, for second day profit , I have to use leftover item from previous day with their respecting price.
Here is the tables and desired output result
CREATE TABLE Production
(
id int identity(1,1) primary key,
Productid varchar(10),
pdate date,
Qty int,
Price decimal(18, 2),
);
INSERT INTO Production (Productid,pDate, Qty ,Price) VALUES ('PD1', '01/01/2017', 8, 200);
INSERT INTO Production (Productid,pDate ,Qty ,Price) VALUES ('PD2', '02/01/2017', 14, 300);
INSERT INTO Production (Productid,pDate ,Qty ,Price) VALUES ('PD3', '03/01/2017', 15, 150);
CREATE TABLE Sales
(
id int identity(1,1) primary key,
Sid varchar(10),
sDate date,
Productid varchar(10),
Qty int,
);
INSERT INTO Sales (Sid,sDate ,Productid ,Qty) VALUES ('S001', '04/01/2017', 'PD1', 5);
INSERT INTO Sales (Sid,sDate ,Productid ,Qty) VALUES ('S002', '05/01/2019', 'PD2', 4);
INSERT INTO Sales (Sid,sDate ,Productid ,Qty) VALUES ('S003', '06/01/2019', 'PD3', 6);
Manual calculation for leftover formula for each day
( existing - sales qty ) + purchase qty = leftover
I think a simple check on sales.qty < purchase.qty won't work. Since even if you have sales.qty < purchase.qty but have leftovers from last day then you will be using those leftovers first.
You should use try this:
with cte as(
select s.id,s.Sid,sDate,s.Productid,s.qty AS Qty,s.qty as saleqty,p.qty as productqty,p.price
,sum(p.qty-s.qty) over (order by sdate) as leftover
from purchase P
inner join sales S
on p.productid=s.productid
and p.pdate=s.sdate
)
select id, Sid,sDate,Productid,Qty,
case when lag(leftover) over (order by sdate)>0 then lag(leftover *price) over( order by sdate)
+( saleqty-lag(leftover) over (order by sdate)) * price
else saleqty * price end as profit
from cte;
Hope this would help.
SELECT
s.sid,
s.sdate,
p.productid,
s.qty,
CASE
WHEN s.qty <= p.qty
THEN s.qty*p.price
ELSE p.qty*p.price + (s.qty-p.qty) * (SELECT price FROM purchase WHERE pdate IN (SELECT MAX(pdate) FROM purchase WHERE pdate < s.sdate))
END AS PROFIT
FROM purchase p
JOIN sales s
ON p.productid = s.productid
AND p.pdate = s.sdate
Current SQL fiddle demo: http://sqlfiddle.com/#!18/1163a/1
I am looking for a total of how many items were added in a specific month but then a total of how many were never a part of an order in the months after that.
Tables:
CREATE TABLE Item (
ItemNo varchar(10)
,DateAdded date
);
CREATE TABLE Order1 (
OrderNo int,
ItemNo varchar(10),
OrderDate date
);
INSERT INTO Item (ItemNo, DateAdded)
VALUES ('111', '01-01-17'),
('222', '03-01-17'),
('333', '05-01-17'),
('444', '06-02-17'),
('555', '10-02-17'),
('666', '20-02-17');
INSERT INTO Order1 (ItemNo, OrderDate)
VALUES ('111', '10-01-17'),
('111', '20-02-17'),
('222', '07-05-17'),
('333', '20-01-17'),
('333', '08-03-17'),
('444', '25-01-17');
Currently i have:
SELECT
-- b.OrderDate,
A.DateAdded,
COUNT(DISTINCT A.ItemNo) AS [Items Added],
COUNT(CASE WHEN c.ItemNo IS NULL THEN 1 END) as [Items Never Ordered],
COUNT(A.ItemNo) OVER (Partition by MONTH(B.OrderDate))
FROM Item a
CROSS JOIN (SELECT DISTINCT OrderDate FROM Order1) b
LEFT JOIN Order1 c
ON a.ItemNo = c.ItemNo
AND b.OrderDate = c.OrderDate
GROUP BY A.DateAdded, b.OrderDate
The result i am looking for is:
| DateAdded | Items Added | Items Never Ordered |
|-----------|-------------|---------------------|
| Feb-17 | 3 | 2 |
| Jan-17 | 3 | 0 |
Can anyone advise me on the best way too approach this? Thanks
This is not a duplicate - there were no answers to the previous question because of date issue
Like pointed out by Abdul. Always use the right datatype, a date is not a string.
If you want your data grouped by month, than group by year and month, not by date.
Your cross join did not make sense to me, I left it out.
CREATE TABLE Item (
ItemNo varchar(10)
,DateAdded date
);
CREATE TABLE Order1 (
OrderNo int,
ItemNo varchar(10),
OrderDate date
);
INSERT INTO Item (ItemNo, DateAdded)
VALUES ('111', '2017-01-01'),
('222', '2017-01-03'),
('333', '2017-01-05'),
('444', '2017-02-06'),
('555', '2017-02-10'),
('666', '2017-02-20');
INSERT INTO Order1 (ItemNo, OrderDate)
VALUES ('111', '2017-01-10'),
('111', '2017-02-20'),
('222', '2017-05-07'),
('333', '2017-01-20'),
('333', '2017-03-08'),
('444', '2017-01-25');
SELECT
datepart(year,A.DateAdded) Year,datepart(Month,A.DateAdded) Month,
COUNT(DISTINCT A.ItemNo) AS [Items Added],
COUNT(CASE WHEN c.ItemNo IS NULL THEN 1 END) as [Items Never Ordered]
FROM Item a
LEFT JOIN Order1 c ON a.ItemNo = c.ItemNo
GROUP BY datepart(year,A.DateAdded),datepart(Month,A.DateAdded)
I browsed SO but could not quite find the exact answer or maybe it was for a different language.
Let's say I have a table, where each row is a record of a trade:
trade_id customer trade_date
1 A 2013-05-01 00:00:00
2 B 2013-05-01 10:00:00
3 A 2013-05-02 00:00:00
4 A 2013-05-05 00:00:00
5 B 2013-05-06 12:00:00
I would like to have the average time between trades, in days or fraction of days, for each customer, and the number of days since last trade. So for instance for customer A, time between trades 1 and 3 is 1 day and between trades 3 and 4 is 3 days, for an average of 2. So the end table would look like something like this (assuming today it's the 2013-05-10):
customer avg_time_btw_trades time_since_last_trade
A 2.0 5.0
B 5.08 3.5
If a customer has only got 1 trade I guess NULL is fine as output.
Not even sure SQL is the best way to do this (I am working with SQL server), but any help is appreciated!
SELECT
customer,
DATEDIFF(second, MIN(trade_date), MAX(trade_date)) / (NULLIF(COUNT(*), 1) - 1) / 86400.0,
DATEDIFF(second, MAX(trade_date), GETDATE() ) / 86400.0
FROM
yourTable
GROUP BY
customer
http://sqlfiddle.com/#!6/eb46e/7
EDIT: Added final field that I didn't notice, apologies.
The following SQL script uses your data and gives the expected results.
DECLARE #temp TABLE
( trade_id INT,
customer CHAR(1),
trade_date DATETIME );
INSERT INTO #temp VALUES (1, 'A', '20130501');
INSERT INTO #temp VALUES (2, 'B', '20130501 10:00');
INSERT INTO #temp VALUES (3, 'A', '20130502');
INSERT INTO #temp VALUES (4, 'A', '20130505');
INSERT INTO #temp VALUES (5, 'B', '20130506 12:00');
DECLARE #getdate DATETIME
-- SET #getdate = getdate();
SET #getdate = '20130510';
SELECT s.customer
, AVG(s.days_btw_trades) AS avg_time_between_trades
, CAST(DATEDIFF(hour, MAX(s.trade_date), #getdate) AS float)
/ 24.0 AS time_since_last_trade
FROM (
SELECT CAST(DATEDIFF(HOUR, t2.trade_date, t.trade_date) AS float)
/ 24.0 AS days_btw_trades
, t.customer
, t.trade_date
FROM #temp t
LEFT JOIN #temp t2 ON t2.customer = t.customer
AND t2.trade_date = ( SELECT MAX(t3.trade_date)
FROM #temp t3
WHERE t3.customer = t.customer
AND t3.trade_date < t.trade_date)
) s
GROUP BY s.customer
You need a date difference between every trade and average them.
select
a.customer
,avg(datediff(a.trade_date, b.trade_date))
,datediff(now(),max(a.trade_date))
from yourTable a, yourTable b
where a.customer = b.customer
and b.trade_date = (
select max(trade_date)
from yourTable c
where c.customer = a.customer
and a.trade_date > c.trade_date)
#gets the one earlier date for every trade
group by a.customer
Just for grins I added a solution that would use CTE's. You could probably use a temp table if the first query is too large. I used #MatBailie creation script for the table:
CREATE TABLE customer_trades (
id INT IDENTITY(1,1),
customer_id INT,
trade_date DATETIME,
PRIMARY KEY (id),
INDEX ix_user_trades (customer_id, trade_date)
)
INSERT INTO
customer_trades (
customer_id,
trade_date
)
VALUES
(1, '2013-05-01 00:00:00'),
(2, '2013-05-01 10:00:00'),
(1, '2013-05-02 00:00:00'),
(1, '2013-05-05 00:00:00'),
(2, '2013-05-06 12:00:00')
;
;WITH CTE as(
select customer_id, trade_date, datediff(hour,trade_date,ISNULL(LEAD(trade_date,1) over (partition by customer_id order by trade_date),GETDATE())) Trade_diff
from customer_trades
)
, CTE2 as
(SELECT customer_id, trade_diff, LAST_VALUE(trade_diff) OVER(Partition by customer_id order by trade_date) Curr_Trade from CTE)
SELECT Customer_id, AVG(trade_diff) AV, Max(Curr_Trade) Curr_Trade
FROM CTE2
GROUP BY customer_id