Unable to understand the SQL code - sql-server

I am finding it difficult to understand the code below. Can you give me a logical flow of how the code works ?
The question -
suppose you want to display the total number of orders placed by every customer in your Customers table. Orders are stored in the Orders table along with the appropriate customer ID.
The steps -
retrieve the list of customers from the customers table.
for each customer retrieved, count the number of associated orders in the Orders table.
The solution -
SELECT cust_name, cust_state,
(SELECT COUNT(*)
FROM Orders
WHERE Orders.cust_id = Customers.cust_id
)
AS order_nos
FROM Customers
ORDER BY cust_name
I am unable to understand the count(*) part inside the brackets. Please help me to figure it out.
Thanks.

It's just counting the number of rows. In this case, that's the number of orders per customer. The alternative COUNT(column_name) gives you the number of rows where column_name isn't null.
Equivalent without the sub-query (using a group instead):
SELECT cust_name, cust_state, COUNT(orders.cust_id) order_nos
FROM Customers
LEFT OUTER JOIN Orders on Customers.cust_id = Orders.cust_id
GROUP BY cust_name, cust_state
ORDER BY cust_name

It's called a correlated subquery. Essentially, for each customer, you're going to get a count of how many orders that customer has. The "magic" is in the WHERE Orders.cust_id = Customers.cust_id clause in the subquery. That's the part that correlates this result to your main query. It's saying "take the cust_id from the main query and now find the count of orders where the cust_id is that cust_id".

Related

Subquery is returning more than one value? Not getting a result set?

Write a SELECT statement that returns two columns: VendorName and LargestInv
(LargestInv is the correlation name of the subquery)
Subquery portion:
SELECT statement that returns the largest InvoiceTotal from the Invoices table (you will need to perform the JOIN within the subquery in one of the clauses).
Sort the results by LargestInv from largest to smallest.(Subquery Must be in the Select statement)
I have tried this but My subqueries returning more than one value
USE AP
SELECT VendorName, (SELECT MAX(InvoiceTotal) FROM Invoices JOIN Vendors
ON Invoices.VendorID = Vendors.VendorID
GROUP BY Invoices.VendorID) AS LargestInv
FROM Vendors
Your issue is scope.
The sub-query shouldn't be joining to the Vendor table if the goal is a correlated sub-query. The "correlated" part comes from joining the results of the inner query (the sub-query) to the outer query.
As written, you're finding VendorID inside the sub-query and the results aren't correlated to the outer query at all. Hence your error message.
SELECT
VendorName,
(SELECT MAX(InvoiceTotal)
FROM Invoices
WHERE Invoices.VendorID = Vendors.VendorID
) AS LargestInv
FROM Vendors
ORDER BY LargestInv DESC;
Edit (extended explanation):
A correlated sub-query isn't designed to pull up a full result set, like the sub-query you were writing at first. It's designed to go over to another table and use a value (or values) from the outer query to bring back a single result, one row at a time.
In this case, using the VendorID from the Vendors table, go over to Invoices, calculate a MAX value "WHERE" the VendorID in Invoices matches the VendorID ON THIS ROW, bring that single value back, then, next row, go back and do that again. And again and again.
It's one way to get the data, but it's not usually efficient. Later, though, you'll learn to use correlated sub-queries in (NOT) EXISTS clauses, and in that context they tend to be extremely efficient. Story for another day, but it's one reason the construct is important to know.
So, your way was good, because it was set based and would tend to be more efficient as a sub-query in the FROM clause, but this way, row by row, is important to understand conceptually.
This is how I would do it.
SELECT VendorName, LargestInv.MaxI
FROM Vendors
FROM (
SELECT VendorName, MAX(InvoiceTotal) as MaxI
FROM Invoices
JOIN Vendors ON Invoices.VendorID = Vendors.VendorID
GROUP BY VendorName
) AS LargestInv ON LargestInv.VendorName = Vendors.VendorName
Now having more than one in the sub-query won't give you an error and you can look at the results.

SQL query where person has as request for every product

I have a shortcut query where I just counted the total number of products and used count to display that the person has requested that many products. (Which is 8 products)
I want to know if there's an easier way where I wouldn't need to count the the products myself and have the query do it. Basically, replace the 8 with the total amount of products that the database has.
SELECT DISTINCT
Tb_Consumer.Name
FROM
Tb_Consumer, Tb_Product, Tb_Requests
WHERE
Tb_Consumer.Con_ID = Tb_Requests.Con_ID
AND Tb_Requests.Prod_ID = Tb_Product.Prod_ID
GROUP BY
Tb_Consumer.Name
HAVING
COUNT(Tb_Product.Name) = 8
Use a subquery to find the number of products in the Tb_Product table:
SELECT
tbc.Name
FROM Tb_Consumer tbc
INNER JOIN Tb_Request tbr
ON tbc.Con_ID = tbr.Con_ID
INNER JOIN Tb_Product tbp
ON tbr.Prod_ID = tbp.Prod_ID
GROUP BY
tbc.Name
HAVING
COUNT(tbp.Name) = (SELECT COUNT(*) FROM Tb_Product); -- count products here
This assumes that every record in Tb_Product corresponds to a single unique product. If there could be duplication for some reason, then you can count distinct products, e.g.
(SELECT COUNT(DISTINCT Name) FROM Tb_Product)
Other changes I made include removing DISTINCT from the select clause, since the GROUP BY should already make each name distinct. I also refactored your query to remove the commas in the FROM clause. Instead, I use explicit joins between the three tables.

SQLite - Return 0 if null

I have an assignment in Database Management Systems in which I have to write queries for given problems.
I have 4 problems, of which I solved 3 and stuck with the last one.
Details:
Using version 1.4 of the Chinook Database
(https://chinookdatabase.codeplex.com/).
SQLite DB Browser
Chinook Sqlite AutoIncrementPKs.sqlite file​ in the directory with Chinook files is the database I am working on
Problem Statement:
Write a query to generate a ranked list of employees based upon the amount of money brought in via customer invoices for which they were the support representative. The result set (see figure below) should have the following fields (in order) for all employees (even those that did not support any customers): ID (e_id), first name (e_first name), last name (e_last_name), title (e_title), and invoice total (total_invoices). The rows should be sorted by the invoice total (greatest first), then by last name (alphabetically), then first name (alphabetically). The invoice total should be preceded by a dollar sign ($) and have two digits after the decimal point (rounded, as appropriate); in the case of employees without any invoices, you should output a $0.00, not NULL. You may find it useful to look at the IFNULL, ROUND, and PRINTF functions of SQLite.
Desired Output:
My Query:
Select Employee.EmployeeId as e_id,
Employee.FirstName as e_first_name,
Employee.LastName as e_last_name,
Employee.Title as e_title,
'$' || printf("%.2f", Sum(Invoice.Total)) as total_invoices
From Invoice Inner Join Customer On Customer.CustomerId = Invoice.CustomerId
Inner Join Employee On Employee.EmployeeId = Customer.SupportRepId
Group by Employee.EmployeeId
Having Invoice.CustomerId in
(Select Customer.CustomerId From Customer
Where Customer.SupportRepId in
(Select Employee.EmployeeId From Employee Inner Join Customer On Employee.EmployeeId = Customer.SupportRepId)
)
order by sum(Invoice.Total) desc
My Output:
As you can see, the first three rows are correct but the later rows are not printed because employees don't have any invoices and hence EmployeeID is null.
How do I print the rows in this condition?
I tried with Coalesce and ifnull functions but I can't get them to work.
I'd really appreciate if someone can modify my query to get matching solutions.
Thanks!
P.S: This is the schema of Chinook Database
It often happens that it is simpler to use subqueries:
SELECT EmployeeId,
FirstMame,
LastName,
Title,
(SELECT printf("...", ifnull(sum(Total), 0))
FROM Invoice
JOIN Customer USING (CustomerId)
WHERE Customer.SupportRepId = Employee.EmployeeId
) AS total_invoices
FROM Employee
ORDER BY total_invoices DESC;
(The inner join could be replaced with a subquery, too.)
But it's possible that you are supposed to show that you have learned about outer joins, which generate a fake row containing NULL values if a matching row is not found:
...
FROM Employee
LEFT JOIN Customer ON Employee.EmployeeId = Customer.SupportRepId
LEFT JOIN Invoice USING (CustomerID)
...
And if you want to be a smartass, replace ifnull(sum(...), 0) with total(...).

SQL Aggregate function issues using DISTINCT function

I'm needing help with a question that I've been racking my brain on for two days now (Almost), on this assignment. I'm still pretty new to SQL and I'm just struggling.
I DO NOT WANT THE ANSWER!! I'm just looking for help getting going in the right direction.
Here is the question:
Write a SELECT statement that answers this question: Which customers have ordered more than one product? Return these columns:
The email address from the Customers table
The count of distinct products from the customer’s orders
Here is what I have so far:
SELECT Customers.CustomerID,
Count(DISTINCT ProductID) AS ProductsCount
FROM Customers
INNER JOIN Orders
ON Customers.CustomerID = Orders.CustomerID
JOIN Products
ON Products.ProductID = OrderItems.ProductID
GROUP BY Customers.CustomerID,
Orders.CustomerID
But I keep getting this error:
Msg 4104, Level 16, State 1, Line 2
The multi-part identifier "OrderItems.ProductID" could not be bound.
The structure of the three tables in play here is.
The Customer table has an Emailaddress and CustomerID column.
The Orders table has CustomerID and OrderID columns.
The Products table has ProductID column.
The OrderItems table has OrderID, ProductID, and Quantity columns.
Any Help would be really really helpful!
Thanks!
Fix the syntax as suggested by joining to the OrderItems table and in order to look for something more than once, you need to use group by field1, field2, etc. having count(field) > 1. You are almost there.
You are almost there :-)
You forgot to join OrderItems.
You don't need the table Products in this query (you don't want to see anything from that table; you get the product count from OrderItems).
To limit by aggregates (as by the number of products here) use HAVING.
To group by Orders.CustomerID is superfluous, as it equals Customers.CustomerID.
Here is a tip to find the answer,
find the orders which has has more than one item in OrderItems
table by using having clause with Count() aggregate and Group by orderID.
so from the first step you will have the orders that have more than
one item and count of products in every order. next join the first step result with
order table to get the customerid.
next join the second step result with customers table to get the
customers information who bought more than one one product in single
order with count.

SQL Server Stored Procedure Combining 2 table results

I have two tables:
CustomerTransaction
Id (int auto int)
CustomerName (varchar)
CustomerNumber (int)
Date (date)
WeeklyAmount (int)
Customers:
CustomerName (varchar)
CustomerNumber (int)
CustomerType (int)
CustomerDate (date)
This database is not normalized, and I can not change it. The CustomerName and what I need to do:
I need a result that will show one table with all of the information from Customers for each row that matches customer number. In CustomerTransaction, I am merely grouping all of the total sums per CustomerName of their amount.
I am using:
Select
CustomerNumber, SUM (WeeklyAmount) as Total
from
Customers.RECORDS
GROUP BY
CustomerNumber;
To get the sum of each CustomerNumber. The problem is that I can not include CustomerName in the group by. Sometimes the name of customers change over time. I was told to grab the data from Customers and it to the result above and match the CustomerNumbers
The problem is that I do not know that with a stored procedure. Anyone know how this is done?
I need all the rows matched.
Select
CustomerNumber, SUM (WeeklyAmount) as Total from
Customers.RECORDS GROUP BY CustomerNumber;
What is Customers.RECORDS? That's a bit confusing. Apart from that, I'd answer
SELECT
c.*,
sq.Total
FROM
(
SELECT
CustomerNumber,
SUM(WeeklyAmount) AS Total
FROM
CustomersTransaction
GROUP BY CustomerNumber
) sq
INNER JOIN Customers c ON c.CustomerNumber = sq.CustomerNumber
If that's not what you're looking for, you have to rephrase your question. It's a bit hard to understand where the actual problem is. Usually I'd write the query a bit different, but this should deal with the non-normalization issue.
Select
Trans.CustomerNumber, C.CustomerName, Trans.SUM (WeeklyAmount) as Total
from
CustomerTransaction Trans
join
Customers C
on
Trans.CustomerNumber=C.CustomerNumber
GROUP BY
Trans.CustomerNumber,C.CustomerName

Resources