postgres #my table syntax error - postgresql

The following code shows syntax error near #my table seq in postgres sql.
IF (p_BusinessID = '')
THEN
SELECT RANK() OVER(ORDER BY SSO) SNO,Business, SSO, DisplayName, UPPER(ExServerName) ExServerName, POP, IMAP, EWS, DisplayMonth, DisplayYear
FROM "POP_IMAP_EWS_Data"
WHERE DisplayYear = COALESCE(p_YearID, DisplayYear) AND MonthID = COALESCE(p_MonthID, MonthID) AND SSO = COALESCE(p_SSO, SSO);
ELSE
CREATE SEQUENCE #myTable_seq;
CREATE TABLE #myTable
(
ID INT DEFAULT NEXTVAL ('#myTable_seq'),
Item VARCHAR(100),
);
INSERT INTO #myTable(Item)
SELECT RTRIM(LTRIM(Item)) FROM SplitString(;p_BusinessID, ',');
SELECT RANK() OVER(ORDER BY SSO) SNO, A.BusinessID, Business, SSO, DisplayName, UPPER(ExServerName) ExServerName, POP, IMAP, EWS, DisplayMonth, DisplayYear
FROM "POP_IMAP_EWS_Data"A
INNER JOIN #myTable B ON B.Item = A.BusinessID
WHERE DisplayYear = COALESCE(p_YearID, DisplayYear) AND MonthID = COALESCE(p_MonthID, MonthID) AND SSO = COALESCE(p_SSO, SSO);
Can someone tell why?

Using a # prefix to indicate a temporary table is specific to SQL Server (and possibly Sybase). PostgreSQL wants you to say create temporary table ...:
create temporary table mytable (
id serial primary key,
item varchar(100)
);
Also, you'd usually just make your id column a serial column and let PostgreSQL take care of the sequence rather than hooking it all up yourself so I changed that as well; using serial also takes care of setting the sequence's owner.

Related

How to reference query values in Postgres res policy

I am trying to build a system with Postgres in which users can connect with each other by sending requests. I am handling the security logic with RLS. However, I am having some trouble with the structure of one of my policies.
Here are the tables of concern, stripped of any nonessential columns:
CREATE TABLE profile (
id UUID PRIMARY KEY,
name text
);
CREATE TABLE request (
id serial PRIMARY KEY,
sender_id UUID,
recipient_id UUID
);
CREATE TABLE connection (
id serial PRIMARY KEY,
owner_id UUID,
contact_id UUID
);
Users are only allowed to add a connection if:
There is a row which references their profile id in recipient_id in the request table.
There is no existing connection that involves both users (no matter which one is the owner_id and which is the contact_id)
Here is the Policy I've written:
CREATE POLICY "Participants INSERT" ON public.connection FOR
INSERT
WITH CHECK (
auth.uid() = owner_id
AND NOT EXISTS(
SELECT
*
FROM
public.connection c
WHERE
(
(
c.owner_id = owner_id --> Problem: How to reference query
AND c.contact_id = contact_id
)
OR (
c.contact_id = owner_id
AND c.owner_id = contact_id
)
)
)
AND EXISTS(
SELECT
*
FROM
public.request r
WHERE
(
r.sender_id = contact_id
AND r.recipient_id = owner_id
)
)
);
However, whenever I try to create a contact with a user whose id is already present in any row in the connection table (irrespective if it exists together with the correct contact_id), I get a policy violation error.
I think I might be referencing owner_id and contact_id wrong in the subquery because if I replace them manually with the appropriate id as a string, it works.
I'd really appreciate everyones input.
I found the answer. The parent query can be accessed through the name of the table. So all I had to do was to access owner_id and contact_id like this:
connection.owner_id
connection.contact_id
On a side-note: The query values can NOT be accessed like this:
public.connection.owner_id
public.connection.contact_id

Insert results in non-sequential IDs

I have the following query:
INSERT INTO hosts (name, domain, ip)
SELECT name, domain, ip
FROM staging_hosts
ON CONFLICT (ip) DO UPDATE
SET name = excluded.name, domain = excluded.domain;
It works fine aside from the fact that the IDs in my "hosts" table are not being incremented sequentially. For example, I'll get the following new IDs after running this query:
114855
114859
114873
114977
117389
115326
The ID column on the hosts table is serial so I'm not sure why the IDs are incrementing sequentially.
If you insist on minimizing gaps, you could do that, with example tables included:
CREATE TABLE foo (
id SERIAL PRIMARY KEY,
ip INT,
name TEXT,
UNIQUE(ip) );
CREATE TABLE staging (
ip INT,
name TEXT );
INSERT INTO foo (ip,name) VALUES (1,'hello'),(2,'abc'),(10,'world');
INSERT INTO staging VALUES (1,'byebye'),(2,'def'),(11,'world');
WITH s AS (
SELECT COALESCE( foo.id, nextval('foo_id_seq'::regclass)) AS id,
staging.ip,staging.name
FROM staging LEFT JOIN foo USING (ip))
INSERT INTO foo (id,ip,name) SELECT * FROM s
ON CONFLICT(id) DO UPDATE
SET ip=excluded.ip, name=excluded.name;
COALESCE does not evaluate the second argument if it is not needed, which means it only calls nextval when required.

Create view on table which exist in multiple schemas with same name

I am trying to create one view (or view in each schema to be used without modifications) on table which exists in multiple schemas with the same name
create schema company_1;
create schema company_2;
...
CREATE TABLE company_1.orders
(
id serial NOT NULL,
amount real,
paid real,
CONSTRAINT orders_pkey PRIMARY KEY (id )
)
WITH (
OIDS=FALSE
);
CREATE TABLE company_2.orders
(
id serial NOT NULL,
amount real,
paid real,
CONSTRAINT orders_pkey PRIMARY KEY (id )
)
WITH (
OIDS=FALSE
);
....
What is correct way of creating view on table orders without specifying schema for every view or specifying current schema?
What I need and failed to get is either
CREATE OR REPLACE VIEW
public.full_orders AS
SELECT id, amount FROM orders;
or
CREATE OR REPLACE VIEW
company_1.full_orders AS
-- company_2.full_orders AS
-- company_n.full_orders AS
SELECT id, amount FROM current_schema.orders;
Using postgresql 9.2.2
EDIT: The way I went:
CREATE VIEW company_1.full_orders AS
SELECT id, amount FROM company_1.orders;
On schema copy discussed here I butaly do this
FOR src_table IN
SELECT table_name
FROM information_schema.TABLES
WHERE table_schema = source_schema AND table_type = 'VIEW'
LOOP
SELECT view_definition
FROM information_schema.views
WHERE table_name = src_table AND table_schema = source_schema INTO q;
trg_table := target_schema||'.'||src_table;
EXECUTE 'CREATE VIEW ' || trg_table || ' AS '||replace(q, source_schema, target_schema);
END LOOP;
Still looking for better solution...
It's not possible to do with with a straightforward view. The view records the underlying table's identity at creation time, so it is not affected by schema settings done later on.
You could do it using a set-returning function using dynamic SQL, and then wrap that into a view. But I don't think that's a good solution.
I would just create quasi-duplicates for the view, as you have been doing, and enhance my deployment script to keep them all up to date.

SELECT or INSERT a row in one command

I'm using PostgreSQL 9.0 and I have a table with just an artificial key (auto-incrementing sequence) and another unique key. (Yes, there is a reason for this table. :)) I want to look up an ID by the other key or, if it doesn't exist, insert it:
SELECT id
FROM mytable
WHERE other_key = 'SOMETHING'
Then, if no match:
INSERT INTO mytable (other_key)
VALUES ('SOMETHING')
RETURNING id
The question: is it possible to save a round-trip to the DB by doing both of these in one statement? I can insert the row if it doesn't exist like this:
INSERT INTO mytable (other_key)
SELECT 'SOMETHING'
WHERE NOT EXISTS (SELECT * FROM mytable WHERE other_key = 'SOMETHING')
RETURNING id
... but that doesn't give the ID of an existing row. Any ideas? There is a unique constraint on other_key, if that helps.
Have you tried to union it?
Edit - this requires Postgres 9.1:
create table mytable (id serial primary key, other_key varchar not null unique);
WITH new_row AS (
INSERT INTO mytable (other_key)
SELECT 'SOMETHING'
WHERE NOT EXISTS (SELECT * FROM mytable WHERE other_key = 'SOMETHING')
RETURNING *
)
SELECT * FROM new_row
UNION
SELECT * FROM mytable WHERE other_key = 'SOMETHING';
results in:
id | other_key
----+-----------
1 | SOMETHING
(1 row)
No, there is no special SQL syntax that allows you to do select or insert. You can do what Ilia mentions and create a sproc, which means it will not do a round trip fromt he client to server, but it will still result in two queries (three actually, if you count the sproc itself).
using 9.5 i successfully tried this
based on Denis de Bernardy's answer
only 1 parameter
no union
no stored procedure
atomic, thus no concurrency problems (i think...)
The Query:
WITH neworexisting AS (
INSERT INTO mytable(other_key) VALUES('hello 2')
ON CONFLICT(other_key) DO UPDATE SET existed=true -- need some update to return sth
RETURNING *
)
SELECT * FROM neworexisting
first call:
id|other_key|created |existed|
--|---------|-------------------|-------|
6|hello 1 |2019-09-11 11:39:29|false |
second call:
id|other_key|created |existed|
--|---------|-------------------|-------|
6|hello 1 |2019-09-11 11:39:29|true |
First create your table ;-)
CREATE TABLE mytable (
id serial NOT NULL,
other_key text NOT NULL,
created timestamptz NOT NULL DEFAULT now(),
existed bool NOT NULL DEFAULT false,
CONSTRAINT mytable_pk PRIMARY KEY (id),
CONSTRAINT mytable_uniq UNIQUE (other_key) --needed for on conflict
);
you can use a stored procedure
IF (SELECT id FROM mytable WHERE other_key = 'SOMETHING' LIMIT 1) < 0 THEN
INSERT INTO mytable (other_key) VALUES ('SOMETHING')
END IF
I have an alternative to Denis answer, that I think is less database-intensive, although a bit more complex:
create table mytable (id serial primary key, other_key varchar not null unique);
WITH table_sel AS (
SELECT id
FROM mytable
WHERE other_key = 'test'
UNION
SELECT NULL AS id
ORDER BY id NULLS LAST
LIMIT 1
), table_ins AS (
INSERT INTO mytable (id, other_key)
SELECT
COALESCE(id, NEXTVAL('mytable_id_seq'::REGCLASS)),
'test'
FROM table_sel
ON CONFLICT (id) DO NOTHING
RETURNING id
)
SELECT * FROM table_ins
UNION ALL
SELECT * FROM table_sel
WHERE id IS NOT NULL;
In table_sel CTE I'm looking for the right row. If I don't find it, I assure that table_sel returns at least one row, with a union with a SELECT NULL.
In table_ins CTE I try to insert the same row I was looking for earlier. COALESCE(id, NEXTVAL('mytable_id_seq'::REGCLASS)) is saying: id could be defined, if so, use it; whereas if id is null, increment the sequence on id and use this new value to insert a row. The ON CONFLICT clause assure
that if id is already in mytable I don't insert anything.
At the end I put everything together with a UNION between table_ins and table_sel, so that I'm sure to take my sweet id value and execute both CTE.
This query needs to search for the value other_key only once, and is a "search this value" not a "check if this value not exists in the table", that is very heavy; in Denis alternative you use other_key in both types of searches. In my query you "check if a value not exists" only on id that is a integer primary key, that, for construction, is fast.
Minor tweak a decade late to Denis's excellent answer:
-- Create the table with a unique constraint
CREATE TABLE mytable (
id serial PRIMARY KEY
, other_key varchar NOT NULL UNIQUE
);
WITH new_row AS (
-- Only insert when we don't find anything, avoiding a table lock if
-- possible.
INSERT INTO mytable ( other_key )
SELECT 'SOMETHING'
WHERE NOT EXISTS (
SELECT *
FROM mytable
WHERE other_key = 'SOMETHING'
)
RETURNING *
)
(
-- This comes first in the UNION ALL since it'll almost certainly be
-- in the query cache. Marginally slower for the insert case, but also
-- marginally faster for the much more common read-only case.
SELECT *
FROM mytable
WHERE other_key = 'SOMETHING'
-- Don't check for duplicates to be removed
UNION ALL
-- If we reach this point in iteration, we needed to do the INSERT and
-- lock after all.
SELECT *
FROM new_row
) LIMIT 1 -- Just return whatever comes first in the results and allow
-- the query engine to cut processing short for the INSERT
-- calculation.
;
The UNION ALL tells the planner it doesn't have to collect results for de-duplication. The LIMIT 1 at the end allows the planner to short-circuit further processing/iteration once it knows there's an answer available.
NOTE: There is a race condition present here and in the original answer. If the entry does not already exist, the INSERT will fail with a unique constraint violation. The error can be suppressed with ON CONFLICT DO NOTHING, but the query will return an empty set instead of the new row. This is a difficult problem because getting that info from another transaction would violate the I in ACID.

Getting a query to index seek (rather than scan)

Running the following query (SQL Server 2000) the execution plan shows that it used an index seek and Profiler shows it's doing 71 reads with a duration of 0.
select top 1 id from table where name = '0010000546163' order by id desc
Contrast that with the following with uses an index scan with 8500 reads and a duration of about a second.
declare #p varchar(20)
select #p = '0010000546163'
select top 1 id from table where name = #p order by id desc
Why is the execution plan different? Is there a way to change the second method to seek?
thanks
EDIT
Table looks like
CREATE TABLE [table] (
[Id] [int] IDENTITY (1, 1) NOT NULL ,
[Name] [varchar] (13) COLLATE Latin1_General_CI_AS NOT NULL)
Id is primary clustered key
There is a non-unique index on Name and a unique composite index on id/name
There are other columns - left them out for brevity
Now you've added the schema, please try this. SQL Server treats length differences as different data types and will convert the varchar(13) column to match the varchar(20) variable
declare #p varchar(13)
If not, what about collation coercien? Is the DB or server different to the column?
declare #p varchar(13) COLLATE Latin1_General_CI_AS NOT NULL
If not, add this before and post results
SET SHOWPLAN_TEXT ON
GO
If the name column is NVARCHAR then u need your parameter to be also of the same type. It should then pick it up by index seek.
declare #p nvarchar(20)
select #p = N'0010000546163'
select top 1 id from table where name = #p order by id desc