SQL: payments and orders query - sql

I have 2 tables...
the first one "orders" collects
memberid
order_id
authorization date
the 2nd one "payments" collects
memberid
paymentid
payment number
last_udpate
a memberid might contain more than 1 card and I need to create a query that brings me the orderid and which payment number has used on each order taking into account when was the last update and authorization date..
I've got till here..
SELECT orders.order_id, payments.payments_number
FROM orders
JOIN payments
ON orders.memberid = payments.memberid
I'm a bit lost what subquery should I use to make it work..
Thanks!

Hmmm . . . I think a correlated subquery does what you want:
select o.*,
(select p.payment_number
from payments p
where p.memberid = o.memberid and
p.last_update <= o.authorization_date
order by last_update desc
limit 1
) as payment_number
from orders o;
You can use an additional join if you need other columns from payments.

Since you specify the cardinality of 1 to many and I am not sure of which way your cardinality is most accurately represented.
If each order has many payments then maybe you want something like this.
SELECT o.memberid,
o.order_id,
o.[authorization date],
p.payments_number,
p.last_udpate
FROM orders o
LEFT JOIN payments p
ON o.memberid = p.memberid
If it is the other way round, maybe something like this
SELECT o.memberid,
o.order_id,
o.[authorization date],
p.payments_number,
p.last_udpate
FROM payments p
LEFT JOIN orders o
ON o.memberid = p.memberid

Something similar to what was proposed but using a CTE could be:
;
with cte_partial_results as
(
SELECT o.memberid,
o.order_id,
o.[authorization date] as [authorization_date],
p.paymentid,
p.[payment number] as payment_number
p.last_update,
ROW_NUMBER() OVER(PARTITION BY o.memberid, o.order_id ORDER BY p.last_udpate desc) as RowNo
FROM orders o
LEFT JOIN payments p ON o.memberid = p.memberid
WHERE
p.last_udpate <= o.[authorization date]
)
SELECT
memberid,
order_id,
payment_number,
last_update as most_recent_update
--add other cols you want.
FROM
cte_partial_results
WHERE
RowNo = 1

Related

Getting max value before given date

I am pretty new to using MS SQL 2012 and I am trying to create a query that will:
Report the order id, the order date and the employee id that processed the order
report the maximum shipping cost among the orders processed by the same employee prior to that order
This is the code that I've come up with, but it returns the freight of the particular order date. Whereas I am trying to get the maximum freight from all the orders before the particular order.
select o.employeeid, o.orderid, o.orderdate, t2.maxfreight
from orders o
inner join
(
select employeeid, orderdate, max(freight) as maxfreight
from orders
group by EmployeeID, OrderDate
) t2
on o.EmployeeID = t2.EmployeeID
inner join
(
select employeeid, max(orderdate) as mostRecentOrderDate
from Orders
group by EmployeeID
) t3
on t2.EmployeeID = t3.EmployeeID
where o.freight = t2.maxfreight and t2.orderdate < t3.mostRecentOrderDate
Step one is to read the order:
select o.employeeid, o.orderid, o.orderdate
from orders o
where o.orderid = #ParticularOrder;
That gives you everything you need to go out and get the previous orders from the same employee and join each one to the row you get from above.
select o.employeeid, o.orderid, o.orderdate, o2.freight
from orders o
join orders o2
on o2.employeeid = o.employeeid
and o2.orderdate < o.orderdate
where o.orderid = #ParticularOrder;
Now you have a whole bunch of rows with the first three values the same and the fourth is the freight cost of each previous order. So just group by the first three fields and select the maximum of the previous orders.
select o.employeeid, o.orderid, o.orderdate, max( o2.freight ) as maxfreight
from orders o
join orders o2
on o2.employeeid = o.employeeid
and o2.orderdate < o.orderdate
where o.orderid = #ParticularOrder
group by o.employeeid, o.orderid, o.orderdate;
Done. Build your query in stages and many times it will turn out to be much simpler than you at first thought.
It is unclear why you are using t3. From the question it doesn't sound like the employee's most recent order date is relevant at all, unless I am misunderstanding (which is absolutely possible).
I believe the issue lies in t2. You are grouping by orderdate, which will return the max freight for that date and employeeid, as you describe. You need to calculate a maximum total from all orders that occurred before the date that the order occurred on, for that employee, for every row you are returning.
It probably makes more sense to use a subquery for this.
SELECT o.employeeid, o.orderid, o.orderdate, m.maxfreight
FROM
orders o LEFT OUTER JOIN
(SELECT max(freight) as maxfreight
FROM orders AS f
WHERE f.orderdate <= o.orderdate AND f.employeeid = o.employeeid
) AS m
Hoping this is syntactically correct as I'm not in front of SSMS right now. I also included a left outer join as your previous query with an inner join would have excluded any rows where an employee had no previous orders (i.e. first order ever).
You can do what you want with a correlated subquery or apply. Here is one way:
select o.employeeid, o.orderid, o.orderdate, t2.maxfreight
from orders o outer apply
(select max(freight) as maxfreight
from orders o2
where o2.employeeid = o.employeid and
o2.orderdate < o.orderdate
) t2;
In SQL Server 2012+, you can also do this with a cumulative maximum:
select o.employeeid, o.orderid, o.orderdate,
max(freight) over (partition by employeeid
order by o.orderdate rows between unbounded preceding and 1 preceding
) as maxfreight
from orders o;

Query two similar tables and combine sorted results

I have three tables
orders.orderid (and other non-pertinent stuff)
payment.orderid
payment.transactiondate
payment.amount
projectedpayment.orderid
projectedpayment.projecteddate
projectedpayment.projectedamount
Essentially, payment represents when actual payments are received; projectedpayment represents when the system thinks they should be received. I need to build a query to compare projected vs actual.
I'd like to query them such that each row in the query has the orderid, payment.transactiondate, payment.amount, projectedpayment.projecteddate, projectedpayment.projectedamount, with the rows from payment and projectedpayment sorted by their respective dates. e.g.,
orderid transactiondate amount projecteddate projectedamount
1 2015-01-01 12.34 2015-01-03 12.34
1 2015-01-15 12.34 2015-01-15 12.44
1 null null 2015-02-01 12.34
2 2014-12-31 50.00 null null
So broken down by order, what are the actual and projected payments, where there may be more projected payments than actual, or more actual payments than projected, aligned by date (simply by sorting the two, nothing more complex than that).
It seems like I should be able to achieve this with a left join from orders to some kind of union of the other two tables sorted with an order by, but I haven't been able to make it work, so it may be something completely different. I know I cannot join all three of order, payment, and projectedpayment or I get the cross-product of the latter two tables.
I happen to be using postgresql 9.4, but hopefully we don't need to get too database-specific.
I dont know postgres sorry :( but if you know how to do partitioned row numbers something like this should work.
select
coalesce(a.orderid,b.orderid) as orderid
,transactiondate
,amount
,projecteddate
,projectedamount
FROM
(select
orderid
,ransactiondate
,amount
,row_number() over (partition by orderid order by orderid,transactiondate) as rn
from payment) as a
full join
(select
orderid
,projecteddate
,projectedamount
,row_number() over (partition by orderid order by orderid,projecteddate) as rn
from projectedpayment) as b
on a.orderid= b.orderid
and a.rn = b.rn
*this is sqlserver syntax (2K5+ AFAIK)
The logic here is that you need to assign a unique number to each predicted and actual payment so that you can join the two tables together but only have each row matching a single row from the other table.
If you have ONLY ONE PAYMENT PER DAY then yo could do the full join on the order ID and date without worrying about row numbers.
the full join allows you to have nulls on either side so you will need to coalesce orderid
*also this doesn't show orders with NO payments or predictions.. comment if this is an issue.
This should work
Select * from Orders o
Left Join Payments p on o.ID = p.OrderID
Left Join ProjectedPaymentp pp on o.ID = pp.OrderID
Order By o.ID
If i correctly understand, the following query should help:
select o.orderid, ap.transactiondate, ap.amount, pp.projecteddate, pp.projectedamount
from orders o
left join
(
select p.orderid, p.transactiondate, p.amount,
row_number() over (partition by p.orderid order by p.transactiondate) n
from payment p
) ap on o.orderid = ap.order
left join
(
select p.orderid, p.projecteddate, p.projectedamount,
row_number() over (partition by p.orderid order by p.projecteddate) n
from projectedpayment p
) pp on o.orderid = ap.order and (ap.n is null or ap.n = pp.n)
order by o.orderid, ap.n, pp.n
UPD
Another option (works in slightly different way and you can have NULL values not only for last records for same orderid but it will be completely sorted by date, in one timeline):
select o.orderid, ap.transactiondate, ap.amount, pp.projecteddate, pp.projectedamount
from orders o
inner join
(
select ap.orderid, ap.transactiondate d from payment ap
union
select ap.orderid, ap.projecteddate d from projectedpayment pp
) d on d.orderid = o.orderid
left join payment ap on ap.orderid = o.orderid and ap.transactiondate = d.d
left join projectedpayment pp on pp.orderid = o.orderid and pp.projecteddate = d.d
order by o.orderid, d.d

SQL INNER JOIN Without Repeats

Getting the next table:
Column1 - OrderID - Earliest orders of customers from Column2
Column2 - CustomerID - Customers from orders in Column1
Column3 - OrderID - All *Other* orders of customers from Column2
which do not appear in Column1
This is my query and I'm looking for a way to apply the rules mentioned above:
SELECT O1.orderid, C1.customerid, O2.Orderid
FROM orders AS O1
INNER JOIN customers AS C1 ON O1.customerid = C1.customerid
RIGHT JOIN orders AS O2 ON C1.customerid = O2.customerid
WHERE O1.orderdate >= '2014-01-01'
AND O1.orderdate <= '2014-03-31'
ORDER BY O1.orderid
Thanks in advance
Not entirely sure why you want to get a result out like this as the earliest order will repeat for each order for the given customer.
SELECT earliestOrders.orderid, C1.customerid, O1.Orderid
FROM orders AS O1
INNER JOIN customers AS C1 ON O1.customerid = C1.customerid
INNER JOIN (
select o.customerid, min(o.OrderId) as OrderId
from orders o
Group by o.customerid
) earliestOrders
ON earliestOrders.CustomerId = C1.CustomerId
AND earliestOrders.orderid <> O1.Orderid
To find the first order per customer, look for first order dates per customer and then pick the one or one of the orders made by the customer then. (If orderdate really is just a date one customer can have placed more than one order that day, so we pick one of them. With MIN(orderid) we are likely to get the first one of that bunch :-)
Outer join the other orders and you are done.
If your dbms supports IN clauses on tuples, you get a quite readable statement:
select first_order.orderid, first_order.customerid, later_order.orderid
from
(
select customerid, min(first_order.orderid) as first_orderid
from orders
where (customerid, orderdate) in
(
select customerid, min(orderdate)
from orders
group by cutomerid
)
) first_order
left join orders later_order
on later_order.customerid = first_order.customerid
and later_order.orderid <> first_order.orderid
;
If your dbms doesn't support IN clauses on tuples, the statement looks a bit more clumsy:
select first_order.orderid, first_order.customerid, later_order.orderid
from
(
select first_orders.customerid, min(first_orders.orderid) as orderid
from orders first_orders
inner join
(
select customerid, min(orderdate)
from orders
group by cutomerid
) first_order_dates
on first_order_dates.customerid = first_orders.customerid
and first_order_dates.orderdate = first_orders.orderdate
group by first_orders.customerid
) first_order
left join orders later_order
on later_order.customerid = first_order.customerid
and later_order.orderid <> first_order.orderid
;

query with subquery with 1 result(max) for each year

I have to make a query where I show for each year wich shipper had the maximum total cost.
My query now show for each year the total cost of each shipper. So in the result i must have a list of the years, for each year the shipper and the total cost.
Thanks in advance.
select year(OrderDate), s.ShipperID, sum(freight)
from orders o
join shippers s on o.ShipVia = s.ShipperID
group by year(OrderDate),s.ShipperID
Select a.FreightYear, a,ShipperID, a.FreightValue
from
(
select year(OrderDate) FreightYear, s.ShipperID, sum(freight) FreightValue
from orders o
join shippers s on o.ShipVia = s.ShipperID
group by year(OrderDate),s.ShipperID
) a
inner join
(
select FreightYear, max(FrieghtTotal) MaxFreight
from
(
select year(OrderDate) FreightYear, s.ShipperID, sum(freight) FreightTotal
from orders o
join shippers s on o.ShipVia = s.ShipperID
group by year(OrderDate),s.ShipperID
) x
group by FreightYear
) max on max.FreightYear = a.FreightYear and max.MaxFreight = a.FreightValue
order by FreightYear
Inner query a is your original query, getting the value of freight by shipper.
Inner query max gets the max value for each year, and then query max is joined to query a, restricting the rows in a to be those with a value for a year = to the max value for the year.
Cheers -
It's marginally shorter if you use windowing functions.
select shippers_ranked.OrderYear as OrderYear,
shippers_ranked.ShipperId as ShipperId,
shippers_ranked.TotalFreight as TotalFreight
from
(
select shippers_freight.*, row_number() over (partition by shippers_freight.OrderYear order by shippers_freight.TotalFreight desc) as Ranking
from
(
select year(OrderDate) as OrderYear,
s.ShipperID as ShipperId,
sum(freight) as TotalFreight
from orders o
inner join shippers s on o.ShipVia = s.ShipperID
group by year(OrderDate), s.ShipperID
) shippers_freight
) shippers_ranked
where shippers_ranked.Ranking = 1
order by shippers_ranked.OrderYear
;
You need to decide what you would like to happen if two shippers have the same TotalFreight for a year - as the code above stands you will get one row (non-deterministically). If you would like one row, I would add ShipperId to the order by in the over() clause so that you always get the same row. If in the same TotalFreight case you would like multiple rows returned, use dense_rank() rather than row_number().

Filter by virtual column?

I have the following database structure :
[Order]
OrderId
Total
[Payment]
OrderId
Amount
Every Order can have X payment rows. I want to get only the list of orders where the sum of all the payments are < than the order Total.
I have the following SQL but I will return all the orders paid and unpaid.
SELECT o.OrderId,
o.UserId,
o.Total,
o.DateCreated,
COALESCE(SUM(p.Amount),0) AS Paid
FROM [Order] o
LEFT JOIN Payment p ON p.OrderId = o.OrderId
GROUP BY o.OrderId, o.Total, o.UserId, o.DateCreated
I have tried to add Where (Paid < o.Total) but it does not work, any idea?
BTM I'm using SQL CE 3.5
What you are looking for is the HAVING clause.
Instead of "Where (COALESCE(SUM(p.Amount),0) < o.Total)", try "HAVING (COALESCE(SUM(p.Amount),0) < o.Total)"
Check out the MSDN Reference on HAVING.
Select Order.* From Order
Left Join
(Select OrderID, Sum(Amount) As t From Payment Group By OrderID) As s On s.OrderID = Oder.OrderID
Where IsNULL(s.t,0) < Order.Total