I'm having some trouble querying a dataset with a nested subquery, which I thought would be pretty straightforward.
I have a table of customers and their addresses dbo.PERSON_ADDRESSES and transactions with customers dbo.TRANSACT_CUSTOMERS. It is very common for customers to have multiple addresses stored in the dbo.PERSON_ADDRESSES table over time. I simply would like to use the most recent transaction in the dbo.TRANSACT_CUSOMTERS table to a table of most recent addresses from the dbo.PERSON_ADDRESSES table.
When I run the inner subquery independently, it works fine: it shows the one most recent transaction per customer like I envisioned. But, for some reason when I run this entire query, I obtain many, many addresses per customer. I don't understand why.
SELECT MaxTransaction.PERSON_ID, Addr.*
FROM dbo.PERSON_ADDRESSES AS Addr
INNER JOIN
(SELECT PERSON_ID, Max(TRANSACTION_ID) AS MaxTID
FROM dbo.TRANSACTION_CUSTOMERS
GROUP BY PERSON_ID) AS MaxTransaction
ON MaxTransaction.MaxTID = Addr.TRANSACTION_ID
I am guessing that one transaction can have multiple customers. To get one row per person, use an additional JOIN condition:
SELECT maxp.PERSON_ID, pa.*
FROM dbo.PERSON_ADDRESSES pa JOIN
(SELECT PERSON_ID, Max(TRANSACTION_ID) AS MaxTID
FROM dbo.TRANSACTION_CUSTOMERS
GROUP BY PERSON_ID
) maxp
ON maxp.person_id = pa.person_id AND
maxp.MaxTID = pa.TRANSACTION_ID;
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I want to add a column to the query that calculates between These two columns In the same query ....................................................................
,isnull(sum(ORDERS.Net_Amount) + 0,0) as orders
,isnull(sum (convert(float,(RECEIPTS.Amount))) + 0,0) as recepts
IN SQL SERVER
SELECT CUSTOMERS.[ID_CUSTOMER]
,[FIRST_NAME]
,[TEL]
,[EMAIL]
,isnull(sum(ORDERS.Net_Amount) + 0,0) as orders
,isnull(sum (convert(float,(RECEIPTS.Amount))) + 0,0) as recepts
,[CRIDIT_LIMIT]
,[CUSTOMER_SINCE]
,[ADRESS]
,CUSTOMERS.[state]
FROM [CUSTOMERS]
LEFT JOIN ORDERS on CUSTOMERS.ID_CUSTOMER = ORDERS.CUSTOMER_ID
LEFT JOIN RECEIPTS on CUSTOMERS.ID_CUSTOMER = RECEIPTS.ID_CUSTOMER
GROUP BY CUSTOMERS.[ID_CUSTOMER]
,[FIRST_NAME]
,[TEL]
,[EMAIL]
,[CRIDIT_LIMIT]
,[CUSTOMER_SINCE]
,[ADRESS]
,CUSTOMERS.[state]
CUSTOMERS table
SELECT [ID_CUSTOMER]
,[FIRST_NAME]
,[TEL]
,[EMAIL]
,[IMAGE_CUSTOMER]
,[CRIDIT_LIMIT]
,[CUSTOMER_SINCE]
,[ADRESS]
,[Balance]
,[state]
FROM [CUSTOMERS]
ORDERS table
SELECT [ID_ORDER]
,[DATE_ORDER]
,[CUSTOMER_ID]
,[DESCRIPTION_ORDERS]
,[SALEMAN]
,[ORDER_TOTAL]
,[Discount_Of_Total]
,[Total_After_Discount]
,[Paid_Up]
,[Net_Amount]
,[state]
FROM [ORDERS]
RECEIPTS table
SELECT [image_state]
,[ID_RECEIPT]
,[ID_CUSTOMER]
,[Date]
,[Ref]
,[Amount]
,[Memo]
,[User_Name]
,[state]
,[Payment_Method]
,[Account_ID]
FROM [RECEIPTS]
Orders and receipts are not really related to each other (the receipt doesn't refer to a specific order), so don't join the two. What you want to do instead is find the order amount and the receipt amount per customer and show them. So aggregate the two tables per customer and outer-join the results to the customer table.
select
c.id_customer,
c.first_name,
c.tel,
c.email,
coalesce(o.sum_net_amount, 0) as order_amount,
coalesce(r.sum_amount, 0) as receipt_amount,
c.cridit_limit,
c.customer_since,
c.adress,
c.balance,
c.state
from customers c
left join
(
select customer_id, sum(net_amount) as sum_net_amount
from orders
group by customer_id
) o on c.id_customer = o.customer_id
left join
(
select id_customer, sum(amount) as sum_amount
from receipts
group by id_customer
) r on c.id_customer = r.id_customer;
I see you have updated your request now asking also for the difference of the sums. Well, the operator for subtraction in SQL is - little surprising - the minus sign:
coalesce(o.sum_net_amount, 0) - coalesce(r.sum_amount, 0) as diff
Please try the following...
SELECT Customers.ID_Customer,
first_name,
tel,
email,
SUM( net_amount ) AS OrdersTotal,
COALESCE( sumAmount, 0 ) AS PaymentsTotal,
SUM( net_amount ) - COALESCE( sumAmount, 0 ) AS DifferenceInTotals,
cridit_limit,
customer_since,
adress,
balance,
state
FROM Customers
INNER JOIN Orders ON Customers.ID_Customer = Orders.Customer_ID
LEFT JOIN ( SELECT ID_Customer,
SUM( amount ) AS sumAmount
FROM Receipts
GROUP BY ID_Customer
) AS sumAmountFinder ON Customers.ID_Customer = sumAmountFinder.ID_Customer
GROUP BY Customers.ID_Customer,
first_name,
tel,
email,
cridit_limit,
customer_since,
adress,
balance,
state;
This Answer is based on the assumption that every Customer will have at least one Order, but possibly no Receipts.
This statement is essentially the one that you supplied with the following modifications...
I have changed the JOIN to Orders to an INNER JOIN, since I am assuming that each Customer will have at least one Order. The LEFT JOIN is only necessary where you wish to retain all records from the left table that do not have at least one matching record from the right table as defined by the ON clause. (Note : If you wish to retain all the records from the right table where there is no matching records from the left table, use a RIGHT JOIN).
I have replaced the JOIN to the Receipts table with a subquery that calculates the total of the amount field for each Customer in the Receipts table. A LEFT JOIN is necessary between Customers and the results of this subquery as not all Customers will have a Receipt. In such situations the LEFT JOIN will set each of the fields from the subquery in the joined dataset to NULL.
Where the SUM() function encounters only NULL values it returns NULL, not 0. So that PaymentsTotal will be set to 0 for records where the Customer has no Receipts, I have used the COALESCE() function. This function will return the first non-NULL argument it encounters. Here I have set it to return the total of amount where it encounters one, and 0 where it encounters no total amount.
I have removed all of the square brackets from your field and table names. They are only required where you have used an otherwise disallowed name, such as names with spaces (use [Full Name] instead of Full Name) or names that are also reserved by SQL-Server (if you had decided to call PaymentsTotal Sum, then you would have had to use AS [Sum]). Many programmers consider giving fields such names to be bad practice, even when it is possible with []'s, but fortunately you have not used any otherwise names.
I have removed the table names from your SUM() calculations. Since only one table has a field called net_amount, then it will be a unique field name in the joined dataset, and you will be able to refer to them without specifying the name of the source table as well. Specifying the source table is still necessary in the case of Customers.ID_Customer as the joined dataset will have more than one field called ID_Customer. Also, you will need to specify the source tables names when creating the joined dataset using the JOIN's.
I have also taken the liberty of changing your capitalisation scheme. Having just about everything in constant upper-case is monotonous to the eye. Using different casing for SQL terms, table names and field names makes recognising each type of statement part much easier, and thus makes debugging code much easier.
Finally, and relatively trivially, cridit is actually spelt credit and adress is actually spelt address.
If you have any questions or comments, then please feel free to post a Comment accordingly.
Hi I’m trying to write a query and I’m struggling to figure out how to go about it.
I have a suppliers table and a supplier parts table I want to write a query that lists suppliers that have specified related Parts in the supplier parts table. If a supplier doesn’t have all specified related parts then they should not be listed.
At the moment I have written a very basic query that lists the supplier if they have a related supplier part that meets the criteria.
SELECT id ,name
FROM
efacdb.dbo.suppliers INNER JOIN [efacdb].[dbo].[spmatrix] ON
id = spmsupp
WHERE spmpart
IN ('ALUM_5083', 'ALUM_6082')
I only want to show the supplier if they have both parts related. Does anyone know how I could do this?
Use a subquery with counting distinct occurences:
select * from suppliers s
where 2 = (select count(distinct spmpart) from spmatrix
where id = spmsupp and spmpart in ('ALUM_5083', 'ALUM_6082'))
As a note, you can modify your query to get what you want, just by using an aggregation:
SELECT id, name
FROM efacdb.dbo.suppliers INNER JOIN
[efacdb].[dbo].[spmatrix]
ON id = spmsupp
WHERE spmpart IN ('ALUM_5083', 'ALUM_6082')
GROUP BY id, name
HAVING MIN(spmpart) <> MAX(spmpart);
If you know there are no duplicates, then having count(*) = 2 also solves the problem.
Learning SQL so please forgive me.
I have a table that holds many accounts(and sub accounts) and another table that holds many orders they are custom tables taken from different databases.
What I am trying to do is update order_amt on the account table with the order_val value from the orders table but if there are more than one order with that account I want the order amount from the earliest order date only.
Account Table
Acc _Num..........Comp_Name.......Order_Amt.......Int_Id
123456789-1.....ABC Ltd.......................................123456789
123456789-2.....ABC Ltd.......................................123456789
987654321-1.....Xyz Ltd.........................................987654321
987654321-2.....Xyz Ltd.........................................987654321
Orders Table
Order_Num.....Order_Dt.....Order_Val.....Acc_num
1......................01/01/13......£20.00...........123456789
2......................01/01/14......£10.00...........123456789
3......................01/01/10......£100.00..........987654321
4......................01/01/11......£200.00..........987654321
So the order_amt for accounts 123456789-1 & 2 = £20.00 and from 987654321-1 & 2 would be £100.00.
UPDATE accounts a
SET a.order_amt =
(
SELECT order_val
FROM orders o
WHERE a.int_id = o.acc_num
AND EXISTS
(
SELECT MIN(order_dt)
FROM orders oa
WHERE o.order_num = oa.order_num
);
I am getting a few errors including the error that more than one row returned? Could anyone please help me?
kind Regards
Eden
I think you need something like this:
UPDATE accounts a
SET a.order_amt =
(
SELECT order_val
FROM orders o
WHERE a.int_id = o.acc_num
GROUP BY order_val
HAVING order_dt = MIN(order_dt);
);
Create a FK on the column account_number inside of the orders table and have it reference the auto-increment account id of the accounts table.
Then as Roger suggest, with the slight modification of the column int_id AND a modification of the HAVING clause
UPDATE accounts a
SET a.order_amount =
(
SELECT order_value
FROM orders o
WHERE a.account_number = o.account_number
GROUP BY order_value
HAVING MIN(order_date)
)
This will not fix the error that is occurring with more than one row being returned though since there are multiple orders with the same account id attached to them. You will need to be more specific about how what you are needing. Do you want the average of all order amounts to equal the order value in the accounts table? Or maybe the MAX/MIN value?
I am having difficulty updating records within a database based on the most recent date and am looking for some guidance. By the way, I am new to SQL.
As background, I have a windows forms application with SQL Express and am using ADO.NET to interact with the database. The application is designed to enable the user to track employee attendance on various courses that must be attended on a periodic basis (e.g. every 6 months, every year etc.). For example, they can pull back data to see the last time employees attended a given course and also update attendance dates if an employee has recently completed a course.
I have three data tables:
EmployeeDetailsTable - simple list of employees names, email address etc., each with unique ID
CourseDetailsTable - simple list of courses, each with unique ID (e.g. 1, 2, 3 etc.)
AttendanceRecordsTable - has 3 columns { EmployeeID, CourseID, AttendanceDate, Comments }
For any given course, an employee will have an attendance history i.e. if the course needs to be attended each year then they will have one record for as many years as they have been at the company.
What I want to be able to do is to update the 'Comments' field for a given employee and given course based on the most recent attendance date. What is the 'correct' SQL syntax for this?
I have tried many things (like below) but cannot get it to work:
UPDATE AttendanceRecordsTable
SET Comments = #Comments
WHERE AttendanceRecordsTable.EmployeeID = (SELECT EmployeeDetailsTable.EmployeeID FROM EmployeeDetailsTable WHERE (EmployeeDetailsTable.LastName =#ParameterLastName AND EmployeeDetailsTable.FirstName =#ParameterFirstName)
AND AttendanceRecordsTable.CourseID = (SELECT CourseDetailsTable.CourseID FROM CourseDetailsTable WHERE CourseDetailsTable.CourseName =#CourseName))
GROUP BY MAX(AttendanceRecordsTable.LastDate)
After much googling, I discovered that MAX is an aggregate function and so I need to use GROUP BY. I have also tried using the HAVING keyword but without success.
Can anybody point me in the right direction? What is the 'conventional' syntax to update a database record based on the most recent date?
So you want to update the AttendantsRecordsTable, and set the comment to the comment in the most recent CourseDetailsTable for each employee?
UPDATE
dbo.AttendanceRecordsTable
SET
Comments = #Comments
FROM
CourseDetailsTable cd
INNER JOIN
Employee e ON e.EmployeeID = AttendanceRecordTable.EmployeeID
WHERE
e.LastName = #LastName
AND e.FirstName = #FirstName
AND cd.CourseName = #CourseName
AND AttendanceRecordsTable.CourseID = cd.CourseID
AND AttendanceRecordsTable.LastDate =
(SELECT MAX(LastDate)
FROM AttendanceRecordsTable a
WHERE a.EmployeeID = e.EmployeeID
AND a.CourseID = cd.CourseID)
I think something like that should work.
You basically need to do a join between the AttendanceRecordTable, which you want to update, and the Employee and CourseDetailsTable tables. For these two, you have defined certain parameters to select a single row each, and then you need to make sure to update only that last AttendanceRecordTable entry which you do by making sure it's the MAX(LastDate) of the table.
The subselect here:
(SELECT MAX(LastDate)
FROM AttendanceRecordsTable a
WHERE a.EmployeeID = e.EmployeeID AND a.CourseID = cd.CourseID)
will select the MAX (last) of the LastDate entries in AttendanceRecordsTable, based on selection of a given employee (e.EmployeeID) and a given course (cd.CourseID).
Pair that with the selects to select the single employee by first name and last name (that of course only works if you never have two John Miller in your employee table!). You also select the course by means of the course name, so that too must be unique - otherwise you'll get multiple hits in the course table.
Marc
Assuming that you primary key on the AttendanceRecordsTable is id:
UPDATE AttendanceRecordsTable SET Comments = #Comments
WHERE AttendanceRecordsTable.id = (
SELECT AttendanceRecordsTable.id
FROM EmployeeDetailsTable
JOIN AttendanceRecordsTable ON AttendanceRecordsTable.EmployeeID = EmployeeDetailsTable.EmployeeID·
JOIN CourseDetailsTable ON AttendanceRecordsTable.CourseID = CourseDetailsTable.CourseID
WHERE
EmployeeDetailsTable.LastName =#ParameterLastName AND EmployeeDetailsTable.FirstName =#ParameterFirstName AND
CourseDetailsTable.CourseName =#CourseName
ORDER BY AttendanceRecordsTable.LastDate DESC LIMIT 1)
Basically, that sub select will first join the attendence, employee and coursedetail tables, extract those rows where the employee's and course details' name match those given by your parameters and limit the output in reverted order to one line. You might want to test that sub-select statement first.
Edit: I just read your posting again, you don't have a single primary key column on AttendanceRecordsTable. Bummer.