Batch SELECT ids in an array while preserving the array order - sql

I have an array of ids that might repeats: [1,4,2,1,4,6,7]
Manually I have to do
SELECT * FROM products WHERE catid='1'
SELECT * FROM products WHERE catid='4'
SELECT * FROM products WHERE catid='2'
SELECT * FROM products WHERE catid='1'
.....
one by one and combine everything later
Is there a way to do it in a single query while preserving its order?
So I would get
| id | props |
|----|--------|
| 1 | 1_props|
| 4 | 4_props|
| 2 | 2_props|
| 1 | 1_props|

You can unnest the array and then join against it. The option with ordinality will include the index of the element in the array as a column. That can be used to sort the result:
select p.*
from products p
join unnest(array[1,4,2,1,4,6,7]) with ordinality as t(id, idx) on t.id = p.catid
order by t.idx;

Related

How to convert JSONB array of pair values to rows and columns?

Given that I have a jsonb column with an array of pair values:
[1001, 1, 1002, 2, 1003, 3]
I want to turn each pair into a row, with each pair values as columns:
| a | b |
|------|---|
| 1001 | 1 |
| 1002 | 2 |
| 1003 | 3 |
Is something like that even possible in an efficient way?
I found a few inefficient (slow) ways, like using LEAD(), or joining the same table with the value from next row, but queries take ~ 10 minutes.
DDL:
CREATE TABLE products (
id int not null,
data jsonb not null
);
INSERT INTO products VALUES (1, '[1001, 1, 10002, 2, 1003, 3]')
DB Fiddle: https://www.db-fiddle.com/f/2QnNKmBqxF2FB9XJdJ55SZ/0
Thanks!
This is not an elegant approach from a declarative standpoint, but can you please see whether this performs better for you?
with indexes as (
select id, generate_series(1, jsonb_array_length(data) / 2) - 1 as idx
from products
)
select p.id, p.data->>(2 * i.idx) as a, p.data->>(2 * i.idx + 1) as b
from indexes i
join products p on p.id = i.id;
This query
SELECT j.data
FROM products
CROSS JOIN jsonb_array_elements(data) j(data)
should run faster if you just need to unpivot all elements within the query as in the demo.
Demo
or even remove the columns coming from products table :
SELECT jsonb_array_elements(data)
FROM products
OR
If you need to return like this
| a | b |
|------|---|
| 1001 | 1 |
| 1002 | 2 |
| 1003 | 3 |
as unpivoting two columns, then use :
SELECT MAX(CASE WHEN mod(rn,2) = 1 THEN data->>(rn-1)::int END) AS a,
MAX(CASE WHEN mod(rn,2) = 0 THEN data->>(rn-1)::int END) AS b
FROM
(
SELECT p.data, row_number() over () as rn
FROM products p
CROSS JOIN jsonb_array_elements(data) j(data)) q
GROUP BY ceil(rn/2::float)
ORDER BY ceil(rn/2::float)
Demo

Find the count of IDs that have the same value

I'd like to get a count of all of the Ids that have have the same value (Drops) as other Ids. For instance, the illustration below shows you that ID 1 and 3 have A drops so the query would count them. Similarly, ID 7 & 18 have B drops so that's another two IDs that the query would count totalling in 4 Ids that share the same values so that's what my query would return.
+------+-------+
| ID | Drops |
+------+-------+
| 1 | A |
| 2 | C |
| 3 | A |
| 7 | B |
| 18 | B |
+------+-------+
I've tried the several approaches but the following query was my last attempt.
With cte1 (Id1, D1) as
(
select Id, Drops
from Posts
),
cte2 (Id2, D2) as
(
select Id, Drops
from Posts
)
Select count(distinct c1.Id1) newcnt, c1.D1
from cte1 c1
left outer join cte2 c2 on c1.D1 = c2.D2
group by c1.D1
The result if written out in full would be a single value output but the records that the query should be choosing should look as follows:
+------+-------+
| ID | Drops |
+------+-------+
| 1 | A |
| 3 | A |
| 7 | B |
| 18 | B |
+------+-------+
Any advice would be great. Thanks
You can use a CTE to generate a list of Drops values that have more than one corresponding ID value, and then JOIN that to Posts to find all rows which have a Drops value that has more than one Post:
WITH CTE AS (
SELECT Drops
FROM Posts
GROUP BY Drops
HAVING COUNT(*) > 1
)
SELECT P.*
FROM Posts P
JOIN CTE ON P.Drops = CTE.Drops
Output:
ID Drops
1 A
3 A
7 B
18 B
If desired you can then count those posts in total (or grouped by Drops value):
WITH CTE AS (
SELECT Drops
FROM Posts
GROUP BY Drops
HAVING COUNT(*) > 1
)
SELECT COUNT(*) AS newcnt
FROM Posts P
JOIN CTE ON P.Drops = CTE.Drops
Output
newcnt
4
Demo on SQLFiddle
You may use dense_rank() to resolve your problem. if drops has the same ID then dense_rank() will provide the same rank.
Here is the demo.
with cte as
(
select
drops,
count(distinct rnk) as newCnt
from
( select
*,
dense_rank() over (partition by drops order by id) as rnk
from myTable
) t
group by
drops
having count(distinct rnk) > 1
)
select
sum(newCnt) as newCnt
from cte
Output:
|newcnt |
|------ |
| 4 |
First group the count of the ids for your drops and then sum the values greater than 1.
select sum(countdrops) as total from
(select drops , count(id) as countdrops from yourtable group by drops) as temp
where countdrops > 1;

How to count occurences in a list column on postgres?

I've a table with the following structure:
user | medias
----------------------
1 | {ps2,xbox}
1 | {nintendo,ps2}
How do i count the occurrences of each string in an array column?
Expected result:
media | amount
------------------
ps2 | 2
nintendo | 1
xbox | 1
You can unnest the array with a lateral join, then aggregate:
select x.media, count(*) amount
from myable t
cross join lateral unnest(t.medias) x(media)
group by x.media
order by amount desc, x.media

ARRAY_AGG without duplicates

In PostgreSQL database I have table which has columns like ITEM_ID and PARENT_ITEM_ID.
| ITEM_ID | ITEM_NAME | PARENT_ITEM_ID |
|---------|-----------|----------------|
| 1 | A | 0 |
| 2 | B | 0 |
| 3 | C | 1 |
My task to take all values from these columns and put them to one array. In the same time I need delete all duplicates. I started with such SQL query but what the best way to delete duplicates?
SELECT
ARRAY_AGG(ITEM_ID || ',' || PARENT_ITEM_ID)
FROM
ITEMS_RELATIONSHIP
GROUP BY
ITEM_ID
I want such result:
[1,0,2,3]
Right now I have such result:
|{1,0}|
|{2,0}|
|{3,1}|
If you want one array of all item IDs, don't group by item_id. Something like this might be what you want:
select
array_agg(item_id, ',') as itemlist
from
(
select item_id from items_relationship
union
select parent_item_id from items_relationship
) as allitems;
Here is one method to get the parent item ids in with the other item ids:
select array_agg(distinct item_id)
from items_relationship ir cross join lateral
(values (ir.item_id), (ir.parent_item_id)) v(item_id);
This unpivots the data using a lateral join and then aggregates.

Cartesian product without duplicates using select statement on table valued function returning equal number of rows

I'm not sure if the title properly describes my problem but here it goes..
I have a table valued function that takes in comma separated values.
I have two strings which will have same number of comma separated entries.
Following query returns me a Cartesian product
select * from dbo.SplitString('test,test1',',') as a,dbo.SplitString('45,78',',') as b
+-------+-------+
| items | items |
+-------+-------+
| test | 45 |
| test | 78 |
| test1 | 45 |
| test1 | 78 |
+-------+-------+
I need a output with the corresponding values in a row with no duplicates like
+-------+-------+
| items | items |
+-------+-------+
| test | 45 |
| test1 | 78 |
+-------+-------+
Any thoughts on how I can get the above output using the select statement along with function only
This is not possible unless SplitString returns something that you can derive an order from (for example, it could return a column that is the index of the item).
SQL Server is set-based and sets do not have order. Order is specified only explicitly by ordering on a particular colums.
Add an Index int not null column to SplitString and join the results on the index together. Use the query from Jayvee's answer for that (but order by Index, not by colx). I'll copy the query:
--adapted from Jayvee's answer:
select a.colx, b.colx from
(select colx, row_number() over(order by a.Index) rown from dbo.SplitString('test,test1',',')) a
join a
(select colx, row_number() over(order by b.Index) rown from dbo.SplitString('45,78',',')) b
on a.rown=b.rown
assuming a couple of things about your data and function this should work:
select a.colx, b.colx from
(select colx, row_number() over(order by colx) rown from dbo.SplitString('test,test1',',')) a
join a
(select colx, row_number() over(order by colx) rown from dbo.SplitString('45,78',',')) b
on a.rown=b.rown