Remove duplicates from a FIELD in SQL table - sql

I have a situation where records imported into an access database contain some duplicates values in a specific field. I do not want to remove any rows in the table with duplicates, rather just the duplicate values in the specific field in the relevant rows. I tried a building a query around a kind of split in string but is hasn't worked. How do I process this?
Example of rows in table currently
What I need to achieve

Take a deep look at your data...
If there's a duplicate within a field, every occurance of string is separated by , ,.
I'd suggest to create custom function as is described here
Public Function String_Split(sInput As String, _
lIndex As Long, _
Optional sDelim As String = ", ,") As String
On Error GoTo Error_Handler
String_Split = Split(sInput, sDelim)(lIndex)
Error_Handler_Exit:
On Error Resume Next
Exit Function
Error_Handler:
String_Split = "Error!"
Resume Error_Handler_Exit
End Function
Then, use it in update query:
UPDATE YourTable
SET [Needs Details] = String_Split([Needs Details], 0)
If you want to preview the result of String_Split function, before you run update query, use this:
SELECT [Pupil Ref], [Needs Details], String_Split([Needs Details], 0) NewDetails
FROM YourTable
That's all!

You have duplication based on ", ," so if you find where that happens then take the left from that point.
Base your query on this:
select *, iif(instr(field1,", ,")>1, left(field1,instr(field1,", ,")-1), field1)
from table1

Related

Using VBA Function in Access Query

I want to write an SQL query using MS access, which calculates a serial number using a VBA Function.
I have a big "samplebasicinformation" table which has lots of foreign keys and want to use the text fields in the foreign tables to create a text-based serial number.
I want the first field of the query to abbreviate a few text fields from other tables, then concatenate them and create this serial number.
In order to abbreviate the text fields I have the following function:
Function GetFirstLetters(rng As String)
Dim arr
Dim I As Long
arr = VBA.Split(rng, " ")
If IsArray(arr) Then
For I = LBound(arr) To UBound(arr)
GetFirstLetters = GetFirstLetters & Left(arr(I), 1)
Next I
Else
GetFirstLetters = Left(arr, 1)
End If
End Function
I've tried the below SQL to achieve this but failed due to syntax error.
SELECT
(getfirstletters(select sl.locationname from samplebasicinformation as sbi join samplelocation as sl
on sbi.samplelocationid = sl.samplelocationid)),
samplebasicinformationid
from samplebasicinformation as sbi1
Would anyone be able to offer some advice?
You need to feed a single column to your function.
Your query is rather confusing, I think it is as simple as this:
SELECT
getfirstletters(sl.locationname),
sbi.samplebasicinformationid
FROM samplebasicinformation as sbi
INNER JOIN samplelocation as sl
on sbi.samplelocationid = sl.samplelocationid
Note that you need INNER JOIN in Access Sql.

Allen Browne's ConcatRelated() Error 3061: Too few parameters

I am trying to create a list of products at a given warehouse.
Allen Browne's ConcatRelated() function seems to be the tried and true method to create lists using when a linked variable is the same, but I can't get it to work.
I have broken my information down into a single query... "qry_Products"
SELECT qry_AX_LineItems_LINES.Warehouse, tblREF_Chemical.[Sales Name]
FROM qry_AX_LineItems_LINES INNER JOIN tblREF_Chemical ON
qry_AX_LineItems_LINES.ItemId = tblREF_Chemical.[Item Number]
GROUP BY qry_AX_LineItems_LINES.Warehouse, tblREF_Chemical.[Sales Name];
It produces a table with the Sales Name and Warehouse(s).
What I need to see happen is a list of the Sales Names when their warehouse matches.
I have tried using the function in a textbox of my form...
=ConcatRelated("[Sales Name]","[qry_Products]"," Warehouse ='" & [Warehouse]
& "'")
It causes an Error 3061 and leaves the cell blank.
I double checked my syntax within the quotes by using Dlookup(), and it produced the first result of the list.
I have also tried altering my query...
SELECT qry_AX_LineItems_LINES.Warehouse, ConcatRelated("[Sales Name]","
[tblREF_Chemical]") AS Expr1
FROM qry_AX_LineItems_LINES INNER JOIN tblREF_Chemical ON
qry_AX_LineItems_LINES.ItemId = tblREF_Chemical.[Item Number];
Unfortunately it then lists every product in my database as a list.
I also tried creating a new query to reference the one producing minimal information.
SELECT ConcatRelated("[Sales Name]","qry_Products") AS Expr1
FROM qry_Products;
I know that the initial query is correct but when I go to run the new query I get multiple pop-ups of Error 3061 and empty cells for results.
I double checked that I am copying the module exactly. http://allenbrowne.com/func-concat.html
Module is named "Concat".
I'm reading every help guide out there but I just can't see what I should try next.
Thank you so much for time and any advice!
SubForm frm_LineItems
Query qry_Products
I found another thread that gives an alternative to the Allen Browne method.
https://bytes.com/topic/access/answers/569535-combining-rows-opposite-union
This seems to be working.
'Concat Returns lists of items which are within a grouped field
Public Function Concat(strGroup As String, strItem As String) As String
Static strLastGroup As String
Static strItems As String
If strGroup = strLastGroup Then
strItems = strItems & ", " & strItem
Else
strLastGroup = strGroup
strItems = strItem
End If
Concat = strItems
End Function
with Query SQL
SELECT WH,
Max(Concat(WH, [Sales Name])) AS Products
FROM [qry_Products]
GROUP BY WH
I wanted to leave this here in case anyone else was having a similar issue.

Create row number field within query access 2010 [duplicate]

I have an employee table with has name, age, city as columns. I want to display a column at run-time for my row numbers starting from 1. I am using SQL in Access.
Call the following function from your query.
Public Function GetNextNum(str As String) As Long
num = num + 1
GetNextNum = num
End Function
The caveat is that you must have at least one parameter (even if you don't need one) otherwise the function only gets called once and returns 1 for all the rows.
Before running the query set the global variable num to 0.
You only need one function to obtain a very speedy and even "groupable" row counter with or without automatic reset of the counter.
See in-line comments for typical usage:
Public Function RowCounter( _
ByVal strKey As String, _
ByVal booReset As Boolean, _
Optional ByVal strGroupKey As String) _
As Long
' Builds consecutive RowIDs in select, append or create query
' with the possibility of automatic reset.
' Optionally a grouping key can be passed to reset the row count
' for every group key.
'
' Usage (typical select query):
' SELECT RowCounter(CStr([ID]),False) AS RowID, *
' FROM tblSomeTable
' WHERE (RowCounter(CStr([ID]),False) <> RowCounter("",True));
'
' Usage (with group key):
' SELECT RowCounter(CStr([ID]),False,CStr[GroupID])) AS RowID, *
' FROM tblSomeTable
' WHERE (RowCounter(CStr([ID]),False) <> RowCounter("",True));
'
' The Where statement resets the counter when the query is run
' and is needed for browsing a select query.
'
' Usage (typical append query, manual reset):
' 1. Reset counter manually:
' Call RowCounter(vbNullString, False)
' 2. Run query:
' INSERT INTO tblTemp ( RowID )
' SELECT RowCounter(CStr([ID]),False) AS RowID, *
' FROM tblSomeTable;
'
' Usage (typical append query, automatic reset):
' INSERT INTO tblTemp ( RowID )
' SELECT RowCounter(CStr([ID]),False) AS RowID, *
' FROM tblSomeTable
' WHERE (RowCounter("",True)=0);
'
' 2002-04-13. Cactus Data ApS. CPH
' 2002-09-09. Str() sometimes fails. Replaced with CStr().
' 2005-10-21. Str(col.Count + 1) reduced to col.Count + 1.
' 2008-02-27. Optional group parameter added.
' 2010-08-04. Corrected that group key missed first row in group.
Static col As New Collection
Static strGroup As String
On Error GoTo Err_RowCounter
If booReset = True Then
Set col = Nothing
ElseIf strGroup <> strGroupKey Then
Set col = Nothing
strGroup = strGroupKey
col.Add 1, strKey
Else
col.Add col.Count + 1, strKey
End If
RowCounter = col(strKey)
Exit_RowCounter:
Exit Function
Err_RowCounter:
Select Case Err
Case 457
' Key is present.
Resume Next
Case Else
' Some other error.
Resume Exit_RowCounter
End Select
End Function
You have 5 methods available.
Reports only - Running Sum
If you are using this information for Access reports, there is an easy way that requires no VBA or fancy SQL. Simply add a textbox with control source set =1 then set Running Sum to Over All, done.
The rest of methods listed below applies to forms/datasheets/recordsets
Correlated subquery
You can do a correlated subquery. This solution is totally self-contained but is not very generic. It would be something similar to this:
SELECT
(
SELECT COUNT(*)
FROM Employees AS x
WHERE x.EmployeeID <= e.EmployeeID
ORDER BY x.EmployeeID
) AS RowNumber,
e.EmployeeID
FROM Employees AS e;
Note that because of the correlated subqueries, the performance will rapidly decrease as the amount of records increase in the table. You might have to customize the ORDER BY clause to get the desired number assignment if it's not supposed to depend on EmployeeID but something else (e.g. HireDate for instance)
VBA Function to maintain count, forward-only recordset
This method can perform much faster but can be only used once; and certainly not within forms/datasheets because VBA functions are continually evaluated as you navigate around. Thus, this is only appropriate when reading recordset in a forward-only manner. Using a standard VBA module:
Private Counter As Long
Public Function ResetRowNumber() As Boolean
Counter = 0
ResetRowNumber = (Counter = 0)
End Function
Public Function GetRowNumber(PrimaryKeyField As Variant) As Long
Counter = Counter + 1
GetRowNumber = Counter
End Function
To then use in a query:
SELECT
GetRowNumber([EmployeeID]) AS RowNumber,
EmployeeID
FROM Employees
WHERE ResetRowNumber();
Note the trick of using WHERE to implicitly call the ResetRowNumber function first. Please note this will work only as long there is only one query active; having multiple queries that takes row numbers will cause incorrect results. However the implementation is very simple and much faster.
VBA Function to maintain count and preserve the assignment
This is more expensive than the previous method but still can be cheaper than the correlated subquery solution for a sufficiently large table. This has the advantage of being useful in a form / datasheet because once number are given out, it is given out again. Again, in a standard VBA module:
Private NumberCollection As VBA.Collection
Public Function ResetRowNumber() As Boolean
NumberCollection = New VBA.Collection
ResetRowNumber = (NumberCollection.Count = 0)
End Function
Public Function GetRowNumber(PrimaryKeyField As Variant) As Variant
On Error Resume Next
Dim Result As Long
Result = NumberCollection(CStr(PrimaryKeyField))
If Err.Number Then
Result = 0
Err.Clear
End If
If Result Then
GetRowNumber = Result
Else
NumberCollection.Add NumberCollection.Count + 1, CStr(PrimaryKeyField)
GetRowNumber = NumberCollection.Count
End If
If Err.Number Then
GetRowNumber = "#Error " & Err.Description
End If
End Function
It's important that the input parameter PrimaryKeyValue references a non-nullable column (which a primary key column should be by definition). Otherwise, we'd have no way of knowing which number we should give out if it's already been given out to the record. The SQL is similar as previous method:
SELECT
GetRowNumber([EmployeeID]) AS RowNumber,
EmployeeID
FROM Employees
WHERE ResetRowNumber();
As with previous method, this is only good for one query at a time. If you need multiple queries, then you need twice the layer; a collection to reference which query's collection, then to inspect that query's collection. That might get a bit hairy. You might be also able to get more bang with a Scripting.Dictionary, so that's an alternative looking into.
Note also that the function now returns Variant due to the fact that it may encounter unexpected errors. Because the function can get called several times, potentially hundreds or even thousands of time, we can't pop open a message box, so we can mimic what built-in functions do and return a #Error, which is incompatible with the underlying type of Long we're really using.
Upgrade to SQL Server or other RDBMS
Access is a phenomenal RAD tool for building data-centric application. However, you are not necessarily tied to using its database engine. You could just migrate your data to one of free RDBMS, link using ODBC and continue to use your Access application as before, and get to benefit the power of SQL, including the window function ROW_NUMBER() that makes this much easier to achieve than VBA. If you are looking at doing more than just getting a row number, you might need to consider if you should migrate your data to a different database engine.
For additional references, this may be helpful.

Ms Access SQL: Concatenate seperated by commas one to many relationship [duplicate]

This question already has an answer here:
Combine values from related rows into a single concatenated string value
(1 answer)
Closed 6 years ago.
With the following kind of table:
tblRequest
-RequestID(double)
-RequestDescription (String)
tblError
-ErrorID(long integer)
-ErrorName(String)
-RequestID(double)
The above relationship is a ONE to MANY relationship.
I want to VIEW the data in the following manner. Therefore, I need a SELECT query which displays the data in the following manner.
Request Error(s)
1 Error1, Error6
2 Error2, Error3
3.4 Error4, Error2, Error1
I tried to search for an answer which involved FOR XML PATH('')). However, I do not think it can work in Ms-Access.
Here's a potential solution.
Step 1:
Create this function in your MS Access App.I don't think this is the most efficient solution, however it should work well enough if the number of records isn't very large.
Public Function getErrorText(ByVal MyId As Double) As String
Dim myrs As Recordset
'Create the recordset
Set myrs = CurrentDb.OpenRecordset("select distinct ErrorID from tblError where RequestID = " & MyId)
'Build the error string
Do Until myrs.EOF
getErrorText = myrs.Fields(0).Value & ", " & getErrorText
myrs.MoveNext
Loop
'Clean up
myrs.Close
Set myrs = Nothing
'Return the correct cleaned up string
getErrorText = Left(getErrorText, Len(getErrorText) - 2)
End Function
Step 2:
You should then be able to run the following SQL statement to get the output desired.
SELECT distinct tblError.RequestID, getErrorText( tblError.[RequestID]) AS [Error(s)]
FROM tblError INNER JOIN tblRequest ON tblError.RequestID = tblRequest.RequestID
WHERE (((getErrorText( tblError.[RequestID])) Is Not Null));

Displaying Row numbers column at runtime

I have an employee table with has name, age, city as columns. I want to display a column at run-time for my row numbers starting from 1. I am using SQL in Access.
Call the following function from your query.
Public Function GetNextNum(str As String) As Long
num = num + 1
GetNextNum = num
End Function
The caveat is that you must have at least one parameter (even if you don't need one) otherwise the function only gets called once and returns 1 for all the rows.
Before running the query set the global variable num to 0.
You only need one function to obtain a very speedy and even "groupable" row counter with or without automatic reset of the counter.
See in-line comments for typical usage:
Public Function RowCounter( _
ByVal strKey As String, _
ByVal booReset As Boolean, _
Optional ByVal strGroupKey As String) _
As Long
' Builds consecutive RowIDs in select, append or create query
' with the possibility of automatic reset.
' Optionally a grouping key can be passed to reset the row count
' for every group key.
'
' Usage (typical select query):
' SELECT RowCounter(CStr([ID]),False) AS RowID, *
' FROM tblSomeTable
' WHERE (RowCounter(CStr([ID]),False) <> RowCounter("",True));
'
' Usage (with group key):
' SELECT RowCounter(CStr([ID]),False,CStr[GroupID])) AS RowID, *
' FROM tblSomeTable
' WHERE (RowCounter(CStr([ID]),False) <> RowCounter("",True));
'
' The Where statement resets the counter when the query is run
' and is needed for browsing a select query.
'
' Usage (typical append query, manual reset):
' 1. Reset counter manually:
' Call RowCounter(vbNullString, False)
' 2. Run query:
' INSERT INTO tblTemp ( RowID )
' SELECT RowCounter(CStr([ID]),False) AS RowID, *
' FROM tblSomeTable;
'
' Usage (typical append query, automatic reset):
' INSERT INTO tblTemp ( RowID )
' SELECT RowCounter(CStr([ID]),False) AS RowID, *
' FROM tblSomeTable
' WHERE (RowCounter("",True)=0);
'
' 2002-04-13. Cactus Data ApS. CPH
' 2002-09-09. Str() sometimes fails. Replaced with CStr().
' 2005-10-21. Str(col.Count + 1) reduced to col.Count + 1.
' 2008-02-27. Optional group parameter added.
' 2010-08-04. Corrected that group key missed first row in group.
Static col As New Collection
Static strGroup As String
On Error GoTo Err_RowCounter
If booReset = True Then
Set col = Nothing
ElseIf strGroup <> strGroupKey Then
Set col = Nothing
strGroup = strGroupKey
col.Add 1, strKey
Else
col.Add col.Count + 1, strKey
End If
RowCounter = col(strKey)
Exit_RowCounter:
Exit Function
Err_RowCounter:
Select Case Err
Case 457
' Key is present.
Resume Next
Case Else
' Some other error.
Resume Exit_RowCounter
End Select
End Function
You have 5 methods available.
Reports only - Running Sum
If you are using this information for Access reports, there is an easy way that requires no VBA or fancy SQL. Simply add a textbox with control source set =1 then set Running Sum to Over All, done.
The rest of methods listed below applies to forms/datasheets/recordsets
Correlated subquery
You can do a correlated subquery. This solution is totally self-contained but is not very generic. It would be something similar to this:
SELECT
(
SELECT COUNT(*)
FROM Employees AS x
WHERE x.EmployeeID <= e.EmployeeID
ORDER BY x.EmployeeID
) AS RowNumber,
e.EmployeeID
FROM Employees AS e;
Note that because of the correlated subqueries, the performance will rapidly decrease as the amount of records increase in the table. You might have to customize the ORDER BY clause to get the desired number assignment if it's not supposed to depend on EmployeeID but something else (e.g. HireDate for instance)
VBA Function to maintain count, forward-only recordset
This method can perform much faster but can be only used once; and certainly not within forms/datasheets because VBA functions are continually evaluated as you navigate around. Thus, this is only appropriate when reading recordset in a forward-only manner. Using a standard VBA module:
Private Counter As Long
Public Function ResetRowNumber() As Boolean
Counter = 0
ResetRowNumber = (Counter = 0)
End Function
Public Function GetRowNumber(PrimaryKeyField As Variant) As Long
Counter = Counter + 1
GetRowNumber = Counter
End Function
To then use in a query:
SELECT
GetRowNumber([EmployeeID]) AS RowNumber,
EmployeeID
FROM Employees
WHERE ResetRowNumber();
Note the trick of using WHERE to implicitly call the ResetRowNumber function first. Please note this will work only as long there is only one query active; having multiple queries that takes row numbers will cause incorrect results. However the implementation is very simple and much faster.
VBA Function to maintain count and preserve the assignment
This is more expensive than the previous method but still can be cheaper than the correlated subquery solution for a sufficiently large table. This has the advantage of being useful in a form / datasheet because once number are given out, it is given out again. Again, in a standard VBA module:
Private NumberCollection As VBA.Collection
Public Function ResetRowNumber() As Boolean
NumberCollection = New VBA.Collection
ResetRowNumber = (NumberCollection.Count = 0)
End Function
Public Function GetRowNumber(PrimaryKeyField As Variant) As Variant
On Error Resume Next
Dim Result As Long
Result = NumberCollection(CStr(PrimaryKeyField))
If Err.Number Then
Result = 0
Err.Clear
End If
If Result Then
GetRowNumber = Result
Else
NumberCollection.Add NumberCollection.Count + 1, CStr(PrimaryKeyField)
GetRowNumber = NumberCollection.Count
End If
If Err.Number Then
GetRowNumber = "#Error " & Err.Description
End If
End Function
It's important that the input parameter PrimaryKeyValue references a non-nullable column (which a primary key column should be by definition). Otherwise, we'd have no way of knowing which number we should give out if it's already been given out to the record. The SQL is similar as previous method:
SELECT
GetRowNumber([EmployeeID]) AS RowNumber,
EmployeeID
FROM Employees
WHERE ResetRowNumber();
As with previous method, this is only good for one query at a time. If you need multiple queries, then you need twice the layer; a collection to reference which query's collection, then to inspect that query's collection. That might get a bit hairy. You might be also able to get more bang with a Scripting.Dictionary, so that's an alternative looking into.
Note also that the function now returns Variant due to the fact that it may encounter unexpected errors. Because the function can get called several times, potentially hundreds or even thousands of time, we can't pop open a message box, so we can mimic what built-in functions do and return a #Error, which is incompatible with the underlying type of Long we're really using.
Upgrade to SQL Server or other RDBMS
Access is a phenomenal RAD tool for building data-centric application. However, you are not necessarily tied to using its database engine. You could just migrate your data to one of free RDBMS, link using ODBC and continue to use your Access application as before, and get to benefit the power of SQL, including the window function ROW_NUMBER() that makes this much easier to achieve than VBA. If you are looking at doing more than just getting a row number, you might need to consider if you should migrate your data to a different database engine.
For additional references, this may be helpful.