I have this ms access query:
SELECT t1.sb, suchbegriff2, menge
FROM (SELECT artnr & '/' & [lfdnr-kal] AS sb, left(suchbegriff,7) &
val(right(suchbegriff,4)) AS suchbegriff2
FROM kvks
WHERE suchbegriff like '*/*') AS t1
INNER JOIN (SELECT artnr & '/' & [lfdnr-kal] AS sb,
[artnr-hz] & '/' & val(lfdnr) AS hz, menge
FROM konf
WHERE [artnr-hz]<>'') AS t2
ON (t1.sb=t2.sb) AND (t1.suchbegriff2=t2.hz);
It runs really very slow (over 30 sec.). I figured out, it is because the inner join part. If I leave this, the speed is correct.
Maybe it is because of the fact slow, that the joined fields are calculated expressions?
EDIT:
I modified the query based on the answer of Smandoli:
SELECT kvks.artnr & '/' & kvks.[lfdnr-kal] AS sb,
left(suchbegriff,7) & val(right(suchbegriff,4)) AS suchbegriff2,
konf.menge
FROM kvks, konf
WHERE kvks.suchbegriff like '*/*'
and konf.[artnr-hz]<>''
and kvks.artnr=konf.artnr
and kvks.[lfdnr-kal]=konf.[lfdnr-kal]
and left(suchbegriff,7) & val(right(suchbegriff,4))=[artnr-hz] & '/' & val(lfdnr)
It runs now correct.
Thanks for your contribution.
You do have a complicated mess with those calculated fields. Why not join more directly? This query below leaves one '/' unaccounted for, but should tell you what I'm thinking of.
SELECT
t1.sb,
left(st1.uchbegriff,7) & val(right(t1.suchbegriff,4)) AS suchbegriff2,
t1.menge
FROM kvks AS t1
INNER JOIN konf AS t2
WHERE (t1.suchbegriff like '*/*')
AND (t2.artnr-hz<>'')
AND (t1.artnr=t2.artnr)
AND (t1.lfdnr-kal=t2.lfdnr-kal)
AND (left(t1.suchbegriff,7)=t1.[artnr-hz])
AND (val(right(t1.suchbegriff,4))=val(t2.hz));
For the inner join, you can try to use a saved query (or temp table) instead of writing the query at run time.
So, I would first try to abstract this query
SELECT artnr & '/' & [lfdnr-kal] AS sb,
[artnr-hz] & '/' & val(lfdnr) AS hz, menge
FROM konf
WHERE [artnr-
hz]<>'') AS t2
ON (t1.sb=t2.sb) AND (t1.suchbegriff2=t2.hz)
Second of all, if possible, I would abstract some of the functions in the queries. You could do this with VBA, or manipulating the data outside of the queries.
Third, you could always create a field on your table that combines the two fields together that you need.
E.x: Make a new column in your konf table that stores the value of artnr & '/' & [lfdnr-kal]
What you need to do is limit the functions/calculations/coalescing of fields at run time. That's a lot for a query to do, and if it's running slow I would see a direction correlation either between that, or something incorrect with your indexes/joins.
If you've identified this as a join issue, you can use VBA to spin up a temp table with your queries, and use those as the record source instead of the SQL.
Also, if you don't utilize a temp table, at least save the queries. This allows Access to have a plan for running the queries, whereas your query is 100% run-time dependent.
Your query runs slow because of "Nesting" and then "Joining". You can try creating the temp tables and used that table in query. Creating temp table is good practice rather than making the query complex.
99999!!! I think you have just to implement some index
(t1.sb=t2.sb) AND (t1.suchbegriff2=t2.hz);
these ones are very suspicious. Are the 4 indexed?
Related
I am working with a database in MS Access.
I have a table(Table A) with different categories (criteria)
I have another table (Table B), where I have to pull values from Table A based on two categories, (year and amount).
For example,
From table B, the cost is $15,000, so we go to table A and find the contingency from year 2018 which falls between $0-$20,000 and report a contingency of 25%.
Is there a way to go about this? I've been racking my brain trying to use nested "IIF" and "AND" functions but i can't figure it out
Add both tables to a query.
Join on C_YEAR.
Use BETWEEN AND to grab the appropriate range hence the appropriate contingency.
Something like:
SELECT tableA.CONTINGENCY
FROM tableA INNER JOIN tableB ON tableA.C_YEAR = tableB.C_YEAR
WHERE tableB.COST BETWEEN tableA.MIN_VALUE AND tableA.MAX_VALUE;
It looks like the contingency rates for all the years are the same:
25% for values between $0 and $20,000
15% for values between $20,001 and $200,000
10% for values between $200,000 and $100,000
Is this always the case, or just based on the sample data you're using?
Is it possible you'll have data where the cost is > $100,000,000? If so, how should the data be handled?
I'm just wondering if there's not a better way to represent your contingency rate rules. Otherwise, I'd agree with Rene about joining the tables and adding a WHERE condition to get the rates you need. I'd also add that, since you'll always be pulling the rate from the query, you don't need an actual field on Table B to store the contingency rate.
Consider a DLookUp in an UPDATE query. The domain aggregate is needed for query to be updateable.
UPDATE tableB b
SET b.Contingency = DLookUp("CONTINGENCY", "tableA", "[C_YEAR] = " & b.[C_YEAR] &
" AND [MIN_VALUE] <= " & b.[COST] & " AND [MAX_VALUE] >= " & b.[Cost])
I am stumped on how to make this query run more efficiently/correctly. Here is the query first and then I can describe the tables that are involved:
UPDATE agg_pivot_test AS p
LEFT JOIN jd_cleaning AS c
ON c.Formerly = IIF(c.Formerly LIKE '*or*', '*' & p.LyFinalCode & '*', CStr(p.LyFinalCode))
SET p.CyFinalCode = c.FinalCode
WHERE p.CyFinalCode IS NULL AND c.Formerly IS NOT NULL;
agg_pivot_test has 200 rows of data and only 99 fit the criteria of WHERE p.CyFinalCode IS NULL. The JOIN needs some explaining. It is an IIF because some genius decided to link last year's data to this year's data using Formerly. It is a string because sometimes multiple items have been consolidated down to one so they use "or" (e.g., 632 or 631 or 630). So if I want to match this year's data I have to use Formerly to match last year's LyFinalCode. So this year the code might be 629, but I have to use the Formerly to map the items that were 632, 631, or 630 to the new code. Make sense? That is why the ON has an IIF. Also, Formerly is a string and LyFinalCode is an integer... fun.
Anyway, when you run the query it says it is updating 1807 records when again, there are only 200 records and only 99 that fit the criteria.
Any suggestions about what this is happening or how to fix it?
An interesting problem. I don't think I've ever come across something quite like this before.
I'm guessing what's happening is that rows where CyFinalCode is null, are being matched multiple times by the join statement, and thus the join expression is calculating a cartesian product of row-matches, and this is the basis of the rows updated message. It seems odd, as I would have expected access to complain about multiple row matches, when row matches should only be 1:1 in an update statement.
I would suggest rewriting the query (with this join) as a select statement, and seeing what the query gives you in the way of output; Something like:
SELECT p.*, c.*
FROM agg_pivot_test p LEFT JOIN jd_cleaning c
ON c.Formerly = IIF(c.Formerly LIKE '*or*', '*' & p.LyFinalCode & '*', CStr(p.LyFinalCode))
WHERE p.CyFinalCode IS NULL AND c.Formerly IS NOT NULL
I'm also inclined to suggest changing "... & p.LyFinalCode & ..." to "... & CStr(p.LyFinalCode) & ..." - though I can't really see why it should make a difference.
The only other thing I can think to suggest is change the join a bit: (this isnt guaranteed to be better necessarily - though it might be)
UPDATE agg_pivot_test AS p LEFT JOIN jd_cleaning AS c
ON (c.Formerly = CStr(p.LyFinalCode) OR InStr(c.Formerly, CStr(p.LyFinalCode)) > 0)
(Given the syntax of your statement, I assume this sql is running within access via ODBC; in which case this should be fine. If I'm wrong the sql is running server side, you'll need to change InStr to SubString.)
I use this forum all the time for VBA help but this is the first time I have to post something myself.
I am trying to make a report that provides a summary of various alarms stored in Access. I want to provide a simple Count of each alarm, each day. I have used some SQL queries but not really any Access. I took the fact that Access can do Pivot tables from Access itself. If there is a better way, please let me know.
Set CommandQuery.activeConnection = conn
commandQuery.CommandText = _
"TRANSFORM Count(FixAlarms.[Alm_NativeTimeLast]) AS CountOfAlm_NativeTimeLast " & _
"SELECT FixAlarms.Alm_Tagname, FixAlarms.Alm_Desc " & _
"FROM FixAlarms " & _
"WHERE ((FixAlarms.Alm_Tagname) <> """")) AND FixAlarms.Alm_NativeTimeIn > CellTime " & _
"GROUP BY FixAlarms.[Alm_Tagname], FixAlarms.Alm_Descr " & _
"PIVOT Format([Alm_NativeTimeIn],""Short Date"")"
rec.Open commandQuery
This is the code I am using. I had to retype it, so please forgive any typo. It does most of what I want but it does not give me any indication of what day each column is. I need a header on each column in case there were no alarms one day. I think the answer lies within the IN part of the PIVOT but I can't get it to work without syntax errors. I thought all I had to do was add on
PIVOT Format([Alm_NativeTimeIn],""Short Date"") IN 01/20/15"
Please help if you can.
Thanks.
In order to get the records for all day, even those where there were no activity you need to create these days. The simplest way to do so in access is to use a set of UNION statements to create a fake table for the days similar to this:
SELECT #2015-01-20# as dt FROM dual
UNION ALL
SELECT #2015-01-21# as dt FROM dual
UNION ALL
SELECT #2015-01-22# as dt FROM dual
If you try the above query in Access it will not work, as there is no table called dual. You will have to create it. Check this SO question.
After you created the above query you can LEFT JOIN it with the source table.
TRANSFORM Count(FixAlarms.[Alm_NativeTimeLast]) AS CountOfAlm_NativeTimeLast
SELECT FixAlarms.Alm_Tagname, FixAlarms.Alm_Desc
FROM
(SELECT #2015-01-20# as dt FROM dual
UNION ALL
SELECT #2015-01-21# as dt FROM dual
UNION ALL
SELECT #2015-01-22# as dt FROM dual) as dates LEFT JOIN
FixAlarms ON DateValue(FixAlarms.[Alm_NativeTimeIn]) = dates.dt
WHERE ((FixAlarms.Alm_Tagname) <> """")) AND FixAlarms.Alm_NativeTimeIn > CellTime
GROUP BY FixAlarms.[Alm_Tagname], FixAlarms.Alm_Descr
PIVOT Format(dates.dt, 'Short Date')
EDIT: I must add that this is not the only way of achieving it. Another way is to use a Numbers table. Create a table called Numbers with a single numeric column n and fill it with numbers 0 to 100 (depends on the maximum number of days you wish to include into your query). Then your query for the dates will be:
SELECT DateAdd('d', n, #2015-01-20#) as dt FROM numbers where n < 30;
And the resulting query will be:
TRANSFORM Count(FixAlarms.[Alm_NativeTimeLast]) AS CountOfAlm_NativeTimeLast
SELECT FixAlarms.Alm_Tagname, FixAlarms.Alm_Desc
FROM
(SELECT DateAdd('d', n, #2015-01-20#) as dt FROM numbers where n < 30) as dates LEFT JOIN
FixAlarms ON DateValue(FixAlarms.[Alm_NativeTimeIn]) = dates.dt
WHERE ((FixAlarms.Alm_Tagname) <> """")) AND FixAlarms.Alm_NativeTimeIn > CellTime
GROUP BY FixAlarms.[Alm_Tagname], FixAlarms.Alm_Descr
PIVOT Format(dates.dt, 'Short Date')
When using PIVOT columnName IN (ValueList) ValueList is
In parentheses
In quotes
Comma separated
So you're
PIVOT Format([Alm_NativeTimeIn],""Short Date"") IN 01/20/15"
Needs to become
PIVOT Format([Alm_NativeTimeIn],""Short Date"") IN (""01/20/15"")
With that said, this will not filter your records using PIVOTS in IN statement. You need to use the WHERE clause still.
If the end goal is to represent your data left to right then this will work. It will be a lot of extra work to make this work as a report though because your controls will not be bound to predictable columns. The Column names will change for different parameters.
You could leave this as a traditional query (not pivoted) and have a much easier time reporting it. If you are showing users the grid directly or exporting to Excel then this is not a problem.
So, I just wanted to add a header to my pivot table that would tell me what date the particular column was for.
The part of the code that I did not show was that I was using a rec.getrows to move all of my data into a simpler array variable. While this had all the data from Access, it did not have any headers to inform me what was a tagname, what was a description, and what was which date.
I found that in the recordset itself under fields.item(n) there was a Name attribute. This name told me where the column data came from or the date of the data. Using this and a simple day(date) function, I was able to make my monthly report summarizing all of the alarms.
Thanks for your help guys, but I either was not clear in my description of the problem or it was being over thought.
I am having an issue with a query which returns results that are very far from reality (not only does it not make sense at all but I can also calculate the correct answer using filters).
I am building a KPI db for work and this query returns KPIs by employee by period. I have a very similar query from which this one is derived which returns KPIs by sector by period which gives the exact results I have calculated using a spreadsheet. I really have no idea what happens here. Basically, I want to sum a few measures that are in the maintenances table like temps_requete_min, temps_analyse_min, temps_maj_min and temps_rap_min and then create a subtotal AND present these measures as hours (measures are presented in minutes, thus the divide by 60).
SELECT
[anal].[prenom] & " " & [anal].[nom] AS Analyste,
maint.periode, maint.annee,
Round(Sum(maint.temps_requete_min)/60,2) AS REQ,
Round(Sum(maint.temps_analyse_min)/60,2) AS ANA,
Round(Sum(maint.temps_maj_min)/60,2) AS MAJ,
Round(Sum(maint.temps_rap_min)/60,2) AS RAP,
Round((Sum(maint.temps_requete_min)+Sum(maint.temps_analyse_min)+Sum(maint.temps_maj_min)+Sum(maint.temps_rap_min))/60,2) AS STOTAL,
Count(maint.periode) AS Nombre,
a.description
FROM
rapports AS rap,
analyste AS anal,
maintenances AS maint,
per_annuelle,
annees AS a
WHERE
(((rap.id_anal_maint)=anal.id_analyste) And
((maint.id_fichier)=rap.id_rapport) And
((maint.maint_effectuee)=True) And
((maint.annee)=per_annuelle.annee) And
((per_annuelle.annee)=a.annees))
GROUP BY
[anal].[prenom] & " " & [anal].[nom],
maint.periode,
maint.annee,
a.description,
anal.id_analyste
ORDER BY
maint.annee, maint.periode;
All measures are many orders of magnitude higher than what they should be. I suspect that my Count() is wrong, but I can't see what would be wrong with the sums :|
Edit: Finally I have come up with this query which shows the same measures I have calculated using Excel from the advice given in the comments and the answer provided. Many thanks to everyone. What I would like to know however, is why it makes a difference to use explicit joins rather than implicit joins (WHERE clause on PKs).
SELECT
maintenances.periode,
[analyste].[prenom] & " " & analyste.nom,
Round(Sum(maintenances.temps_requete_min)/60,2) AS REQ,
Round(Sum(maintenances.temps_analyse_min)/60,2) AS ANA,
Round(Sum(maintenances.temps_maj_min)/60,2) AS MAJ,
Round(Sum(maintenances.temps_rap_min)/60,2) AS RAP,
Round((Sum(maintenances.temps_requete_min)+Sum(maintenances.temps_analyse_min)+Sum(maintenances.temps_maj_min)+Sum(maintenances.temps_rap_min))/60,2) AS STOTAL,
Count(maintenances.periode) AS Nombre
FROM
(maintenances INNER JOIN rapports ON maintenances.id_fichier = rapports.id_rapport)
INNER JOIN analyste ON rapports.id_anal_maint = analyste.id_analyste
GROUP BY analyste.prenom, maintenances.periode
In this case, the problem is typically that your joins are bringing together multiple dimensions. You end up doing a cross product across two or more categories.
The fix is to do the summaries independently along each dimension. That means that the "from" clause contains subqueries with group bys, and these are then joined together. The group by would disappear from the outer query.
This would suggest having a subquery such as:
from (select maint.periode, maint.annee,
Round(Sum(maint.temps_requete_min)/60,2) AS REQ,
Round(Sum(maint.temps_analyse_min)/60,2) AS ANA,
Round(Sum(maint.temps_maj_min)/60,2) AS MAJ,
Round(Sum(maint.temps_rap_min)/60,2) AS RAP,
Round((Sum(maint.temps_requete_min)+Sum(maint.temps_analyse_min) +Sum(maint.temps_maj_min)+Sum(maint.temps_rap_min))/60,2) AS STOTAL,
Count(maint.periode) AS Nombre,
from maintenances maint
group by maint.periode, maint.annee
) m
I say "such as" because without a layout of the tables, it is difficult to see exactly where the problem is and what the exact solution is.
Background:
We narrowed down some performance issues in our ERP to one SQL statement. Our support person made one small change to the SQL statment and performance improved. The back-end is Microsoft Access. (yes I know, yes it's embarrassing, no I have no choice in the matter)
We went from 75% of the time the statement would take 90 seconds to run, and 25% 2-3 seconds to 100% of the time 2-3 seconds.
The POHDR table is large 20,000 plus rows, SUPPNAME is under 1000. Both tables have the vnum field indexed.
SQL statements below, but all that changed is the Where clause uses SUPPNAME.vnum instead of POHDR.vnum.
BEFORE:
SELECT DISTINCTROW POHDR.*,
SUPPNAME.SNAME1,
suppname.sname1 & chr(13) & chr(10) & POHDR.vnum as SUPPFLD
FROM POHDR
INNER JOIN SUPPNAME
ON POHDR.VNUM = SUPPNAME.VNUM
WHERE ((POHDR.VNUM= '20023' AND POHDR.RECDATE Is Null))
AND [POHDR].[CANCEL] Is Null and ((POHDR.CLOSED=No))
order by IIf(InStr(PO,'-'),Left(PO,InStr(PO,'-')) & '_' & Mid(PO,InStr(PO,'-')),PO)
AFTER:
SELECT DISTINCTROW POHDR.*,
SUPPNAME.SNAME1,
suppname.sname1 & chr(13) & chr(10) & POHDR.vnum as SUPPFLD
FROM POHDR
INNER JOIN SUPPNAME
ON POHDR.VNUM = SUPPNAME.VNUM
WHERE ((SUPPNAME.VNUM= '26037' AND POHDR.RECDATE Is Null))
AND [POHDR].[CANCEL] Is Null
and ((POHDR.CLOSED=No))
order by IIf(InStr(PO,'-'),Left(PO,InStr(PO,'-')) & '_' & Mid(PO,InStr(PO,'-')),PO)
Does changing where the vnum is selected to a smaller table with less or no duplication of the vnum really make that big of a difference, or is there something else going on?
thanks, Brian the Curious.
p.s. Also, I did not write or have control of this sql statment. And not sure exactly what is going on with the if in the order by clause either.
I don't know if there is a very good "WHY" answer to your question, other than the fact that changing that WHERE clause caused a different query plan to be chosen by the optimizer.
The optimizer can choose a query plan based on many factors (indexes, statistics, black magic, etc). As long as you've got good indexes, the best you can usually do is carefully test different similar versions of the query, and compare the query plans they generate.
The key in this kind of query optimization is indexing - so first thing to check if all fields are indexed, in particular SUPPNAME.VNUM and POHDR.VNUM.
Then the second, of course, is the number of records in each table that satisfy each of these conditions. If there is only one SUPPNAME.VNUM= '26037' it could eliminate a lot of POHDR.VNUM= '20023' which would fail on other criteria, which then does not need to be executed.
But without seeing the structure of your DB and data in it, I would not make any definite conclusions.