I have this table and some sample data as well. I want to get the index of each value in array in separate column.
CREATE TABLE contacts (
id serial PRIMARY KEY,
name VARCHAR (100),
phones TEXT []
);
Sample data.
INSERT INTO contacts (name, phones)
VALUES
(
'John Doe',
'{"(408)-589-5846","(408)-589-5555"}'
),
(
'Lily Bush',
'{"(408)-589-5841"}'
),
(
'William Gate',
'{"(408)-589-5842","(408)-589-58423"}'
);
Now I run this query to unnest the data into rows which is something like this.
select name, unnest(phones) from contacts
It gives me the data correctly but I want the number of index for the phone numbers in another column which will help me identify which phone number is at which index.
I came to know with array_position() function but it's not working as expected and throwing some error, maybe I'm not putting in right way. I am new to postgresql so any help would be appreciated.
Use unnest() in the FROM clause and you get the the index using the option with ordinality
select c.name,
p.phone,
p.idx
from contacts c
cross join lateral unnest(phones) with ordinality as p(phone, idx)
order by c.id, p.idx;
Online example
The above would not return rows from the contacts table that have an empty phones array, if you need that you need to use a LEFT JOIN
select c.name,
p.phone,
p.idx
from contacts c
left join lateral unnest(phones) with ordinality as p(phone, idx) on true
order by c.id, p.idx;
Related
I need to write a sql code that probably is very simple but I am very new to it.
I need to find all the records from one table that have matching id (but no more than one) from the other table. eg. one table contains records of the employees and the second one with employees' telephone numbers. i need to find all employees with only one telephone no
Sample data would be nice. In absence of:
SELECT
employees.employee_id
FROM
employees
LEFT JOIN
(SELECT distinct on(employee_id) employee_id FROM emp_phone) AS phone
ON
employees.employee_id = phone.employee_id
WHERE
phone.employee_id IS NOT NULL;
You need a join of the 2 tables, group by employee and the condition in the having clause:
SELECT e.employee_id, e.name
FROM employees e INNER JOIN numbers n
ON e.employee_id = n.employee_id
GROUP BY e.employee_id, e.name
HAVING COUNT(*) = 1;
If there can be more than a few numbers per employee in the table with the employees' telephone numbers (calling it tel), then it's cheaper to avoid GROUP BY and HAVING which has to process all rows. Find employees with "unique" numbers using a self-anti-join with NOT EXISTS.
While you don't need more than the employee_id and their unique phone number, you don't even have to involve the employee table at all:
SELECT *
FROM tel t
WHERE NOT EXISTS (
SELECT FROM tel
WHERE employee_id = t.employee_id
AND tel_number <> t.tel_number -- or use PK column
);
If you need additional columns from the employee table:
SELECT * -- or any columns you need
FROM (
SELECT employee_id AS id, tel_number -- or any columns you need
FROM tel t
WHERE NOT EXISTS (
SELECT FROM tel
WHERE employee_id = t.employee_id
AND tel_number <> t.tel_number -- or use PK column
)
) t
JOIN employee e USING (id);
The column alias in the subquery (employee_id AS id) is just for convenience. Then the outer join condition can be USING (id), and the ID column is only included once in the result, even with SELECT * ...
Simpler with a smart naming convention that uses employee_id for the employee ID everywhere. But it's a widespread anti-pattern to use employee.id instead.
Related:
JOIN table if condition is satisfied, else perform no join
I have a temp table with multiple rows in it and each row has a column called Categories; which contains a very simple json array of ids for categories in a different table.
A few example rows of the temp table:
Id Name Categories
---------------------------------------------------------------------------------------------
'539f7e28-143e-41bb-8814-a7b93b846007' Test 1 ["category1Id", "category2Id", "category3Id"]
'f29e2ecf-6e37-4aa9-aa56-4a351d298bfc' Test 2 ["category1Id", "category2Id"]
'34e41a0a-ad92-4cd7-bf5c-8df6bfd6ed5c' Test 3 NULL
Now what I would like to do is to select all of the category ids from all of the rows in the temp table.
What I have is the following and it's not working as it's giving me the error of :
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
SELECT
c.Id
,c.[Name]
,c.Color
FROM
dbo.Category as c
WHERE
c.Id in (SELECT [value] FROM OPENJSON((SELECT Categories FROM #TempTable)))
and c.IsDeleted = 0
Which I guess it makes sense that's failing on that because I'm selecting multiple rows and needing to parse each row's respective category ids json. I'm just not sure what to do/change to give me the results that I want. Thank you in advance for any help.
You'd need to use CROSS APPLY like so:
SELECT id ,
name ,
t.Value AS category_id
FROM #temp
CROSS APPLY OPENJSON(categories, '$') t;
And then, you can JOIN to your Categories table using the category_id column, something like this:
SELECT id ,
name ,
t.Value AS category_id,
c.*
FROM #temp
CROSS APPLY OPENJSON(categories, '$') t
LEFT JOIN Categories c ON c.Id = t.Value
Assuming I have a table with 3 columns, ID, Name, City and I want to use named query to return rows with unique city..can it be done?
Are you asking whether it is possible to write a query that will return the cities that appear in exactly one row, in a table that has ID/Name/City triplets where there could be multiple rows for the same city but with different names?
If so, it would depend on the database engine behind the scenes - but you could try things like:
with candidates (city, num) as (
select city, count(*) from table
group by city
)
select city from candidates where num = 1
Or
select t1.city from table t1
where not exists (
select * from table t2
where t2.city = t1.city and t2.id <> t1.id
)
where table is your table with these triplets.
I have two table like this:
create table product (
id serial primary key,
name text
);
create table selectedattribute (
id serial primary key,
product integer references product,
attribute text,
val text
);
and I'm creating a materialized view with this select query
select product.name,
jsonb_build_object(
'color', COALESCE(jsonb_agg(val) FILTER (WHERE attribute='color'), '[]'),
'diameter', COALESCE(jsonb_agg(val) FILTER (WHERE attribute='diameter'), '[]')
)
from product
left join selectedattribute on product.id = selectedattribute.product
group by product.id;
the problem with this select query is when I add a new attribute, I have to add it to select query in order to create an up-to-date materialized view.
Is there a way to write an aggregate expression that dynamically gets attributes without all these hard-coded attribute names?
You can try my code in SQL Fiddle: http://sqlfiddle.com/#!17/c4150/4
You need to nest the aggregation. First collect all values for an attribute then aggregate that into a JSON:
select id, name, jsonb_object_agg(attribute, vals)
from (
select p.id, p.name, a.attribute, jsonb_agg(a.val) vals
from product p
left join selectedattribute a on p.id = a.product
group by p.id, a.attribute
) t
group by id, name;
Updated SQLFiddle: http://sqlfiddle.com/#!17/c4150/5
I'm dealing with a pretty unique record matching problem within postgres right now. Essentially I have a table (A) with a lot of records in it, including a key value that I need to match on and the date of the record. Then I have this other table (B) that I want to match the first table on that key value. However, there can be multiple of the same 'key values' in both tables. To get around this I need to match the earliest key value from table A to the earliest key value to table B, the second earliest to the second earliest, and so on... However, if table B runs out of key value matches in table B then I want to default to the latest key value match in A, even though something else already matched on it.
My initial thought is to use a something like this on both tables:
ROW_NUMBER() OVER ( PARTITION BY key_value ORDER BY date) AS rank
And then join on the rank and key_value field. However, I'm not exactly sure how to get that default scenario to work with this method. And if records are added to one table and not the other and I try the join again, I feel like it might get out of sync.
My other thought was to use a cursor, but I'm really struggling to see how I'd implement that.
Any help would be greatly appreciated!
first you need number all your rows, the find the one with matching ranks.
After that match the one without matching to the latest_date
with cteA as (
SELECT *, ROW_NUMBER() OVER ( PARTITION BY key_value ORDER BY date) AS rank
FROM tableA
), cteB as (
SELECT *, ROW_NUMBER() OVER ( PARTITION BY key_value ORDER BY date) AS rank
FROM tableB
), ranked_match as (
SELECT ctA.*, cteB.*
FROM cteA
LEFT JOIN cteB
ON cteA.key_value = cteB.key_value
AND cteA.rank = cteB.rank
), latest_row as (
SELECT *, ROW_NUMBER() OVER ( PARTITION BY key_value ORDER BY date DESC) AS rank
FROM tableB
)
SELECT *
FROM ranked_match
WHERE cteB.key_value IS NOT NULL
UNION ALL
SELECT *
FROM ranked_match
JOIN latest_row
ON ranked_match.key_value = latest_row .key_value
WHERE cteB.key_value IS NULL
AND latest_row .rank = 1