Looking for an alternative to a T-SQL Sub-Query - sql-server-2008

Account
=======
int AccountId PK
Order
=====
int OrderId PK
int AccountId FK
DateTime Date
int Status
For each account I want to know the most recent order that has a status of 1 (Success) otherwise the most recent order that has a status of 0 (Unsuccessful). Date is not unique.
I've got this working with a correlated sub-query in a view like this...
SELECT
a.AccountId,
(SELECT TOP 1
o.orderId
FROM
tbl_order o
WHERE
o.Accountid = a.AccountId
AND
o.Status IN (0, 1)
ORDER BY
o.Status DESC, o.Date DESC
) AS OrderId
FROM
tbl_account a
...but its slow.
Is there a better way?

You could use a CTE with ROW_NUMBER function:
WITH cte AS(
SELECT a.AccountId, o.OrderId, o.Date, o.Status
, RN = ROW_NUMBER()OVER(Partition By a.AccountId ORDER BY o.Status DESC, o.Date DESC)
FROM Account a
LEFT OUTER JOIN [Order] o ON a.AccountId = o.AccountId
)
SELECT AccountId, OrderId
FROM cte
WHERE RN = 1
Here's a fiddle: http://sqlfiddle.com/#!3/4e1e3/4/0

Try this:
SELECT
a.AccountId,
o.OrderId
FROM
tbl_account a OUTER APPLY
(
SELECT TOP 1 o.orderId
FROM tbl_order o
WHERE o.Accountid = a.AccountId
AND o.Status IN (0, 1)
ORDER BY o.Status DESC, o.Date DESC
) AS o

Related

How to limit to maximum count of rows in single table in MySQL 5.5

I have a single table, with the following columns:
Id BigInt
CustomerId BigInt
Order varchar(50)
DateOfOrder date
What I try to achieve is the following:
Getting all CustomerId, which have a specific string for the column order and the DateOfOrder is since 2018 and from this only the last two orders.
I started with the following SQL-Statement
Select o.CustomerId as CustomerId, o.Id as Id
from order o
where o.Order="Merchandise"
and year(o.DateOfOrder)>= 2018
order by o.DateOfOrder desc;
But how do I get only the 2 top orders of each CustomerId?
THX a lot in advance
what are you looking for is called row_number and you should use it with Subquery in order to get 2 top orders of each CustomerId based on DateOfOrder
SELECT
*
FROM
(
SELECT
o.customerid AS customerid,
o.id AS id,
row_number() over (
partition by o.customerid
ORDER BY o.DateOfOrder DESC
--ORDER BY o.Order DESC
) rn
FROM
ORDER o
WHERE
o.ORDER = "Merchandise"
AND year(o.dateoforder)>= 2018
)
WHERE
rn <= 2
for lower versions of Mysql that does not support row_number use correlated-subquery
SELECT o1.id ,
o1.customerid ,
o1.order ,
o1.dateoforder
FROM order O1
WHERE 2 >
(
SELECT Count(*)
FROM order O2
WHERE o1.customerid = o2.customerid
AND o1.order > o2.order )
AND
o1.ORDER="Merchandise"
AND year(o1.dateoforder)>= 2018
I think, I solved my problem.
Not beatuiful, but working.
If someone is interested in the solution:
First I get all oldest Orders into a Temporary Table.
Then I get second oldest orders, by using the id of the oldest orders, which are not allowed to be in the query The result is also stored in a temporary table..
And then, I union the results of the temporary tables to get my data, which I wanted to have.
Create Temporary Table Top_Orders
Select o.Id Id, o.CustomerId CustomerId, Max(o.DateOfOrder) OrderDate from order o where o.Order = "Merchandise" and year(o.DateOfOrder) >= 2018 group by CustomerId;
Create Temporary Table Top_Orders2
Select o.Id Id, o.CustomerId CustomerId, Max(o.DateOfOrder) OrderDate from order o where o.Order = "Merchandise" and year(o.DateOfOrder) >= 2018 and o.Id not in (Select Id from Top_Orders) group by CustomerId;
Select o1.CustomerID, o1.Id from order o1 where o1.Id in (Select t1.Id from Top_Orders t1) union Select o2.CustomerId, o2.Id from order o2 where o2.Id in (Select t2.Id from Top_Orders2 t2);
Drop Temporary Table Top_Orders;
Drop Temporary Table Top_Orders2;

MySQL: get last order, first order and order amount per customer

I have 2 tables, one is "Customer", and the second is "Order".
Columns in Customer are:
customer_id
fname
lname
email
Columns in Order are:
order_id
customer_id
date
amount
I need to run a query to get for each customer their first order, last order and their amount (and details for each customer)
To get first order and last order per customer, that's my solution:
select fname
, lname
, email
, max(date)
, min(date)
from customer c
join order o
on c.customer_id = o.customer_id
group
by c.customer_id;
However, I'm not really sure how to get their amount too, since if I add amount, since the results are wrong if I do that. Should I use a different solution?
Thanks
You can use window functions:
select *
from customers c
inner join (
select o.*,
row_number() over(partition by customer_id order by date) as rn_asc,
row_number() over(partition by customer_id order by date desc) as rn_desc
from orders o
) o on o.customer_id = c.customer_id
where 1 in (o.rn_asc, o.rn_desc)
This generates two rows per customer, with the earlierst and latest order.
If you want one row per customer only, then you can aggregate in the outer query:
select c.*,
min(o.date) as first_order_date,
max(case when rn_asc = 1 then o.amount end) as first_order_amount,
max(o.date) as last_order_date,
max(case when rn_desc = 1 then o.amount end) as last_order_amount
from customers c
inner join (
select o.*,
row_number() over(partition by customer_id order by date) as rn_asc,
row_number() over(partition by customer_id order by date desc) as rn_desc
from orders o
) o on o.customer_id = c.customer_id
where 1 in (o.rn_asc, o.rn_desc)
group by c.customer_id
One method is to use row_number() to enumerate the orders and then use conditional aggregation:
select c.fname, c.lname, c.email, max(o.date), min(o.date),
max(case when seqnum_asc = 1 then amount end) as first_order_amount,
max(case when seqnum_desc = 1 then amount end) as last_order_amount,
from customer c join
(select o.*,
row_number() over (partition by customer_id order by date asc) as seqnum_asc,
row_number() over (partition by customer_id order by date desc) as seqnum_desc
from order o
) o
on c.customer_id = o.customer_id
group by c.customer_id

mysql: get two values from subquery

I am trying to use a subquery to retrieve the oldest order for each customer. I want to select email_address, order_id, and order_date
Tables:
customers(customer_id, email_address)
orders(order_id, order_date, customer_id)
What I've tried:
I can get either the order_id or the order_date by doing
SELECT email_address,
(SELECT order_date /* or order_id */
FROM orders o
WHERE o.customer_id = c.customer_id
ORDER BY order_date LIMIT 1)
FROM customers c
GROUP BY email_address;
but if I try to do SELECT order_id, order_date in my subquery, I get the error:
Operand should contain 1 column(s)
You can solve this with a JOIN, but you need to be careful to only JOIN to the oldest values for a given customer:
SELECT c.email_address, o.order_id, o.order_date
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id AND
o.order_date = (SELECT MIN(order_date) FROM orders o2 WHERE o2.customer_id = c.customer_id)
You could use a JOIN to get the result you want, or modify your query as below:
SELECT email_address,
(SELECT order_date
FROM orders o1
WHERE o1.customer_id = c.customer_id
ORDER BY order_date LIMIT 1) as `order_date`,
(SELECT order_id
FROM orders o2
WHERE o2.customer_id = c.customer_id
ORDER BY order_date LIMIT 1) as `order_id`
FROM customers c
GROUP BY email_address;
The JOIN is of your choice.
How can I select multiple columns from a subquery (in SQL Server) that should have one record (select top 1) for each record in the main query?
SELECT o.order_id, c.email_address, o.order_date
FROM customers c
INNER JOIN (
SELECT order_date, order_id, customer_id
FROM orders o
ORDER BY order_date
) as o on o.customer_id = c.customer_id
GROUP BY email_address;

Trying to find 2nd last order date for the customer

I am trying to find maximum orderdate(means most recent) and the second last orderdate(means second last purchase)
output looks like this
> Emailaddress MostRecentPurchase 2ndMostRecentPurchase totalorder
> xyz#gmail.com 1/29/2018 1/11/2018 $30
SELECT
Customers.EmailAddress,
'$'+CAST(SUM(Orders.PaymentAmount) AS VARCHAR(12)) as TotalOrdered
FROM Customers,Orders
WHERE Customers.CustomerID=Orders.CustomerID
AND Orders.OrderDate BETWEEN '01/01/2017 00:00' AND '12/31/2017 23:59'
AND Orders.OrderDate = ( SELECT MAX(Orders.OrderDate)
FROM Orders
WHERE OrderDate < ( SELECT MAX(OrderDate)
FROM Orders
)
)
GROUP BY Customers.EmailAddress
ORDER BY TotalOrdered DESC
Hmmm. How about this?
select c.customerid, min(o.orderdate), max(o.orderdate), sum(o.paymentamount)
from customers c cross apply
(select top (2) o.*
from orders o
where o.customerid = c.customerid
order by o.orderdate desc
) o
group by c.customerid;
You can also do this using row_number(), but I'm into practicing lateral joins today.
Note: This leaves out the date comparisons (because the text description does not mention them) and the final formatting for the amount.
EDIT:
The equivalent query with row_number() is probably a tad less efficient:
select c.customerid, min(o.orderdate), max(o.orderdate), sum(o.paymentamount)
from customers c join
(select o.*, row_number() over (partition by o.customerid order by o.oderdate desc) as seqnum
from orders o
) o
on o.customerid = c.customerid and o.seqnum <= 2
group by c.customerid;
Y

this type of clause was previously parsed

I have the following query working fine.
SELECT d.customer_id,
d.fname,
d.lname,
m.lastDate,
(SELECT COUNT(order_id)
FROM `orders`
WHERE `customer_id`=d.customer_id
) AS 'total_orders',
d.isActive
FROM customers d
JOIN `orders` m ON m.order_id=
(SELECT order_id
FROM `orders`
WHERE customer_id=d.customer_id
ORDER BY order_id DESC LIMIT 1
)
WHERE d.user_id=382
AND d.customer_id NOT IN
(SELECT `customer_id`
FROM `orders`
WHERE `balance`>0
AND `isActive`=1
)
The above query works fine but when add and union query to also includes customer that have not placed any orders it does work.
SELECT d.customer_id,
d.fname,
d.lname,
m.lastDate,
(SELECT COUNT(order_id)
FROM `orders`
WHERE `customer_id`=d.customer_id
) AS 'total_orders',
d.isActive
FROM customers d
JOIN `orders` m ON m.order_id=
(SELECT order_id
FROM `orders`
WHERE customer_id=d.customer_id
ORDER BY order_id DESC LIMIT 1
)
WHERE d.user_id=382
AND d.customer_id NOT IN
(SELECT `customer_id`
FROM `orders`
WHERE `balance`>0
AND `isActive`=1
)
UNION
#customer WITH NO ORDERS
SELECT `customer_id`,`fname`,`lname`,`state`,`city`,`isActive`
FROM `customers`
WHERE `user_id`=382
AND `isActive` >-1
AND `customer_id` NOT IN
(SELECT `customer_id`
FROM `orders`
)
It display this error in my phpmyadmin
This type of clause was previously parsed (near select)
Based on your comment, I don't believe you want to use union -- you want to use an outer join instead. Here's a slightly simplified version of your query utilizing joins instead of all those correlated subqueries.
SELECT d.customer_id, d.fname, d.lname, d.isactive,
o.lastdate,
Count(o2.order_id) AS 'total_orders'
FROM customers d
LEFT JOIN (SELECT MAX(order_id) order_id, customer_id
FROM orders
GROUP BY customer_id) m on d.customer_id = m.customer_id
LEFT JOIN orders o on m.order_id = o.order_id
LEFT JOIN orders o2 on d.customer_id = o2.customer_id
AND o2.balance > 0 AND o2.isactive = 1
WHERE d.user_id = 382
AND o2.customer_id IS NULL
GROUP BY d.customer_id
BTW -- Reviewing your edits, when using union statements, you must have the same number of columns with the same types in each select list. The state and city fields in the second query probably don't have the same data type as the lastDate and count fields from the first.