I am doing an example of indexing with JSONB in PostgreSQL and want add random uuid to a piece of JSON like below. However I can't get the syntax just right the closest I have got is "{"lookup_id": " || uuid || "}".
But I require
{"lookup_id": "92b3b21a-a87c-1798-5d91-3dbf3043c209"}
My code is:
INSERT INTO test (id, json)
SELECT x.id, '{
"lookup_id": " || uuid || "
}'::jsonb
FROM generate_series(1,100) AS x(id),
uuid_in(md5(now()::text)::cstring) AS uuid;
you can use row_to_json function:
select x.id, row_to_json(r.*)::jsonb
from generate_series(1,100) AS x(id)
cross join (select uuid_in(md5(now()::text)::cstring) as lookup_id) as r;
update
first, you can use uuid so you can create unique uids:
CREATE EXTENSION "uuid-ossp";
with cte as (
select
*, uuid_generate_v4() as uuid
from generate_series(1,5) AS x(id)
)
select distinct uuid from cte
------------------------------------------------
"e980c784-8aae-493f-90fb-1091280fe4f7"
"45a80660-3be8-4538-a039-13d97d6306af"
"5380f285-5d6b-467a-a83a-7fdc5c0ebc4c"
"7a435b36-95d3-49fc-808f-359838a866ed"
"3164a544-a2c9-4cd0-b0c4-199a99986cea"
next, merging this to your existing json. The stupid and easiest way for now could be something like this:
with cte as (
select
'{"a":1}'::json as j, uuid_generate_v4() as uuid
from generate_series(1,5) AS x(id)
)
select
left(j::text, length(j::text) - 1) || ', "uuid":' || to_json(uuid) || '}'
from cte
But you can also write some function to merge jsons together, or you can use hstore extension to merge jsons together:
with cte as (
select
id, '{"a":1, "b":2}'::json as data, uuid_generate_v4() as uuid
from generate_series(1,5) AS x(id)
), cte2 as (
select
id,
(
select hstore(array_agg(r.key), array_agg(r.value))
from (
select *
from json_each_text(c.data) as j
union all
select 'uuid', c.uuid::text
) as r
) as data
from cte as c
)
select
id, hstore_to_json(data)
from cte2
And I'm sure bigger experts on PostgreSQL could advice more elegant way to merge jsons together
Related
Recently started working on Postgres and need to pivot data.
I wrote the following query:
select *
from crosstab (
$$
with tmp_kv as (
select distinct pat_id
,col.name as key, replace(replace(replace(value, '[',''), ']', ''),'"','') as value
from (
select p.Id as pat_id, nullif(kv.key,'undefined')::int as key, trim(kv.value::text,'"') as value
from pat_table p
left join e_table e on e.pat_id = p.id and e.id is null
,jsonb_each_text(p.data) as kv
) t
left join lateral (
select name::text as name from public.config_fields fld
where id = t.key
) col on true
)
select pat_id, key, value
from tmp_kv
where nullif(trim(key),'') is not null
order by pat_id, key
$$,$$
select distinct key from tmp_kv -- (Get error "relation "tmp_kv" does not exist" )
where nullif(trim(key),'') is not null
order by 1
$$
) as (
pat_id bigint
...
...
);
Query works if I take the WITH clause out into temporary table. But will be deploying it to production with read replicas, so need it to be working with a CTE. Is there a way?
The two queries passed as strings to the crosstab() function are separate queries.
A CTE can only be attached to a single query.
What you ask for is strictly impossible.
Since you have to spell out the (static) return type for crosstab() anyway, and the result of the query in the 2nd parameter has to match that, it's pointless to use a query with a dynamic result as 2nd parameter to begin with.
I have a select query return and it shows the result like below:
select * from table gives the result like below
I have parameter called Apple If I pass the parameter somewhere in query I should get the result like below
How to get this in postgresql. If anyone knows please share the answer below.
I would do this with a helper function for clarity. And it might be reusable.
create or replace function filter_jsonb_array(arr jsonb, fruit text)
returns jsonb language sql immutable as
$$
select coalesce
(
(select jsonb_agg(j) from jsonb_array_elements(arr) j where j ->> 'fruit' = fruit),
'[]'::jsonb
);
$$;
and then
select "Column_A", "Column_B", filter_jsonb_array("Column_JSONARRAY", 'Apple') from table_;
If you do not want a function then the function body can be placed directly into the select query.
select
"Column_A",
"Column_B",
coalesce
(
(select jsonb_agg(j) from jsonb_array_elements("Column_JSONARRAY") j where j ->> 'fruit' = 'Apple'),
'[]'::jsonb
) "Column_JSONARRAY"
from table_;
Considering your datatype of column Column_JSONARRAY is JSONB, try This:
with cte as (
SELECT column_a, column_b, (column_jsonarray ->> ( index_-1 )::int)::jsonb AS "column_jsonarray"
FROM table_
CROSS JOIN jsonb_array_elements(column_jsonarray)
WITH ORDINALITY arr(array_,index_)
WHERE array_->>'fruit' in ('Apple')
)
select t1.column_a, t1.column_b, jsonb_agg(t2.column_jsonarray)
from table_ t1
left join cte t2 on t1.column_a =t2.column_a and t1.column_b =t2.column_b
group by t1.column_a, t1.column_b
I have a view from a query select * from table which returns the below data
I want to group by the name column which have same name and merge the JSONArray column like mentioned below
One way to do this, is to unnest the arrays and then aggregate them back:
select t.id, t.name, jsonb_agg(a.e)
from the_table t
cross join lateral jsonb_array_elements(t.json_array) as a(e)
group by t.id, t.name;
If you do that a lot, a custom aggregate makes this a bit easier to user (but probably not faster)
create function jsonb_array_combine(p_one jsonb, p_two jsonb)
returns jsonb
as
$$
select jsonb_agg(e)
from (
select e
from jsonb_array_elements(p_one) as o(e)
union all
select e
from jsonb_array_elements(p_two) as t(e)
) t
$$
language sql
immutable;
create aggregate jsonb_array_agg(jsonb)
(
SFUNC = jsonb_array_combine(jsonb, jsonb),
STYPE = jsonb
);
Then you can use it like this:
select t.id, t.name, jsonb_array_agg(t.json_array)
from the_table t
group by t.id, t.name;
I have a simple data set that looks like this:
Name Code
A A-One
A A-Two
B B-One
C C-One
C C-Two
C C-Three
I want to output it so it looks like this:
Name Code1 Code2 Code3 Code4 Code...n ...
A A-One A-Two
B B-One
C C-One C-Two C-Three
For each of the 'Name' values, there can be an undetermined number of 'Code' values.
I have been looking at various examples of Pivot SQL [including simple Pivot sql and sql using the XML function?] but I have not been able to figure this out - or to understand if it is even possible.
I would appreciate any help or pointers.
Thanks!
Try it like this:
DECLARE #tbl TABLE([Name] VARCHAR(100),Code VARCHAR(100));
INSERT INTO #tbl VALUES
('A','A-One')
,('A','A-Two')
,('B','B-One')
,('C','C-One')
,('C','C-Two')
,('C','C-Three');
SELECT p.*
FROM
(
SELECT *
,CONCAT('Code',ROW_NUMBER() OVER(PARTITION BY [Name] ORDER BY Code)) AS ColumnName
FROM #tbl
)t
PIVOT
(
MAX(Code) FOR ColumnName IN (Code1,Code2,Code3,Code4,Code5 /*add as many as you need*/)
)p;
This line
,CONCAT('Code',ROW_NUMBER() OVER(PARTITION BY [Name] ORDER BY Code)) AS ColumnName
will use a partitioned ROW_NUMBER in order to create numbered column names per code. The rest is simple PIVOT...
UPDATE: A dynamic approach to reflect the max amount of codes per group
CREATE TABLE TblTest([Name] VARCHAR(100),Code VARCHAR(100));
INSERT INTO TblTest VALUES
('A','A-One')
,('A','A-Two')
,('B','B-One')
,('C','C-One')
,('C','C-Two')
,('C','C-Three');
DECLARE #cols VARCHAR(MAX);
WITH GetMaxCount(mc) AS(SELECT TOP 1 COUNT([Code]) FROM TblTest GROUP BY [Name] ORDER BY COUNT([Code]) DESC)
SELECT #cols=STUFF(
(
SELECT CONCAT(',Code',Nmbr)
FROM
(SELECT TOP((SELECT mc FROM GetMaxCount)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) t(Nmbr)
FOR XML PATH('')
),1,1,'');
DECLARE #sql VARCHAR(MAX)=
'SELECT p.*
FROM
(
SELECT *
,CONCAT(''Code'',ROW_NUMBER() OVER(PARTITION BY [Name] ORDER BY Code)) AS ColumnName
FROM TblTest
)t
PIVOT
(
MAX(Code) FOR ColumnName IN (' + #cols + ')
)p;';
EXEC(#sql);
GO
DROP TABLE TblTest;
As you can see, the only part which will change in order to reflect the actual amount of columns is the list in PIVOTs IN() clause.
You can create a string, which looks like Code1,Code2,Code3,...CodeN and build the statement dynamically. This can be triggered with EXEC().
I'd prefer the first approach. Dynamically created SQL is very mighty, but can be a pain in the neck too...
I have the following query:
WITH Orders(Id)
AS (
SELECT DISTINCT anfrageid FROM MPHotlineAnfrageAnhang
)
SELECT Id,
(
SELECT CONVERT(VARCHAR(255),anfragetext) + ' | '
FROM MPHotlineAnfrageAnhang
WHERE anfrageid = Id
ORDER BY anfrageid, erstelltam
FOR XML PATH('')
) AS Descriptions
FROM Orders
Its concatenates varchar values of diferents rows grouped by an id. But now i want to include it as a subquery and it gives some errors i cant solve.
Simplified example of use:
select descriptions from
(
WITH Orders(Id)
AS (
SELECT DISTINCT anfrageid FROM MPHotlineAnfrageAnhang
)
SELECT Id,
(
SELECT CONVERT(VARCHAR(255),anfragetext) + ' | '
FROM MPHotlineAnfrageAnhang
WHERE anfrageid = Id
ORDER BY anfrageid, erstelltam
FOR XML PATH('')
) AS Descriptions
FROM Orders
) as tx where id=100012
Errors (Aproximate translation from spanish):
-Incorrect sintaxis near 'WITH'.
-Incorrect sintaxis near 'WITH'. If the instruction is a common table expression or a xmlnamespaces clause, the previous instruction must end with semicolon.
-Incorrect sintaxis near ')'.
What im doing wrong?
Chain your queries as CTEs, like this:
WITH Orders(Id) AS (
SELECT DISTINCT anfrageid
FROM MPHotlineAnfrageAnhang
),
OrderDescs AS (
SELECT Id, (
SELECT CONVERT(VARCHAR(255),anfragetext) + ' | '
FROM MPHotlineAnfrageAnhang
WHERE anfrageid = Id
ORDER BY anfrageid, erstelltam
FOR XML PATH('')
) AS Description
FROM Orders
)
SELECT Description
FROM OrderDescs
WHERE Id = 100012
You can have as many CTEs as you like, each referencing the previous, before the actual query.
Also, you need to have a semi-colon before a WITH statement.
;with Orders(id)
Or terminate the previous statement with the semi-colon instead.