I have a little problem with some query.
This is the task:
Create a query that displays the employees with no sale in the last 3 months to customers who are from "USA".
This is what i wrote:
Select emp.EmployeeID, (emp.FirstName + ' ' + emp.LastName) AS Name
From Employees AS emp
Join Orders AS o ON emp.EmployeeID = o.EmployeeID
Join Customers AS c ON o.CustomerID = c.CustomerID
Where c.Country LIKE 'USA';
One of the problem is that i don't know where to put this select query (it's for calculating the last 3 months but i'm not sure that this is true):
Select DATEDIFF(MM, '1998-02-01', '1998-05-31') From Orders
The second problem is that i don't have an idea for the part "employees with no sale" How can i find this?
Should i use other kind of joins or something else?
Sorry for my question but i'm new in SQL and i'll appreciate any kind of help.
If you have any questions, please ask. :)
Create a query that displays the employees with no sale in the last 3 months to customers who are from "USA"
Select * From Employees e
Where not exists
(Select * from orders o join customers c
on c.CustomerID = o.CustomerID
Where c.Country = "USA"
and o.saleDate >= DateAdd(month, -3, getdate()))
How you actually treat "last 3 months" depends on you, but this is one way to approach the task:
select e.*
from employees e
where not exists (
select 1
from orders o
join customers c on c.customerid = o.customerid
where e.employeeid = o.employeeid
and c.country = 'USA'
and c.orderdate > dateadd(month, datediff(month, 0, getdate()), 3)
);
Last three months could mean months per se or just days that make exactly last three months (eg. 91)
If you're just learning SQL, please don't take one of these answers and use it to do your homework. You'll need to figure out what's going on for yourself.
Try to take this task in pieces. First, let's look at 'customers from USA.' You can probably write this one yourself:
Select * from customers where country = 'usa'
Next, consider how to find the orders for those customers (all of them, for now).
Since you've posted an example of an inner join, I'm going to assume you could write that one yourself, too:
Select o.* from customers c inner join orders o on
c.customerID = o.customerID
where country = 'usa'
Now you've asked where/how to apply the criteria for 'sales in the last 3 months.' Note each order has an orderDate, representing the date the order was placed. You need to use the order table's orderdate field with today's date and compare the number of months between them. The getdate() function returns the date and time from the server. Try executing:
select getdate()
then you can experiment with the datediff() function on the orderDates in the orders table:
Select orderid, getdate(), orderdate, datediff(mm,orderdate,getdate())
from orders
I think you'll have better luck, though, adding 3 months to orderdate and comparing with getdate():
Select orderid, orderdate, dateadd(month, orderdate, 3),
getdate(), dateadd(month, getdate(), -3)
from orders
Now you can see all the employee IDs you don't want, the ones that have orders within the last 3 months, and you already know how to limit orders by customers in USA. Those are the IDs you want to exclude from the employees table. You can do that in a couple of different ways, depending on what you've learned so far, but typically you're exposed to LEFT OUTER JOINs for this sort of thing. You'll want to left outer join your employee table with the set of IDs you don't want (the ones with orders in the past 3 months) on the employeeID field, and return rows where the employeeID from the order subquery is null.
Try:
select count(*) from employees -- note rowcount
then:
select min(employeeID) from orders -- pick one employee
then:
select count(e.employeeID)
from employees e left outer join
(select * from orders
where employeeID in (select min(employeeID) from orders)
) o on e.employeeID = o.employeeID
where o.employeeID is null
this count should be one less than the total number of rows in your employees table, it should exclude the lowest employeeID with an order.
Then see if you can figure out how to do your homework.
Left/Right joins exist as well and you can somewhat think of them as Venn diagrams (Join or Inner Join is the intersect, Left Join is intersect and left circle, etc.). Its one perspective on how to think what will be returned. Fields returned which are not in the intersect like with a Left Join will be NULL.
The below is another way to do it:
Select emp.EmployeeID, (emp.FirstName + ' ' + emp.LastName) AS Name
From Employees AS emp
Left Join Orders As o ON emp.EmployeeID = o.EmployeeID
And o.OrderDate > DateAdd(month,-3,GetDate())
Join Customers AS c ON o.CustomerID = c.CustomerID
Where c.Country = 'USA' And o.EmployeeID is Null
Group By emp.EmployeeID, emp.FirstName, emp.LastName;
Related
select D.[Date], E.emp_name, E.emp_jde, count(C.[agent_no]) calls, count(S.[EMPJDENUM]) sales
from
(select cast([start_date] as date) dte, [agent_no]
from call_table
where [skill_name] like '%5700 sales l%'
and [Agent_Time] != '0'
) C
full outer join
(select [AC#DTE_dt], [EMPJDENUM]
from sales_table
where [ICGCD2] in ('LAWN', 'HORT')
and [CHANNEL]= 'INQ'
and [ITMQTY]>3
) S on c.dte=s.[AC#DTE_dt]
right join
(select [Date]
from Date_table
) D on c.dte=d.[Date] or s.[AC#DTE_dt]=d.[Date]
right join
(select [emp_name], [emp_jde], [agent_no]
from Employee_table
) E on C.[agent_no]=E.agent_no and S.[EMPJDENUM]=E.emp_jde
group by D.[Date], E.emp_name, E.emp_jde
Date Tables -
Note: Not all dates will have both calls and sales.
Additional Tables -
What needs to be accomplished -
1) Join and Aggregate calls and sales by Employee by joining the calls table (on agent_no) and sales (on JDE) table
2) Since not all dates will include both calls and sales - use the date dimension table to ensure all dates are represented
The desired result would look like this -
The query I wrote executes - it takes so long I just end up canceling the query.
Any help would be appreciated.
Without seeing the query plan, it is a little tricky, but here are a couple of suggestions that might improve the performance:
remove the leading wildcard in where [skill_name] like '5700 sales l%'
put the group by into the subqueries
I have an example here that implements both of those. (Note that I did some reformatting just to try to understand what your query was doing.)
select D.[Date], E.emp_name, E.emp_jde, C.Calls, S.Sales
from Date_table As D
Left Join (
select cast([start_date] as date) As CallDate, [agent_no], Count(*) As Calls
from call_table
where [skill_name] like '5700 sales l%'
and [Agent_Time] != '0'
Group By Cast([start_date] As date), [agent_no]) As C On D.[Date] = C.CallDate
Left Join (
select [AC#DTE_dt] As SaleDate, [EMPJDENUM], Count(*) As Sales
from sales_table
where [ICGCD2] in ('LAWN', 'HORT')
and [CHANNEL]= 'INQ'
and [ITMQTY]>3
Group By [AC#DTE_dt], [EMPJDENUM]) As S on D.[Date] = s.SaleDate
right join Employee_table As E
on C.[agent_no]=E.agent_no
and S.[EMPJDENUM]=E.emp_jde;
Edit
In order to get a row for each possible combination of date and employee, you will need a cross join of the date table and the employee table.
select D.[Date], E.emp_name, E.emp_jde, C.Calls, S.Sales
from Date_table As D,
Employee_table as E
Left Join (
select cast([start_date] as date) As CallDate, [agent_no], Count(*) As Calls
from call_table
where [skill_name] like '5700 sales l%'
and [Agent_Time] != '0'
Group By Cast([start_date] As date), [agent_no]) As C
On D.[Date] = C.CallDate
And E.agent_no = C.agent_no
Left Join (
select [AC#DTE_dt] As SaleDate, [EMPJDENUM], Count(*) As Sales
from sales_table
where [ICGCD2] in ('LAWN', 'HORT')
and [CHANNEL]= 'INQ'
and [ITMQTY]>3
Group By [AC#DTE_dt], [EMPJDENUM]) As S
on D.[Date] = s.SaleDate
and E.emp_jde = S.[EMPJDENUM];
Question
I have a query and would like to join one column from another table based on its matching ID
Query So far
SELECT DENumber, AcquiredDate, ItemDescription, ItemName, LocationID FROM dbo.Assets JOIN LocationName FROM dbo.Locations on ProductID
WHERE DATEDIFF(YEAR, AcquiredDate, GetDate()) >= 6
The matching ID in both tables is LocationID
Problem
My query is wrong and throws errors
Msg 156, Level 15, State 1, Line 1
Incorrect syntax near the keyword 'FROM'.
Rewrite your query to this:
SELECT DENumber, AcquiredDate, ItemDescription, ItemName, LocationName
FROM dbo.Assets INNER JOIN dbo.Locations ON Assets.LocationId = Locations.LocationId
WHERE DATEDIFF(YEAR, AcquiredDate, GetDate()) >= 6
To clarify: In a JOIN, you must specify the names of both tables on either side of the JOIN keyword. Then, you specify the join criteria after the ON keyword:
FROM <<table1>> INNER JOIN <<table2>> ON <<join criteria>>
Here, we only want records that exist in both tables, which is why we use the INNER join, but you can also take all records from either the LEFT table, the RIGHT table or BOTH tables. In that case, you would do a LEFT JOIN, RIGHT JOIN our OUTER JOIN respectively.
SELECT L.DENumber, A.AcquiredDate, A.ItemDescription, A.ItemName, L.LocationID
FROM dbo.Assets A JOIN
ON dbo.Locations L on
L.ProductID = A.ProductID
WHERE DATEDIFF(YEAR, A.AcquiredDate, GetDate()) >= 6
I was trying to write a query for the SQL Server sample DB Northwind. The question was: "Show the most recent five orders that were purchased by a customer who has spent more than $25,000 with Northwind."
In my query the Alias name - "Amount" is not being recognized. My query is as follows:
select top(5) a.customerid, sum(b.unitprice*b.quantity) as "Amount", max(c.orderdate) as Orderdate
from customers a join orders c
on a.customerid = c.customerid
join [order details] b
on c.orderid = b.orderid
group by a.customerid
--having Amount > 25000 --throws error
having sum(b.unitprice*b.quantity) > 25000 --works, but I don't think that this is a good solution
order by Orderdate desc
Pls let me know what I am doing wrong here, as I am a newbie in writing T Sql. Also can this query and my logic be treated as production level query?
TIA,
You must use the aggregate in the query you have. This all has to do with the order in which a SELECT statement is executed. The syntax of the SELECT statement is as follows:
SELECT
FROM
WHERE
GROUP BY
HAVING
ORDER BY
The order in which a SELECT statement is executed is as follows. Since the SELECT clause isn't executed until after the HAVING clause, you can't use the alias like you can in the ORDER BY clause.
FROM
WHERE
GROUP BY
HAVING
SELECT
ORDER BY
Reference Article: http://www.bennadel.com/blog/70-sql-query-order-of-operations.htm
This is a known limitation in SQL Server, at least, but no idea if it's a bug, intentional or even part of the standard. But the thing is, neither the WHERE or HAVING clauses accept an alias as part of their conditions, you must use only columns from the original source tables, which means that for filtering by calculated expressions, you must copy-paste the very same thing in both the SELECT and WHERE parts.
A workaround for avoiding this duplication can be to use a subquery or cte and apply the filter on the outer query, when the alias is just an "input" table:
WITH TopOrders AS (
select a.customerid, sum(b.unitprice*b.quantity) as "Amount", max(c.orderdate) as Orderdate
from customers a join orders c
on a.customerid = c.customerid
join [order details] b
on c.orderid = b.orderid
group by a.customerid
--no filter here
order by Orderdate desc
)
SELECT TOP(5) * FROM TopOrders WHERE Amount > 25000 ;
Interesting enough, the ORDER BY clause does accepts aliases directly.
You must use Where b.unitprice*b.quantity > 25000 instead of having Amount > 25000.
Having used for aggregate conditions. Your business determine your query condition. If you need to calculate sum of prices that have above value than 25000, must be use Where b.unitprice*b.quantity > 25000 and if you need to show customer that have total price above than 25000 must be use having Amount > 25000 in your query.
select top(5) a.customerid, sum(b.unitprice*b.quantity) as Amount, max(c.orderdate) as Orderdate
from customers a
JOIN orders c ON a.customerid = c.customerid
join [order details] b ON c.orderid = b.orderid
group by a.customerid
having sum(b.unitprice*b.quantity) > 25000 --works, but I don't think that this is a good solution
Order by Amount
I don't have that schema at hand, so table' and column' names might go a little astray, but the principle is the same:
select top (5) ord2.*
from (
select top (1) ord.CustomerId
from dbo.Orders ord
inner join dbo.[Order Details] od on od.OrderId = ord.OrderId
group by ord.CustomerId
having sum(od.unitPrice * od.Quantity) > $25000
) sq
inner join dbo.Orders ord2 on ord2.CustomerId = sq.CustomerId
order by ord2.OrderDate desc;
The Having Clause will works with aggregate function like SUM,MAX,AVG..
You may try like this
SELECT TOP 5 customerid,SUM(Amount)Amount , MAX(Orderdate) Orderdate
FROM
(
SELECT A.customerid, (B.unitprice * B.quantity) As "Amount", C.orderdate As Orderdate
FROM customers A JOIN orders C ON A.customerid = C.customerid
JOIN [order details] B ON C.orderid = B.orderid
) Tmp
GROUP BY customerid
HAVING SUM(Amount) > 25000
ORDER BY Orderdate DESC
The question is little ambiguos.
Show the most recent five orders that were purchased by a customer who
has spent more than $25,000 with Northwind.
Is it asking to show the 5 recent orders by all the customers who have spent more than $25,000 in all of their transactions (which can be more than 5).
The following query shows all the customers who spent $25000 in all of their transactions (not just the recent 5).
In one of the Subquery BigSpenders it gets all the Customers who spent more than $25000.
Another Subquery calculates the total amount for each order.
Then it gets rank of all the orders by OrderDate and OrderID.
Then it filters it by Top 5 orders for each customer.
--
SELECT *
FROM (SELECT C.customerid,
C.orderdate,
C.orderid,
B3.amount,
Row_number()
OVER(
partition BY C.customerid
ORDER BY C.orderdate DESC, C.orderid DESC) Rank
FROM orders C
JOIN
--Get Amount Spend Per Order
(SELECT b2.orderid,
Sum(b2.unitprice * b2.quantity) AS Amount
FROM [order details] b2
GROUP BY b2.orderid) B3
ON C.orderid = B3.orderid
JOIN
--Get Customers who spent more than 25000
(SELECT c.customerid
FROM orders c
JOIN [order details] b
ON c.orderid = b.orderid
GROUP BY c.customerid
HAVING Sum(b.unitprice * b.quantity) > 25000) BigSpenders
ON C.customerid = BigSpenders.customerid) X
WHERE X.rank <= 5
Here is my query:
select custnmbr,custname,slprsnid,cdatetime,cdur,cnumber,cext,
finalcalledpartynumber,sono,invno,ordamt,invamt,adduser
from table1 calls left join table2 cust
on (calls.number = cust.phone1 or calls.cext = cust.phone1)
left outer join table3 sales on (cust.custnmbr = sales.custno
and sales.adddate = #date)
where (cnumber = #phone or cext = #phone) and cdatetime >= #date
Here is what I am trying to do:
Get all the calls from table 1, and get the customer from table 2. Then get all the sales from table 3 and the customer from table 2.
What I am getting is all the calls, the customer, and then if there is an order for that customer I get that as well. What I want is all the orders as well.
Just looking for some pointers on joining 3 tables.
You have to think about how you're going to handle the calls and sales results since there does not appear to be a correlation between calls and sales. So, a customer with 3 calls and 4 sales will produce 12 rows in the result set. This is where we could help you better if you provide more specifics.
The concept is that if calls is a necessary requirement, then you could do something like...
SELECT ca.CallId, ca.TimeStarted, ...,
c.CustomerId, c.FirstName, ...,
s.SaleDate, s.SaleAmount, ...
FROM calls AS ca
INNER JOIN customers AS c ON ca.CustomerId = c.CustomerId
OUTER APPLY ( --or CROSS APPLY, depending on your needs
SELECT s.SaleDate, s.SaleAmount, ...
FROM sales AS s
WHERE s.CustomerId = c.CustomerId
AND s.SaleDate = ca.CallDate --It would help if this relationship existed
)
Generally I would start with the table you want ALL the data from, the one that you are going to form the basis of your navigation to the other entities.:
SELECT
C.*,
Ca.*,
S.*
FROM Sales S
LEFT JOIN Customer C
ON (S.CustomerId = C.CustomerId) -- your condition
LEFT JOIN Calls Ca
ON (C.CustomerId = Ca.CustomerId) -- your condition
The objective is below the list of tables.
Tables:
Table: Job
JobID
CustomerID
Value
Year
Table: Customer
CustomerID
CustName
Table: Invoice
SaleAmount
CustomerID
The Objective
Part 1: (easy) I need to select all invoice records and sort by Customer (To place nice w/ Crystal Reports)
Select * from Invoice as A inner join Customer as B on A.CustomerID = B.CustomerID
Part 2: (hard) Now, we need to add two fields:
JobID associated with that customer's job that has the Maximum Value (from 2008)
Value associated with that job
Pseudo Code
Select * from
Invoice as A
inner join Customer as B on A.CustomerID = B.CustomerID
inner join
(select JobID, Value from Jobs where Job:JobID has the highest value out of all of THIS customer's jobs from 2008)
General Thoughts
This is fairly easy to do If I am only dealing with one specific customer:
select max(JobId), max(Value) as MaxJobID from Jobs where Value = (select max(Value) from Jobs where CustomerID = #SpecificCustID and Year = '2008') and CustomerID = SpecificCustID and CustomerID = '2008'
This subquery determines the max Value for this customer in 2008, and then its a matter of choosing a single job (can't have dupes) out of potential multiple jobs from 2008 for that customer that have the same value.
The Difficulty
What happens when we don't have a specific customer ID to compare against? If my goal is to select ALL invoice records and sort by customer, then this subquery needs access to which customer it is currently dealing with. I suppose this can "sort of" be done through the ON clause of the JOIN, but that doesn't really seem to work because the sub-sub query has no access to that.
I'm clearly over my head. Any thoughts?
How about using a CTE. Obviously, I can't test, but here is the idea. You need to replace col1, col2, ..., coln with the stuff you want to select.
Inv( col1, col2, ... coln)
AS
(
SELECT col1, col2, ... coln,
ROW_NUMBER() OVER (PARTITION BY A.CustomerID
ORDER BY A.Value DESC) AS [RowNumber]
FROM Invoice A INNER JOIN Customer B ON A.CustomerID = B.CustomerID
WHERE A.CustomerID = #CustomerID
AND A.Year = #Year
)
SELECT * FROM Inv WHERE RowNumber = 1
If you don't have a CustomerID, this will return the top value for each customer (that will hurt on performance tho).
The row_number() function can give you what you need:
Select A.*, B.*, C.JobID, C.Value
from
Invoice as A
inner join Customer as B on A.CustomerID = B.CustomerID
inner join (
select JobID, Value, CustomerID,
ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY Value DESC) AS Ordinal
from Jobs
WHERE Year = 2008
) AS C ON (A.CustomerID = C.customerID AND C.Ordinal = 1)
The ROW_NUMBER() function in this query will order by value in descending order and the PARTITION BY clause will do this separately for each different value of CustomerID. This means that the highest Value for each customer will always be 1, so we can join to that value.
The over function is an awesome, but often neglected function. You can use it in a subquery to pull back your valid jobs, like so:
select
a.*
from
invoice a
inner join customer b on
a.customerid = b.customerid
inner join (select customerid, max(jobid) as jobid, maxVal from
(select customerid,
jobid,
value,
max(value) over (partition by customerid) as maxVal
from jobs
where Year = '2008') s
where s.value = s.maxVal
group by customerid, maxVal) c on
b.customerid = c.customerid
and a.jobid = c.jobid
Essentially, that first inner query looks like this:
select
customerid,
jobid,
value,
max(value) over (partition by customerid) as maxVal
from jobs
where Year = '2008'
You'll see that this pulls back all of the jobs, but with that additional column which lets you know what the maximum value is for each customer. With the next subquery, we filter out any rows that have value and maxVal equal. Additionally, it finds the max JobID based on customerid and maxVal, because we need to pull back one and only one JobID (as per the requirements).
Now, you have a complete listing of CustomerID and JobID that meet the conditions of having the highest JobID that contains the maximum Value for that CustomerID in a given year. All that's left is to join it to Invoice and Customer, and you're good to go.
Just to be complete with the non row_number solution for those < MSSQL 2005. Personanly, I find it easier to follow myslef...but that could be biased considering how much time I spend in MSSQL 2000 vs 2005+.
SELECT *
FROM Invoice as A
INNER JOIN Customer as B ON
A.CustomerID = B.CustomerID
INNER JOIN (
SELECT
CustomerId,
--MAX in case dupe Values.
==If UC on CustomerId, Value (or CustomerId, Year, Value) then not needed
MAX(JobId) as JobId
FROM Jobs
JOIN (
SELECT
CustomerId,
MAX(Value) as MaxValue
FROM Jobs
WHERE Year = 2008
GROUP BY
CustomerId
) as MaxValue ON
Jobs.CustomerId = MaxValue.CustomerId
AND Jobs.Value = MaxValue.MaxValue
WHERE Year = 2008
GROUP BY
CustomerId
) as C ON
B.CustomerID = C.CustomerID