How to query customers data with different customer levels in Postgresql? - sql

Currently, I encounter with running query in Posgtresql server, there are 3 tables which contain customer information, customers who apply membercard and customers payment as below:
table customer
customerid, name, lastname, age
table payment
customerid, payment, product, date
table membercard
customerid, membercardno, createddate, status
In my system, there are 2 kind of customers such as regular and member customers.
Regular customers are those customers that haven't applied to be member customers yet.
Member customers are those customers that already applied to be customers.
In this scenario, I would like to query all of 2 kind customers in these 3 tables.
ex: customerid, name, lastname, age, membercardno, payment, product, date
For my query:
SELECT C.CUSTOMERID, C.NAME, C.LASTNAME, C.AGE,
M.MEMBERCARDNO, P.PAYMENT, P.PRODUCT, P.DATE
FROM CUSTOMER C,
MEMBERCARD M,
PAYMENT P
WHERE C.CUSTOMERID = M.MEMBERCARD AND
C.CUSTOMERID = P.CUSTOMERID
ORDER BY CUSTOMERID
in this case, just can query only those customers are member customers.
How to query both kind of customers? it's very hard to do for me.
Any help from our expert will be very appreciate.

You need to use an OUTER JOIN to pull in the membercard data, because not all customers have a member card.
SELECT C.CUSTOMERID, C.NAME, C.LASTNAME, C.AGE,
M.MEMBERCARDNO, P.PAYMENT, P.PRODUCT, P.DATE
FROM CUSTOMER C
LEFT OUTER JOIN MEMBERCARD M
ON M.MEMBERCARD = c.CUSTOMERID
LEFT OUTER JOIN PAYMENT P
ON P.CUSTOMERID = C.CUSTOMERID
ORDER BY C.CUSTOMERID
An OUTER JOIN is an optional join - if the data exists in the root table but doesn't exist in the joined table it will still be returned. Contrast this with an INNER JOIN (which is what you were doing, although you probably didn't realize it) where matching data must exist in both tables of the join for results to be returned.
Also, get in the habit of using ANSI-style joins as shown above, instead of the old-style implicit joins as used in your original query. It's easier to understand what's going on with ANSI joins.
Best of luck.

Related

Beginner: LEFT JOIN not doing what it should?

I'm having trouble with a really simple left join statement that's driving me nuts
I wanted to count the numbers of orders from each customer, that's fine, but I want to display the name, and I'm joining with the customers table and trying to select the name and it says that CustomerName is not part of an aggregate function, it's really weird.
SELECT Customers.CustomerName as 'Name',
COUNT(*) AS 'Order Count'
FROM Orders
LEFT JOIN Customers
ON Orders.CustomerID = Customers.CustomerID
GROUP BY Customers.CustomerID
Thanks for any tips.
You need to count the rows from the orders table, and the left join should be in the other direction:
SELECT c.customerid,
c.CustomerName as "Name",
COUNT(o.customerid) AS "Order Count"
FROM Customers c
LEFT JOIN Orders o ON o.CustomerID = cs.CustomerID
GROUP BY c.CustomerID, c.customername;
count() will ignore NULL values that come into the result due to the outer join so it will count the number of orders for each customers. Customers without orders will be show with a zero count.
Include CustomerName in Group BY instead of CustomerID
SELECT Customers.CustomerName as 'Name', COUNT(*) AS 'Order Count'
FROM Orders LEFT JOIN Customers ON Orders.CustomerID = Customers.CustomerID
GROUP BY Customers.CustomerName
If you are using SQL Server then try using OVER() without Group BY
SELECT Customers.CustomerName as 'Name', COUNT(*) OVER (PARTITION BY Customers.CustomerName ORDER BY Customers.CustomerName)AS 'Order Count'
FROM Orders LEFT JOIN Customers ON Orders.CustomerID = Customers.CustomerID
Modify as below. column used in group by clause should be in column queried in select clause
SELECT Customers.CustomerName as 'Name',
COUNT(*) AS 'Order Count'
FROM Orders
LEFT JOIN Customers
ON Orders.CustomerID = Customers.CustomerID
GROUP BY Customers.CustomerName
I have just reordered your query,please try this it will definitely work for you.
SELECT Customers.CustomerName as 'Name',
COUNT(*) AS 'Order Count'
FROM Customers
LEFT JOIN Orders
ON Customers.CustomerID=Orders.CustomerID
GROUP BY Customers.CustomerID
A simple approach to get all the columns in the customers table is to use a correlated subquery:
select c.*, -- or whatever columns you want
(select count(*)
from orders o
where o.CustomerID = c.CustomerID
) as order_count
from customers c;
Because this avoids the outer GROUP BY, this also has the advantage of having better performance in most databases, particularly with an index on orders(CustomerId). Plus, it returns 0 if the customer has no orders. And, it allows you to choose any or all of the columns from Customers.
The correct way to get the counts you want is to count a column from Orders:
SELECT c.CustomerName, c.CustomerID,
COUNT(o.CustomerId) AS Order_Count
FROM Customers c LEFT JOIN
Orders o
ON o.CustomerID = c.CustomerID
GROUP BY c.CustomerID, c.CustomerName;
Notes:
The Customers table goes first in the LEFT JOIN because presumably you want all rows in Customers.
Table aliases make the query easier to write and to read.
Do not use single quotes for column aliases, even if your database supports it. The best method is to choose aliases that do not need to be supported.
Include the CustomerId in the logic, just in case two customers have the same name.
Count a column from Orders so you get a count of 0 for customers with no orders.

how to select duplicated column value in sql

Write a query that determines the customer that has spent the most on music for each country. Write a query that returns the country along with the top customer and how much they spent. For countries where the top amount spent is shared, provide all customers who spent this amount.
You should only need to use the Customer and Invoice tables.
i want to select the customer with the maximum money spent for each country and there is two customers have the same money spent and the same country so when using group by country i got only 1 customer what should i do ?
select c.CustomerId,c.FirstName,c.LastName, c.Country , max(c.Invoices) as TotalSpent
from
(select * , sum(i.Total) as 'Invoices'
from Customer d
join Invoice i on i.CustomerId = d.CustomerId
group by i.CustomerId
) c
group by c.Country
the table i got is the same expected table except 1 customer
Consider joining unit level with two aggregate queries: 1) first to calculate total amount by CustomerId and Country and 2) second to calculate max total amount by Country.
Below assumes your database supports Common Table Expression (CTE) using the WITH clause (nearly supported by all major commercial or open-source RDBMS's). CTE here avoids the need to repeat sum_agg as a subquery.
with sum_agg AS (
select i.CustomerId, sub_c.Country, sum(i.Total) as Sum_Amount
from Customer sub_c
join Invoice i on i.CustomerId = sub_c.CustomerId
group by i.CustomerId, sub_c.Country
)
select c.CustomerId, c.FirstName, c.LastName, c.Country, max_agg.Max_Sum
from Customer c
join sum_agg
on c.CustomerId = sum_agg.Customer_Id and c.Country = sum_agg.Country
join
(select Country, max(Sum_Amount) as Max_Sum
from sum_agg
group by Country
) max_agg
on max_agg.Country = sum_agg.Country and max_agg.Max_Sum = sum_agg.Sum_Amount
Your inner query is almost correct. It should be
select d.*, sum(i.Total) as Invoices
from Customer d
join Invoice i on i.CustomerId = d.CustomerId
group by d.CustomerId
It is allowed to use d.* here, as we can assume d.CustomerId to be the table's primary key, so all columns in the table are functionally dependent on it. If we grouped by d.country instead for instance, that would not be the case and d.* would be forbidden in the select clause (as well as d.firstname etc.). We can only select columns we grouped by (directly or indirectly) and aggregates such as MIN, MAX, SUM etc.
This query gives you the totals per customer along with the customers' countries.
But then you are taking this result and group by country. If you do this, you can only access country and its aggregates. Selecting c.CustomerId for instance is invalid, as there is no the customer ID per country. If your DBMS allows this, it it flawed in this regard and you get a kind of random result.
If your DBMS features window functions, you can get the maximum amounts per country on-the-fly:
select customerid, firstname, lastname, country, invoices
from
(
select
c.*,
sum(i.total) as invoices,
max(sum(i.total)) over (partition by c.country) as max_sum
from customer c
join invoice i on i.customerid = c.customerid
group by c.customerid
) per_customer
where invoices = max_sum
order by country, customerid;
Otherwise you'd have to use your inner query twice, once to get the country totals, once to get the customers matching these totals:
select
c.customerid, c.firstname, c.lastname, c.country,
sum(i.total) as invoices
from customer c
join invoice i on i.customerid = c.customerid
group by c.customerid
having (c.country, invoices) in
(
select country, max(invoices)
from
(
select
--c.customerid, <- optional, it may make this query more readable
c.country,
sum(i.total) as invoices
from customer c
join invoice i on i.customerid = c.customerid
group by c.customerid
) per_customer
);

SQL query with subquery and without subquery comparison

I'd like someone who can explain me the logic difference between these two queries. Maybe you can explain performance difference also. (DB is Microsoft Northwind).
-- Join
select distinct c.CustomerID, c.CompanyName, c.ContactName from orders as o inner join customers as c
on o.CustomerID = c.CustomerID
-- SubQuery
select customerid, companyname, contactname, country from customers
where customerid in (select distinct customerid from orders)
Thanks in advance.
The first generates an intermediate result set with all orders for all customers. It then reduces them using select distinct.
The second just selects the customers without having to reduce them later. It should be much more efficient. However, the select distinct is not needed in the subquery (it is done automatically with in).
I would write the logic as:
select c.customerid, c.companyname, c.contactname, c.country
from customers c
where exists (select 1
from orders o
where o.customerid = c.customerid
);
This can readily make use of an index on orders(customerid).

Sql query to display records that appear more than once in a table

I have two tables, Customer with columns CustomerID, FirstName, Address and Purchases with columns PurchaseID, Qty, CustomersID.
I want to create a query that will display FirstName(s) that have bought more than two products, product quantity is represented by Qty.
I can't seem to figure this out - I've just started with T-SQL
You could sum the purchases and use a having clause to filter those you're interested in. You can then use the in operator to query only the customer names that fit these IDs:
SELECT FirstName
FROM Customer
WHERE CustomerID IN (SELECT CustomerID
FROM Purchases
GROUP BY CustomerID
HAVING SUM(Qty) > 2)
Please try this, it should work for you, according to your question.
Select MIN(C.FirstName) FirstName from Customer C INNER JOIN Purchases P ON C.CustomerID=P.CustomersID Group by P.CustomersID Having SUM(P.Qty) >2
Please try this:
select c.FirstName,p.Qty
from Customer as c
join Purchase as p
on c.CustomerID = p.CustomerID
where CustomerID in (select CustomerID from Purchases group by CustomerID having count(CustomerID)>2);
SELECT
c.FirstName
FROM
Customer c
INNER JOIN Purchases p
ON c.CustomerId = p.CustomerId
GROUP BY
c.FirstName
HAVING
SUM(p.Qty) > 2
While the IN suggestions would work they are kind of overkill and more than likely less performant than a straight up join with aggregation. The trick is the HAVING Clause by using it you can limit your result to the names you want. Here is a link to learn more about IN vs. Exists vs JOIN (NOT IN vs NOT EXISTS)
There are dozens of ways of doing this and to introduce you to Window Functions and common table expressions which are way over kill for this simplified example but are invaluable in your toolset as your queries continue to get more complex:
;WITH cte AS (
SELECT DISTINCT
c.FirstName
,SUM(p.Qty) OVER (PARTITION BY c.CustomerId) as SumOfQty
FROM
Customer c
INNER JOIN Purchases p
ON c.CustomerId = p.CustomerId
)
SELECT *
FROM
cte
WHERE
SumOfQty > 2

Oracle: Using Sub-Queries, JOIN and distinct function together

Here is how I contructed the step-by-step:
M1. create a sub-query that will return CustomerId and total invoiced for that customer
M2. A second subquery that will give a list of distinct ProductIDs (along with product SKUs) and CustomerIDs.
M3. The M1 and M2 subqueries will be joined to make association between customer totals and products (for the same CustomerId).
M4. The query M3 will be fed to the final query that will just find the top 5 products.
I'm stuck on creating the distinct ProductID and customerID because they would have to be in aggregate functions in order to make them distinct.
Attached is an image that is the erwin diagram which helps understand the system.
If you can help me with M1-M4, I will greatly appreciate it. I'm not a programmer by trade but a business analyst.
--M1--
select C.CustomerId, COUNT(I.InvoiceId) TotalNumInvoices
from Customer C
JOIN Invoice I ON (I.CustomerId = C.CustomerId)
group by C.CustomerId
--M2: Incomplete--
select P.ProductID, P.SKU, C.CustomerID
from Product P
JOIN InvoiceLine IL ON (IL.ProductId = P.ProductId)
JOIN Invoice I ON (IL.InvoiceId = I.InvoiceId)
JOIN Customer C ON (C.CustomerId = I.CustomerId)
you can also use the DISTINCT keyword your select clause in order to get unique values. Try this for m2:
select DISTINCT p.productID, p.sku, i.customerID
from invoice i INNER JOIN invoiceLine il
ON i.invoiceID = il.invoiceID
JOIN product p
ON il.productID = p.productID;