Using a CASE Expression in the Default Value - tsql

I am creating a script for my dba to run. In development I have added a new column to an existing table. When I run the following script I have separated the ALTER and UPDATE on my table. I was trying to combine the statements into one. Any suggestions? Not sure if this is possible or I am on the right track. Thanks in advance!
Current Script that is working fine:
if not exists(select * from sys.columns where Name = N'NewColumn' and
Object_ID = Object_ID(N'TableA'))
begin
ALTER TABLE dbo.TableA
ADD NewColumn BIT NULL
DEFAULT 0
end
if exists(select * from sys.columns where Name = N'NewColumn' and Object_ID = Object_ID(N'TableA'))
begin
UPDATE dbo.TableA
SET Newcolumn = CASE WHEN CodeID IN ('A','B') THEN 1 END
WHERE CodeID IN ('A','B')
end
Trying to combine the ALTER and UPDATE into one:
if not exists(select * from sys.columns where Name = N'NewColumn' and Object_ID = Object_ID(N'TableA'))
begin
ALTER TABLE dbo.TableA
ADD NewColumn BIT NULL
DEFAULT case CodeID
WHEN 'A' THEN 1
WHEN 'B' THEN 1
end
I get the following error:
The name "CodeID" is not permitted in this context. Valid expressions are constants, constant expressions, and (in some contexts) variables. Column names are not permitted.

I haven't actually tested this, but I noticed you used different syntax for the CASE statement in the working script and the non-working script. Try this:
if not exists(select * from sys.columns where Name = N'NewColumn' and Object_ID = Object_ID(N'TableA'))
begin
ALTER TABLE dbo.TableA
ADD NewColumn BIT NULL
DEFAULT case WHEN CodeID = 'A' THEN 1 ELSE 0 END
end
But it would not surprise me if you can't reference another column in the default for a column. For example, what happens when you insert a new record without specifying a value for NewColumn? At insert time, CodeID doesn't have a value because the row doesn't exist yet.

Ok, so i did some more digging around. The following solves my problem:
if not exists(select * from sys.columns where Name = N'NewColumn' and
Object_ID = Object_ID(N'TableA'))
begin
ALTER TABLE dbo.TableA
ADD NewColumn BIT NULL
DEFAULT 0 with Values
end
if exists(select * from sys.columns where Name = N'NewColumn' and Object_ID = Object_ID(N'TableA'))
begin
UPDATE dbo.TableA
SET Newcolumn = 1 WHERE CodeID IN ('A','B')
end
I was over complicating things. In order to combine the update I would have to try and add a trigger. This works for what I need to do. Thanks.

Related

Make duplicate row in Postgresql

I am writing migration script to migrate database. I have to duplicate the row by incrementing primary key considering that different database can have n number of different columns in the table. I can't write each and every column in query. If i simply just copy the row then, I am getting duplicate key error.
Query: INSERT INTO table_name SELECT * FROM table_name WHERE id=255;
ERROR: duplicate key value violates unique constraint "table_name_pkey"
DETAIL: Key (id)=(255) already exist
Here, It's good that I don't have to mention all column names. I can select all columns by giving *. But, same time I am also getting duplicate key error.
What's the solution of this problem? Any help would be appreciated. Thanks in advance.
If you are willing to type all column names, you may write
INSERT INTO table_name (
pri_key
,col2
,col3
)
SELECT (
SELECT MAX(pri_key) + 1
FROM table_name
)
,col2
,col3
FROM table_name
WHERE id = 255;
Other option (without typing all columns , but you know the primary key ) is to CREATE a temp table, update it and re-insert within a transaction.
BEGIN;
CREATE TEMP TABLE temp_tab ON COMMIT DROP AS SELECT * FROM table_name WHERE id=255;
UPDATE temp_tab SET pri_key_col = ( select MAX(pri_key_col) + 1 FROM table_name );
INSERT INTO table_name select * FROM temp_tab;
COMMIT;
This is just a DO block but you could create a function that takes things like the table name etc as parameters.
Setup:
CREATE TABLE public.t1 (a TEXT, b TEXT, c TEXT, id SERIAL PRIMARY KEY, e TEXT, f TEXT);
INSERT INTO public.t1 (e) VALUES ('x'), ('y'), ('z');
Code to duplicate values without the primary key column:
DO $$
DECLARE
_table_schema TEXT := 'public';
_table_name TEXT := 't1';
_pk_column_name TEXT := 'id';
_columns TEXT;
BEGIN
SELECT STRING_AGG(column_name, ',')
INTO _columns
FROM information_schema.columns
WHERE table_name = _table_name
AND table_schema = _table_schema
AND column_name <> _pk_column_name;
EXECUTE FORMAT('INSERT INTO %1$s.%2$s (%3$s) SELECT %3$s FROM %1$s.%2$s', _table_schema, _table_name, _columns);
END $$
The query it creates and runs is: INSERT INTO public.t1 (a,b,c,e,f) SELECT a,b,c,e,f FROM public.t1. It's selected all the columns apart from the PK one. You could put this code in a function and use it for any table you wanted, or just use it like this and edit it for whatever table.

Get all instances of primary keys of a table

This is a simple example of what I need, for any given table, I need to get all the instances of the primary keys, this is a little example, but I need a generic way to do it.
create table foo
(
a numeric
,b text
,c numeric
constraint pk_foo primary key (a,b)
)
insert into foo(a,b,c) values (1,'a',1),(2,'b',2),(3,'c',3);
select <the magical thing>
result
a|b
1 |1|a|
2 |2|b|
3 |3|c|
.. ...
I need to control if the instances of the primary keys are changed by the user, but I don't want to repeat code in too many tables! I need a generic way to do it, I will put <the magical thing>
in a function to put it on a trigger before update and blah blah blah...
In PostgreSQL you must always provide a resulting type for a query. However, you can obtain the code of the query you need, and then execute the query from the client:
create or replace function get_key_only_sql(regclass) returns string as $$
select 'select '|| (
select string_agg(quote_ident(att.attname), ', ' order by col)
from pg_index i
join lateral unnest(indkey) col on (true)
join pg_attribute att on (att.attrelid = i.indrelid and att.attnum = col)
where i.indrelid = $1 and i.indisprimary
group by i.indexrelid
limit 1) || ' from '||$1::text
end;
$$ language sql;
Here's some client pseudocode using the function above:
sql = pgexecscalar("select get_key_only_sql('mytable'::regclass)");
rs = pgopen(sql);

Unusual table table1 is mutating, trigger/function may not see it error in Oracle

I have a trigger like this: (Basically on update of a column in table1, I update a column in table 2)
CREATE OR REPLACE TRIGGER AAA AFTER UPDATE
ON TABLE_1 REFERENCING NEW AS NEWROW OLD AS OLDROW
FOR EACH ROW
WHEN (
NEWROW.DELETED ='Y' AND NEWROW.ID IN (41,43)
AND OLDROW.DELETED = 'N'
)
DECLARE
id_1 number;
id_2 number;
id_3 number;
BEGIN
select id_1, id_2,id_3 into id_1,id_2,id_3 from table_1 where id_1 = :NEWROW.id1 and id2 = some_other_row.id2;
if id_1 is null
then
update table2 set deleted = 'Y' , where table2.id_1 = id_1 and table2.id_2=id_2 and table2.id_3 = id_3;
end if;
EXCEPTION
WHEN OTHERS
THEN
-- Consider logging the error and then re-raise
RAISE;
END AAA;
/
When I update table1 I get:
ORA-04091: table table1 is mutating, trigger/function may not see it
I thought this error happens only when you are updating the table on which the trigger is trying to update something. But here I am updating table1 and trigger is supposed to update table2. SO why is the error?
It's the SELECT statement that is causing the problem here. Inside the trigger, you cannot SELECT from the same table. In your example, you don't need/can't use the SELECT statement. You can get the values by simply using :newrow.id_1, :newrow.id_2 and :newrow.id_3.

Duplicate single database record

Hello what is the easiest way to duplicate a DB record over the same table?
My problem is that the table where I am doing this has many column, like 100+, and I don't like how the solution looks like. Here is what I do (this is inside plpqsql function):
...
1. duplicate record
INSERT INTO history
(SELECT NEXTVAL('history_id_seq'), col_1, col_2, ... , col_100)
FROM history
WHERE history_id = 1234
ORDER BY datetime DESC
LIMIT 1)
RETURNING
history_id INTO new_history_id;
2. update some columns
UPDATE history
SET
col_5 = 'test_5',
col_23 = 'test_23',
datetime = CURRENT_TIMESTAMP
WHERE history_id = new_history_id;
Here are the problems I am attempting to solve
Listing all these 100+ columns looks lame
When new column is added eventually the function should be updated too
On separate DB instances the column order might differ, which would cause the function fail
I am not sure if I can list them once more (solving issue 3) like insert into <table> (<columns_list>) values (<query>) but then the query looks even uglier.
I would like to achieve something like 'insert into ', but this seems impossible the unique primary key constraint will raise a duplication error.
Any suggestions?
Thanks in advance for you time.
This isn't pretty or particularly optimized but there are a couple of ways to go about this. Ideally, you might want to do this all in an UPDATE trigger though you could implement a duplication function something like this:
-- create source table
CREATE TABLE history (history_id serial not null primary key, col_2 int, col_3 int, col_4 int, datetime timestamptz default now());
-- add some data
INSERT INTO history (col_2, col_3, col_4)
SELECT g, g * 10, g * 100 FROM generate_series(1, 100) AS g;
-- function to duplicate record
CREATE OR REPLACE FUNCTION fn_history_duplicate(p_history_id integer) RETURNS SETOF history AS
$BODY$
DECLARE
cols text;
insert_statement text;
BEGIN
-- build list of columns
SELECT array_to_string(array_agg(column_name::name), ',') INTO cols
FROM information_schema.columns
WHERE (table_schema, table_name) = ('public', 'history')
AND column_name <> 'history_id';
-- build insert statement
insert_statement := 'INSERT INTO history (' || cols || ') SELECT ' || cols || ' FROM history WHERE history_id = $1 RETURNING *';
-- execute statement
RETURN QUERY EXECUTE insert_statement USING p_history_id;
RETURN;
END;
$BODY$
LANGUAGE 'plpgsql';
-- test
SELECT * FROM fn_history_duplicate(1);
history_id | col_2 | col_3 | col_4 | datetime
------------+-------+-------+-------+-------------------------------
101 | 1 | 10 | 100 | 2013-04-15 14:56:11.131507+00
(1 row)
As I noted in my original comment, you might also take a look at the colnames extension as an alternative to querying the information schema.
You don't need the update anyway, you can supply the constant values directly in the SELECT statement:
INSERT INTO history
SELECT NEXTVAL('history_id_seq'),
col_1,
col_2,
col_3,
col_4,
'test_5',
...
'test_23',
...,
col_100
FROM history
WHERE history_sid = 1234
ORDER BY datetime DESC
LIMIT 1
RETURNING history_sid INTO new_history_sid;

What's wrong with this tsql?

When executing the following script, I get the error:
Msg 207, Level 16, State 1, Line 15 Invalid column name 'b'.
Anyone can explain it please? Thanks.
DROP TABLE ttt;
CREATE TABLE ttt(a nvarchar)
IF NOT EXISTS ( SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.ttt')
AND name = 'b' )
AND EXISTS ( SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.ttt')
AND name = 'a' )
BEGIN
ALTER TABLE [dbo].ttt ADD b NVARCHAR
UPDATE [dbo].ttt
SET b = a
ALTER TABLE [dbo].ttt DROP COLUMN a
END
It's trying to compile all of these statements before it executes the 1st:
ALTER TABLE [dbo].ttt ADD b NVARCHAR
UPDATE [dbo].ttt
SET b = a
ALTER TABLE [dbo].ttt DROP COLUMN a
(In fact, it tries to compile the entire batch, not just these statements, but the point still stands - at the point it's trying to compile the UPDATE, the column does not exist)
When it's trying to compile the UPDATE statement, it consults the table metadata and correctly finds that the column doesn't exist.
Try EXECing the update statement.
EXEC('UPDATE [dbo].ttt
SET b = a');
And also, what Oded says about you probably wanting to specify a size for the column (otherwise, it defaults to the most pointless datatype ever - an nvarchar(1))
This script definitely runs without errors:
CREATE TABLE ttt(a nvarchar)
IF NOT EXISTS ( SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.ttt')
AND name = 'b' )
AND EXISTS ( SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.ttt')
AND name = 'a' )
BEGIN
ALTER TABLE [dbo].ttt ADD b NVARCHAR
EXEC('UPDATE [dbo].ttt
SET b = a');
ALTER TABLE [dbo].ttt DROP COLUMN a
END
If you put the code in a stored procedure it should work. Just make sure the table exists with both columns a and column b when you create the procedure. Then after the procedure has been created you can drop the table and test it.