0

I'm trying to make a report that pulls data between two dates and where a string occurs only once in the description column which has a text datatype

I've tried using a solution from the "postgresql-select-if-string-contains" stackoverflow question but it didn't account for the amount of how many times the string occurs in the description block.

SELECT o.ownername, to_char(a.auditdate, 'MM/DD/YYYY') as dateday, 
o.additionalflags, 
o.emailaddress, o.id, a.description, a.username, 
CASE WHEN a.Action = 0 THEN 'ADD'
WHEN a.Action = 1 THEN 'EDIT'
WHEN a.Action = 2 THEN 'DELETE'
WHEN a.Action = 3 THEN 'MOVE'
WHEN a.Action = 4 THEN 'LOGIN'
WHEN a.Action = 5 THEN 'LOGOUT'
END AS Action
FROM owner o
INNER JOIN audittrail a ON o.id = a.linkid
/*
WHERE array_length(regexp_matches(description, '1\-Gets Email'), 1) = 1
*/
AND a.auditdate >= '$ASK DATE Enter the from date$' 
AND a.auditdate <= '$ASK DATE Enter the to date$'
ORDER BY o.ownername

The column I'm trying to sift through has data that looks like this:

(ID 2) >>> LASTCHANGEDDATE: 2011-11-11 11:11:11.653868 ==> 2018-12-23 14:24:28.694724, ADDITIONALFLAGS: *1-Atest|1-Gets Email|1-Gets Snail Mail|Adopter-|donor|driver|fosterer|homechecked|homechecker|member|V-Foster Home: short-term/emergency|* ==> **1-Gets Email|1-Gets Snail Mail|Adopter-NCGSPR Dog|donor|driver|fosterer|homechecked|homechecker|member|V-Foster Home: short-term/|volunteer|,**

The column pretty much is a description log of what has changed in the database. The italicized words are what ADDITIONALFLAGS are the past and the bold words are the new and current ADDITIONALFLAGS after the change. I'm looking for description rows where '1-Gets Email' once because I only care about ones that have changed and not ones that had '1-Gets Email' before the dates I queried for.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • 1
    Your join will fail.. Is this your query running? Sample data and expected result will be helpful.. – dwir182 Dec 28 '18 at 01:11

2 Answers2

0

Somewhat hacky, but should work: Check if the length of the description minus the length the text you search for is equal to the lenght of the description where the text you search for is replaced with the empty string.

...
WHERE length(description) - length('1-Gets Email') = length(replace(description, '1-Gets Email', ''))
...

Or a more generic approach: Check the number of matches regexp_matches() found, in this case 1.

...
WHERE (SELECT count(*)
              FROM regexp_matches(description, '1\-Gets Email', 'g')) = 1
...
sticky bit
  • 36,626
  • 12
  • 31
  • 42
  • I fixed the syntax issues with the inner join, I tried using your first code but I'd get some description columns without the '1-Gets Email' within the row. I think the 2nd code given is correct but I've been getting an error: argument of AND must not return a set LINE 12: WHERE array_length(regexp_matches(description, '1\-Gets Emai... ^ – Sam Richardson Dec 28 '18 at 21:42
  • @SamRichardson: Hmm, thought it was returning an array but it didn't. Also forgot the `g` flag. Corrected it, see my edit and here: https://dbfiddle.uk/?rdbms=postgres_9.4&fiddle=0847f2bfc04b861fa08e3070d43bacc7 – sticky bit Dec 28 '18 at 21:58
  • Thank you so much it works now but I found out that there are instances where '1-Gets Email' occurs once but only before '==>' and that means the people will show up but they won't have '1-Gets Email' currently in their ADDITIONALFLAGS. Is there a way to only see '1-Gets Email' after '==>'. I tried replacing the WHERE line with WHERE a.description LIKE '%' || '==> 1-Gets Email' || '%', but nothing appears. – Sam Richardson Dec 28 '18 at 22:56
  • @SamRichardson: You can extend the regular expression to any regular expression (your version of) Postgres understands, including lookarounds. – sticky bit Dec 28 '18 at 23:11
0

split_part() can be used for a simple and fast solution:

...
WHERE  description ~ '1-Gets Email'
AND    split_part(description, '1-Gets Email', 3) = '';

This treats the search pattern as delimiter. If there is no second delimiter, there is not third part - empty in this case.
If the search pattern could appear at the start or end of the string we could get false positives, but that corner case can be ruled out in your case since values are enclosed in custom delimiters.

If the table is big you might add a trigram index to support the first WHERE clause (description ~ 'pattern') and make it much faster. Like:

CREATE INDEX tbl_description_gin_trgm_idx ON tbl USING gin (description gin_trgm_ops);

See:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • "`split_part(description, '1-Gets Email', 3) = ''`" -- That's what I thought of too, but it misses the edge case, when the second `1-Gets Email` is at the end of the string or is immediately followed by another one. https://dbfiddle.uk/?rdbms=postgres_9.4&fiddle=b469ee21b96ee64fcd4ec0f672f95908 – sticky bit Dec 28 '18 at 23:04
  • @stickybit: I did mention that - along with why I think it doesn't apply to the case at hand. – Erwin Brandstetter Dec 28 '18 at 23:38
  • Now that you mention it, I see it. Must have overlooked that, sorry. – sticky bit Dec 29 '18 at 01:07