I wanted to replace data in tableB with data in tableA.
Table A:
Source_Col
Target_Col
DB_DEV
DB_UAT
CDB_DEV
CDB_UAT
I have another table which has one col with the value "Create or replace DB_DEV.SCH.VIEW AS SELECT * FROM CBD_DEV.SCH.TABLENAME".
I wanted to replace DB_DEV with DB_UAT and CBD_DEV with CBD_UAT querying from TableA. Is that possible?
-- Revised to use tables:
-- Create the source and target string patterns to replace
create or replace table str_replace (source string, target string);
insert into str_replace values ('DB_DEV','DB_UAT'),('CDB_DEV', 'CDB_UAT');
-- Create the table containing the original strings to be modified
create or replace table orig_strings (query string, new_query string);
insert into orig_strings values ('Create or replace view DB_DEV.SCH.V_TABLEA AS SELECT * FROM CDB_DEV.SCH.TABLENAME;',null),('Create or replace view DB_DEV.SCH.V_TABLEB AS SELECT * FROM CDB_DEV.SCH.TABLENAME;',null);
--- Query with row_number partition to pick 1st row from Cartesian join
select
os.query,
regexp_replace(os.query, str.source, str.target) as new_query
from
orig_strings os,
str_replace str QUALIFY row_number() over (
partition by os.query
order by
os.query
) = 1;
-- Results
QUERY
NEW_QUERY
Create or replace view DB_DEV.SCH.V_TABLEA AS SELECT * FROM CDB_DEV.SCH.TABLENAME; Create or replace view DB_UAT.SCH.V_TABLEA AS SELECT * FROM CDB_UAT.SCH.TABLENAME;
Create or replace view DB_DEV.SCH.V_TABLEB AS SELECT * FROM CDB_DEV.SCH.TABLENAME; Create or replace view DB_UAT.SCH.V_TABLEB AS SELECT * FROM CDB_UAT.SCH.TABLENAME;
-- Update the table with the modified string
update orig_strings os
set os.new_query = regexp_replace(os.query,str.source,str.target)
from str_replace str;
select * from orig_strings;
-- Results:
QUERY NEW_QUERY
Create or replace view DB_DEV.SCH.V_TABLEA AS SELECT * FROM CDB_DEV.SCH.TABLENAME; Create or replace view DB_UAT.SCH.V_TABLEA AS SELECT * FROM CDB_UAT.SCH.TABLENAME;
Create or replace view DB_DEV.SCH.V_TABLEB AS SELECT * FROM CDB_DEV.SCH.TABLENAME; Create or replace view DB_UAT.SCH.V_TABLEB AS SELECT * FROM CDB_UAT.SCH.TABLENAME;
Related
So I'm not sure the right buzzwords to use, but how to use a single input at the top of the query that can then be used many times throughout the query? I'd like to reduce and limit the risk of error and missing a filter entry for the next user.
where column1 in ('a','b',...'n')
assign this to a variable or something at the top of the SQL file, to limit risk of error.
You can place the list into a CTE table using a values clause, and then do an IN on the selection from that table.
create temporary table foo (column1 string);
insert into foo (column1) values ('1'), ('2'), ('3'), ('a'), ('b'), ('c');
select * from foo where column1 in ('a','b','c','d','e','f','g');
with
VALUE_LIST(vals) as
(
select * from values ('a'),('b'),('c'),('d'),('e'),('f'),('g')
)
select * from foo where column1 in (select vals from value_list)
You can reference the VALUE_LIST table in the CTE as many times as you need and keep it updated in one location.
Here's an example of how I've done this before, using the SPLIT_TO_TABLE function.
CREATE OR REPLACE TEMPORARY TABLE tmp_names (fname varchar(20));
INSERT INTO tmp_names
VALUES ('rich'), ('joe'), ('sally'), ('bill'), ('ted');
SELECT *
FROM tmp_names
WHERE fname in (
SELECT t.value
FROM TABLE(SPLIT_TO_TABLE('sally,ted', ',')) as t);--2 records returned
set my_in_stmt = 'sally,ted';
SELECT *
FROM tmp_names
WHERE fname in (
SELECT t.value
FROM TABLE(SPLIT_TO_TABLE($my_in_stmt, ',')) as t); --2 records returned
Documentation of the SPLIT_TO_TABLE function is at:
https://docs.snowflake.com/en/sql-reference/functions/split_to_table.html
I have the data ready to Insert into my Production table however the ID column is NULL and that needs to be pre-populated with the IDs prior to Insert. I have these IDs in another Temp Table... all I want is to simply apply these IDs to the records in my Temp Table.
For example... Say I have 10 records all simply needing IDs. I have in another temp table exactly 10 IDs... they simply need to be applied to my 10 records in my 'Ready to INSERT' Temp Table.
I worked in Oracle for about 9 years and I would have done this simply by looping over my 'Collection' using a FORALL Loop... basically I would simply loop over my 'Ready to INSERT' temp table and for each row apply the ID from my other 'Collection'... in SQL Server I'm working with Temp Tables NOT Collections and well... there's no FORALL Loop or really any fancy loops in SQL Server other than WHILE.
My goal is to know the appropriate method to accomplish this in SQL Server. I have learned that in the SQL Server world so many of the DML operations are all SET Based whereas when I worked in oracle we handled data via arrays/collections and using CURSORS or LOOPs we would simply iterate thru the data. I've seen in the SQL Server world using CURSORS and/or iterating thru data record by record is frowned upon.
Help me get my head out of the 'Oracle' space I was in for so long and into the 'SQL Server' space I need to be in. This has been a slight struggle.
The code below is how I've currently implemented this however it just seems convoluted.
SET NOCOUNT ON;
DECLARE #KeyValueNewMAX INT,
#KeyValueINuse INT,
#ClientID INT,
#Count INT;
DROP TABLE IF EXISTS #InterOtherSourceData;
DROP TABLE IF EXISTS #InterOtherActual;
DROP TABLE IF EXISTS #InterOtherIDs;
CREATE TABLE #InterOtherSourceData -- Data stored here for DML until data is ready for INSERT
(
UniqueID INT IDENTITY( 1, 1 ),
NewIntOtherID INT,
ClientID INT
);
CREATE TABLE #InterOtherActual -- Prod Table where the data will be INSERTED Into
(
IntOtherID INT,
ClientID INT
);
CREATE TABLE #InterOtherIDs -- Store IDs needing to be applied to Data
(
UniqueID INT IDENTITY( 1, 1 ),
NewIntOtherID INT
);
BEGIN
/* TEST Create Fake Data and store it in temp table */
WITH fakeIntOtherRecs AS
(
SELECT 1001 AS ClientID, 'Jake' AS fName, 'Jilly' AS lName UNION ALL
SELECT 2002 AS ClientID, 'Jason' AS fName, 'Bateman' AS lName UNION ALL
SELECT 3003 AS ClientID, 'Brain' AS fName, 'Man' AS lName
)
INSERT INTO #InterOtherSourceData (ClientID)
SELECT fc.ClientID--, fc.fName, fc.lName
FROM fakeIntOtherRecs fc
;
/* END TEST Prep Fake Data */
/* Obtain count so we know how many IDs we need to create */
SELECT #Count = COUNT(*) FROM #InterOtherSourceData;
PRINT 'Count: ' + CAST(#Count AS VARCHAR);
/* For testing set value OF KeyValuePre to the max key currently in use by Table */
SELECT #KeyValueINuse = 13;
/* Using the #Count let's obtain the new MAX ID... basically Existing_Key + SourceRecordCount = New_MaxKey */
SELECT #KeyValueNewMAX = #KeyValueINuse + #Count /* STORE new MAX ID in variable */
/* Print both keys for testing purposes to review */
PRINT 'KeyValue Current: ' + CAST(#KeyValueINuse AS VARCHAR) + ' KeyValue Max: ' + CAST(#KeyValueNewMAX AS VARCHAR);
/* Using recursive CTE generate a fake table containing all of the IDs we want to INSERT into Prod Table */
WITH CTE AS
(
SELECT (#KeyValueNewMAX - #Count) + 1 AS STARTMINID, #KeyValueNewMAX AS ENDMAXID UNION ALL
/* SELECT FROM CTE to create Recursion */
SELECT STARTMINID + 1 AS STARTMINID, ENDMAXID FROM CTE
WHERE (STARTMINID + 1) < (#KeyValueNewMAX + 1)
)
INSERT INTO #InterOtherIDs (NewIntOtherID)
SELECT c.STARTMINID AS NewIntOtherID
FROM CTE c
;
/* Apply New IDs : Using the IDENTITY fields on both Temp Tables I can JOIN the tables by the IDENTITY columns
| Is there a BETTER Way to do this?... like LOOP over each record rather than having to build up common IDs in both tables using IDENTITY columns?
*/
UPDATE #InterOtherSourceData SET NewIntOtherID = oi.NewIntOtherID
FROM #InterOtherIDs oi
JOIN #InterOtherSourceData o ON o.UniqueID = oi.UniqueID
;
/* View data that is ready for insert */
--SELECT *
--FROM #InterOtherSourceData
--;
/* INSERT DATA INTO PRODUCTION TABLE */
INSERT INTO #InterOtherActual (IntOtherID, ClientId)
SELECT NewIntOtherID, ClientID
FROM #InterOtherSourceData
;
SELECT * FROM #InterOtherActual;
END
To pre-generate key values in SQL Server use a sequence rather than an IDENTITY column.
eg
drop table if exists t
drop table if exists #t_stg
drop sequence t_seq
go
create sequence t_seq start with 1 increment by 1
create table t(id int primary key default (next value for t_seq),a int, b int)
create table #t_stg(id int, a int, b int)
insert into #t_stg(a,b) values (1,2),(3,3),(4,5)
update #t_stg set id = next value for t_seq
--select * from #t_stg
insert into t(id,a,b)
select * from #t_stg
How to use SELECT Sub-Query in alias for a column?
Here's my script:-
/*Declaring variables:*/
SET period= '3';
SET smryseg=concat('sku',$period,'_smry');
SET spend= concat('sku',$period,'')
/*Printing it:*/
SELECT $period; /* #O/P: 3 */
SELECT $smryseg; /* #O/P: sku3_smry */
SELECT $spend; /* #O/P: sku3_spend */
/*now I want to use this variable in my INNER SELECT query:*/
create table IDENTIFIER ($smryseg) as
SELECT sum(spend) as (SELECT $spend)
FROM my_table;
Here, the last query is giving me an error, I also tried using IDENTIFIER, CONCAT, SUBSRING, $ ,removing parenthesis and much more.
I just want the name of column 'sum(spend)' obtained as 'sku3_spend' i.e in dynamic format
You can rename the column afterwards. Here's a self-contained example:
set spend_col='sku3_spend';
create or replace table t as select sum(spend) as x from values(1),(2),(3) s(spend);
alter table t rename column x to identifier($spend_col);
UPDATE
You can store dynamically named columns in an object:
set spend_col='sku3_spend';
create or replace table t as
select object_construct($spend_col, sum(spend)) data
from values(1),(2),(3) s(spend);
select data:sku3_spend from t;
I'm quite new to T-SQL and currently struggling with an insert statement in my stored procedure: I use as a parameter in the stored procedure a list of ids of type INT.
If the list is NOT empty, I want to store the ids into the table Delivery.
To pass the list of ids, i use a table type:
CREATE TYPE tIdList AS TABLE
(
ID INT NULL
);
GO
Maybe you know a better way to pass a list of ids into a stored procedure?
However, my procedure looks as follows:
-- parameter
#DeliveryModelIds tIdList READONLY
...
DECLARE #StoreId INT = 1;
-- Delivery
IF EXISTS (SELECT * FROM #DeliveryModelIds)
INSERT [MyDB].[Delivery] ([DeliveryModelId], [StoreId])
OUTPUT inserted.DeliveryId
SELECT ID FROM #DeliveryModelIds;
If the list has values, I want to store the values into the DB as well as the StoreId which is always 1.
If I insert the DeliveryIds 3,7,5 The result in table Delivery should look like this:
DeliveryId | StoreId | DeliveryModelId
1...............| 1...........| 3
2...............| 1...........| 7
3...............| 1...........| 5
Do you have an idea on how to solve this issue?
THANKS !
You can add #StoreId to your select for your insert.
...
IF EXISTS (SELECT * FROM #DeliveryModelIds)
INSERT [MyDB].[Delivery] ([DeliveryModelId], [StoreId])
OUTPUT inserted.DeliveryId
SELECT ID, #StoreId FROM #DeliveryModelIds;
Additionally, if you only want to insert DeliveryModelId that do not currently exist in the target table, you can use not exists() in the where clause like so:
...
IF EXISTS (SELECT * FROM #DeliveryModelIds)
INSERT [MyDB].[Delivery] ([DeliveryModelId], [StoreId])
OUTPUT inserted.DeliveryId
SELECT dmi.ID, #StoreId
FROM #DeliveryModelIds dmi
where not exists (
select 1
from MyDb.Delivery i
where i.StoreId = #StoreId
and i.DeliveryModeId = dmi.ID
);
You need to modify the INSERT statement to:
INSERT [MyDB].[Delivery] ([DeliveryModelId], [StoreId])
OUTPUT inserted.DeliveryId
SELECT ID, 1 FROM #DeliveryModelIds;
So you are also selecting a literal, 1, along with ID field.
I have two tables with different columns in PostgreSQL 9.3:
CREATE TABLE person1(
NAME TEXT NOT NULL,
AGE INT NOT NULL
);
CREATE TABLE person2(
NAME TEXT NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR(50),
SALARY REAL
);
INSERT INTO person2 (Name, Age, ADDRESS, SALARY)
VALUES ('Piotr', 20, 'London', 80);
I would like to copy records from person2 to person1, but column names can change in program, so I would like to select joint column names in program. So I create an array containing the intersection of column names. Next I use a function: insert into .... select, but I get an error, when I pass the array variable to the function by name. Like this:
select column_name into name1 from information_schema.columns where table_name = 'person1';
select column_name into name2 from information_schema.columns where table_name = 'person2';
select * into cols from ( select * from name1 intersect select * from name2) as tmp;
-- Create array with name of columns
select array (select column_name::text from cols) into cols2;
CREATE OR REPLACE FUNCTION f_insert_these_columns(VARIADIC _cols text[])
RETURNS void AS
$func$
BEGIN
EXECUTE (
SELECT 'INSERT INTO person1 SELECT '
|| string_agg(quote_ident(col), ', ')
|| ' FROM person2'
FROM unnest(_cols) col
);
END
$func$ LANGUAGE plpgsql;
select * from cols2;
array
------------
{name,age}
(1 row)
SELECT f_insert_these_columns(VARIADIC cols2);
ERROR: column "cols2" does not exist
What's wrong here?
You seem to assume that SELECT INTO in SQL would assign a variable. But that is not so.
It creates a new table and its use is discouraged in Postgres. Use the superior CREATE TABLE AS instead. Not least, because the meaning of SELECT INTO inside plpgsql is different:
Combine two tables into a new one so that select rows from the other one are ignored
Concerning SQL variables:
User defined variables in PostgreSQL
Hence you cannot call the function like this:
SELECT f_insert_these_columns(VARIADIC cols2);
This would work:
SELECT f_insert_these_columns(VARIADIC (TABLE cols2 LIMIT 1));
Or cleaner:
SELECT f_insert_these_columns(VARIADIC array) -- "array" being the unfortunate column name
FROM cols2
LIMIT 1;
About the short TABLE syntax:
Is there a shortcut for SELECT * FROM?
Better solution
To copy all rows with columns sharing the same name between two tables:
CREATE OR REPLACE FUNCTION f_copy_rows_with_shared_cols(
IN _tbl1 regclass
, IN _tbl2 regclass
, OUT rows int
, OUT columns text)
LANGUAGE plpgsql AS
$func$
BEGIN
SELECT INTO columns -- proper use of SELECT INTO!
string_agg(quote_ident(attname), ', ')
FROM (
SELECT attname
FROM pg_attribute
WHERE attrelid IN (_tbl1, _tbl2)
AND NOT attisdropped -- no dropped (dead) columns
AND attnum > 0 -- no system columns
GROUP BY 1
HAVING count(*) = 2
) sub;
EXECUTE format('INSERT INTO %1$s(%2$s) SELECT %2$s FROM %3$s'
, _tbl1, columns, _tbl2);
GET DIAGNOSTICS rows = ROW_COUNT; -- return number of rows copied
END
$func$;
Call:
SELECT * FROM f_copy_rows_with_shared_cols('public.person2', 'public.person1');
Result:
rows | columns
-----+---------
3 | name, age
Major points
Note the proper use of SELECT INTO for assignment inside plpgsql.
Note the use of the data type regclass. This allows to use schema-qualified table names (optionally) and defends against SQL injection attempts:
Table name as a PostgreSQL function parameter
About GET DIAGNOSTICS:
Count rows affected by DELETE
About OUT parameters:
Returning from a function with OUT parameter
The manual about format().
Information schema vs. system catalogs.