HAVING statement causes Invalid SQL statement - sql-server

New here and beginner with SQL. I'm designing an accounting program in Excel, using SQLs in VBA to pull data from an Access database.
The point of the SQL below is to list the invoices that have NOT been (entirely) paid yet.
The fields are as follows:
reference: the reference number of the invoice (when i get an invoice, I book that I have to pay it. When I pay it a few days later (different db record), it has the same reference number)
amount_credit: the amount that needs to be paid
amount_debit: the amount that has been paid (so far - it's possible it's paid in multiple fractions and so far only partially paid)
account_debit and account_credit must have value 52 (which is "Accounts payable")
When for a specific reference the total amount_credit = the total amount_debit, the invoice with that reference has been fully paid, and it should not be listed.
My sql is as follows:
WITH p AS
(
SELECT reference,
SUM(IIF(account_debit = 52, amount_debit, 0)) as paidamount,
SUM(IIF(account_credit = 52, amount_credit, 0)) as totalamount
FROM Transactions t
WHERE account_debit = 52 OR account_credit = 52
GROUP BY reference
)
SELECT p.reference, p.paidamount, p.totalamount FROM p
HAVING p.paidamount <> p.totalamount
ORDER BY p.reference;
Everything works fine if I exclude the HAVING statement. Of course then it displays all the transactions (grouped by reference), including those that have been paid.
However the HAVING statement triggers Error code "Invalid SQL statement;expected "Delete, Insert, Procedure, Select, or...).
Can you tell what is causing this and how I can make this work? Thank you in advance.
Rudy
Edit: current coding is as follows and is very close but still shows some results where amount_credit=amount_debit
SELECT reference, SUM(IIF(account_debit = 52, amount_debit, 0)) as paidamount, SUM(IIF(account_credit = 52, amount_credit, 0)) as totalamount FROM Transactions t WHERE t.account_debit = 52 OR t.account_credit = 52 GROUP BY t.reference HAVING SUM(IIF(account_debit = 52, amount_debit, 0)) <> SUM(IIF(account_credit = 52, amount_credit, 0))

Related

Possible to do a SUM IF equivalent in TSQL where the condition is not constant?

I have data available for licenses used and max licenses available every 10 minutes, I'd like to aggregate by day, and need to know the count of license maxouts (used licenses = total licenses) for each day. I've seen plenty of conditionals where a column is equal to a constant, but can't find anything for a condition where one column is equal to another column. I can't hard code the maximum licenses because this needs to work for thousands of different software packages, and theoretically the same package could have a different number of max licenses in a given time period. Please help!
Here's what I have so far, I know the 3rd line in the SELECT statement is wrong but don't know what to use instead.
DECLARE #unixTimeStamp INT = 1495571176;
DECLARE #featValue INT = 877;
DECLARE #unixMark INT = #unixTimeStamp - 2629746;
SELECT
FORMAT(DATEADD(S, StampedUnixTime, '1970-01-01'), 'MM/dd') AS Date
COUNT(*) FILTER WHERE UsedLicenses = TotalLicenses AS Maxouts,
MAX(FH.UsedLicenses) AS UsedLicenses,
MAX(FH.TotalLicenses) AS TotalLicenses
FROM Feature_History as FH
WHERE ([FeatureID] = #featValue) AND (FH.StampedUnixTime BETWEEN #unixMark AND #unixTimeStamp)
GROUP BY FORMAT(DATEADD(S, StampedUnixTime, '1970-01-01'), 'MM/dd')
ORDER BY Date
I think all you need is a conditional aggregation.
DECLARE #unixTimeStamp INT = 1495571176;
DECLARE #featValue INT = 877;
DECLARE #unixMark INT = #unixTimeStamp - 2629746;
SELECT
FORMAT(DATEADD(S, StampedUnixTime, '1970-01-01'), 'MM/dd') AS Date,
sum(case when UsedLicenses = TotalLicenses then 1 else 0 end) as MaxOuts,
MAX(FH.UsedLicenses) AS UsedLicenses,
MAX(FH.TotalLicenses) AS TotalLicenses
FROM Feature_History as FH
WHERE ([FeatureID] = #featValue) AND (FH.StampedUnixTime BETWEEN #unixMark AND #unixTimeStamp)
GROUP BY FORMAT(DATEADD(S, StampedUnixTime, '1970-01-01'), 'MM/dd')
ORDER BY Date

Microsoft SQL Server: wrong query execution plan taking too long

This is on Windows SQL Server Cluster.
Query is coming from 3rd party application so I can not modify the query permanently.
Query is:
DECLARE #FromBrCode INT = 1001
DECLARE #ToBrCode INT = 1637
DECLARE #Cdate DATE = '31-mar-2017'
SELECT
a.PrdCd, a.Name, SUM(b.Balance4) as Balance
FROM
D009021 a, D010014 b
WHERE
a.PrdCd = LTRIM(RTRIM(SUBSTRING(b.PrdAcctId, 1, 8)))
AND substring(b.PrdAcctId, 9, 24) = '000000000000000000000000'
AND a.LBrCode = b.LBrCode
AND a.LBrCode BETWEEN #FromBrCode AND #ToBrCode
AND b.CblDate = (SELECT MAX(c.CblDate)
FROM D010014 c
WHERE c.PrdAcctId = b.PrdAcctId
AND c.LBrCode = b.LBrCode
AND c.CblDate <= #Cdate)
GROUP BY
a.PrdCd, a.Name
HAVING
SUM(b.Balance4) <> 0
ORDER BY
a.PrdCd
This particular query is taking too much time to complete execution. The same problem happens on a different SQL Server.
No table lock was found, processor and memory usage is normal while the query is running.
Normal "select top 1000" working and showing output instantly in both tables (D009021, D010014)
Reindex and rebuild / update stats done in both tables but problem did not resolve (D009021, D010014)
The same query is working if we reduce number of branch but slowly
(
DECLARE #FromBrCode INT =1001
DECLARE #ToBrCode INT =1001
)
The same query is working faster giving output within 2 mins if we replace any one variable and use the value directly
AND a.LBrCode BETWEEN #FromBrCode AND #ToBrCode
changed to
AND a.LBrCode BETWEEN 1001 AND #ToBrCode
The same query is working faster and giving output within 2 mins if we add "OPTION (RECOMPILE)" at end
I tried to clean cache query execution plan and optimized new one but problem still exists
Found that the query estimate plan and actual execution plan are different (see screenshots)
Table D010014 is aliased twice once as b and once as c
the they are joined to the same table.
Try toto remove the sub query below and create a temp table to store
the values you need. I added * to the fields you self join
SELECT MAX(c.CblDate)
FROM D010014 c
WHERE c.PrdAcctId = b.PrdAcctId
AND c.LBrCode = b.LBrCode
AND c.CblDate <= #Cdate
if you cant do that then try
SELECT TOP 1 c.CblDate
FROM D010014 c
WHERE c.PrdAcctId = b.PrdAcctId
AND c.LBrCode = b.LBrCode
AND c.CblDate <= #Cdate
ORDER BY c.CblDate DESC

SQL Server, Having Clause, Where, Aggregate Functions

In my problem which I am trying to solve, there is a performance values table:
Staff PerformanceID Date Percentage
--------------------------------------------------
StaffName1 1 2/15/2016 95
StaffName1 2 2/15/2016 95
StaffName1 1 2/22/2016 100
...
StaffName2 1 2/15/2016 100
StaffName2 2 2/15/2016 100
StaffName2 1 2/22/2016 100
And the SQL statement as follows:
SELECT TOP (10)
tbl_Staff.StaffName,
ROUND(AVG(tbl_StaffPerformancesValues.Percentage), 0) AS AverageRating
FROM
tbl_Staff
INNER JOIN
tbl_AcademicTermsStaff ON tbl_Staff.StaffID = tbl_AcademicTermsStaff.StaffID
INNER JOIN
tbl_StaffPerformancesValues ON tbl_AcademicTermsStaff.StaffID = tbl_StaffPerformancesValues.StaffID
WHERE
(tbl_StaffPerformancesValues.Date >= #DateFrom)
AND (tbl_AcademicTermsStaff.SchoolCode = #SchoolCode)
AND (tbl_AcademicTermsStaff.AcademicTermID = #AcademicTermID)
GROUP BY
tbl_Staff.StaffName
ORDER BY
AverageRating DESC, tbl_Staff.StaffName
What I am trying to do is, from a given date, for instance 02-22-2016,
I want to calculate average performance for each staff member.
The code above gives me average without considering the date filter.
Thank you.
Apart from your join conditions and table names which looks quite complex, One simple question, If you want the results for a particular date then why is the need of having
WHERE tbl_StaffPerformancesValues.Date >= #DateFrom
As you said your query is displaying average results but not for a date instance. Change the above line to WHERE tbl_StaffPerformancesValues.Date = #DateFrom.
Correct me if I am wrong.
Thanks for the replies, the code above, as you all say and as it is also expected is correct.
I intended to have a date filter to see the results from the given date until now.
The code
WHERE tbl_StaffPerformancesValues.Date >= #DateFrom
is correct.
The mistake i found from my coding is, in another block i had the following:
Protected Sub TextBoxDateFrom_Text(sender As Object, e As System.EventArgs) Handles TextBoxDate.PreRender, TextBoxDate.TextChanged
Try
Dim strDate As String = Date.Parse(DatesOfWeekISO8601(2016, WeekOfYearISO8601(Date.Today))).AddDays(-7).ToString("dd/MM/yyyy")
If Not IsPostBack Then
TextBoxDate.Text = strDate
End If
SqlDataSourcePerformances.SelectParameters("DateFrom").DefaultValue = Date.Parse(TextBoxDate.Text, CultureInfo.CreateSpecificCulture("id-ID")).AddDays(-7)
GridViewPerformances.DataBind()
Catch ex As Exception
End Try
End Sub
I, unintentionally, applied .AddDays(-7) twice.
I just noticed it and removed the second .AddDays(-7) from my code.
SqlDataSourcePerformances.SelectParameters("DateFrom").DefaultValue = Date.Parse(TextBoxDate.Text, CultureInfo.CreateSpecificCulture("id-ID"))
Because of that mistake, the SQL code was getting the performance values 14 days before until now. So the average was wrong.
Thanks again.

Yet another subquery issue

Hello from an absolute beginner in SQL!
I have a field I want to populate based on another table. For this I have written this query, which fails with: Msg 512, Level 16, State 1, Line 1
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
The statement has been terminated.
oK, here goes:
Update kre.CustomerOrderLineCopy
SET DepNo = (SELECT customerordercopy.DepNo
FROM kre.CustomerOrderCopy , kre.CustomerOrderLineCopy
WHERE CustomerOrderLineCopy.OrderCopyNo =kre.CustomerOrderCopy.OrderCopyNo)
WHERE CustomerOrderLineCopy.OrderCopyNo = (SELECT CustomerOrderCopy.OrderCopyNo
FROM kre.CustomerOrderCopy, kre.CustomerOrderLineCopy
WHERE kre.CustomerOrderLineCopy.OrderCopyNo = kre.CustomerOrderCopy.OrderCopyNo)
What I'm trying to do is to change DepNo in CustomerOrderLineCopy, with the value in DepNo in CustomerOrderCopy - based on the same OrderCopyNo in both tables.
I'm open for all suggestion.
Thanks,
ohalvors
If you just join the tables together the update is easier:
UPDATE A SET A.DepNo = B.DepNo
FROM kre.CustomerOrderLineCopy A
INNER JOIN kre.CustomerOrderCopy B ON A.OrderCopyNo = B.OrderCopyNo
The problem is that at least one of your sub queries return more than one value. Think about this:
tablePerson(name, age)
Adam, 11
Eva, 11
Sven 22
update tablePerson
set name = (select name from tablePerson where age = 11)
where name = 'Sven'
Which is equivalent to: set Sven's name to Adam and Eva. Which is not possible.
If you want to use sub queries, either make sure your sub queries can only return one value or force one value by using:
select top 1 xxx from ...
This may be enough to quieten it down:
Update kre.CustomerOrderLineCopy
SET DepNo = (SELECT customerordercopy.DepNo
FROM kre.CustomerOrderCopy --, kre.CustomerOrderLineCopy
WHERE CustomerOrderLineCopy.OrderCopyNo =kre.CustomerOrderCopy.OrderCopyNo)
WHERE CustomerOrderLineCopy.OrderCopyNo = (SELECT CustomerOrderCopy.OrderCopyNo
FROM kre.CustomerOrderCopy --, kre.CustomerOrderLineCopy
WHERE kre.CustomerOrderLineCopy.OrderCopyNo = kre.CustomerOrderCopy.OrderCopyNo)
(Where I've commented out kre.CustomerOrderLineCopy in the subqueries) That is, you were hopefully trying to correlate these subqueries with the outer table - not introduce another instance of kre.CustomerOrderLineCopy.
If you still get an error, then you still have multiple rows in kre.CustomerOrderCopy which have the same OrderCopyNo. If that's so, you need to give us (and SQL Server) the rules that you want to apply for how to select which row you want to use.
The danger of switching to the FROM ... JOIN form shown in #Avitus's answer is that it will no longer report if there are multiple matching rows - it will just silently pick one of them - which one is never made clear.
Now I look at the query again, I'm not sure it even needs a WHERE clause now. I think this is the same:
Update kre.CustomerOrderLineCopy
SET DepNo = (
SELECT customerordercopy.DepNo
FROM kre.CustomerOrderCopy
WHERE CustomerOrderLineCopy.OrderCopyNo = kre.CustomerOrderCopy.OrderCopyNo)

Linq - how get the minimum, if value = 0, get the next value

I have a test database which logs data from when a store logs onto a store portal and how long it stays logged on.
Example:
(just for visualizing purposes - not actual database)
Stores
Id Description Address City
1 Candy shop 43 Oxford Str. London
2 Icecream shop 45 Side Lane Huddersfield
Connections
Id Store_Ref Start End
1 2 2011-02-11 09:12:34.123 2011-02-11 09:12:34.123
2 2 2011-02-11 09:12:36.123 2011-02-11 09:14:58.125
3 1 2011-02-14 08:42:10.855 2011-02-14 08:42:10.855
4 1 2011-02-14 08:42:12.345 2011-02-14 08:50:45.987
5 1 2011-02-15 08:35:19.345 2011-02-15 08:38:20.123
6 2 2011-02-19 09:08:55.555 2011-02-19 09:12:46.789
I need to get various data from the database. I've already gotten the max and average connection duration. (So probably very self-evident that..) I also need to have some information about which connection lasted the least. I ofcourse immediately thought of the Min() function of Linq, but as you can see, the database also includes connections that started and ended instantly. Therefore, that data isn't actually "valid" for data analysis.
So my question is how to get the minimum value, but if the value = 0, get the next value that is the lowest.
My linq query so far (which implements the Min() function):
var min = from connections in Connections
join stores in Stores
on connections.Store_Ref equals stores.Id
group connections
by stores.Description into groupedStores
select new
{
Store_Description = groupedStores.Key,
Connection_Duration = groupedStores.Min(connections =>
(SqlMethods.DateDiffSecond(connections.Start, connections.End)))
};
I know that it's possible to get the valid values through multiple queries and/or statements though, but I was wondering if it's possible to do it all in just one query, since my program expects linq queries to be returned and my preference goes to keeping the program as "light" as possible.
If you have to great/simple method to do so, please share it. Your contribution is very appreciated! :)
What if you add, before the select new, a let clause for the duration of the conection with something like:
let duration = SqlMethods.DateDiffSecond(connections.Start, connections.End)
And then add a where clause
where duration != 0
var min = from connections in Connections.Where(connections => (SqlMethods.DateDiffSecond(connections.Start, connections.End) > 0)
join stores in Stores
on connections.Store_Ref equals stores.Id
group connections
by stores.Description into groupedStores
select new
{
Store_Description = groupedStores.Key,
Connection_Duration = groupedStores.Min(connections =>
(SqlMethods.DateDiffSecond(connections.Start, connections.End)))
};
Try this, With filtering the "0" values you will get the right result, at least that is my taught.
Include a where clause before calculating the Min value.
groupedStores.Where(conn => SqlMethods.DateDiffSecond(conn.Start, conn.End) > 0)
.Min(conn => (SqlMethods.DateDiffSecond(conn.Start, conn.End))

Resources