Stack Overflow is a community of 4.7 million programmers, just like you, helping each other.

Join them; it only takes a minute:

Sign up
Join the Stack Overflow community to:
  1. Ask programming questions
  2. Answer and help your peers
  3. Get recognized for your expertise

I have data as follows:

ID   Name    Data
1    Joe     ["Mary","Joe"]
2    Mary    ["Sarah","Mary","Mary"]
3    Bill    ["Bill","Joe"]
4    James   ["James","James","James"]

I want to write a query that selects the LAST element from the array, which does not equal the Name field. For example, I want the query to return the following results:

ID   Name   Last
1    Joe    Mary
2    Mary   Sarah
3    Bill   Joe
4    James  (NULL)

I am getting close - I can select the last element with the following query:

SELECT ID, Name,
(Data::json->(json_array_length(Data::json)-1))::text AS Last
FROM table;

ID    Name    Last
1     Joe     Joe
2     Mary    Mary
3     Bill    Joe
4     James   James

However, I need one more level - to evaluate the last item, and if it is the same as the name field, to try the next to last field, and so on.

Any help or pointers would be most appreciated!

share|improve this question
1  
Crucial: your Postgres version? Data type json or jsonb? – Erwin Brandstetter Jan 15 '15 at 4:52
    
PostgreSQL version? – wingedpanther Jan 15 '15 at 4:55
up vote 2 down vote accepted

json in Postgres 9.3

This is hard in pg 9.3, because useful functionality is missing.

Method 1

Unnest in a LEFT JOIN LATERAL (clean and standard-conforming), trim double-quotes from json after casting to text. See links below.

SELECT DISTINCT ON (1)
       t.id, t.name, d.last
FROM   tbl t
LEFT   JOIN LATERAL (
  SELECT ('[' || d::text || ']')::json->>0 AS last
  FROM   json_array_elements(t.data) d
  ) d ON d.last <> t.name
ORDER  BY 1, row_number() OVER () DESC;

While this works, and I have never seen it fail, the order of unnested elements depends on undocumented behavior. See links below!
Improved the conversion from json to text with the expression provided by @pozs in the comment. Still hackish, but should be safe.

Method 2

SELECT DISTINCT ON (1)
       id, name, NULLIF(last, name) AS last
FROM (
   SELECT t.id, t.name
        ,('[' || json_array_elements(t.data)::text || ']')::json->>0 AS last
        , row_number() OVER () AS rn
   FROM   tbl t
   ) sub
ORDER  BY 1, (last = name), rn DESC;
  • Unnest in the SELECT list (non-standard).
  • Attach row number (rn) in parallel (more reliable).
  • Convert to text like above.
  • The expression (last = name) in the ORDER BY clause sorts matching names last (but before NULL). So a matching name is only selected if no other name is available. Last link below. In the SELECT list, NULLIF replaces a matching name with NULL, arriving at the same result as above.

SQL Fiddle.

json or jsonb in Postgres 9.4

pg 9.4 ships all the necessary improvements:

SELECT DISTINCT ON (1)
       t.id, t.name, d.last
FROM   tbl t
LEFT   JOIN LATERAL json_array_elements_text(data) WITH ORDINALITY d(last, rn)
       ON d.last <> t.name
ORDER  BY d.rn DESC;

Use jsonb_array_elements_text() for jsonb. All else the same.

json / jsonb functions in the manual

Related answers with more explanation:

share|improve this answer
1  
Great answer, but trimming a json string can fail, if that string contains some escaping. There is a (hack-ish, i know, but) more reliable way to do the "cast" in 9.3: ('[' || d::text || ']')::json ->> 0 sqlfiddle.com/#!15/99d11/1 – pozs Jan 15 '15 at 9:51
    
Thanks. That's also what Marti Raudsepp suggested in the discussion on plpgsql-general. It's better than the alternatives I had before, so I updated with your expression. – Erwin Brandstetter Jan 15 '15 at 10:12
    
Thanks for this - I will test this afternoon. Looks promising! – Scott Switzer Jan 15 '15 at 16:06

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.