2

I need to insert into an access database. But skip the row if column dup is already in the table.

What I have so far is this:

<cfoutput  query="hours">
<cfquery name="Employee" datasource="test">

INSERT INTO Tbl_Hours_Comments
(ID, ship_num, dup)
    values(#hours.id#, #hours.ship#, #hours.dup#)
</cfquery>
</cfoutput>

If I don't make dup a Primary Key. Then it will INSERT duplicates. If I make dup a Primary Key. Then I get an error.

I think I need something like MySQL. ON DUPLICATE KEY UPDATE.

Or maybe dup_val_on_index exception handling like in Oracle.

@Gord Thompson

I tried (Not sure if I'm doing it right):

<cfoutput  query="hours">
<cfquery name="Insert_Employee" datasource="trung">
INSERT Tbl_Hours_Comments (ID, ship_num, dup)

values(#hours.id#, #hours.ship#, #hours.dup#)
SELECT ? as dup
  FROM( SELECT COUNT(*) as n from Tbl_Hours_Comments) as Dual



WHERE NOT EXISTS
        (   SELECT *
            FROM Tbl_Hours_Comments
            WHERE dup = ?
        )


</cfquery>
</cfoutput>

But I get [Microsoft][ODBC Microsoft Access Driver]COUNT field incorrect .

What are the ? in your select statement?

Thomas
  • 85
  • 1
  • 9
  • If this were VBA, could turn off the warnings with `Set Warnings False` run the SQL and then `Set Warnings True` or use CurrentDb.Execute. Review http://stackoverflow.com/questions/14383503/on-duplicate-key-update-same-as-insert – June7 Apr 15 '17 at 18:46
  • Possible duplicate of [On Duplicate Key Update same as insert](http://stackoverflow.com/questions/14383503/on-duplicate-key-update-same-as-insert) – June7 Apr 15 '17 at 18:47
  • What is the source of the data in the "hours" query? If it is from a table in the same database, it is a LOT more efficient to skip the cfloop and use a single `insert into / select ... from ...` statement containing a `where not exists...` clause. – Leigh Apr 15 '17 at 19:52
  • hours is from a cf query of a query. The data is in another data source. – Thomas Apr 15 '17 at 19:59
  • To clarify, do you mean another *table* within that same MS Access database? Or a totally separate database? If so, which type - MS Access, SQL Server, etcetera? MS Access supports [querying external databases](https://support.office.com/en-us/article/IN-Clause-3F9369A8-2032-4637-81AF-699DB411FBFA). – Leigh Apr 15 '17 at 20:08
  • Totally separate DB. It's an Oracel database. – Thomas Apr 15 '17 at 20:11
  • Okay, [the docs say](https://msdn.microsoft.com/en-us/library/cc722917.aspx) Oracle is a supported type, so I would give it a whirl. – Leigh Apr 15 '17 at 20:21
  • Thanks I will try it. In your example Ex is the hours table? – Thomas Apr 15 '17 at 20:25
  • (Edit) The "ex" is a [table alias](https://en.wikipedia.org/wiki/Alias_(SQL)). See also my updated answer. – Leigh Apr 15 '17 at 23:19
  • *"What are the ? in your select statement?"* - They are parameter placeholders. The parameter values in `params` are substituted for the placeholders when the query is executed. – Gord Thompson Apr 15 '17 at 23:51

3 Answers3

3

If you are using the Microsoft Access ODBC driver to update the database then you can use INSERT ... SELECT ... WHERE NOT EXISTS. The following example is in Python, but it looks like you should be able to do something similar in ColdFusion.

import pyodbc
cnxn = pyodbc.connect("DSN=db1")  # Microsoft Access ODBC
crsr = cnxn.cursor()

hours_ship = "ship102"  # test data
hours_dup = 3           #

sql = """\
INSERT INTO Tbl_Hours_Comments (ship_num, dup)
SELECT ? as ship_num, ? AS dup
FROM (SELECT COUNT(*) AS n FROM Tbl_Hours_Comments) AS Dual
WHERE NOT EXISTS (SELECT * FROM Tbl_Hours_Comments WHERE dup = ?)
"""
params = (hours_ship, hours_dup, hours_dup)
crsr.execute(sql, params)
cnxn.commit()
print("{} row(s) inserted".format(crsr.rowcount))
crsr.close()
cnxn.close()

Note that this approach will not work using the Microsoft Access OLEDB provider.

Gord Thompson
  • 116,920
  • 32
  • 215
  • 418
  • Just out of curiosity, why would it not work with OLEDB? – Leigh Apr 15 '17 at 23:20
  • @Leigh - I don't know for sure, it's just one of the subtle differences between the Access ODBC driver and the Access OLEDB provider. – Gord Thompson Apr 15 '17 at 23:53
  • Good to know. Been a while since I have used OLEDB. Never really got the hang of all the differences, beyond knowing there are some :) – Leigh Apr 16 '17 at 00:39
3

Update:

If for some reason you cannot query the external database directly, try using a variation on Gord Thompson's suggestion. It is not as efficient as INSERT /SELECT .. FROM Table, but ... will work with MS Access.

INSERT INTO Tbl_Hours_Comments (ID, ship_num, dup)
SELECT <cfqueryparam value="#hours.id#" cfsqltype="(your column type here)">
       , <cfqueryparam value="#hours.ship#" cfsqltype="(your column type here)">
       , <cfqueryparam value="#hours.dup#" cfsqltype="(your column type here)">
WHERE  NOT EXISTS
       (
          SELECT NULL
          FROM   Tbl_Hours_Comments dupe
          WHERE  dupe.id = <cfqueryparam value="#hours.id#" cfsqltype="(your column type here)">
          AND    dupe.ship = <cfqueryparam value="#hours.ship#" cfsqltype="(your column type here)">
          AND    dupe.dup = <cfqueryparam value="#hours.dup#" cfsqltype="(your column type here)">
       )

What is the source of the data in the "hours" query? MS Access supports querying certain external databases (MS Access, SQL Server, Excel, ecetera) directly using an IN clause. Example:

 SELECT Column1, Column2
 FROM   TableName IN 'c:\path\to\external.mdb'

So if the data is from a (supported) external database OR another table within the same database, it is a LOT more efficient to skip the cfloop and run a single INSERT INTO / SELECT query. Just combine it with an OUTER JOIN or NOT EXISTS clause to exclude records that are already exist in the target table.

Disclaimer - I have not used Access in a long while, but the syntax should be something like this:

Option #1 - OUTER JOIN

INSERT INTO Tbl_Hours_Comments (ID, ship_num, dup)
SELECT ex.id, ex.ship, ex.dup
FROM   OtherTable ex LEFT JOIN Tbl_Hours_Comments dupe
          ON dupe.id = ex.id
          AND dupe.ship = ex.ship
          AND dupe.dup = ex.dup
WHERE  dupe.id IS NULL

Option #2 - NOT EXISTS

INSERT INTO Tbl_Hours_Comments (ID, ship_num, dup)
SELECT ex.id, ex.ship, ex.dup
FROM   OtherTable ex
WHERE  NOT EXISTS
       (
          SELECT NULL
          FROM   Tbl_Hours_Comments dupe
          WHERE  dupe.id = ex.id
          AND    dupe.ship = ex.ship
          AND    dupe.dup = ex.dup
       )
Community
  • 1
  • 1
Leigh
  • 28,765
  • 10
  • 55
  • 103
  • (Edit) In theory, but .. I do not have Access installed anymore, so I cannot verify it. Give it a try. Skip the INSERT part for now and just see if either of the SELECT statements works. – Leigh Apr 15 '17 at 20:18
  • BTW, the concept is sound and this would definitely work with tables in the same database. However, from what I remember MS Access has some limitations regarding cross db queries, so it is possible this is not supported. – Leigh Apr 15 '17 at 20:24
  • 1
    *"MS Access has some limitations regarding cross db queries"* - It does, but if the Access database contains an ODBC linked table that points to the external table then it behaves very much like a local table. ("Some conditions apply. Your mileage may vary. And so on." :) – Gord Thompson Apr 15 '17 at 23:59
  • @GordThompson - Ooh... forgot about those. So "in theory" they could link to the remote table and the INSERT / SELECT *should* work. Cool. – Leigh Apr 16 '17 at 00:40
0

I wanted to post what I ended up doing in case it helps someone.

Thank you to @Leigh and @Gord for help on this problem.

This what I finally did to solve it. It is very similar to Leigh and Gord's way of a sub query.

I CFQUERY both tables (QueryA and QueryB).

Then I used this (from https://coldfusionblog.wordpress.com/2012/01/11/how-to-do-an-outer-join-in-query-of-queries/):

<cfquery name="unique">
SELECT *
FROM QueryA
WHERE QueryA.ID NOT IN (#ValueList(QueryB.ID)#)
</cfquery>

To get a list of element in QueryA that are not in QueryB.

Then I used another insert query to insert all the new unique elements into QueryB.

Thomas
  • 85
  • 1
  • 9
  • Thanks for posting your final code. Though they look similar, the QoQ's approach uses CF memory rather than off-loading the work to the database (usually better equipped for the job). Also, keep in mind a `where not in (....)` clause tends to slow down the more items there are in the list. So you might still want to investigate the "linked query" and/or "external database" options to see if it is more efficient. Especially if the final `insert` query involves a loop to add one record at a time. While sometimes unavoidable, looping tends to be the slowest method of inserting data... – Leigh Apr 28 '17 at 18:05