How to add 1 records data to previous? - sql

i am stuck in problem like i am passing accountID and on the basis of that SP picks amount details of a person e.g.
AccountID AccountTitle TransactionDate Amount
1 John01 2014/11/28 20
now if there is 2nd or more records for same accountID then it should add with previous e.g. if 2nd record for accountID 1 is 40 then amount should display 60 (such that it should be already added to 20 and display total in 2nd record)
AccountID AccountTitle TransactionDate Amount
1 John01 2014/12/30 60 (in real it was 40 but it should show result after being added to 1st record)
and same goes for further records
Select Payments.Accounts.AccountID, Payments.Accounts.AccountTitle,
Payments.Transactions.DateTime as TranasactionDateTime,
Payments.Transactions.Amount from Payments.Accounts
Inner Join Payments.Accounts
ON Payments.Accounts.AccountID = Payments.Transactions.Account_ID
Inner Join Payments.Transactions
where Payments.Transactions.Account_ID = 1
it has wasted my time and can't tackle it anymore, so please help me,

SQL Server 2012+ supports cumulative sums (which seems to be what you want):
Select a.AccountID, a.AccountTitle, t.DateTime as TranasactionDateTime,
t.Amount,
sum(t.Amount) over (partition by t.Account_Id order by t.DateTime) as RunningAmount
from Payments.Accounts a Inner Join
Payments.Transactions t
on a.AccountID = t.Account_ID
where t.Account_ID = 1;
In earlier versions of SQL Server you can most easily do this with a correlated subquery or using cross apply.
I also fixed your query. I don't know why you were joining to the Accounts table twice. Also, table aliases make queries much easier to write and to read.

Here is the answer if grouping by all columns is acceptable to you.
Select AccountID, AccountTitle, TransactionDate, SUM(Payments.Transactions.Amount)
from Payments.Accounts
group by AccountID, AccountTitle, TransactionDate
If you want to group only by AccountId, The query is this:
Select AccountID, SUM(Payments.Transactions.Amount)
from Payments.Accounts
group by AccountID
In the second query, the AccountTitle and TransactionDate are missing because they are not used in the group by clause. To include them in the results, you must think of a rule to decide which row of the multiple rows with the same AccountID is used to get the values AccountTitle and TransactionDate.

What version of SQL-Server are you using? This should do the trick:
Select AccountID, AccountTitle, TransactionData,
SUM(Amount) OVER (partiton by AccountID order by TransactionDate) .
from yourtable group by AccountID, AccountTitle, TransactionData
You take group of rows with AccountID, order them by Transaction date and count SUM in that group by Transaction date .

Related

MIN and DISTINCT for ORACLE DB

I have this query
SELECT table_article.articleID, table_article_to_date.sellDate, MIN(table_article.price) AS minPrice
FROM table_article table_article
LEFT JOIN table_article_to_date table_article_to_date ON (table_article.ord_no=table_article_to_date.ord_no)
WHERE table_article.price > 0 AND table_article_to_date.sellDate BETWEEN_TWO_DATES
GROUP BY table_article.articleID, table_article_to_date.sellDate, table_article.price
For the sell_date I use a time range. My problem is, that i get more than one entrie each articleID.
I wish to have the lowest price of each articleID in a specified time range. DISTINCT is not woking with MIN
Givs a change to make this with a query?
The problem here is that you are adding the sell date to the GROUP BY clause. TO solve the issue, you need to take it out and make use of subqueries to get it back. To achieve this, you can do an inner join of the query and a query with the id, sell date and prize on the id and prize.
Also, no need for the prize in the group by, since it's already in the MIN.
SELECT articleData.articleId, articleData.sellDate, articleData.price FROM
(
SELECT articleId, MIN(price)
FROM table
[...]
GROUP BY articleId
) AS minPrice
INNER JOIN
(
SELECT articleId, sellDate, price
FROM table
) AS articleData
ON minPrice.price = articleData.price AND minPrice.articleId = articleData.articleId

SELECT AVG(invoiceamount) per debtor where date< date row

I have a SQL table with the following columns: Invoice ID, debtor ID, invoice date and invoice amount. where invoice ID is unique.
I'm trying to create an extra column with the average invoice amount. So per row I want the average invoice amount of the debtor, but only of the invoices where the invoice date <= the invoicedate of the column.
I'm not sure where to start, all ideas are welcome
Try this-
SELECT
A.Invoice_ID,
A.debtor_ID,
A.invoice_date,
A.invoice_amount,
(
SELECT AVG(B.invoice_amount)
FROM your_table B
WHERE B.debtor_ID = A.debtor_ID
AND B.invoice_date <= A.invoice_date
) AS average_invoice_amount
FROM your_table A
You want to use window functions:
select t.*,
avg(invoice_amount) over (partition by debtor_id order by invoice_date) as running_average
from t;
I strongly recommend this over a correlated subquery because it should be much faster -- even if you attempt to optimize the correlated subquery with indexes.

How do I proceed on this query

I want to know if there's a way to display more than one column on an aggregate result but without it affecting the group by.
I need to display the name alongside an aggregate result, but I have no idea what I am missing here.
This is the data I'm working with:
It is the result of the following query:
select * from Salesman, Sale,Buyer
where Salesman.ID = Buyer.Salesman_ID and Buyer.ID = sale.Buyer_ID
I need to find the salesman that sold the most stuff (total price) for a specific year.
This is what I have so far:
select DATEPART(year,sale.sale_date)'year', Salesman.First_Name,sum(sale.price)
from Salesman, Sale,Buyer
where Salesman.ID = Buyer.Salesman_ID and Buyer.ID = sale.Buyer_ID
group by DATEPART(year,sale.sale_date),Salesman.First_Name
This returns me the total sales made by each salesman.
How do I continue from here to get the top salesman of each year?
Maybe the query I am doing is completely wrong and there is a better way?
Any advice would be helpful.
Thanks.
This should work for you:
select *
from(
select DATEPART(year,s.sale_date) as SalesYear -- Avoid reserved words for object names
,sm.First_Name
,sum(s.price) as TotalSales
,row_number() over (partition by DATEPART(year,s.sale_date) -- Rank the data within the same year as this data row.
order by sum(s.price) desc -- Order by the sum total of sales price, with the largest first (Descending). This means that rank 1 is the highest amount.
) as SalesRank -- Orders your salesmen by the total sales within each year, with 1 as the best.
from Buyer b
inner join Sale s
on(b.ID = s.Buyer_ID)
inner join Salesman sm
on(sm.ID = b.Salesman_ID)
group by DATEPART(year,s.sale_date)
,sm.First_Name
) a
where SalesRank = 1 -- This means you only get the top salesman for each year.
First, never use commas in the FROM clause. Always use explicit JOIN syntax.
The answer to your question is to use window functions. If there is a tie and you wand all values, then RANK() or DENSE_RANK(). If you always want exactly one -- even if there are ties -- then ROW_NUMBER().
select ss.*
from (select year(s.sale_date) as yyyy, sm.First_Name, sum(s.price) as total_price,
row_number() over (partition by year(s.sale_date)
order by sum(s.price) desc
) as seqnum
from Salesman sm join
Sale s
on sm.ID = s.Salesman_ID
group by year(s.sale_date), sm.First_Name
) ss
where seqnum = 1;
Note that the Buyers table is unnecessary for this query.

SQL count of 90 day gaps between records

Say I have a Payment table. I need to know the number of times the gap between payments is greater than 90 days grouped by personID. Payment frequency varies. There is no expected number of payments. There could 0 or hundreds of payments in a 90 day period. If there was no payment for a year, that would count as 1. If there was a payment every month, the count would be 0. If there were 4 payments the first month, then a 90 day gap, then 2 more payments, then another 90 day gap, the count would be 2.
CREATE TABLE Payments
(
ID int PRIMARY KEY,
PersonID int FOREIGN KEY REFERENCES Persons(ID),
CreateDate datetime
)
If you have SQL Server 2014, you can use the LAG or LEAD function to peek at other rows, making this easy:
Select PersonId, Sum(InfrequentPayment) InfrequentPayments
from
(
select PersonId
, case
when dateadd(day,#period,paymentdate) < coalesce(lead(PaymentDate) over (partition by personid order by PaymentDate),getutcdate())
then 1
else 0
end InfrequentPayment
from #Payment
) x
Group by PersonId
Demo: http://sqlfiddle.com/#!6/9eecb7d/491
Explanation:
The outer SQL is fairly trivial; we take the results of the inner SQL, group by PersonId, and count/sum the number of times they've paid payment judged as Infrequent.
The inner SQL is also simple; we're selecting every record, making a note of the person and whether that payment (or rather the delay after that payment) was judged infrequent.
The case statement determines what constitutes an infrequent payment.
Here we say that if the record's paymentdate plus 90 days is still earlier than the next payment (or current date if it's the last payment, so there's no next payment) then it's infrequent (1); otherwise it's not (0).
The coalesce is simply there to handle the last record for a person; i.e. so that if there is no next payment the current date is used (thus capturing anyone who's last payment was over 90 days before today).
Now for the "clever" bit: lead(PaymentDate) over (partition by personid order by PaymentDate).
LEAD is a new SQL function which lets you look at the record after the current one (LAG is to see the previous record).
If you're familiar with row_number() or rank() you may already understand what's going on here.
To determine the record after the current one we don't look at the current query though; rather we specify an order by clause just for this function; that's what's in the brackets after the over keyword.
We also want to only compare each person's payment dates with other payments made by them; not by any customer. To achieve that we use the partition by clause.
I hope that makes sense / meets your requirement. Please say if anything's unclear and I'll try to improve my explanation.
EDIT
For older versions of SQL, the same effect can be achieved by use or ROW_NUMBER and a LEFT OUTER JOIN; i.e.
;with cte (PersonId, PaymentDate, SequenceNo) as
(
select PersonId
, PaymentDate
, ROW_NUMBER() over (partition by PersonId order by PaymentDate)
from #Payment
)
select a.PersonId
, sum(case when dateadd(day,#period,a.paymentdate) < coalesce(b.paymentdate,getutcdate()) then 1 else 0 end) InfrequentPayments
from cte a
left outer join cte b
on b.PersonId = a.PersonId
and b.SequenceNo = a.SequenceNo + 1
Group by a.PersonId
Another method which should work on most databases (though less efficient)
select PersonId
, sum(InfrequentPayment) InfrequentPayments
from
(
select PersonId
, case when dateadd(day,#period,paymentdate) < coalesce((
select min(PaymentDate)
from #Payment b
where b.personid = a.personid
and b.paymentdate > a.paymentdate
),getutcdate()) then 1 else 0 end InfrequentPayment
from #Payment a
) x
Group by PersonId
Generic query for this problem given a timestamp field would be something like this:
SELECT p1.personID, COUNT(*)
FROM payments p1
JOIN payments p2 ON
p1.timestamp < p2.timestamp
AND p1.personID = p2.personID
AND NOT EXISTS (-- exclude combinations of p1 and p2 where p exists between them
SELECT * FROM payments p
WHERE p.personID = p1.personID
AND p.timestamp > p1.timestamp
AND p.timestamp < p2.timestamp)
WHERE
DATEDIFF(p2.timestamp, p1.timestamp) >= 90
GROUP BY p1.personID

How to make a SQL query for last transaction of every account?

Say I have a table "transactions" that has columns "acct_id" "trans_date" and "trans_type" and I want to filter this table so that I have just the last transaction for each account. Clearly I could do something like
SELECT acct_id, max(trans_date) as trans_date
FROM transactions GROUP BY acct_id;
but then I lose my trans_type. I could then do a second SQL call with my list of dates and account id's and get my trans_type back but that feels very cludgy since it means either sending data back and forth to the sql server or it means creating a temporary table.
Is there a way to do this with a single query, hopefully a generic method that would work with mysql, postgres, sql-server, and oracle.
This is an example of a greatest-n-per-group query. This question comes up several times per week on StackOverflow. In addition to the subquery solutions given by other folks, here's my preferred solution, which uses no subquery, GROUP BY, or CTE:
SELECT t1.*
FROM transactions t1
LEFT OUTER JOIN transactions t2
ON (t1.acct_id = t2.acct_id AND t1.trans_date < t2.trans_date)
WHERE t2.acct_id IS NULL;
In other words, return a row such that no other row exists with the same acct_id and a greater trans_date.
This solution assumes that trans_date is unique for a given account, otherwise ties may occur and the query will return all tied rows. But this is true for all the solutions given by other folks too.
I prefer this solution because I most often work on MySQL, which doesn't optimize GROUP BY very well. So this outer join solution usually proves to be better for performance.
This works on SQL Server...
SELECT acct_id, trans_date, trans_type
FROM transactions a
WHERE trans_date = (
SELECT MAX( trans_date )
FROM transactions b
WHERE a.acct_id = b.acct_id
)
Try this
WITH
LastTransaction AS
(
SELECT acct_id, max(trans_date) as trans_date
FROM transactions
GROUP BY acct_id
),
AllTransactions AS
(
SELECT acct_id, trans_date, trans_type
FROM transactions
)
SELECT *
FROM AllTransactions
INNER JOIN LastTransaction
ON AllTransactions.acct_id = LastTransaction.acct_id
AND AllTransactions.trans_date = LastTransaction.trans_date
select t.acct_id, t.trans_type, tm.trans_date
from transactions t
inner join (
SELECT acct_id, max(trans_date) as trans_date
FROM transactions
GROUP BY acct_id;
) tm on t.acct_id = tm.acct_id and t.trans_date = tm.trans_date