Combine max and columns in a single query - sql

I have 2 tables:
customer -> int ID, string NAME
purchase -> date DATE, int VALUE, int C_ID
In the table customer I have:
1, A
2, B
In the table purchase:
01-01-2019, 10, 1
02-01-2019, 20, 2
03-01-2019, 30, 2
I'd like to do a single query that will return the latest purchase for each Customer. So I'd like to get:
1, 01-01-2019
2, 03-01-2019
I've tried different select without success:
select max(purchase.date), customer.id
from customer, purchase
where purchase.c_id = customer.id
But the result is only:
2, 03-01-2019
So I cannot get the max per each line...
Is it possible? Can you help me?

I would recommend filtering as opposed to aggregation:
select p.*
from purchases p
where p.date = (select max(p2.date) from purchases p2 where p2.c_id = p.c_id);

Basically, your query just needs a GROUP BY clause. Without it, all records are aggregated into a unique row, which contains the maximum date and a randomly picked customer id.
If you only need the customer id and date, there is no need to involve the customer table:
select c_id, max(date) from purchase group by c_id
If you need information that come from the customer table (like the customer name), then:
select max(p.date), c.id, c.name
from customer c
inner join purchase p on p.c_id = c.id
group by c.id, c.name
Note:
always use prefer explicit standard joins over old-school implicit joins
using table aliases make the query shorter and easier to read

Related

Writing Common Table Expressions in SQL (Snowflake)

Just learning how to use common table expressions, I wish I was writing like this from the gate.
I've converted all of my queries in my database to a CTE format using WITH ... AS but this one and I am struggling .
So there are two tables:
Table 1. customers
customer_id: unique id for each customer
full_name: customer full name
Table 2. subscriptions
subscription_id: unique id for subscription
customer_id: id for customer who subscribed to subscription
title: name of subscription
The following query is used to return how many subscriptions each of your customers has:
SELECT c.customer_id, c.full_name,
(
SELECT COUNT(*)
FROM subscriptions s
WHERE s.customer_id = c.customer_id
GROUP BY s.customer_id
) subscriptions_count
FROM customers c
How can I rewrite this as a Common Table Expression?
If you really want to use CTE here is one way. You can rewrite it to use left join if you wish to show customers with no counts
with cte as
(select customer_id, count(*) as counts
from subscriptions
group by customer_id)
select c.customer_id, c.full_name, s.counts
from customers c
join cte s on s.customer_id=c.customer_id;
Sure. You can calculate the aggregate first, then join with customers:
WITH cte AS (
SELECT customer_id
, COUNT(*) AS n
FROM subscriptions
GROUP BY customer_id
)
SELECT c.*
, COALESCE(cte.n, 0) AS n
FROM customers AS c
LEFT JOIN cte
ON c.customer_id = cte.customer_id
;

I have a table Claims with a 1-N relation to an Invoice table, I need to find all claims for which its invoices have a certain value, in SQL

Claim table has columns: id NUMBER, claim_presented_on DATE
Invoices table has columns: id NUMBER, fkey_Response NUMBER, fkey_claim NUMBER
I want to have all claims for which at least one of its invoices have a fkey_Response value of 1.
The SQL code is something like this (but not this, I am beginner to SQL:
select
c.id,
(
SELECT count(*) > 0
from Invoices q
WHERE q.fkey_claim = c.id
and q.fkey_INSResponse = 1
)
from
Invoices i
INNER JOIN
Claims c
on i.fkey_claim = c.id_claim
Can someone give me some light here please?
Thanks
As I understand it, you don't actually want any data from the invoices, you just want to check that at least one invoice exists with a specific response.
That being the case, you can use EXISTS(), which just checks whether a sub-subquery returns at least one row or not.
SELECT
c.*
FROM
Claims c
WHERE
EXISTS (
SELECT *
FROM Invoices i
WHERE i.fkey_claim = c.id
AND i.fkey_INSResponse = 1
)
To show all claims, and the number of invoices with a desired response, you can join on a sub-query...
SELECT
c.*,
ISNULL(i.response_1_count, 0) AS response_1_count
FROM
Claims c
LEFT JOIN
(
SELECT fkey_claim, COUNT(*) AS response_1_count
FROM Invoices
WHERE fkey_INSResponse = 1
GROUP BY fkey_claim
)
AS i
ON i.fkey_claim = c.id

How can I sum data from mulitple rows which have the same foreign key into one row?

I have two tables which have a 1 to n relation. One table contains general Information of a bill (named bill)
(< -1 to n ->)
and the other contains Items which are on the bill (named items). I want a query that Lists all Bills and sums up the prices from the items in a new row. But of course i want every Bill listed just once not for every item.
Usually i don't post anything. But i can't find an answer because i don't know how to search for this problem. Sorry when this is obvious.
What my tables look like:
bill:
bill_id - customer - date
items:
item_id - bill_id - amount - price
A simple join with aggregation should work here:
SELECT
b.bill_id,
COALESCE(SUM(i.price), 0) AS total_price
FROM bill b
LEFT JOIN items i
ON b.bill_id = i.bill_id
GROUP BY
b.bill_id;
If you want to include the other two columns from the bill table, then just add them to the SELECT and GROUP BY clauses.
You may try this.
; with cte as (
select b.bill_id, i.item_id ,isnull(i.price,0) as Price from
Bill as b inner join items as i on b.bill_id =i.bill_id
union all
select b.bill_id , null, sum(isnull(i.price,0)) from
Bill as b inner join items as i on b.bill_id =i.bill_id
group by b.bill_id
)
select * from cte order by bill_id, item_id desc

How do you conditionally join a string from Table B to Table A, using an aggregate function on Table B date?

I am attempting to do a conditional join from Tickets to Sales. Tickets:Sales is 1:M. My goal is to provide a list of tickets, and the Sales Channel of the first transaction:
Ex:
select r.ticket_id, s.channel, min(s.transaction_date)
from reservations r
join sales s on r.ticket_id = s.ticket_id
where r.order_id = '0151841621'
group by select r.ticket_id, s.channel;
If I have Reservation ID 123 and it has two records in the Sales table, an online sale and a retail refund, I get the following result
r.ticket_id, s.channel, transaction_date
123, Ecommerce, 2019-07-01:00:00:00
123, Retail, 2019-07-02:00:00:00
I'm looking for a way to combine this into a single table with 1 reservation record, based on the min(transaction_date).
i.e.
123, Ecommerce
Pusedo code
select r.ticket_id, [s.channel where min(transaction_date)]
i.e. select the Channel with the first transaction date.
I've been searching for "conditional select / conditional join" without much luck.
I think you want distinct on:
select distinct on (r.ticket_id) r.ticket_id, s.channel, s.transaction_date
from reservations r join
sales s
on r.ticket_id = s.ticket_id
where r.order_id = '0151841621'
order by r.ticket_id, s.transaction_date;

SQL LEFT JOIN - Inner select not returning columns

I have two tables called 'Customers' and 'Orders'. Tables column names are as follow:
Customers: id, name, address
Orders: id, person_id, product, price
The desired outcome is to query all customers with one of their latest purchases. I have a lot of duplicates in 'Orders' table whereby two records with same time-stamp due to some bug.
I have written the following code but the issue is that the query does not return table 2(Orders) column values. Can anyone advise what the issue is?
SELECT C.Id,C.Name, O.item, O.price, O.product
FROM Customers C
LEFT JOIN
(
SELECT TOP 1 person_id
FROM Orders
WHERE status = 'Pending'
) O ON C.ID = O.person_id
Results: O.item, O.price, O.product values are all null
Edit: Sample Data
ID/ NAME/ ADDRESS/
1/ A/ Ad1/
2/ B/ Ad2/
3/ C/ Ad3/
ID/ Person ID/ PRODUCT PRICE/ Created Date
ID-1234/ 1/ Book/ $5/ 26-2-2017
ID-1235/ 1/ Book/ $5/ 26-2-2017
ID-1236/ 2/ Calendar/ $10/ 4-2-2017
ID-1238/ 1/ Pen/ $2/ 1-1-2016
Assuming that the id column in Orders is a primary key autoincrement, then the following should work:
SELECT c.id,
c.name,
COALESCE(t1.price, 0.0) AS price,
COALESCE(t1.product, 'NA') AS product
FROM Customers c
LEFT JOIN Orders t1
ON c.id = t1.person_id
LEFT JOIN
(
SELECT person_id, MAX(CAST(SUBSTRING(id, 4, LEN(id)) AS INT)) AS max_id
FROM Orders
GROUP BY person_id
) t2
ON t1.person_id = t2.person_id AND
t2.max_id = CAST(SUBSTRING(t1.id, 4, LEN(t1.id)) AS INT)
This answer assumes that taking the greatest order ID per customer will yield the most recent purchase. Ideally you should have a timestamp column which captures when a transaction took place. Note that even in the query above, we still have no way of knowing when the most recent transaction took place.
So where is the timestamp column? It's not mentioned in your table schema. But your description does not mention the status column either, and that is clearly in there.
Is orders.id unique? Is it the key for the Orders table?> If it is, then your schema has no way to identify "duplicate" records. You cannot mean to imply that only one order per customer is allowed, so if there are multiple orders for a single customer, how do we identify the duplicates? By the unmentioned timestamp column?
If there IS a `timestamp column, and that's how you would identify dupes, then use it.
SELECT C.Id,C.Name, O.item, O.price, O.product
FROM Customers C LEFT JOIN Orders o
on o.id = (Select Min(id) from orders
where person_id = c.Id
and timestamp = o.timestamp
and status = 'Pending')