PostgreSql: how to update table from array? - postgresql

How to update table fields from array to avoid temp table using?
User passes array to reorder table.
create TABLE items
(
id serial primary key,
name text,
select_order int
)
insert into items(name, select_order)
values
('cucumber',0),
('milk',1),
('coffee',2),
('water',3)
select * from items order by select_order;
DO
$body$
DECLARE var_array int[]:='{3,0,2,1}';
BEGIN
update items ??
END;
$body$
The final result in this example should be
select * from items order by select_order;
name select_order
------------------------
water 0
cucumber 1
coffee 2
milk 3

Assuming the index in the array corresponds to the value in select_order the following query returns the new and old value:
select i.id, i.select_order, t.new_sort
from items i
join lateral unnest(array[3,0,2,1]) with ordinality as t(new_sort, idx) on t.idx - 1 = i.select_order
Now this can be used to update the target table:
update items
set select_order = r.new_sort
from (
select i.id, i.select_order, t.new_sort
from items i
join lateral unnest(array[3,0,2,1]) with ordinality as t(new_sort, idx) on t.idx - 1 = i.select_order
) r
where r.id = items.id;
This also assumes that select_order starts a 0 and has no gaps.

Related

How to add items to local function array in postgress?

I need to do multiple inserts in the table that number depends on how many records we got from select. I need to iterate over records from select and then insert them to another table. I want to get all new Ids from insert to array to use them later in the following inserts, how can I do this?
I can't collect them using select after insert, because there can be old records.
for record in (select test, test1, test2
from public.a
join public.b on a.reg_id = b.id
where a.id = arg_id) loop
(INSERT into public.c
( a, b, c)
(select test, test1, test2
from record)--need to get ids from this
end loop;
---
some block where I have old_ids
---
--to insert them there
insert into public.d(d,e,f,g)values(..,..,old_id,(id from previous insert))
upd
Tried to make like this:
with a2 as(
INSERT INTO public.reg
(name_, code, state)
(select a.secondname, a.code, b.state_name--multiple rows from select
from public.client a
left join public.states b on a.state_id = b.id
where a.id = id_p) RETURNING id
)
INSERT INTO public.request
(phone, address, qty, prod_id, reg_id)
(select phone, address, qty, prod_id, (select id from a2)--maybe something wrong there, but error happend before
from public.shp a
where a.id = id_p);
but getting an error: more than one row returned by a subquery used as an expression
Demonstration of using the result of a query:
\i tmp.sql
CREATE TABLE aa(aa integer not null primary key);
CREATE TABLE bb(bb integer not null primary key);
CREATE TABLE cc(cc integer not null primary key);
WITH x0 AS (
INSERT INTO aa(aa) values (1),(2),(3)
returning aa
)
, x1 AS (
INSERT INTO bb(bb)
SELECT aa*aa from x0
returning bb
)
, x2 AS (
INSERT INTO cc(cc)
SELECT bb*bb from x1
returning cc
)
-- main query
SELECT *
FROM x2
;
-- Check cc
SELECT *
FROM cc
;
Output:
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
CREATE TABLE
CREATE TABLE
cc
----
1
16
81
(3 rows)
cc
----
1
16
81
(3 rows)

how to sort rows according to multiple dependencies

The table has many columns, but for the problematic part let's assume only two, [ID] and [dependency].
[Dependency] means which [ID]s should be listed before this [ID].
Each row has its unique [ID] but it might have none, one or more dependencies in the [Dependency] column
ID
Dependency
1
4
2
null
3
1,2
4
2
5
1,2,4
Expected Result
ID
Dependency
2
null
4
2
1
4
3
1,2
5
1,2,4
I have no prior experience in Postgres, I found this very useful:
SELECT aa.dep_id::integer FROM unnest(string_to_array(ss.dependency, ',')) aa(dep_id)
But still, I can't make it right.
EDIT: Added one row with 3 dependencies'
http://sqlfiddle.com/#!15/894c3/4
Use a recursive CTE:
WITH RECURSIVE o AS (
SELECT ss.id, ss.dependency,
1 AS level
FROM ss
WHERE dependency IS NULL
UNION ALL
SELECT ss.id, ss.dependency,
o.level + 1
FROM ss
JOIN o
ON o.id IN (SELECT x
FROM unnest(ss.dependency) AS x(x)
)
)
SELECT o.id, o.dependency
FROM o
GROUP BY o.id, o.dependency
ORDER BY max(o.level);
My working solution, I hope it can be improved
DO $do$
DECLARE v_RowCountInt Int;
--copy all values to temp table
begin
--This is the origin table, all rows to sort
drop table if exists _tempAll;
create temp table _tempAll as select id, dependency from XXXXX;
-- create temp table for results
drop table if exists _tempSort;
create temp table _tempSort (
id integer
,dependency varchar
,pos serial primary key);
-- move all IDs with no depencencies
WITH tmp AS (DELETE FROM _tempAll where dependency is null RETURNING id, dependency)
INSERT INTO _tempSort ( id, dependency)
SELECT id, dependency FROM tmp;
GET DIAGNOSTICS v_RowCountInt = ROW_COUNT;
while v_RowCountInt>0 loop
-- move rows with all dependencies met
WITH tmp AS (DELETE FROM _tempAll a
where a.id in(SELECT a.id FROM _tempSort s inner join _tempAll a on
s.id in (select split.dep_sid::integer from unnest(string_to_array(a.dependency, ',')) split(dep_sid))
group by a.id, a.dependency
-- verify all dependencies are already sorted
having count(*)=(CHAR_LENGTH(a.dependency) - CHAR_LENGTH(REPLACE(a.dependency, ',', ''))+1))
RETURNING a.id, a.dependency)
INSERT INTO _tempSort (id, dependency)
SELECT id, dependency FROM tmp;
GET DIAGNOSTICS v_RowCountInt = ROW_COUNT;
end loop;
end;
$do$

postgresql procedure for fetching top 10%,20% and 30% values of the total values

i have a table named Scoreboard which contains a field named as score which is an array containing values 27,56,78,12,89,77,34,23,90,87,33,55,30,67,76,87,56and i want to write a PostgreSQL procedure to fetch three categories
category 1 = top 10% values of the total no of values in array
category 2 = top 20% values of the total no of values in array
category 3 = top 30% values of the total no of values in array
and put it in an array in the same format i.e
[category 1 values,category 2 values,category 3 values]
smth like this should do:
t=# with p as (
with ntile as (
with v as (
select unnest('{27,56,78,12,89,77,34,23,90,87,33,55,30,67,76,87,56}'::int[]) a
)
select a,ntile(10) over(order by a desc)
from v
)
select distinct string_agg(a::text,',') over (partition by ntile),ntile
from ntile
where ntile <=3 order by ntile
)
select array_agg(concat('category ',ntile,' ',string_agg))
from p;
array_agg
------------------------------------------------------------
{"category 1 90,89","category 2 87,87","category 3 78,77"}
(1 row)
Time: 0.493 ms
I am assuming , you have a table with one column as id and another one is an array type. Based on assumption
I have created table as below and inserted two values to it.
create table test_array (id int, values int[]);
insert into test_array values(1 ,'{27,56,78,12,89,77,34,23,90,87,33,55,30,67,76,87,56}' );
insert into test_array values(2 ,'{72,65,84,21,98,77,43,32,9,78,41,66,3,76,67,88,56}' );
Below is function which is used to find category as mentioned by you. If you do not have any id column in your table
then you can add number by using window function hint: row_number().
create or replace function find_category() returns table(category text[]) as
$$
BEGIN
return query with unnestColumn as (
select id, unnest(values) as values, ntile(10) over(partition by id order by unnest(values) desc) as ntilenumber
from test_array
) ,groupedCategory as ( select id, ntilenumber, string_agg(values::text,',') as combinedvalues from unnestColumn
where
ntilenumber <= 3
group by id, ntilenumber )
select array_agg(concat('Categoty',ntilenumber, ' ', combinedvalues ))
from groupedCategory
group by id;
END;
$$
language 'plpgsql';
Execute below function to check output.
select * from find_category();

Most effective way to get value if select count(*) = 1 with grouping

Lets say I have table with ID int, VALUE string:
ID | VALUE
1 abc
2 abc
3 def
4 abc
5 abc
6 abc
If I do select value, count(*) group by value I should get
VALUE | COUNT
abc 5
def 1
Now the tricky part, if there is count == 1 I need to get that ID from first table. Should I be using CTE? creating resultset where I will add ID string == null and run update b.ID = a.ID where count == 1 ?
Or is there another easier way?
EDIT:
I want to have result table like this:
ID VALUE count
null abc 5
3 def 1
If your ID values are unique, you can simply check to see if the max(id) = min(id). If so, then use either one, otherwise you can return null. Like this:
Select Case When Min(id) = Max(id) Then Min(id) Else Null End As Id,
Value, Count(*) As [Count]
From YourTable
Group By Value
Since you are already performing an aggregate, including the MIN and Max function is not likely to take any extra (noticeable) time. I encourage you to give this a try.
The way I would do it would indeed be a CTE:
using #group AS (SELECT value, Count(*) as count from MyTable GROUP BY value HAVING count = 1)
SELECT MyTable.ID, #group.value, #group.count from MyTable
JOIN #group ON #group.value = MyTable.value
When using group by, after the group by statement you can use a having clause.
So
SELECT [ID]
FROM table
GROUP BY [VALUE]
HAVING COUNT(*) = 1
Edit: with regards to your edited question: this uses some fun joins and unions
CREATE TABLE #table
(ID int IDENTITY,
VALUE varchar(3))
INSERT INTO #table (VALUE)
VALUES('abc'),('abc'),('def'),('abc'),('abc'),('abc')
SELECT * FROM (
SELECT Null as ID,VALUE, COUNT(*) as [Count]
FROM #table
GROUP BY VALUE
HAVING COUNT(*) > 1
UNION ALL
SELECT t.ID,t.VALUE,p.Count FROM
#table t
JOIN
(SELECT VALUE, COUNT(*) as [Count]
FROM #table
GROUP BY VALUE
HAVING COUNT(*) = 1) p
ON t.VALUE=p.VALUE
) a
DROP TABLE #table
maybe not the most efficient but something like this works:
SELECT MAX(Id) as ID,Value FROM Table WHERE COUNT(*) = 1 GROUP BY Value

Update a value in DB with ascending values with no duplicates?

I am using this query to update a column with ascending values:
DECLARE #counter NUMERIC(10, 0)
SET #counter = 1400000
UPDATE SomeTable
SET #counter = SomeColumn = #counter + 1
Question is, how can I not put duplicates there? For example the column already has 1400002 as value. Normally it has NULLs, but sometimes it doesnt. I could add
where SomeColumn is null
but this would not avoid duplicates. Any ideas?
Thanks
I am not sure that this will help or not but you can put your existing data into temp table and then use that temp table to remove duplicates like:
WHERE (#counter + 1) not in ( select SomeColumn from #temp)
If above is not correct then please explain your question a little more.
This worked for me in SQL Server 2008:
DECLARE #StartNumber int, #EndNumber int;
SET #StartNumber = 100;
SELECT #EndNumber = #StartNumber + COUNT(*) - 1 FROM SomeTable;
WITH numbers AS (
SELECT #StartNumber AS Value
UNION ALL
SELECT
Value + 1
FROM numbers
WHERE Value < #EndNumber
),
validnumbers AS (
SELECT
n.Value,
rownum = ROW_NUMBER() OVER (ORDER BY n.Value)
FROM numbers n
LEFT JOIN SomeTable t ON n.Value = t.Value
WHERE t.Value IS NULL
),
RowsToUpdate AS (
SELECT
Value,
rownum = ROW_NUMBER() OVER (ORDER BY Value)
FROM SomeTable
WHERE Value IS NULL
OR Value NOT IN (SELECT Value FROM numbers)
)
UPDATE r
SET Value = v.Value
FROM RowsToUpdate r
INNER JOIN validnumbers v ON v.rownum = r.rownum;
Basically, it implements the following steps:
Create a number table.
Exclude the numbers present in SomeTable.
Rank the rest of the rows.
Exclude the values from SomeTable that are present in the number table.
Rank the rest of the rows.
Update the ranked rows of SomeTable from the ranked number list.
Not sure how good this solution would be for big tables, though...