SQLite query with LIMIT per column - sql

I am trying to compose a query with a where condition to get multiple unique sorted columns without having to do it in multiple queries. That is confusing so here is an example...
Price Table
id | item_id | date | price
I want to query to find the most recent price of multiple items given a date. I was previously iterating through items in my application code and getting the most recent price like this...
SELECT * FROM prices WHERE item_id = ? AND date(date) < date(?) ORDER BY date(date) DESC LIMIT 1
Iterating through each item and doing a query is too slow so I am wondering if there is a way I can accomplish this same query for multiple items in one go. I have tried UNION but I cannot get it to work with the ORDER BY and LIMIT commands like this thread says (https://stackoverflow.com/a/1415380/4400804) for MySQL
Any ideas on how I can accomplish this?

Try this (based on adapting the answer):
SELECT * FROM prices a WHERE a.RowId IN (
SELECT b.RowId
FROM prices b
WHERE a.item_id = b.item_id AND date < ?
ORDER BY b.item_id LIMIT 1
) ORDER BY date DESC;

Window functions (Available with sqlite 3.25 and newer) will likely help:
WITH ranked AS
(SELECT id, item_id, date, price
, row_number() OVER (PARTITION BY item_id ORDER BY date DESC) AS rn
FROM prices
WHERE date < ?)
SELECT id, item_id, date, price
FROM ranked
WHERE rn = 1
ORDER BY item_id;
will return the most recent of each item_id from all records older than a given date.

I would simply use a correlated subquery in the `where` clause:
SELECT p.*
FROM prices p
WHERE p.DATE = (SELECT MAX(p2.date)
FROM prices p2
WHERE p2.item_id = p.item_id
);
This is phrase so it works on all items. You can, of course, add filtering conditions (in the outer query) for a given set of items.

With NOT EXISTS:
SELECT p.* FROM prices p
WHERE NOT EXISTS (
SELECT 1 FROM prices
WHERE item_id = p.item_id AND date > p.date
)
or with a join of the table to a query that returns the last date for each item_id:
SELECT p.*
FROM prices p INNER JOIN (
SELECT item_id, MAX(date) date
FROM prices
GROUP BY item_id
) t ON t.item_id = p.item_id AND t.date = p.date

Related

SQL Improvement in PostgreSQL

I have two tables (Order, OrderStatus) as below. Order.OrderCode and OrderStatus.Status are indexed.
Order table:
Id(PK),OrderCode
1,"AA-001"
2,"AA-001"
3,"AA-002"
4,"AA-002"
5,"AA-003"
OrderStatus table:
Id(PK),OrderId(FK),Status,CreatedAt
1,1,"Open",2021-05-01 13:00:00
2,1,"Close",2021-05-01 13:05:00
3,2,"Open",2021-05-01 13:10:01
4,3,"Open",2021-05-01 13:10:02
5,3,"Close",2021-05-01 13:11:00
6,4,"Open",2021-05-01 13:11:01
7,4,"Close",2021-05-01 13:11:05
8,5,"Open",2021-05-01 13:12:00
I have a query to return the total Order count for any OrderCode group which has a latest Open status.
For example OrderCode AA-001 and OrderCode AA-003 have Order (ID:2,5) who have latest Open status. The query result would be 3.
OrderCode,Count
"AA-001",2
"AA-003",1
This is the query I have now. The inner query in the IN condition is basically to find Order record whose latest Status is Open. Is there any other way to improve this query? Thanks.
SELECT COUNT(1)
FROM Order as ord
WHERE ord.OrderCode in (
SELECT distinct(or.OrderCode)
from Order as or
left JOIN (
SELECT DISTINCT ON (OrderId) *
FROM OrderStatus
ORDER BY OrderId, CreatedAt DESC
) orsts ON orsts.OrderId = or.Id
where orsts.Status = 'Open'
)
You seem to be describing:
SELECT COUNT(DISTINCT o.OrderCode)
FROM Order o JOIN
(SELECT DISTINCT ON (os.OrderId) *
FROM OrderStatus os
ORDER BY os.OrderId, os.CreatedAt DESC
) orsts
ON orsts.OrderId = o.Id
WHERE orsts.Status = 'Open';
For performance, you want an index on OrderStatus(OrderId, CreatedAt DESC).

Select customer with highest price of all his orders

i want to list customer id with highest sum of price of his orders. Please see graph oí orders down.
SQL DEMO
SELECT *
FROM (
SELECT "customerid", SUM("price")
FROM Orders
GROUP BY "customerid"
ORDER BY SUM("price") DESC
) T
WHERE ROWNUM <= 1
You need to group by customer_id to get all prices of each customers.
Then sum these prices filter it with max( sum(price))or get first row by descending order of sum(price).
--for Oracle
select * from (Select c.name,c.id,sum(o.price) from Customer c
inner join order o on o.customer_id=c.id
group by c.name,c.id
order by sum(o.price)desc
)where rownum =1
--For sql server and mysql
Select top 1 c.name,c.id,sum(o.price) from Customer c
inner join order o on o.customer_id=c.id
group by c.name,c.id
order by sum(o.price)desc

SQL Select Group By Min() - but select other

I want to select the ID of the Table Products with the lowest Price Grouped By Product.
ID Product Price
1 123 10
2 123 11
3 234 20
4 234 21
Which by logic would look like this:
SELECT
ID,
Min(Price)
FROM
Products
GROUP BY
Product
But I don't want to select the Price itself, just the ID.
Resulting in
1
3
EDIT: The DBMSes used are Firebird and Filemaker
You didn't specify your DBMS, so this is ANSI standard SQL:
select id
from (
select id,
row_number() over (partition by product order by price) as rn
from orders
) t
where rn = 1
order by id;
If your DBMS doesn't support window functions, you can do that with joining against a derived table:
select o.id
from orders o
join (
select product,
min(price) as min_price
from orders
group by product
) t on t.product = o.product and t.min_price = o.price;
Note that this will return a slightly different result then the first solution: if the minimum price for a product occurs more then once, all those IDs will be returned. The first solution will only return one of them. If you don't want that, you need to group again in the outer query:
select min(o.id)
from orders o
join (
select product,
min(price) as min_price
from orders
group by product
) t on t.product = o.product and t.min_price = o.price
group by o.product;
SELECT ID
FROM Products as A
where price = ( select Min(Price)
from Products as B
where B.Product = A.Product )
GROUP BY id
This will show the ID, which in this case is 3.

ORACLE SQL Return only duplicated values (not the original)

I have a database with the following info
Customer_id, plan_id, plan_start_dte,
Since some customer switch plans, there are customers with several duplicated customer_ids, but with different plan_start_dte. I'm trying to count how many times a day members switch to the premium plan from any other plan ( plan_id = 'premium').
That is, I'm trying to do roughly this: return all rows with duplicate customer_id, except for the original plan (min(plan_start_dte)), where plan_id = 'premium', and group them by plan_start_dte.
I'm able to get all duplicate records with their count:
with plan_counts as (
select c.*, count(*) over (partition by CUSTOMER_ID) ct
from CUSTOMERS c
)
select *
from plan_counts
where ct > 1
The other steps have me stuck. First I tried to select everything except the original plan:
SELECT CUSTOMERS c
where START_DTE not in (
select min(PLAN_START_DTE)
from CUSTOMERS i
where c.CUSTOMER_ID = i.CUSTOMER_ID
)
But this failed. If I can solve this I believe all I have to add is an additional condition where c.PLAN_ID = 'premium' and then group by date and do a count. Anyone have any ideas?
I think you want lag():
select c.*
from (select c.*,
lag(plan_id) over (partition by customer_id order by plan_start_date) as prev_plan_id
from customers c
) c
where prev_plan_id <> 'premium' and plan_id = 'premium';
I'm not sure what output you want. For the number of times this occurs per day:
select plan_start_date, count(*)
from (select c.*, lag(plan_id) over (partition by customer_id order by plan_start_date) as prev_plan_id
from customers c
) c
where prev_plan_id <> 'premium' and plan_id = 'premium'
group by plan_start_date
order by plan_start_date;

SQL question about GROUP BY

I've been using SQL for a few years, and this type of problem comes up here and there, and I haven't found an answer. But perhaps I've been looking in the wrong places - I'm not really sure what to call it.
For the sake of brevity, let's say I have a table with 3 columns: Customer, Order_Amount, Order_Date. Each customer may have multiple orders, with one row for each order with the amount and date.
My Question: Is there a simple way in SQL to get the DATE of the maximum order per customer?
I can get the amount of the maximum order for each customer (and which customer made it) by doing something like:
SELECT Customer, MAX(Order_Amount) FROM orders GROUP BY Customer;
But I also want to get the date of the max order, which I haven't figured out a way to easily get. I would have thought that this would be a common type of question for a database, and would therefore be easy to do in SQL, but I haven't found an easy way to do it yet. Once I add Order_Date to the list of columns to select, I need to add it to the Group By clause, which I don't think will give me what I want.
Apart from self-join you can do:
SELECT o1.*
FROM orders o1 JOIN orders o2 ON o1.Customer = o2.Customer
GROUP BY o1.Customer, o1.Order_Amount
HAVING o1.Order_Amount = MAX(o2.Order_Amount);
There's a good article reviewing various approaches.
And in Oracle, db2, Sybase, SQL Server 2005+ you would use RANK() OVER.
SELECT * FROM (
SELECT *
RANK() OVER (PARTITION BY Customer ORDER BY Order_Amount DESC) r
FROM orders) o
WHERE r = 1;
Note: If Customer has more than one order with maximum Order_Amount (i.e. ties), using RANK() function would get you all such orders; to get only first one, replace RANK() with ROW_NUMBER().
There's no short-cut... the easiest way is probably to join to a sub-query:
SELECT
*
FROM
orders JOIN
(
SELECT Customer, MAX(Order_Amount) AS Max_Order_Amount
FROM orders
GROUP BY Customer
) maxOrder
ON maxOrder.Customer = orders.Customer
AND maxOrder.Max_Order_Amount = orders.Order_Amount
you will want to join on the same table...
SELECT Customer, order_date, amt
FROM orders o,
( SELECT Customer, MAX(Order_Amount) amt FROM orders GROUP BY Customer ) o2
WHERE o.customer = o2.customer
AND o.order_amount = o2.amt
;
Another approach for the collection:
WITH tempquery AS
(
SELECT
Customer
,Order_Amount
,Order_Date
,row_number() OVER (PARTITION BY Customer ORDER BY Order_Amount DESC) AS rn
FROM
orders
)
SELECT
Customer
,Order_Amount
,Order_Date
FROM
tempquery
WHERE
rn = 1
If your DB Supports CROSS APPLY you can do this as well, but it doesn't handle ties correctly
SELECT [....]
FROM Customer c
CROSS APPLY
(SELECT TOP 1 [...]
FROM Orders o
WHERE c.customerID = o.CustomerID
ORDER BY o.Order_Amount DESC) o
See this data.SE query
You could try something like this:
SELECT Customer, MAX(Order_Amount), Order_Date
FROM orders O
WHERE ORDER_AMOUNT = (SELECT MAX(ORDER_AMOUNT) FROM orders WHERE CUSTOMER = O.CUSTOMER)
GROUP BY CUSTOMER, Order_Date
with t as
(
select CUSTOMER,Order_Date ,Order_Amount,max(Order_Amount) over (partition
by Customer) as
max_amount from orders
)
select * from t where t.Order_Amount=max_amount