MS Access SQL query extremely slow after converting to local table - sql

I have the following SQL query
SELECT
pmma_vt_feldmarkposition.vtvt_recn AS [li_verträge_recn],
pmma_vt_feldmarkposition.feldmark AS [vt_feldmark_nr_pflanze],
switch (gbs is null, gbs_opti, gbs is not null, gbs) AS [ta_istbeitragssatz_hagel],
NZ(pmma_vt_feldmarkposition.ebs_sturm, 0) + NZ(pmma_vt_feldmarkposition.ebs_frost, 0) + NZ(pmma_vt_feldmarkposition.ebs_wolkenbruch, 0) + NZ(pmma_vt_feldmarkposition.ebs_hochwasser, 0) + NZ(pmma_vt_feldmarkposition.ebs_trockenheit, 0) + NZ(pmma_vt_feldmarkposition.ebs_pauschal, 0) AS [ta_istbeitragssatz_elementar],
switch (gbs is null, gbs_opti, gbs is not null, soll_gbs) AS [ta_sollbeitragssatz_hagel]
INTO
vrt_feldmarkpositionen
FROM
PMMA_VT_FELDMARKPOSITION
WHERE
pmma_vt_feldmarkposition.lfd_nr * 1000000000 + pmma_vt_feldmarkposition.vtvt_recn
IN (
SELECT MIN(pmma_vt_feldmarkposition.lfd_nr * 1000000000 + pmma_vt_feldmarkposition.vtvt_recn) AS minhelper
FROM pmma_vt_feldmarkposition
GROUP BY pmma_vt_feldmarkposition.vtvt_recn
);
pmma_vt_feldmarkposition used to be linked to my ODBC database and the query was running without problems. Now, I converted the link to a local table (also named pmma_vt_feldmarkposition, removed the link to the database) to be able to work offline - but the query keeps running forever without finishing. I do not receive any kind of error message.
What could be the reason for this? Could it be because my .accdb file is 1,8GB large now? (I saved a few more local tables and already used the "compact and repair" function)

Actually, I think the best solution will be to get rid of the calculated expression, and work with both fields in a JOIN instead.
Simplifying the first part of the query, this would be:
SELECT
fields
FROM
PMMA_VT_FELDMARKPOSITION T
INNER JOIN (
SELECT MIN(lfd_nr) AS MinLfdNr, vtvt_recn
FROM pmma_vt_feldmarkposition
GROUP BY vtvt_recn
) AS MinGrp
ON T.lfd_nr = MinGrp.MinLfdNr
AND T.vtvt_recn = MinGrp.vtvt_recn
At least I think this should return the same result as your query.
If necessary, performance can be further improved by storing the subquery result in a temp table.
Make sure both lfd_nr and vtvt_recn are indexed.

Related

Divide by zero error when converting Access to SQL Server query

I am trying to convert an Access query to one that works in SQL server. The original query in Access works perfectly well (just terribly slow).
I only changed things slightly to make it compatible with SQL server instead of Access, like changing "NOW()" to "GETDATE()" and we can no longer divide aliases.
Running this query in SQL Server:
SELECT batches.[price-group],
[development].verifier,
Count([development].company) AS SENT,
Sum([order] *- 1) AS ORDS,
Count([development].company) / Sum([order] *- 1) AS PCT
FROM [development]
INNER JOIN batches
ON [development].batch = batches.batch
WHERE (( ( [development].[mail-date] ) < Getdate() - 50 ))
GROUP BY batches.[price-group],
[development].verifier
HAVING (( ( batches.[price-group] ) = 'pgb' ))
ORDER BY batches.[price-group],
[development].verifier,
Count([development].company) DESC;
Returns this error:
Msg 8134, Level 16, State 1, Line 1 Divide by zero error encountered.
Only real change, was like I said, in Access we could do this
[ords] / [sent] AS PCT
Any help will be appreciated, I'm not sure exactly why it isn't working! Removing the converted line above, does work in SQL server without any errors.
Thank you!
Use NULLIF():
Count([development].company) / NULLIF(Sum([order] * -1), 0) AS PCT

Connecting to several data sources from Excel via VBA

Trying to connect to different data sources (VFP database and Access file) from inside Excel to extract specific data, based on the criteria from both sources and encountered problem of creating a combined query. The query to VFP table is already completed and now I need to address MS Access table.
I've tried to do it the following way, but no success:
"left join [tblInAccess] in " & "[Microsoft.ACE.OLEDB.12.0;Data
Source='D:\Data\temp_dump.accdb'] on [tblInVFP].ID=[tblInAccess].ID"
Can anyone point to correct syntax?
UPD:
Whole query code (just query):
SELECT DB.DATE AS DBF_DATE,
DB.EN AS DBF_EN,
DB.NU AS DBF_NUMERO,
LIB.LL AS DBF_LIBELLE,
SUM(IIF(DB.DEBIT = '1000', - DB.AMOUNT, 0.00) + IIF(DB.CREDIT = '1000', DB.AMOUNT, 0.00))
FROM [VFP_DB] AS DB
LEFT JOIN (
SELECT LT.LL AS LL,
LT.EN
FROM [VFP_DB] AS LT
) AS LIB ON DB.EN = LIB.EN
LEFT JOIN (
SELECT id
FROM [Provider=Microsoft.ACE.OLEDB.12.0;Data Source="D:\Data\temp_dump.accdb";].[tblTemp]
) TEMP_DB ON DB.NN = TEMP_DB.id
WHERE DB.DATE >= { ^ 2015.12 .30 }
AND DB.DATE <= { ^ 2017.01 .07 }
AND TEMP_DB.id IS NULL
GROUP BY DB.DATE,
DB.NU,
DB.EN,
LIB.LL
HAVING SUM(IIF(DB.DEBIT = '1000', - DB.AMOUNT, 0.00) + IIF(DB.CREDIT = '1000', DB.AMOUNT, 0.00)) <> 0
ORDER BY DB.DATE,
DB.NU,
DB.EN ASC
Actually, without the 2nd 'Left join' it's working correctly. Problems appeared when I tried to reference Access database
If you have are executing the query on an Access database, you don't need to specify the database type, only the location:
LEFT JOIN [tblInAccess] in D:\Data\temp_dump.accdb on [tblInVFP].ID=[tblInAccess].ID
See the MSDN for further details.
Also note: { ^ 2015.12 .30 } will likely cause a syntax error in Access. If you want to filter by a date, the proper syntax is #2015/12/30# to define a date constant.

PostgreSQL view won't work - column doesnt exist

Hi I am trying to migrate an access database into postgresql and everything was going well until i tried this view. I am wanting it to create a new column called 'CalculatedHours'. And as Im new to postgresql I am slightly confused. Heres the code that I keep putting into pgAdmin and getting the error...
SELECT "SessionsWithEnrolmentAndGroups"."SessionID",
"Assignments"."Staff",
"SessionsWithEnrolmentAndGroups"."groups",
"SessionsWithEnrolmentAndGroups"."SessionQty",
"SessionsWithEnrolmentAndGroups"."Hours",
"SessionsWithEnrolmentAndGroups"."Weeks",
"Assignments"."Percentage",
"Assignments"."AdditionalHours",
Round((coalesce(("groups"),1)*("SessionQty")*("Hours")*("Weeks")
*("Percentage"))) AS CalculatedHours,
(CalculatedHours)+coalesce(("AdditionalHours"),0) AS "TotalHours"
FROM "SessionsWithEnrolmentAndGroups"
INNER JOIN "Assignments"
ON "SessionsWithEnrolmentAndGroups"."SessionID" = "Assignments"."SessionID";
You cannot access column aliases in the same select where they are defined. I would suggest a subquery:
SELECT t.*,
(CalculatedHours)+coalesce(("AdditionalHours"), 0) AS "TotalHours"
FROM (SELECT eag."SessionID", a, eag."groups", eag."SessionQty",
eag."Hours", eag."Weeks", a."Percentage", a."AdditionalHours",
Round((coalesce(("groups"),1)*("SessionQty")*("Hours")*("Weeks")*("Percentage"))) AS CalculatedHours
FROM "SessionsWithEnrolmentAndGroups" eag INNER JOIN
"Assignments" a
ON eag."SessionID" = a."SessionID"
) t;
Your queries would also be much more readable using table aliases and getting rid of the escape characters (double quotes) unless they are really, really needed.

Delphi query parameter usage when all values is also an option

I have a tquery (going thru BDE or BDE emulating component) that has been used to select either a single record or all records.
Traditionally this has been done as such:
select * from clients where (clientid = :clientid or :clientid = -1)
And then they would put a -1 in the field when they wanted the query to return all values. Going through this code though, I have discovered that when they have done this the query does not use proper indexing for the table and only does a natural read.
Is there a best practices method for achieving this? Perhaps a way to tell a parameter to return all values, or must the script be modified to remove the where clause entirely when all values are desired?
Edit: This is Delphi 7, by the way (And going against Firebird 1.5 sorry for leaving that out)
As you use deprecated BDE, that may be one more reason to migrate from BDE to 3d party solutions. AnyDAC (UniDAC, probably others too. Most are commercial libraries) has macros, which allow to dynamically change a SQL command text, depending on the macro values. So, your query may be written:
ADQuery1.SQL.Text := 'select * from clients {IF &clientid} where clientid = &clientid {FI}';
if clientid >= 0 then
// to get a single record
ADQuery1.Macros[0].AsInteger := clientid
else
// to get all records
ADQuery1.Macros[0].Clear;
ADQuery1.Open;
For the queries with "optional" parameters I always use ISNULL (MSSQL, or NVL Oracle), ie.
SELECT * FROM clients WHERE ISNULL(:clientid, clientid) = clientid
Setting the parameter to NULL then selects all records.
You also have to take care of NULL values in the table fields because NULL <> NULL. This you can overcome with a slight modification:
SELECT * FROM clients WHERE COALESCE(:clientid, clientid, -1) = ISNULL(clientid, -1)
I would use this:
SELECT * FROM CLIENTS WHERE clientid = :clientid or :clientid IS NULL
Using two queries is best:
if (clientid <> -1) then
begin
DBComp.SQL.Text := 'select * from clients where (clientid = :clientid)';
DBComp.ParamByName('clientid').Value := clientid;
end else
begin
DBComp.SQL.Text := 'select * from clients';
end;
DBComp.Open;
...
Alternatively:
DBComp.SQL.BeginUpdate;
try
DBComp.SQL.Clear;
DBComp.SQL.Add('select * from clients');
if (clientid <> -1) then
DBComp.SQL.Add('where (clientid = :clientid)');
finally
DBComp.SQL.EndUpdate;
end;
if (clientid <> -1) then
DBComp.ParamByName('clientid').Value := clientid;
DBComp.Open;
...
Remy's answer may be re-formulated as single query.
It may be better, if you gonna prepare it once and then re-open multiple times.
select * from clients where (clientid = :clientid)
and (:clientid is not null)
UNION ALL
select * from clients where (:clientid is null)
This just aggregates two distinct queries (with same results vector) together. And condition just turns one of those off.
Using would be like that:
DBComp.Prepare.
...
DBComp.Close;
DBComp.ParamByName('clientid').Value := clientid;
DBComp.Open;
...
DBComp.Close;
DBComp.ParamByName('clientid').Clear;
DBComp.Open;
However this query would rely on SQL Server optimizer capability to extract query invariant (:clientid is [not] null) and enable/disable query completely. But well, your original query depends upon that too.
Why still use obsolete FB 1.5 ? Won't FB 2.5.2 work better there ?
I think your original query is formulated poorly.
select * from clients where (:clientid = -1) or ((clientid = :clientid) and (:clientid <> -1))
would probably be easier on SQL Server optimizer. Yet i think FB could do better job there. Try to download later FB, and run your query in it, using IDEs like IBExpert or FlameRobin. Re-arranging parenthesis and changing -1 to NULL are obvious ideas to try.
Using BDE is fragile now. It is not very fast, limiting in datatypes and connectivity (no FB/IB Events for example). And would have all sorts of compatibility problems with Vista/Win7 and Win64. If FB/IB is your server of choice, consider switching to some modern component set:
(FLOSS) Universal Interbase by http://uib.sf.net (RIP all Delphi pages of http://Progdigy.com )
(FLOSS) ZeosLib DBO by http://zeos.firmos.at/
(propr) FIB+ by http://DevRace.com
(propr) IB Objects by http://IBobjects.com
(propr) AnyDAC by http://da-soft.com - sold out to Embarcadero, not-avail for D7
(propr) IB-DAC/UniDAC http://DevArt.com
Also it would be good thing to show the table and indices definition and selectivity of those indices.

LINQ to SQL selecting records and converting dates

I'm trying to select records from a table based on a date using Linq to SQL. Unfortunately the date is split across two tables - the Hours table has the day and the related JobTime table has the month and year in two columns.
I have the following query:
Dim qry = From h As Hour In ctx.Hours Where Convert.ToDateTime(h.day & "/" & h.JobTime.month & "/" & h.JobTime.year & " 00:00:00") > Convert.ToDateTime("01/01/2012 00:00:00")
This gives me the error "Arithmetic overflow error converting expression to data type datetime."
Looking at the SQL query in SQL server profiler, I see:
exec sp_executesql N'SELECT [t0].[JobTimeID], [t0].[day], [t0].[hours]
FROM [dbo].[tbl_pm_hours] AS [t0]
INNER JOIN [dbo].[tbl_pm_jobtimes] AS [t1] ON [t1].[JobTimeID] = [t0].[JobTimeID]
WHERE (CONVERT(DateTime,(((((CONVERT(NVarChar,[t0].[day])) + #p0) + (CONVERT(NVarChar,COALESCE([t1].[month],NULL)))) + #p1) + (CONVERT(NVarChar,COALESCE([t1].[year],NULL)))) + #p2)) > #p3',N'#p0 nvarchar(4000),#p1 nvarchar(4000),#p2 nvarchar(4000),#p3 datetime',#p0=N'/',#p1=N'/',#p2=N' 00:00:00',#p3='2012-01-31 00:00:00'
I can see that it's not passing in the date to search for correctly but I'm not sure how to correct it.
Can anyone please help?
Thanks,
Emma
The direct cause of the error may have to do with this issue.
As said there, the conversions you use are a very inefficient way to build a query. On top of that, it is inefficient because the expressions are not sargable. I.e. you are using a computed value from database columns in a comparison which disables the query analyzer to use indexes to jump to individual column values. So, you could try to fix the error by doctoring the direct cause, but I think it's better to rewrite the query in a way that only the single column values are used in comparions.
I've worked this out in C#:
var cfg = new DateTime(12,6,12);
int year = 12, month = 6, day = 13; // Try some more values here.
// Date from components > datetime value?
bool gt = (
year > cfg.Year || (
(year == cfg.Year && month > cfg.Month) || (
year == cfg.Year && month == cfg.Month && day > cfg.Day)
)
);
You see that it's not as straightforward as it may look at first, but it works. There are much more comparisons to work out, but I'm sure that the ability to use indexes will easily outweigh this.
A more straightforward, but not sargable, way is to use sortable dates, like 20120101 and compare those (as integers).