How to update null values with sequence number? - postgresql

I have a table like this
id val
1 null
2 10
3 null
4 null
5 7
I want to get
id val
1 1
2 10
3 2
4 3
5 7
I tried to do it like this
CREATE SEQUENCE new_val START 1
UPDATE tb1
SET val = new_val
WHERE val is null
But I get an error that new_val doesn't exist

You need to use nextval() and provide the sequence name as a string:
CREATE SEQUENCE new_val 1;
UPDATE tb1
SET val = nextval('new_val')
WHERE val is null;
Another option is to use row_number()
UPDATE tb1
SET val = t.rn
FROM (
select id, row_number() over (order by id) as rn
from tb1
)
WHERE tb1.val is null
and tb1.id = t.id;
id is assumed to be the primary key of the table.

You get the next value of a sequence with the nextval function. The function takes regclass as argument type, for which you can supply the name of the sequence (as single quoted string) or the object identifier:
nextval('new_val')

Related

Preserve the order by ids in postgresql with DISTINCT

I have a query, which returns a simple list of numbers:
SELECT unnest(c) FROM t ORDER BY f LIMIT 10;
And it goes like
1
1
3
4
2
3
5
1
5
6
3
2
I want to keep the result unique, but also preserve order:
1
3
2
4
5
6
select distinct(id) from (select ...) as c;
does not work, beacuse it uses HashAggregate, which breaks order (and processes all rows to return just 10?). I tried GROUP BY, it also uses HashAggregate the whole table(?) and then sort and return 10 required rows.
Is it possible to do it effectively on DB size? Or should I just read rows from my first query in my application and do the stream filtering?
with ordinality is your friend to preserve the order.
select val
from unnest('{1,1,3,4,2,3,5,1,5,6,3,2}'::int[]) with ordinality t(val, ord)
group by val
order by min(ord); -- the first time that this item appeared
val
1
3
4
2
5
6
Or it may make sense to define this function:
create function arr_unique(arr anyarray)
returns anyarray language sql immutable as
$$
select array_agg(val order by ord)
from
(
select val, min(ord) ord
from unnest(arr) with ordinality t(val, ord)
group by val
) t;
$$;
select elem
from (
select
elem, elem_no, row_no, row_number() over (partition by elem order by row_no) as occurence_no
from (
select elem, elem_no, row_number() over () as row_no from t, unnest(c) WITH ORDINALITY a(elem, elem_no)
) A
) B
where occurence_no = 1
order by row_no

How to convert timestamp to numbers

Suppose I have a table like this:
Id Types Timestamp
1 A 2014-02-04 00:00:00
2 A 2014-02-05 00:00:00
1 A 2014-02-05 03:59:00
3 C 2014-05-06 03:59:00
1 B 2014-02-04 03:00:00
2 D 2014-02-05 00:40:00
I would like the output to be like this:
Id 1 2 3 4 5 etc
1 A B A C D ...
2 A D NULL NULL NULL
3 C NULL NULL NULL NULL
Is it possible to make time expresses the type's order.
Thanks for any hints.
Preliminary comments:
SQL can only return a predefined number of columns returned. IMHO, the best you can get is values concatenated in an array.
I have name your input table MyTable and renamed the column Timestamp to MyTimestamp to avoid conflict with the corresponding type's keyword.
You have put C and D in the 1 row of your output. I will treat it as a typo (they are not on ID = 1)
-
WITH RECURSIVE ConcatAndOrder(ID, MyResult, RowNumForOrder, RowCountForOrder) AS (
SELECT ID, ARRAY[Type], RowNumForOrder, RowCountForOrder
FROM IndexedTable
WHERE RowNumForOrder = 1
UNION ALL
SELECT I.ID, MyResult || I.Type, I.RowNumForOrder, I.RowCountForOrder
FROM IndexedTable I
JOIN ConcatAndOrder C on I.ID = C.ID and I.RowNumForOrder = C.RowNumForOrder + 1
), IndexedTable(ID, Type, RowNumForOrder, RowCountForOrder) AS (
SELECT ID, Type,
row_number() OVER (PARTITION BY ID ORDER BY MyTimestamp),
count(*) OVER (PARTITION BY ID)
FROM MyTable
)
SELECT ID, MyResult
FROM ConcatAndOrder
WHERE RowNumForOrder = RowCountForOrder
ORDER BY ID

Update Multiple Columns in One Statement Based On a Field with the Same Value as the Column Name

Not sure if this is possible without some sort of Dynamic SQL or a Pivot (which I want to stay away from)... I have a report that displays total counts for various types/ various status combinations... These types and statuses are always going to be the same and present on the report, so returning no data for a specific combination yields a zero. As of right now there are only three caseTypes (Vegetation, BOA, and Zoning) and 8 statusTypes (see below).
I am first setting up the skeleton of the report using a temp table. I have been careful to name the temp table columns the same as what the "statusType" column will contain in my second table "#ReportData". Is there a way to update the different columns in "#FormattedData" based on the value of the "statusType" column in my second table?
Creation of Formatted Table (for report):
CREATE TABLE #FormattedReport (
caseType VARCHAR(50)
, underInvestigation INT NOT NULL DEFAULT 0
, closed INT NOT NULL DEFAULT 0
, closedDPW INT NOT NULL DEFAULT 0
, unsubtantiated INT NOT NULL DEFAULT 0
, currentlyMonitored INT NOT NULL DEFAULT 0
, judicialProceedings INT NOT NULL DEFAULT 0
, pendingCourtAction INT NOT NULL DEFAULT 0
, other INT NOT NULL DEFAULT 0
)
INSERT INTO #FormattedReport (caseType) VALUES ('Vegetation')
INSERT INTO #FormattedReport (caseType) VALUES ('BOA')
INSERT INTO #FormattedReport (caseType) VALUES ('Zoning')
Creation of Data Table (to populate #FormattedReport):
SELECT B.Name AS caseType, C.Name AS StatusType, COUNT(*) AS Amount
INTO #ReportData
FROM table1 A
INNER JOIN table2 B ...
INNER JOIN table3 C ...
WHERE ...
GROUP BY B.Name, C.Name
CURRENT Update Statement (Currently will be 1 update per column in #FormattedReport):
UPDATE A SET underInvestigation = Amount FROM #ReportData B
INNER JOIN #FormattedReport A ON B.CaseType LIKE CONCAT('%', A.caseType, '%')
WHERE B.StatusType = 'Under Investigation'
UPDATE A SET closed = Amount FROM #ReportData B
INNER JOIN #FormattedReport A ON B.CaseType LIKE CONCAT('%', A.caseType, '%')
WHERE B.StatusType = 'Closed'
...
REQUESTED Update Statement: Would like to have ONE update statement knowing which column to update when "#ReportData.statusType" is the same as a "#FormattedData" column's name. For my "other" column, I'll just do that one manually using a NOT IN.
Assuming I understand the question, I think you can use conditional aggregation for this:
;WITH CTE AS
(
SELECT CaseType
,SUM(CASE WHEN StatusType = 'Under Investigation' THEN Amount ELSE 0 END) As underInvestigation
,SUM(CASE WHEN StatusType = 'Closed' THEN Amount ELSE 0 END) As closed
-- ... More of the same
FROM #ReportData
GROUP BY CaseType
)
UPDATE A
SET underInvestigation = B.underInvestigation
,closed = b.closed
-- more of the same
FROM #FormattedReport A
INNER JOIN CTE B
ON B.CaseType LIKE CONCAT('%', A.caseType, '%')

Postgresql: insert the same data a few times

I have table a, in this table after a SQL request, I have the same records a few times.
Here is my request.
for server_id in (select bs.id from status.servers bs
join settings.config blc on bs.id = blc.server_id
where blc.lane_number = (dataitem->>'No')::SMALLINT AND blc.min_length <= (dataitem->>'len')::real
)
LOOP
insert into a(measurement_id, server_id, status)
VALUES (
measurement_id,server_id,false
);
END LOOP;
And as result i have in table a, records like:
id meas_id serv_id status
1 12 1 f
2 12 1 f
3 12 1 f
i've changed code a little, in working code there are not syntax mistakes
answering
"why i have the same records with dif id?"
table a probably have a default value for column id, so values are taken from sequence. most probably you created it with serial data type... Those results are expected then. If you want to define your value, you should not skip column in scalar list, so
insert into a(measurement_id, server_id, status)
must become
insert into a(id, measurement_id, server_id, status)
and the value passed accordingly...
If you expected one result (assuming it from same value of server_id), you need to add distinct to the
for server_id in (select distinct bs.id from status.servers bs
because currently your select returns three rows with same bs.id as result of a join with three matching rows on join key...

PostgreSQL: set a column with the ordinal of the row sorted via another field

I have a table segnature describing an item with a varchar field deno and a numeric field ord. A foreign key fk_collection tells which collection the row is part of.
I want to update field ord so that it contains the ordinal of that row per each collection, sorted by field deno.
E.g. if I have something like
[deno] ord [fk_collection]
abc 10
aab 10
bcd 10
zxc 20
vbn 20
Then I want a result like
[deno] ord [fk_collection]
abc 1 10
aab 0 10
bcd 2 10
zxc 1 20
vbn 0 20
I tried with something like
update segnature s1 set ord = (select count(*)
from segnature s2
where s1.fk_collection=s2.fk_collection and s2.deno<s1.deno
)
but query is really slow: 150 collections per about 30000 items are updated in 10 minutes about.
Any suggestion to speed up the process?
Thank you!
You can use a window function to generate the "ordinal" number:
with numbered as (
select deno, fk_collection,
row_number() over (partition by fk_collection order by deno) as rn,
ctid as id
from segnature
)
update segnature
set ord = n.rn
from numbered n
where n.id = segnature.ctid;
This uses the internal column ctid to uniquely identify each rows. The ctid comparison is quite slow, so if you have a real primary (or unique) key in that table, use that column instead.
Alternatively without the common table expression:
update segnature
set ord = n.rn
from (
select deno, fk_collection,
row_number() over (partition by fk_collection order by deno) as rn,
ctid as id
from segnature
) as n
where n.id = segnature.ctid;
SQLFiddle example: http://sqlfiddle.com/#!15/e997f/1