Error: The order by clause is invalid in views - sql

I'm getting the following error in SQL Server 14 where I am trying to build a view based on 4 tables
The ORDER BY clause is invalid in views, inline functions, derived
tables, subqueries, and common table expressions, unless TOP, OFFSET
or FOR XML is also specified
How do I fix the below so I don't get this error?
SELECT dbo.PS_Proj.[Project ID],
case
when PS_Proj.[City Name] is not null then concat(PS_Proj.[City Name], ' ', PS_Proj.[State])
when PS_Billing.[Location] is not null then max(PS_Billing.[Location])
when PS_Time.[Labor Location ID] is not null then max(PS_Location.[Labor Location Name])
else null
end AS [Location]
FROM dbo.PS_Location RIGHT OUTER JOIN
dbo.PS_Time ON dbo.PS_Location.[Labor Location ID] = dbo.PS_Time.[Labor Location ID] RIGHT OUTER JOIN
dbo.PS_Proj ON dbo.PS_Time.[Project ID] = dbo.PS_Proj.[Project ID] LEFT OUTER JOIN
dbo.PS_Billing ON dbo.PS_Proj.[Project ID] = dbo.PS_Billing.[Project ID]
ORDER BY PS_Billing.[T/S Date], PS_Time.[Date]
I'd like for the most recent PS_Billing location to show and if that is null for the most recent PS_Time location to show.

Create your view without Order By. Remove ORDER BY PS_Billing.[T/S Date], PS_Time.[Date] And add them to your SELECT.
SELECT
PS_Billing.[T/S Date] AS TsDate,
PS_Time.[Date] AS Date,
dbo.PS_Proj.[Project ID],
case
when PS_Proj.[City Name] is not null then concat(PS_Proj.[City Name], ' ', PS_Proj.[State])
when PS_Billing.[Location] is not null then max(PS_Billing.[Location])
when PS_Time.[Labor Location ID] is not null then max(PS_Location.[Labor Location Name])
else null
end AS [Location]
FROM dbo.PS_Location RIGHT OUTER JOIN
dbo.PS_Time ON dbo.PS_Location.[Labor Location ID] = dbo.PS_Time.[Labor Location ID] RIGHT OUTER JOIN
dbo.PS_Proj ON dbo.PS_Time.[Project ID] = dbo.PS_Proj.[Project ID] LEFT OUTER JOIN
dbo.PS_Billing ON dbo.PS_Proj.[Project ID] = dbo.PS_Billing.[Project ID]
And select from your view with order by line
Select FROM YOUR_VIEW_NAME as V
Order By V.TsDate, V.Date

You have some aggregate data inside your case expression and you don't have a group by. I am making some guesses here. Also, you should get in the habit of using aliases, it makes things a lot simpler. I would also suggest not putting spaces in your column names as it forces you to constantly wrap columns in square brackets.
Something like this should get you close.
SELECT p.[Project ID]
, MAX(case
when p.[City Name] is not null then concat(p.[City Name], ' ', p.[State])
when b.[Location] is not null then b.[Location]
when t.[Labor Location ID] is not null then l.[Labor Location Name]
else null
end) AS [Location]
FROM dbo.PS_Location l
RIGHT OUTER JOIN dbo.PS_Time t ON l.[Labor Location ID] = t.[Labor Location ID]
RIGHT OUTER JOIN dbo.PS_Proj p ON t.[Project ID] = p.[Project ID]
LEFT OUTER JOIN dbo.PS_Billing b ON p.[Project ID] = b.[Project ID]
GROUP BY p.[Project ID]

Related

In SSRS How to make a sequential Group Column

I want to make a Sequential Group Column with the help of Matrix in SQL Server Reporting Service (SSRS),
In the image above, there are Dates with Days name, Mon 24, Tue 25 and Fri 28, So I want to make it like that: Keep the sequence of Dates, no matter whether the data is available or not.
My SQL Query for generating that data is:
SELECT tl.[Job No_] + ' - ' + j.[Description] AS [Job Name]
,tl.[Job Task No_] + ' - ' + w.[Description] AS [WBS Code]
,pt.[Description] AS [Pay Type]
,tlt.[Period Date]
,tlt.[Hours] AS [Hours]
FROM TimesheetLine tl
JOIN TimesheetLineDate tld ON tl.[Rec ID] = tld.[Timesheet Line Rec ID]
JOIN TimesheetLineTransactions tlt ON tld.[Rec ID] = tlt.[Timesheet Line Date Rec ID]
LEFT JOIN Job j ON tl.[Job No_] = j.[No_]
LEFT JOIN JobWBS w ON w.[Job Task No_] = tl.[Job Task No_]
LEFT JOIN PayType pt ON tl.[Pay Type] = pt.[Name]
LEFT JOIN Timesheet t ON tl.[Timesheet Rec ID] = t.[Rec ID]
WHERE tl.[Timesheet Rec ID] = #RecId AND t.[Resource No_] = #UserResourceNo
SSRS cannot generate data so you will need to solve this in your dataset.
I would add a right join to a table of Dates e.g. a Date/Time Dimension table. Then I would use the date from that table as the label and values for the SSRS Column Group.

SQL - query to report MAX DATE results or NULL results

I have an SQL database with 3 tables:
Customer record table, holding master data (each row is unique)
Customer notes, each note data/time stamped (there could be many notes per customer, or none at all)
Product table, showing which customer has bought which product
Table: tbl_Customer_Records
CustomerID---- Company Name-----Company Segment----- Promo Code
Table: tbl_Customer_Notes
CustomerID---- Note-----Created Date
Table: tbl_Customer_Products
CustomerID---- Product Category
What I want is to pull a list of customer records which includes the latest note only, so there are no duplicate lines if multiple notes exist. But I also want my report to include customer records if no notes exist at all. I've achieved the first part of this with a SELECT MAX function, and that works well, the problem is when I add the OR = NULL clause in the final line of code below. This doesn't work, and I can't figure out a solution.
Any suggestions will be greatly appreciated!
SELECT
[tbl_Customer_Records].[CustomerID],
[tbl_Customer_Records].[Company Name],
[tbl_Customer_Records].[Company Segment],
[tbl_Customer_Records].[Promo Code],
[tbl_Customer_Notes].[Note],
[tbl_Customer_Products].[Product Category]
FROM
tbl_Customer_Records
LEFT OUTER JOIN tbl_Customer_Notes
ON tbl_Customer_Records.CustomerID = tbl_Customer_Notes.CustomerID
LEFT OUTER JOIN tbl_Customer_Products
ON tbl_Customer_Records.CustomerID = tbl_Customer_Products.CustomerID
WHERE
[Product Category] in ('Nuts','Bolts','Screws','Spanners')
AND
[Created Date] in (SELECT MAX ([Created Date]) FROM tbl.Customer_Notes GROUP BY [CustomerID])
OR tbl_Customer_Note.Note is null
There're a few tricks to do this kind of query (row_number or join with grouped data), but I think most cleanest one in your case is to use outer apply:
select
cr.[CustomerID],
cr.[Company Name],
cr.[Company Segment],
cr.[Promo Code],
cn.[Note],
cp.[Product Category]
from tbl_Customer_Records as cr
left outer join tbl_Customer_Products as cp on cp.CustomerID = cr.CustomerID
outer apply (
select top 1
t.[Note]
from tbl_Customer_Notes as t
where t.[CustomerID] = cr.[CustomerID]
order by t.[Created_Date] desc
) as cn
where
cp.[Product Category] in ('Nuts','Bolts','Screws','Spanners')
Changed all clumsy table name.column name to alias.column name, I think it's much more readable this way.
Or:
select
cr.[CustomerID],
cr.[Company Name],
cr.[Company Segment],
cr.[Promo Code],
cn.[Note],
cp.[Product Category]
from tbl_Customer_Records as cr
left outer join tbl_Customer_Products as cp on cp.CustomerID = cr.CustomerID
left outer join tbl_Customer_Notes as cn on
cn.CustomerID = cr.CustomerID and
cn.[Created_Date] = (select max(t.[Created_Date]) from tbl_Customer_Notes as t where t.CustomerID = cr.CustomerID)
where
cp.[Product Category] in ('Nuts','Bolts','Screws','Spanners')
You can add your filter condition in ON predicate to preserve rows from left table and fetch only required matching rows from right table, from first LEFT OUTER JOIN operator. Following query should work:
SELECT
CR.[CustomerID],
CR.[Company_Name],
CR.[Company_Segment],
CR.[Promo_Code],
CN.[Note],
CP.[Product_Category]
FROM
tbl_Customer_Records CR
LEFT OUTER JOIN tbl_Customer_Notes CN
ON CR.CustomerID = CN.CustomerID AND CN.[Created_Date] in (SELECT MAX ([Created_Date])
FROM tbl_Customer_Notes
WHERE CR.CustomerID = tbl_Customer_Notes.CustomerID
GROUP BY [CustomerID])
LEFT OUTER JOIN tbl_Customer_Products CP
ON CR.CustomerID = CP.CustomerID
WHERE
[Product_Category] in ('Nuts','Bolts','Screws','Spanners')
Should work, tried with NULL values:
SELECT a.[CustomerID],
a.[Company Name],
a.[Company Segment],
a.[Promo Code],
a.[Note],
a.[Product Category]
FROM (
SELECT
cr.[CustomerID],
cr.[Company Name],
cr.[Company Segment],
cr.[Promo Code],
cn.[Note],
cp.[Product Category],
ROW_NUMBER() OVER(PARTITION BY cr.[CustomerID] ORDER BY cn.[Created Date] DESC) as rnk
FROM tbl_Customer_Records cr
LEFT JOIN tbl_Customer_Notes cn
ON cr.CustomerID = cn.CustomerID
LEFT JOIN tbl_Customer_Products cp
ON cr.CustomerID = cp.CustomerID
WHERE cp.[Product Category] in ('Nuts','Bolts','Screws','Spanners') )a
WHERE a.rnk = 1
try to use CustomerID not in (select CustomerID from tbl_Customer_Note)

SQL joins and group by with 3 separate tables

I have two separate queries here that I need to make into one query, I'll post the queries, then try to explain what I'm trying to do.
SELECT Distinct I.ITMCDE, V.VNDRCDE, V.VNAME
FROM (SELECT RIGHT(Items.[Item Number], 3) as ITMCDE FROM Items) I,
(SELECT LEFT(Vendors.[Vendor ID], 3) as VNDRCDE,
Vendors.[Vendor Name] as VNAME
FROM Vendors) V
WHERE I.ITMCDE = V.VNDRCDE
In this first one, I simply match up the vendor code with the item code, to get the vendor name that produces the item.
SELECT DISTINCT (Items.[Item Description]), ItemQuantities.[QTY Available],
Items.[Selling U Of M], Items.[Item Number]
FROM ItemQuantities
INNER JOIN Items ON ItemQuantities.[Item Number] = Items.[Item Number]
WHERE Items.[Item Number] LIKE 'WH%'
AND Items.[Item Number] NOT LIKE '%RMW'
In this second one I'm selecting the item description, quantity available from two separate tables (quantity available is in a different table, match them up using the item number)
As you can see, the only correlation between the three tables is the item number, and not even that in the vendors table. The last three characters of the item number correlate with the first three characters of the vendor id... I did not design this setup. I'm just trying to work with it now.
How do I join these two statements into one single statement that will give me the vendor name, item description, Unit of Measure (Selling U of M), and item quantity where the item description is unique?
I think this should work:
SELECT DISTINCT Items.[Item Description],
ItemQuantities.[QTY Available],
Items.[Selling U Of M],
Items.[Item Number],
V.VNAME
FROM ItemQuantities
INNER JOIN Items ON ItemQuantities.[Item Number] = Items.[Item Number]
INNER JOIN Vendors ON
RIGHT(Items.[Item Number], 3) = LEFT(Vendors.[Vendor ID], 3)
WHERE Items.[Item Number] LIKE 'WH%'
AND Items.[Item Number] NOT LIKE '%RMW'
You can join it in:
SELECT DISTINCT (i.[Item Description]), iq.[QTY Available],
i.[Selling U Of M], i.[Item Number],
V.VNDRCDE, V.VNAME
FROM ItemQuantities iq INNER JOIN
Items i
ON iq.[Item Number] = i.[Item Number] left outer join
Vendors v
on LEFT(v.[Vendor ID], 3) = RIGHT(i.[Item Number], 3)
WHERE i.[Item Number] LIKE 'WH%' and
i.[Item Number] NOT LIKE '%RMW'
I am not sure if you intend anything special with the parentheses around i.[Item Description]. The distinct keyword applies to the entire row.
If you want distinct only on the description, then you need to use group by. Something like:
SELECT i.[Item Description],
max(iq.[QTY Available]),
max(i.[Selling U Of M]), max(i.[Item Number]),
max(V.VNDRCDE), max(V.VNAME)
FROM ItemQuantities iq INNER JOIN
Items i
ON iq.[Item Number] = i.[Item Number] left outer join
Vendors v
on LEFT(v.[Vendor ID], 3) = RIGHT(i.[Item Number], 3)
WHERE i.[Item Number] LIKE 'WH%' and
i.[Item Number] NOT LIKE '%RMW'
group by i.[Item Description])
The max() will return the maximum value. If all are the same, then this is a good way to get an "arbitrary" value.
#sgeddes Thank you.
This is what I came up with
SELECT DISTINCT
Item.ITEMNMBR AS [Item Number],
Item.ITEMDESC AS [Item Description],
Item.ITMGEDSC AS [Item Category],
Item.SELNGUOM AS [Unit of Measure],
(SELECT VENDNAME FROM PM00200 WHERE (VENDORID = IV00103.VENDORID)) AS [Vendor Name],
(CASE WHEN Quan.QTYONHND > 0 THEN 'In Stock' ELSE 'Out of Stock' END) AS [Stock Status]
FROM IV00101 AS Item INNER JOIN
IV00102 AS Quan ON Item.ITEMNMBR = Quan.ITEMNMBR INNER JOIN
IV00103 ON Item.ITEMNMBR = IV00103.ITEMNMBR AND Quan.ITEMNMBR = IV00103.ITEMNMBR
WHERE (Item.ITEMNMBR LIKE 'WH%') AND (IV00103.VENDORID NOT LIKE '%MIL')

Return results of ID NOT IN Certain Table

I am trying to write a write and the logic I am looking for is basically. If any User IDs from Table 1 do not exist in Table 2 show those in the results. So I did:
SELECT
UserColder.ContactName,
UserColder.Phone,
UserColder.Email,
UserColder.Website,
Country.Name,
UserColderZIP.[ZIP Code],
UserColderZIP.[State Abbreviation]
FROM
dbo.UserColder
LEFT OUTER JOIN dbo.CountryUser
ON UserColder.ID = CountryUser.[User ID]
INNER JOIN dbo.Country
ON CountryUser.[Foreign ID] = Country.ID
LEFT OUTER JOIN dbo.UserColderZIP
ON UserColder.ID = UserColderZIP.UserColder
WHERE
UserColder.ID NOT IN (CountryUser.[User ID])
It returns data without the Where But with the Where I am trying to get it to show all the results where the Id from UserColder don't have any records in CountryUser. Right now it is returning no results and I know ID 2 doesn't exist in there. Any idea what I am doing wrong?
Thanks!
Since you have a LEFT OUTER JOIN, to get records from UserColder that don't exist in CountryUser, check for NULL. The joined rows which have no corresponding value will return NULL.
WHERE
CountryUser.[User ID] IS NULL
Just grab the information from UserColder and make sure that the UserColder.ID is not in CountryUser by checking it (select statement in where).
SELECT
UserColder.ContactName,
UserColder.Phone,
UserColder.Email,
UserColder.Website,
Country.Name,
UserColderZIP.[ZIP Code],
UserColderZIP.[State Abbreviation]
FROM
dbo.UserColder
INNER JOIN dbo.Country
ON CountryUser.[Foreign ID] = Country.ID
LEFT OUTER JOIN dbo.UserColderZIP
ON UserColder.ID = UserColderZIP.UserColder
WHERE
UserColder.ID NOT EXISTS (select [user id] from dbo.CountryUser where CountryUser.[User ID])

Filtering based on COUNT

I have a query that works really well. But I am trying to add a filter so if users_in_this_country is > 1. I know to add users_in_this_country > 1 to the WHERE. But if I add it inside the parenthesis it says invalid column and the same if I add it outside of the parenthesis. This is probably really dumb and easy, but what am I over looking? Thanks!
SELECT u.ContactName
,cu.[User ID]
,c.Name
,c.ID
,cu.[Foreign Table]
,count(*) OVER (PARTITION BY c.ID) AS user_in_this_country
FROM dbo.Country AS c
INNER JOIN dbo.CountryUser AS cu ON c.ID = cu.[Foreign ID]
INNER JOIN dbo.UserColder AS u ON cu.[User ID] = u.ID
WHERE EXISTS (
SELECT *
FROM CountryUser AS cu2
WHERE cu2.[Foreign ID] = cu.[Foreign ID]
AND cu2.[User ID] <> cu.[User ID]
AND cu.[Foreign Table] = 'Country')
The reason you can't refer to it in the WHERE clause is that its very meaning, for a given row, depends on what other rows satisfy the WHERE clause; so it would all be too circular.
The simplest fix is to wrap your entire query in SELECT * FROM ( ... ) t WHERE t.user_in_this_country > 1.
That said, it seems like your query already ensures that user_in_this_country > 1, by virtue of the EXISTS clause, which makes sure that there exists a different CountryUser record that belongs to the same Country and a different User. What am I missing?
With aggregate functions (like COUNT) used in conjuction with OVER clause you have to use a CTE or a subquery, like this
WITH CTE AS (
SELECT u.ContactName
,cu.[User ID]
,c.Name
,c.ID
,cu.[Foreign Table]
,count(*) OVER (PARTITION BY c.ID) AS user_in_this_country
FROM dbo.Country AS c
INNER JOIN dbo.CountryUser AS cu ON c.ID = cu.[Foreign ID]
INNER JOIN dbo.UserColder AS u ON cu.[User ID] = u.ID
WHERE EXISTS (
SELECT *
FROM CountryUser AS cu2
WHERE cu2.[Foreign ID] = cu.[Foreign ID]
AND cu2.[User ID] <> cu.[User ID]
AND cu.[Foreign Table] = 'Country')
)
SELECT *
FROM CTE
WHERE user_in_this_country > 1
Because "users_in_this_country" is not a column, it is an alias which is not valid in the scope of the WHERE clause. I 'm not familiar with "OVER" or PARTITION BY but, my guess is you'd have to do something like this:
WHERE blabla AND (count(*) OVER (PARTITION BY c.ID)) > 1