Let's say I have the (fictional) table below to store messages in a discussion board, organised by topic, themselves identified by an integer:
CREATE TABLE MESSAGE(
message_global_id SERIAL PRIMARY KEY,
topic_id INTEGER DEFAULT 0,
message_nb_in_topic INTEGER,
text TEXT
);
I want to set the column message_nb_in_topic to the equivalent of a sequence, but that resets for each topic number.
For example,
I post for topic 0 -> the message has message_nb_in_topic = 0
I post for topic 0 -> the message has message_nb_in_topic = 1
I post for topic 1 -> the message has message_nb_in_topic = 0
I post for topic 0 -> the message has message_nb_in_topic = 2
I post for topic 1 -> the message has message_nb_in_topic = 1
I post for topic 2 -> the message has message_nb_in_topic = 0
Is it possible to generate such sequence in postgresql ? I was thinking of some CREATE FUNCTION, but I am not very familiar with their usages.
Any hint would be greatly appreciated.
EDIT
Gordon's solution is the cleanest, however I forgot to mention I was behind some middleware that does not allow me to carry out any complex update.
Yet I ended up with the trigger bellow:
CREATE FUNCTION set_message_nb()
RETURNS trigger AS '
DECLARE
max_for_topic int;
BEGIN
max_for_topic := (SELECT max(message_nb_in_topic) FROM MESSAGE WHERE topic_id=NEW.topic_id);
IF max_for_topic IS NULL THEN
NEW.message_nb_in_topic := 1;
ELSE
NEW.message_nb_in_topic := 1 + max_for_topic;
END IF;
RETURN NEW;
END' LANGUAGE 'plpgsql';
CREATE TRIGGER add_messagenb_at_insert
BEFORE INSERT ON MESSAGE
FOR EACH ROW
EXECUTE PROCEDURE set_message_nb();
You don't need your own function; row_number() does this for you:
select m.*,
row_number() over (partition by topic order by message_global_id) as seqnum
from messages m;
I missed that you want an update:
update messages m
set message_nb_in_topic = mm.seqnum
from (select m.*,
row_number() over (partition by topic order by message_global_id) as seqnum
from messages m
) mm
where m.message_global_id = mm.message_global_id;
Related
SELECT CASE (SELECT Count(1)
FROM wf_item_activity_statuses_v t
WHERE t.activity_label IN ('WAITING_DISB_REQ',
'LOG_DDE',
'LOG_SENDBACK_DDE')
AND t.item_key IN(
SELECT r.i_item_key
FROM wf_t_item_xref r
WHERE r.sz_appl_uniqueid = '20400000988')
)
WHEN 0 THEN
(
delete
from t_col_val_document_uploaded p
WHERE p.sz_application_no = '20400000988'
AND p.sz_collateral_id = 'PROP000000000PRO1701'
AND p.i_item_key = '648197'
AND p.i_document_srno = '27' )
WHEN 1 THEN
(
DELETE
FROM t_col_val_document_uploaded p
WHERE p.sz_application_no = '20400000988'
AND p.sz_collateral_id = 'PROP000000000PRO1701'
AND p.i_document_srno = '28' )
ELSE NULL
END
FROM dual;
You need to recreate your query and make sure to follow the flow of the clauses properly, please check the next two links to get a better understanding :
[ORA-00936: missing expression tips]
How do I address this ORA-00936 error?
Answer: The Oracle oerr utility notes this about the ORA-00936 error:
ORA-00936 missing expression
Cause: A required part of a clause or expression has been omitted. For example, a SELECT statement may have been entered without a list of columns or expressions or with an incomplete expression. This message is also issued in cases where a reserved word is misused, as in SELECT TABLE.
Action: Check the statement syntax and specify the missing component.
The ORA-00936 happens most frequently:
1 - When you forget list of the column names in your SELECT statement.
2. When you omit the FROM clause of the SQL statement.
ora-00936-missing-expression
I hope this can help you.
You cannot use a simple select query like this. You have to use a PL/SQL block like below -
DECLARE NUM_CNT NUMBER := 0;
BEGIN
SELECT Count(1)
INTO NUM_CNT
FROM wf_item_activity_statuses_v t
WHERE t.activity_label IN ('WAITING_DISB_REQ',
'LOG_DDE',
'LOG_SENDBACK_DDE')
AND t.item_key IN(SELECT r.i_item_key
FROM wf_t_item_xref r
WHERE r.sz_appl_uniqueid = '20400000988');
IF NUM_CNT = 0 THEN
delete
from t_col_val_document_uploaded p
WHERE p.sz_application_no = '20400000988'
AND p.sz_collateral_id = 'PROP000000000PRO1701'
AND p.i_item_key = '648197'
AND p.i_document_srno = '27';
ELSIF NUM_CNT = 1 THEN
DELETE
FROM t_col_val_document_uploaded p
WHERE p.sz_application_no = '20400000988'
AND p.sz_collateral_id = 'PROP000000000PRO1701'
AND p.i_document_srno = '28' )
END IF;
END;
I created this query:
SELECT
(SELECT value from UNNEST(project.labels) where key = "project") as project,
ROUND(SUM(cost), 2) as cost
FROM cloud.dataset.billing_export
group by ar
And it returns me something like:
Row | project | cost
1 | PJ1 | 23
2 | PJ2 | 50
Is there a way to create a VIEW for each value (each project)?
I'm trying with UDF and each view must have a name based on the project (eg: view_PJ1) and got something like (but with a lot of errors):
LOOP
SET vars = (SELECT (SELECT value FROM UNNEST(project.labels) WHERE key = "project") AS project
FROM FROM cloud.dataset.billing_export
GROUP BY project);
IF vars=null THEN
LEAVE;
END IF;
CREATE OR REPLACE VIEW `cloud`.`dataset`.AR
AS
SELECT DISTINCT
(SELECT value from UNNEST(project.labels) where key = "project") as project,
ROUND(SUM(cost), 2) as cost
FROM cloud.dataset.billing_export
WHERE project=vars
GROUP BY project;
END LOOP;
Thanks in advance
This is a script that runs inside BigQuery that generates 4 views - giving each view a name coming out of a SQL query:
DECLARE x INT64 DEFAULT 0;
DECLARE rs ARRAY<STRING>;
SET rs = (
WITH data AS (SELECT i FROM `fh-bigquery.public_dump.numbers_255` WHERE i < 4)
SELECT ARRAY_AGG(
'CREATE OR REPLACE VIEW `temp.number' || i
||'` AS SELECT i FROM `fh-bigquery.public_dump.numbers_255` WHERE i=' || i
)
FROM data
);
LOOP
EXECUTE IMMEDIATE(SELECT rs[OFFSET(x)]);
SET x = x + 1;
IF x >= ARRAY_LENGTH(rs) THEN
LEAVE;
END IF;
END LOOP;
The secret is to use EXECUTE IMMEDIATE with a generated string creating the view.
I am trying to get the location of the last time an item was moved via sql function with the code below. Pretty basic, I'm just trying to grab the max date and time. If I run the sql as a regular select and hard code an item number in ATPRIM I get only one location. But if I create this function and then try to run it and then pass the function an item number I get every occurrence in the history file instead of just the MAX which would be the most recent. Also I have tried a Select Distinct and that did not do anything for me.
ATOGST = Item Location
ATPRIM = Item
ATDATE = Date
ATTIME = Time
CREATE FUNCTION ERPLXU/F#QAT1(AATPRIM VARCHAR(10))
RETURNS CHAR(50)
LANGUAGE SQL
NOT DETERMINISTIC
BEGIN DECLARE F#QAT1 CHAR(50) ;
SET F#QAT1 = ' ' ;
SELECT ATOGST
INTO F#QAT1 FROM ERPLXF/QAT as t1
WHERE ATPRIM = AATPRIM
AND ATDATE = (SELECT MAX(ATDATE) FROM ERPLXF/QAT AS T2
WHERE T2.ATPRIM = AATPRIM)
AND ATTIME = (SELECT MAX(ATTIME) FROM ERPLXF/QAT AS T3
WHERE T3.ATPRIM = AATPRIM
AND T3.ATDATE = T1.ATDATE) ;
RETURN F#QAT1 ;
END
EDIT:
So what I am trying to do is get that location and I got it to work on my iSeries in strsql but the problem is we use a web application called Web Object Wizard (WoW) which lets us use sql to make reports that are more user friendly. Below is what I was trying to get to work but the subquery in the select does not work in WoW so that is where I was trying create a function which we know works in other applications.
SELECT distinct t1.atprim, atdesc, dbtabl, dbdtin, dblife, dblpdp,
dbcost, dbbas, dbresv, dbyrdp, dbcurr,
(select atogst
from erplxf.qat as t2
where t1.atprim = t2.atprim and atdate = (select max(atdate) from
erplxf.qat as t3 where t2.atprim = t3.atprim) and attime = (select
max(attime) from erplxf.qat as t4 where t1.atprim = t4.atprim and
t1.atdate = t4.atdate)
) as #113_ToLoc
FROM erplxf.qat as t1 join erplxf.qdb on atassn = dbassn
where dbrcid = 'DB'
and dbcurr != 0
So instead of that subquery at the end of the select it would just be
, erplxu.f#qat1(atprim) as #113_ToLoc
Try this:
CREATE FUNCTION ERPLXU/F#QAT1(AATPRIM VARCHAR(10))
RETURNS CHAR(50)
LANGUAGE SQL
RETURN
SELECT ATOGST
FROM ERPLXF/QAT
WHERE ATPRIM = AATPRIM
ORDER BY ATDATE DESC, ATTIME DESC
FETCH FIRST 1 ROW ONLY;
Given a table, for example Article(Id,Body,Revisions), I would like to increment the Revisions attribute, and, once a certain limit is reached (it's a constant provided by the developer), an error should be thrown. Is this possible to achieve with a single UPDATE ... SET statement in T-SQL?
What I've done:
To increment Revisions attribute by one, I solved as shown here: Is UPDATE command thread safe (tracking revisions) in MS SQL.
Problem
To find a way that is thread safe, which would allow incrementation of Revisions until a certain upper bound is reached.
Context
Since I'm using EF, the ideal solution would be to either thrown an error or specify a flag of some sort. The code I'm using (shown below) is encapsulated into a try-catch:
context.Database.ExecuteSqlCommand("UPDATE dbo.Articles SET Revisions = Revisions + 1 WHERE Id=#p0;", articleId);
You could do this with a WHERE clause in your UPDATE statement, which would do the test. If the test fails, the update will not happen and your call with context.Database.ExecuteSqlCommand will return 0 instead of 1.
In case of a limit of 1000, the update SQL would be:
count = context.Database.ExecuteSqlCommand(
"UPDATE dbo.Articles SET Revisions = Revisions + 1 WHERE Id=#p0 AND Revisions < 1000;", articleId);
Then afterwards you would test whether count == 0 and raise an error message if so.
Use a CHECK constraint. No update statement can violate bounds that are implemented by a CHECK constraint. Not even an update statement issued by a sleep-deprived DBA at the console.
create table article (
id integer primary key,
body nvarchar(max) not null,
-- Allow six versions. (Original plus five revisions.)
revisions integer not null
check (revisions between 0 and 5)
);
insert into article values (1, 'a', 0);
update article
set body = 'b', revisions = 1
where id = 1;
update article
set body = 'c', revisions = 2
where id = 1;
-- Other updates . . .
-- This update will *always* fail with an error.
update article
set body = 'f', revisions = 6
where id = 1;
Kind of whacked but
UPDATE dbo.Articles
SET Revisions = Revisions + 1
WHERE Id=#p0
AND sqrt(Revisions - #MaxRevisions - 2) >= 0;
If Revisions - #Revisions - 2 is negative it will throw an
An invalid floating point operation occurred.
error
If the limit is 100,
UPDATE Article
SET Revisions = LEAST(Revisions + 1, 100)
where Id = #p0
Right now I have this code to find next and previous rows using SQL Server 2005. intID is the gallery id number using bigint data type:
SQL = "SELECT TOP 1 max(p.galleryID) as previousrec, min(n.galleryID) AS nextrec FROM gallery AS p CROSS JOIN gallery AS n where p.galleryid < '"&intID&"' and n.galleryid > '"&intID&"'"
Set rsRec = Server.CreateObject("ADODB.Recordset")
rsRec.Open sql, Conn
strNext = rsRec("nextrec")
strPrevious = rsRec("previousrec")
rsRec.close
set rsRec = nothing
Problem Number 1:
The newest row will return nulls on the 'next record' because there is none. The oldest row will return nulls because there isn't a 'previous record'. So if either the 'next record' or 'previous record' doesn't exist then it returns nulls for both.
Problem Number 2:
I want to create a stored procedure to call from the DB so intid can just be passed to it
TIA
This will yield NULL for previous on the first row, and NULL for next on the last row. Though your ordering seems backwards to me; why is "next" lower than "previous"?
CREATE PROCEDURE dbo.GetGalleryBookends
#GalleryID INT
AS
BEGIN
SET NOCOUNT ON;
;WITH n AS
(
SELECT galleryID, rn = ROW_NUMBER()
OVER (ORDER BY galleryID)
FROM dbo.gallery
)
SELECT
previousrec = MAX(nA.galleryID),
nextrec = MIN(nB.galleryID)
FROM n
LEFT OUTER JOIN n AS nA
ON nA.rn = n.rn - 1
LEFT OUTER JOIN n AS nB
ON nB.rn = n.rn + 1
WHERE n.galleryID = #galleryID;
END
GO
Also, it doesn't make sense to want an empty string instead of NULL. Your ASP code can deal with NULL values just fine, otherwise you'd have to convert the resulting integers to strings every time. If you really want this you can say:
previousrec = COALESCE(CONVERT(VARCHAR(12), MIN(nA.galleryID)), ''),
nextrec = COALESCE(CONVERT(VARCHAR(12), MAX(nB.galleryID)), '')
But this will no longer work well when you move from ASP to ASP.NET because types are much more explicit. Much better to just have the application code be able to deal with, instead of being afraid of, NULL values.
This seems like a lot of work to get the previous and next ID, without retrieving any information about the current ID. Are you implementing paging? If so I highly recommend reviewing this article and this follow-up conversation.
Try this (nb not tested)
SELECT TOP 1 max(p.galleryID) as previousrec, min(n.galleryID) AS nextrec
FROM gallery AS p
CROSS JOIN gallery AS n
where (p.galleryid < #intID or p.galleryid is null)
and (n.galleryid > #intID or n.galleryid is null)
I'm assuming you validate that intID is an integer before using this code.
As for a stored procedure -- are you asking how to write a stored procedure? If so there are many tutorials which are quite good on the web.
Since Hogan contributed with the SQL statement, let me contribute with the stored proc part:
CREATE PROCEDURE spGetNextAndPreviousRecords
-- Add the parameters for the stored procedure here
#intID int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT TOP 1 max(p.galleryID) as previousrec, min(n.galleryID) AS nextrec
FROM gallery AS p
CROSS JOIN gallery AS n
where (p.galleryid < #intID or p.galleryid is null)
and (n.galleryid > #intID or n.galleryid is null)
END
And you call this from code as follows (assuming VB.NET):
Using c As New SqlConnection(ConfigurationManager.ConnectionStrings("ConnectionString").ConnectionString)
c.Open()
Dim command = New SqlCommand("spGetNextAndPreviousRecords")
command.Parameters.AddWithValue("#intID", yourID)
Dim reader as SqlDataReader = command.ExecuteReader()
While(reader.Read())
' read the result here
End While
End Using