No Such Column in Correlated Subquery - sql

So I wrote a query pretty much the same as the one described in this answer. However I'm getting an error stating that: No such column b.when.
My select statement:
SELECT
mileage,
(SELECT b.mileage FROM MileageEvents as b WHERE `b.when` < `a.when` ORDER BY `b.when` DESC LIMIT 1) as last_mileage,
gallons,
cost_per_gallon,
`when`
FROM MileageEvents as a
I know I've written such queries previously, but I can't seem to figure out what is going on with this query. What am I doing wrong?
A dump of my database:
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "MileageEvents" (
"mileage" INTEGER,
"when" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"cost_per_gallon" INTEGER,
"gallons" INTEGER,
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"car_id" INTEGER NOT NULL
);
INSERT INTO MileageEvents VALUES(150000,'2019-03-12',3.3500000000000000888,12,1,1);
INSERT INTO MileageEvents VALUES(150300,'2019-03-19',3.25,12,2,1);
INSERT INTO MileageEvents VALUES(150693,'2019-03-22',3.4500000000000001776,12,3,1);
INSERT INTO MileageEvents VALUES(151000,'2019-03-25',3.3900000000000001243,12,4,1);
INSERT INTO MileageEvents VALUES(151600,'2019-04-01',2.25,12,5,1);
INSERT INTO MileageEvents VALUES(151883,'2019-06-10 23:01:43',2.4500000000000001776,11.695999999999999729,6,1);
DELETE FROM sqlite_sequence;
INSERT INTO sqlite_sequence VALUES('MileageEvents',6);
COMMIT;

(First, I'd use normal double quote escaping instead of MySQL style backticks, or better yet, an identifier that's not a reserved keyword)
You need to use b."when". What you're doing treats the entire string b.when as an identifier, with no split between table name and column name.
Rewritten:
SELECT
mileage,
(SELECT b.mileage FROM MileageEvents as b WHERE b."when" < a."when" ORDER BY b."when" DESC LIMIT 1) as last_mileage,
gallons,
cost_per_gallon,
"when"
FROM MileageEvents as a;
Note: if using sqlite 3.25 or newer, you can avoid the subquery completely:
SELECT
mileage,
lag(mileage) OVER (ORDER BY "when") AS last_mileage,
gallons,
cost_per_gallon,
"when"
FROM MileageEvents;

Related

IF UPDATE RETURNS no rows been updated INSERT data

I would like to UPDATE records if they exist otherwise INSERT values into the table in one query for my Java project.
DO
$do$
BEGIN
IF EXISTS
(SELECT 1 NOTES WHERE USER_ID = '2' AND EVENT_ID ='4') THEN
UPDATE NOTES SET NOTES='MY notes' WHERE USER_ID = '2' AND EVENT_ID ='4'
ELSE
INSERT INTO NOTES VALUES
((SELECT max(NOTES_ID)+1 FROM NOTES), '4, 2','1',''); END IF; END
$do$
This is what I have until now however since Postgres 9.5 something must have changes and I get:
ERROR: syntax error at or near "ELSE"
LINE 7: ELSE
There are many ways to achieve this. You can use the new UPSERT syntax, EXISTS (just fix the semicolon as Clodoaldo mentioned) or using FOUND variable:
DO $do$
BEGIN
UPDATE NOTES SET NOTES='MY notes' WHERE USER_ID = '2' AND EVENT_ID ='4';
IF NOT FOUND THEN
INSERT INTO NOTES VALUES ((SELECT max(NOTES_ID)+1 FROM NOTES), '4, 2','1','');
END IF;
END $do$;
But beware of racing conditions.
There is no need for a PL/pgSQL block.
This can be done with a single SQL statement using writeable CTE:
with updated as (
UPDATE NOTES
SET NOTES='MY notes'
WHERE USER_ID = 2
AND EVENT_ID = 4
returning *
)
INSERT INTO NOTES (notes_id, event_id, user_id, notes)
select (SELECT max(NOTES_ID)+1 FROM NOTES), --<< Do NOT do this!!
4,
2,
'My notes'
where not exists (select * from updated);
Note that generating "unique" IDs using max(notes_id) + 1 is not safe for concurrent usage and will not scale well if the table size increase.
Also: you should use numeric literals for numbers, not strings. 2 is a number '2' is a string.
If you have a unique index on (user_id, event_id) (which you should given the nature of your question) you can also use insert on conflict which was introduced in Postgres 9.5
INSERT INTO NOTES (notes_id, event_id, user_id, notes)
VALUES (
(SELECT max(NOTES_ID)+1 FROM NOTES), --<< Do NOT do this!
4, 2, 'My notes')
ON CONFLICT (user_id, event_id)
DO UPDATE set notes = excluded.notes;
A word of warning:
If you want to generate unique IDs in a reliable, concurrency-safe and scalable way you have to use a sequence. Do NOT use the anti-pattern max() + 1

Postgres insert on conflict update using other table

I having syntax error for the following sql which spent an hour but cant find any answer form postgres documentation.
CREATE TABLE transaction (userid SMALLINT, point INT, timestamp TIMESTAMPTZ);
CREATE TABLE point (userid SMALLINT PRIMARY KEY, balance1 INT, balance2 INT);
CREATE TABLE settings (userid SMALLINT, bonus INT);
INSERT INTO settings VALUES (1, 10); -- sample data
WITH trans AS (
INSERT INTO transaction (userid, point, timestamp)
VALUES ($1, $2, NOW())
RETURNING *
)
INSERT INTO point (userid, balance1)
SELECT userid, point
FROM trans -- first time
ON CONFLICT (userid) DO UPDATE
SET balance1=point.balance1 + excluded.balance1,
balance2=point.balance2 + excluded.balance1 + settings.bonus
FROM settings -- tried USING, not work
WHERE settings.userid=excluded.userid;
My question is how can I include extra table without using FROM during update? My target is to keep all into 1 query.
It's not totally clear to me what you are trying to do. The way I understand it, is that you are trying to use the value from settings.bonus when updating the row in case on conflict kicks in.
This can be done using a co-related sub-query in the UPDATE part:
WITH trans (userid, point, timestamp) AS (
INSERT INTO transaction (userid, point, timestamp)
VALUES (1, 1, NOW())
RETURNING *
)
INSERT INTO point (userid, balance1)
SELECT trans.userid, trans.point
FROM trans
ON CONFLICT (userid) DO UPDATE
SET balance1 = point.balance1 + excluded.balance1,
balance2 = point.balance2 + excluded.balance1 + (select s.bonus
from settings s
where s.userid = excluded.userid)
Note however, that balance2 will never be incremented this way because on the first insert it will be null subsequent updates will try to add something to null but any expression involving null yields null (null + 5 is null).
So you either need to insert 0 when doing the insert the first time, or use coalesce() when doing the update, e.g.
balance2 = coalesce(point.balance2, 0) + excluded.balance1 + (select ...);
If it's possible that there is no row in settings for that user, then you need to apply coalesce() on the result of the sub-query as well:
... excluded.balance1 + coalesce( (select ... ), 0)
Also: you used excluded.point in your question which is incorrect. excluded refers to the target table point but that does not have a column point - you probably meant: excluded.balance1 (as that is the column into which the inserted trans.point will go)
Unrelated, but: timestamp is a horrible name for a column. For one because it's a keyword, but more importantly because it does not document what the column is for. Does it mean "created at"? "valid until"? "start at"? "obsolete from"? Something entirely different?

invalid identifier in WHERE clause

I'm trying to write the SQL to copy a row from one table to another but I keep getting an invalid identifier in the WHERE clause. I'm using oracle apex.
Here is my code:
INSERT INTO CRIMECLOSED (crimeClosedID, crimeName, crimeDate, crimeNotes,
outsideSourceDescription, dateClosed, relatedCrimes,
staffID, crimeTypeID, locationID)
SELECT CRIMEOPEN.crimeOpenID, CRIMEOPEN.crimeName, CRIMEOPEN.crimeDate,
CRIMEOPEN.crimeNotes, CRIMEOPEN.outsideSourceDescription, CURDATE(),
CRIMEOPEN.relatedCrimes, CRIMEOPEN.staffID, CRIMEOPEN.crimeTypeID,
CRIMEOPEN.locationID
FROM CRIMEOPEN
WHERE CRIMEOPEN.crimeOpenID = '1';
CRIMEOPEN table
CREATE TABLE "CRIMEOPEN"
( "crimeOpenID" VARCHAR2(5),
"crimeName" VARCHAR2(20),
"crimeDate" DATE,
"crimeNotes" VARCHAR2(200),
"outsideSourceDescription" VARCHAR2(200),
"relatedCrimes" VARCHAR2(5),
"staffID" VARCHAR2(5),
"crimeTypeID" VARCHAR2(5),
"locationID" VARCHAR2(5),
CONSTRAINT "CRIMEOPEN_PK" PRIMARY KEY ("crimeOpenID") ENABLE
)
The error I get is:
ORA-00904: "CRIMEOPEN"."CRIMEOPENID": invalid identifier
I think the error is trying to say that 'crimeOpenID' is not a column in 'CRIMEOPEN' but it is.
Any help please?
If you use double quotes when declaring the columns of the table then the same format needs to be used also when "using" the columns in various statements.
So in order to fix the issue you should wrap all columns in double quotes in the select statement (something like below - applied only to the select statement).
INSERT INTO CRIMECLOSED (crimeClosedID, crimeName, crimeDate, crimeNotes,
outsideSourceDescription, dateClosed, relatedCrimes,
staffID, crimeTypeID, locationID)
SELECT "CRIMEOPEN"."crimeOpenID", "CRIMEOPEN"."crimeName", "CRIMEOPEN"."crimeDate",
"CRIMEOPEN"."crimeNotes", "CRIMEOPEN"."outsideSourceDescription", CURDATE(),
"CRIMEOPEN"."relatedCrimes", "CRIMEOPEN"."staffID", "CRIMEOPEN"."crimeTypeID",
"CRIMEOPEN"."locationID"
FROM "CRIMEOPEN"
WHERE "CRIMEOPEN"."crimeOpenID" = '1';

How to to get the value of an auto increment column in postgres from a .sql script file?

In postgres I have two tables like so
CREATE TABLE foo (
pkey SERIAL PRIMARY KEY,
name TEXT
);
CREATE TABLE bar (
pkey SERIAL PRIMARY KEY,
foo_fk INTEGER REFERENCES foo(pkey) NOT NULL,
other TEXT
);
What I want to do is to write a .sql script file that does the following
INSERT INTO foo(name) VALUES ('A') RETURNING pkey AS abc;
INSERT INTO bar(foo_fk,other) VALUES
(abc, 'other1'),
(abc, 'other2'),
(abc, 'other3');
which produces the error below in pgAdmin
Query result with 1 row discarded.
ERROR: column "abc" does not exist
LINE 3: (abc, 'other1'),
********** Error **********
ERROR: column "abc" does not exist
SQL state: 42703
Character: 122
Outside of a stored procedure how do a define a variable that I can use between statements? Is there some other syntax for being able to insert into bar with the pkey returned from the insert to foo.
You can combine the queries into one. Something like:
with foo_ins as (INSERT INTO foo(name)
VALUES ('A')
RETURNING pkey AS foo_id)
INSERT INTO bar(foo_fk,other)
SELECT foo_id, 'other1' FROM foo_ins
UNION ALL
SELECT foo_id, 'other2' FROM foo_ins
UNION ALL
SELECT foo_id, 'other3' FROM foo_ins;
Other option - use an anonymous PL/pgSQL block like:
DO $$
DECLARE foo_id INTEGER;
BEGIN
INSERT INTO foo(name)
VALUES ('A')
RETURNING pkey INTO foo_id;
INSERT INTO bar(foo_fk,other)
VALUES (foo_id, 'other1'),
(foo_id, 'other2'),
(foo_id, 'other3');
END$$;
You can use lastval() to ...
Return the value most recently returned by nextval in the current session.
This way you do not need to know the name of the seqence used.
INSERT INTO foo(name) VALUES ('A');
INSERT INTO bar(foo_fk,other) VALUES
(lastval(), 'other1')
, (lastval(), 'other2')
, (lastval(), 'other3')
;
This is safe because you control what you called last in your own session.
If you use a writable CTE as proposed by #Ihor, you can still use a short VALUES expression in the 2nd INSERT. Combine it with a CROSS JOIN (or append the CTE name after a comma (, ins) - same thing):
WITH ins AS (
INSERT INTO foo(name)
VALUES ('A')
RETURNING pkey
)
INSERT INTO bar(foo_fk, other)
SELECT ins.pkey, o.other
FROM (
VALUES
('other1'::text)
, ('other2')
, ('other3')
) o(other)
CROSS JOIN ins;
Another option is to use currval
INSERT INTO foo
(name)
VALUES
('A') ;
INSERT INTO bar
(foo_fk,other)
VALUES
(currval('foo_pkey_seq'), 'other1'),
(currval('foo_pkey_seq'), 'other2'),
(currval('foo_pkey_seq'), 'other3');
The automatically created sequence for serial columns is always named <table>_<column>_seq
Edit:
A more "robust" alternative is to use pg_get_serial_sequence as Igor pointed out.
INSERT INTO bar
(foo_fk,other)
VALUES
(currval(pg_get_serial_sequence('public.foo', 'pkey')), 'other1'),
(currval(pg_get_serial_sequence('public.foo', 'pkey')), 'other2'),
(currval(pg_get_serial_sequence('public.foo', 'pkey')), 'other3');

SQL Server 2012 sequence

I create a table and sequence in order to replace identity in the table I use SQL Server 2012 Express but I get this error while I tried to insert data to the table
Msg 11719, Level 15, State 1, Line 2
NEXT VALUE FOR function is not allowed in check constraints, default objects, computed columns,
views, user-defined functions, user-defined aggregates, user-defined
table types, sub-queries, common table expressions, or derived
tables.
T-SQL code:
insert into Job_Update_Log(log_id, update_reason, jobid)
values((select next value for Job_Log_Update_SEQ),'grammer fixing',39);
This is my table:
create table Job_Update_Log
(
log_id int primary key ,
update_reason nvarchar(100) ,
update_date date default getdate(),
jobid bigint not null,
foreign key(jobid) references jobslist(jobid)
);
and this is my sequence:
CREATE SEQUENCE [dbo].[Job_Log_Update_SEQ]
AS [int]
START WITH 1
INCREMENT BY 1
NO CACHE
GO
Just get rid of the subselect in the VALUES section, like this:
insert into Job_Update_Log(log_id,update_reason,jobid)
values (next value for Job_Log_Update_SEQ,'grammer fixing',39);
Reference: http://msdn.microsoft.com/en-us/library/hh272694%28v=vs.103%29.aspx
Your insert syntax appears to be wrong. You are attempting to use a SELECT statement inside of the VALUES section of your query. If you want to use SELECT then you will use:
insert into Job_Update_Log(log_id,update_reason,jobid)
select next value for Job_Log_Update_SEQ,'grammer fixing',39;
See SQL Fiddle with Demo
I changed the syntax from INSERT INTO VALUES to INSERT INTO ... SELECT. I used this because you are selecting the next value of the sequence.
However, if you want to use the INSERT INTO.. VALUES, you will have to remove the SELECT from the query:
insert into Job_Update_Log(log_id,update_reason,jobid)
values(next value for Job_Log_Update_SEQ,'grammer fixing',39);
See SQL Fiddle with Demo
Both of these will INSERT the record into the table.
Try this one:
–With a table
create sequence idsequence
start with 1 increment by 3
create table Products_ext
(
id int,
Name varchar(50)
);
INSERT dbo.Products_ext (Id, Name)
VALUES (NEXT VALUE FOR dbo.idsequence, ‘ProductItem’);
select * from Products_ext;
/* If you run the above statement two types, you will get the following:-
1 ProductItem
4 ProductItem
*/
drop table Products_ext;
drop sequence idsequence;
------------------------------