SQL statement HAVING MAX(some+thing)=some+thing - sql

I'm having trouble with Microsoft Access 2003, it's complaining about this statement:
select cardnr
from change
where year(date)<2009
group by cardnr
having max(time+date) = (time+date) and cardto='VIP'
What I want to do is, for every distinct cardnr in the table change, to find the row with the latest (time+date) that is before year 2009, and then just select the rows with cardto='VIP'.
This validator says it's OK, Access says it's not OK.
This is the message I get: "you tried to execute a query that does not include the specified expression 'max(time+date)=time+date and cardto='VIP' and cardnr=' as part of an aggregate function."
Could someone please explain what I'm doing wrong and the right way to do it? Thanks
Note: The field and table names are translated and do not collide with any reserved words, I have no trouble with the names.

Try to think of it like this - HAVING is applied after the aggregation is done.
Therefore it can not compare to unaggregated expressions (neither for time+date, nor for cardto).
However, to get the last (principle is the same for getting rows related to other aggregated functions as weel) time and date you can do something like:
SELECT cardnr
FROM change main
WHERE time+date IN (SELECT MAX(time+date)
FROM change sub
WHERE sub.cardnr = main.cardnr AND
year(date)<2009
AND cardto='VIP')
(assuming that date part on your time field is the same for all the records; having two fields for date/time is not in your best interest and also using reserved words for field names can backfire in certain cases)
It works because the subquery is filtered only on the records that you are interested in from the outer query.
Applying the same year(date)<200 and cardto='VIP' to the outer query can improve performance further.

Related

Access SQL GROUP BY problem (eg. tbl_Produktion.ID not part of the aggregation-function)

I want to group by two columns, however MS Access won't let me do it.
Here is the code I wrote:
SELECT
tbl_Produktion.Datum, tbl_Produktion.Schichtleiter,
tbl_Produktion.ProduktionsID, tbl_Produktion.Linie,
tbl_Produktion.Schicht, tbl_Produktion.Anzahl_Schichten_P,
tbl_Produktion.Schichtteam, tbl_Produktion.Von, tbl_Produktion.Bis,
tbl_Produktion.Pause, tbl_Produktion.Kunde, tbl_Produktion.TeileNr,
tbl_Produktion.FormNr, tbl_Produktion.LabyNr,
SUM(tbl_Produktion.Stueckzahl_Prod),
tbl_Produktion.Stueckzahl_Ausschuss, tbl_Produktion.Ausschussgrund,
tbl_Produktion.Kommentar, tbl_Produktion.StvSchichtleiter,
tbl_Produktion.Von2, tbl_Produktion.Bis2, tbl_Produktion.Pause2,
tbl_Produktion.Arbeiter3, tbl_Produktion.Von3, tbl_Produktion.Bis3,
tbl_Produktion.Pause3, tbl_Produktion.Arbeiter4,
tbl_Produktion.Von4, tbl_Produktion.Bis4, tbl_Produktion.Pause4,
tbl_Produktion.Leiharbeiter5, tbl_Produktion.Von5,
tbl_Produktion.Bis5, tbl_Produktion.Pause5,
tbl_Produktion.Leiharbeiter6, tbl_Produktion.Von6,
tbl_Produktion.Bis6, tbl_Produktion.Pause6, tbl_Produktion.Muster
FROM
tbl_Personal
INNER JOIN
tbl_Produktion ON tbl_Personal.PersID = tbl_Produktion.Schichtleiter
GROUP BY
tbl_Produktion.Datum, tbl_Produktion.Schichtleiter;
It works when I group it by all the columns, but not like this.
The error message say that the rest of the columns aren't part of the aggregation-function (translated from german to english as best as I could).
PS.: I also need the sum of "tbl_Produktion.Stueckzahl_Prod" therefore I tried using the SUM function (couldn't try it yet).
Have you tried something along these lines?
SELECT
tbl_Produktion.Datum, tbl_Produktion.Schichtleiter,
MAX(tbl_Produktion.ProduktionsID), MAX(tbl_Produktion.Linie),
MAX(tbl_Produktion.Schicht), MAX(tbl_Produktion.Anzahl_Schichten_P),
MAX(tbl_Produktion.Schichtteam), MAX(tbl_Produktion.Von), MAX(tbl_Produktion.Bis),
SUM(tbl_Produktion.Stueckzahl_Prod)
FROM
tbl_Personal
INNER JOIN
tbl_Produktion ON tbl_Personal.PersID = tbl_Produktion.Schichtleiter
GROUP BY
tbl_Produktion.Datum, tbl_Produktion.Schichtleiter;
I have used the MAX function for all the data except the two items you specify in the GROUP BY and the one where you desire the SUM. I took the liberty of leaving out mush of your data just to get started.
Using the MAX function turns out to be a convenient workaround when the data item is known to be unique within each group. We cannot know your data or your itent, so we cannot tell you whether MAX will yield the results you need.
If you use an aggregation function in the select clause, you must group by every column that you're selecting that's not an aggregation. If you don't want to do that for some reason (perhaps it changes the output of the aggregation in way that you don't intend) you either must think of an aggregate to use (pick a value. Average? Max? Min?) or just do two selects, one for the aggregate, and one for the non-aggregates. But, then, you have to decide how to get the non-aggregated fields that make sense for the aggregate (or show them all in a table, I suppose?)

MS Access SQL receiving no results when joining tables in where clause

i am trying to update a colleague's MS Access application (with vb-code). I am rather experienced in writing SQL queries but i am not able to solve the following problem.
The query i am looking to fix uses a pass-through-query's result and a local MS Access table and joins them togehter in the where-clause (i tried using the normal way with ON but it seems this doesn't work when there is a pass through query involved). I have little experience with joining tables in the where-clause but is there such a thing that i cant use certain columns (of both tables) in the where-clause when joining tables in the where-clause? -> When i use a filter criteria such as columnA <> 'somerandomtext' (which is always satisfied, just to point out the problem) the query result is empty. When i delete the latter criteria in the where clause, the query returns results (although too many because i cant filter them accordingly).
Furthermore: I checked the pass-through-query, the results are correct. I checked the MS-Access table, the data in the table is correct. Therefore, i think i might be doing something wrong in the query where i join the two mentioned above.
THIS QUERY WORKS AS INTENDED AND RETURNS RESULTS:
SELECT t.tr_id, t.ser_num, t.contrgnt_id, t.pos_ekey, t.sernum AS cmdty, format(t.vol,""##,###,###.00"") AS volume, t.unit_def, t.value_date,
format(t.coup,""##,###,###.00"") AS fixprice,
format(s.calcvarprice,""##,###,###.00"") AS marketprice,
format(s.calcamount,""##,###,###.00"") AS payamount,
format(s.settlevarprice, ""##,###,##0.00"") as settleprice,
format(s.settleamount, ""##,###,##0.00"") as settleamount, s.sync
FROM pms_trans AS t, settledata AS s
WHERE t.tr_id=s.tr_id And t.is_booked='N' And t.value_date>='01.01.2021' And t.value_date<='01.04.2021'
THIS QUERY SOMEHOW RETURNS 0 RESULTS:
SELECT t.tr_id, t.ser_num, t.contrgnt_id, t.pos_ekey, t.sernum AS cmdty, format(t.vol,""##,###,###.00"") AS volume, t.unit_def, t.value_date,
format(t.coup,""##,###,###.00"") AS fixprice,
format(s.calcvarprice,""##,###,###.00"") AS marketprice,
format(s.calcamount,""##,###,###.00"") AS payamount,
format(s.settlevarprice, ""##,###,##0.00"") as settleprice,
format(s.settleamount, ""##,###,##0.00"") as settleamount, s.sync
FROM pms_trans AS t, settledata AS s
WHERE t.tr_id=s.tr_id And t.is_booked='N' And t.pos_ekey <> 'BGGS' And t.value_date>='01.01.2021' And t.value_date<='01.04.2021'
As mentioned before, i suspect that there are some limitations when joining via where-clause (although i didnt find sufficient information online).
Best Regards and thank you in advance,
Peter
First, apply the correct syntax for the date expressions:
WHERE t.tr_id=s.tr_id And t.is_booked='N' And t.pos_ekey <> 'BGGS' And t.value_date >= #2021-01-01# And t.value_date <= #2021-04-01#
Next, double-check the values for pos_ekey. For example, try to apply the filter, <> 'BGGS', directly on the field in table view.

SQLite alias (AS) not working in the same query

I'm stuck in an (apparently) extremely trivial task that I can't make work , and I really feel no chance than to ask for advice.
I used to deal with PHP/MySQL more than 10 years ago and I might be quite rusty now that I'm dealing with an SQLite DB using Qt5.
Basically I'm selecting some records while wanting to make some math operations on the fetched columns. I recall (and re-read some documentation and examples) that the keyword "AS" is going to conveniently rename (alias) a value.
So for example I have this query, where "X" is an integer number that I render into this big Qt string before executing it with a QSqlQuery. This query lets me select all the electronic components used in a Project and calculate how many of them to order (rounding to the nearest multiple of 5) and the total price per component.
SELECT Inventory.id, UsedItems.pid, UsedItems.RefDes, Inventory.name, Inventory.category,
Inventory.type, Inventory.package, Inventory.value, Inventory.manufacturer,
Inventory.price, UsedItems.qty_used as used_qty,
UsedItems.qty_used*X AS To_Order,
ROUND((UsedItems.qty_used*X/5)+0.5)*5*CAST((X > 0) AS INT) AS Nearest5,
Inventory.price*Nearest5 AS TotPrice
FROM Inventory
LEFT JOIN UsedItems ON Inventory.id=UsedItems.cid
WHERE UsedItems.pid='1'
ORDER BY RefDes, value ASC
So, for example, I aliased UsedItems.qty_used as used_qty. At first I tried to use it in the next field, multiplying it by X, writing "used_qty*X AS To_Order" ... Query failed. Well, no worries, I had just put the original tab.field name and it worked.
Going further, I have a complex calculation and I want to use its result on the next field, but the same issue popped out: if I alias "ROUND(...)" AS Nearest5, and then try to use this value by multiplying it in the next field, the query will fail.
Please note: the query WORKS, but ONLY if I don't use aliases in the following fields, namely if I don't use the alias Nearest5 in the TotPrice field. I just want to avoid re-writing the whole ROUND(...) thing for the TotPrice field.
What am I missing/doing wrong? Either SQLite does not support aliases on the same query or I am using a wrong syntax and I am just too stuck/confused to see the mistake (which I'm sure it has to be really stupid).
Column aliases defined in a SELECT cannot be used:
For other expressions in the same SELECT.
For filtering in the WHERE.
For conditions in the FROM clause.
Many databases also restrict their use in GROUP BY and HAVING.
All databases support them in ORDER BY.
This is how SQL works. The issue is two things:
The logic order of processing clauses in the query (i.e. how they are compiled). This affects the scoping of parameters.
The order of processing expressions in the SELECT. This is indeterminate. There is no requirement for the ordering of parameters.
For a simple example, what should x refer to in this example?
select x as a, y as x
from t
where x = 2;
By not allowing duplicates, SQL engines do not have to make a choice. The value is always t.x.
You can try with nested queries.
A SELECT query can be nested in another SELECT query within the FROM clause;
multiple queries can be nested, for example by following the following pattern:
SELECT *,[your last Expression] AS LastExp From (SELECT *,[your Middle Expression] AS MidExp FROM (SELECT *,[your first Expression] AS FirstExp FROM yourTables));
Obviously, respecting the order that the expressions of the innermost select query can be used by subsequent select queries:
the first expressions can be used by all other queries, but the other intermediate expressions can only be used by queries that are further upstream.
For your case, your query may be:
SELECT *, PRC*Nearest5 AS TotPrice FROM (SELECT *, ROUND((UsedItems.qty_used*X/5)+0.5)*5*CAST((X > 0) AS INT) AS Nearest5 FROM (SELECT Inventory.id, UsedItems.pid, UsedItems.RefDes, Inventory.name, Inventory.category, Inventory.type, Inventory.package, Inventory.value, Inventory.manufacturer, Inventory.price AS PRC, UsedItems.qty_used*X AS To_Order FROM Inventory LEFT JOIN UsedItems ON Inventory.id=UsedItems.cid WHERE UsedItems.pid='1' ORDER BY RefDes, value ASC))

SQL trouble with JOIN on INSTR()

I've read lots of examples of JOIN syntax, but none of them work for my problem.
I have a table of quotes and a table of sales enquiries. One quote may result in multiple enquiries, or it may not result in any enquiries. I want to create a list of all quotes and include details of any resulting enquiry. I'm expecting multiple result rows for a quote that resulted in many enquiries. I'm expecting a single row with empty or null enquiries fields, for those quotes that didn't give rise to any enquiries.
The connecting data is in the quotes table, where there's a field called 'activity' that contains the id of any or all enquiries that resulted. It's updated each tim e anew enquiry comes in.
So I tried this:
SELECT q.*, e.id, e.price
FROM quotes as q
LEFT JOIN enquiries as e
ON INSTR(q.activity, e.id) >'0'
WHERE q.date > '2013-07-01'
But every row in my results includes enquiries data. I can't seem to get it to include the quotes that never resulted in anything. I thought LEFT JOIN was supposed to give me all of the quotes regardless of enquiries, and include the enquiries data where there was a match. But every example I've seen is just joining on a.id = b.id, so I suspect that my INSTR() match criteria might be messing things up.
As previous commentators have suggested the issue will be down to the join with Instr. The return value from INSTR of many RDBMSs is an integer value. When you therefore test the value of INSTR against '0' you won't get a match. Also, if Instr doesn't find a match you may get something else returned like MS Access where Null is a possible return value. This is obviously all speculation and we really need to see an example of your data and the issue to confirm if this is the actual problem. In the absence of any more info this is the best you are going to get:
Without knowing which DB you are using I've included a few links for INSTR:
MySql,
Oracle,
MS Access (returns variant Long),
SQL Server - No Instr Function - CharIndex
I think your problem might be somewhere else, because this seems to work fine for me. I assumed the list of enquiries was just a comma separated string. See http://sqlfiddle.com/#!4/71ce1/1
Get rid of the single quotes around the 0, but that doesn't make any difference. Also, you shouldn't be relying on the default date format, but using TO_DATE.You don't say what DBMS you're using, but I tried both Oracle and MySQL.

SQL MIN() returns multiple values?

I am using SQL server 2005, querying with Web Developer 2010, and the min function appears to be returning more than one value (for each ID returned, see below). Ideally I would like it to just return the one for each ID.
SELECT Production.WorksOrderOperations.WorksOrderNumber,
MIN(Production.WorksOrderOperations.OperationNumber) AS Expr1,
Production.Resources.ResourceCode,
Production.Resources.ResourceDescription,
Production.WorksOrderExcel_ExcelExport_View.PartNumber,
Production.WorksOrderOperations.PlannedQuantity,
Production.WorksOrderOperations.PlannedSetTime,
Production.WorksOrderOperations.PlannedRunTime
FROM Production.WorksOrderOperations
INNER JOIN Production.Resources
ON Production.WorksOrderOperations.ResourceID = Production.Resources.ResourceID
INNER JOIN Production.WorksOrderExcel_ExcelExport_View
ON Production.WorksOrderOperations.WorksOrderNumber = Production.WorksOrderExcel_ExcelExport_View.WorksOrderNumber
WHERE Production.WorksOrderOperations.WorksOrderNumber IN
( SELECT WorksOrderNumber
FROM Production.WorksOrderExcel_ExcelExport_View AS WorksOrderExcel_ExcelExport_View_1
WHERE (WorksOrderSuffixStatus = 'Proposed'))
AND Production.Resources.ResourceCode IN ('1303', '1604')
GROUP BY Production.WorksOrderOperations.WorksOrderNumber,
Production.Resources.ResourceCode,
Production.Resources.ResourceDescription,
Production.WorksOrderExcel_ExcelExport_View.PartNumber,
Production.WorksOrderOperations.PlannedQuantity,
Production.WorksOrderOperations.PlannedSetTime,
Production.WorksOrderOperations.PlannedRunTime
If you can get your head around it, I am selecting certain columns from multiple tables where the WorksOrderNumber is also contained within a subquery, and numerous other conditions.
Result set looks a little like this, have blurred out irrelevant data.
http://i.stack.imgur.com/5UFIp.png (Wouldn't let me embed image).
The highlighted rows are NOT supposed to be there, I cannot explicitly filter them out, as this result set will be updated daily and it is likely to happen with a different record.
I have tried casting and converting the OperationNumber to numerous other data types, varchar type returns '100' instead of the '30'. Also tried searching search engines, no one seems to have the same problem.
I did not structure the tables (they're horribly normalised), and it is not possible to restructure them.
Any ideas appreciated, many thanks.
The MIN function returns the minimum within the group.
If you want the minimum for each ID you need to get group on just ID.
I assume that by "ID" you are referring to Production.WorksOrderOperations.WorksOrderNumber.
You can add this as a "table" in your SQL:
(SELECT Production.WorksOrderOperations.WorksOrderNumber,
MIN(Production.WorksOrderOperations.OperationNumber)
FROM Production.WorksOrderOperations
GROUP BY Production.WorksOrderOperations.WorksOrderNumber)