In Snowflake , any one used Merging into table when matched then insert into select statement - merge

In the snowflake ,Merge into when Matched then insert () values () , this is working fine but i have a case where the insert should be from select statements
Merge into when Matched then insert () select * from dummy; - but this is failing with Errors

According to the documentation, the MERGE command allows only:
INSERT [(<col_name> [, ...])] VALUES (<expr> [, ...])
It does not handle the SELECT statement inside an INSERT statement, but it is possible to use a subquery in the USING clause and there you should possibly pass your SELECT and transformations, it could also be a CTE (Common Table Expressions). For example:
merge into target using (
SELECT k, max (v) as v FROM src group by k) as b --<-- Your subquery
on target.k = b.k
when not matched then insert (k, v) values (b.k, b.v);
or with CTE expression:
with b as (
SELECT k, max (v) as v FROM src group by k --<-- Your subquery
)
merge into target using b
on target.k = b.k
when not matched then insert (k, v) values (b.k, b.v);
Reference: MERGE, CTE

Related

postgres - single record with one to many join tables

I have two tables a and b. Where main table is 'a' where I want to select from and table 'b' is for filtering.
Below is sample table with some data.
create table a (
id varchar primary key,
name varchar,
p varchar[]
);
insert into a (id, name, p) values
('1', 'v1', array['p1']),
('2', 'v2', array['p1','p2']),
('3', 'v3', array['p2','p3']);
create table b (
p varchar,
x varchar
);
insert into b(p, x) values
('p1', 'x1'),
('p2', 'x2'),
('p3', 'x1'),
('p3', 'x3'),
('p1', 'x2');
I want only one row from table a based on join on column p and filter on x. I tried few options, it works when I have one to one record in a and b but when I have one to many then I get multiple records.
select a.* from a,b where b.p=any(a.p) and b.x='x2';
Output I get is:
id name p
-----------------
1 v1 p1
2 v2 p1,p2
2 v2 p1,p2
3 v3 p2,p3
What I want is
id name p
-----------------
1 v1 p1
2 v2 p1,p2
3 v3 p2,p3
Also I am expecting table 'a' to have millions of rows and 'b' will have only few, so query has to be perform effectively.
As you only want columns from table a use an exists condition rather than a join:
select a.*
from a
where exists (select *
from b
where b.p = any(a.p)
and b.x='x2');
That will however be quite hard to optimize.
Another option - if not too many rows match the join criteria - is to apply a distinct on the result of the join.
In that case a GIN index on the array column can be used:
create index on a using gin (p);
select distinct a.*
from a
join b on a.p #> array[b.p]
where b.x= 'x2';

How to get a row for each occurrence of Id in IN clause?

Given that I have a list of repeating ids that I need to fetch some additional data to populate xls spreadsheet, how can I do that. "IN" clause returns only 1 match, but I need a row for each occurrence of an Id. I looked at PIVOT, thinking I could create a select list and then do inner join.
Select m.Id, m.LegalName, m.OtherId
from MyTable m
where m.OtherId in (1,2,1,1,3,1,4,4,2,1)
You can use VALUES clause :
SELECT t.id as OtherId, m.id, m.LegalName
FROM ( VALUES (1),(2),(1),(1),(3),(1),(4),(4),(2),(1)
) t(ID) INNER JOIN
MyTable m
ON m.OtherId = t.id;

Postgres - Repeating an element N times as array

For example, where the element is 'hi', and where N is 3, I need a PostgreSQL snippet I can use in a SELECT query that returns the following array:
['hi', 'hi', 'hi']
Postgres provides array_fill for this purpose, e.g.:
SELECT array_fill('hi'::text, '{3}');
SELECT array_fill('hi'::text, array[3]);
The two examples are equivalent but the 2nd form is more convenient if you wish to replace the dimension 3 with a variable.
See also: https://www.postgresql.org/docs/current/functions-array.html
You may use array_agg with generate_series
select array_agg(s) from ( values('hi')) as t(s) cross join generate_series(1,3)
Generic
select array_agg(s) from ( values(:elem)) as t(s) cross join generate_series(1,:n)
DEMO
sql demo
with cte as (
select 'hi' as rep_word, generate_series(1, 3) as value
) -- ^^^ n = 3
select array(SELECT rep_word::text from cte);

Merge in postgres

Am trying to convert below oracle query to postgres,
MERGE INTO table1 g
USING (SELECT distinct g.CDD , d.SGR
from table2 g, table3 d
where g.IDF = d.IDF) f
ON (g.SGR = f.SGR and g.CDD = f.CDD)
WHEN NOT MATCHED THEN
INSERT (SGR, CDD)
VALUES (f.SGR, f.CDD);
I made changes as below compatible to postgres:
WITH f AS (
SELECT distinct g.CDD , d.SGR
from table2 g, table3 d
where g.IDF = d.IDF
),
upd AS (
update table1 g
set
SGR = f.SGR , CDD = f.CDD
FROM f where g.SGR = f.SGR and g.CDD = f.CDD
returning g.CDD, g.SGR
)
INSERT INTO table1(SGR, CDD ) SELECT f.SGR, f.CDD FROM f;
But am doubtful ,my oracle query is not updating any columns if data matched , but am unable to convert it accordingly . Can anyone help me to correct it ?
Assuming you have a primary (or unique) key on (sgr, cdd) you can convert this to an insert ... on conflict statement:
insert into table1 (SGR, CDD)
select distinct g.CDD, d.SGR
from table2 g
join table3 d ON g.IDF = d.IDF
on conflict (cdd, sgr) do nothing;
If you don't have a unique constraint (which bears the question: why?) then a straight-forward INSERT ... SELECT statement should work (which would have worke in Oracle as well).
WITH f AS (
SELECT distinct g.CDD, d.SGR
from table2 g
join table3 d on g.IDF = d.IDF
)
INSERT INTO table1 (SGR, CDD)
SELECT f.SGR, f.CDD
FROM f
WHERE NOT EXISTS (select *
from table1 t1
join f on (t1.sgr, t1.cdd) = (f.cdd, f.sgrf));
Note that this is NOT safe for concurrent execution (and neither is Oracle's MERGE statement). You can still wind up with duplicate values in table1 (with regards to the combination of (sgr,cdd)).
The only sensible way to prevent duplicates is to create a unique index (or constraint) - which would enable you to use the much more efficient insert on conflict. You should really consider that if your business rules disallow duplicates.
Note that I converted your ancient, implicit join in the WHERE clause to a modern, explicit JOIN operator, but it is not required for this to work.

How Dynamicaly columns in UNPIVOT operator

I currently have the following query:
WITH History AS (
SELECT
kz.*,
kz.__$operation AS operation,
map.tran_begin_time as beginT,
map.tran_end_time as endT
FROM cdc.fn_cdc_get_all_changes_dbo_EXT_GeolObject_KategZalezh(sys.fn_cdc_get_min_lsn('dbo_EXT_GeolObject_KategZalezh'), sys.fn_cdc_get_max_lsn(), 'all') AS kz
INNER JOIN [cdc].[lsn_time_mapping] map
ON kz.[__$start_lsn] = map.start_lsn
where kz.GUID_BalanceHC_Zalezh = 'DDA9AB3A-A0AF-4623-9362-0000C8C83D63'
),
UnpivotedValues AS(
SELECT guid, GUID_another, field, val, operation, beginT, endT
FROM History
UNPIVOT ( [val] FOR field IN
(
area,
oilwidthmin,
oilwidthmax,
efectivwidthmin,
efectivwidthmax,
etc...
))t
),
UnpivotedWithLastValue AS (
SELECT
*,
--Use LAG() to get the last value for the same field
LAG(val, 1) OVER (PARTITION BY guid, GUID_another, field ORDER BY BeginT) LastVal
FROM UnpivotedValues
)
SELECT * FROM UnpivotedWithLastValue WHERE val <> LastVal OR LastVal IS NULL ORDER BY guid
This query returns the changed values for a single table that has CDC (Change Data Capture) enabled.
I want to create a stored procedure that receives the columns to be unpivoted, and the cdc function (e.g. cdc.fn_cdc_get_all_...) as parameters and returns the result set.
The result for this tables must be joined in one report.
In my case parameter 1 is cdc.fn_cdc_get_all_changes_dbo_EXT_GeolObject_KategZalezh(sys.fn_cdc_get_min_lsn('dbo_EXT_GeolObject_KategZalezh'), sys.fn_cdc_get_max_lsn(), 'all'). This is the CDC function.
How should I send the list of fields that i want in the result? How's the string?
Also, is there a way to do without dynamic SQL? Dynamic SQL it is not better solution for performance.
As you know SQL Server is declarative by design and does not support macro substitution.
UNPIVOT would clearly be more performant, but here is a simplified example of a UNPIVOT which does not require Dynamic SQL, but only a little XML.
Example
Let's assume your table/results looks like this:
You may notice that I only we only specify key fields to EXCLUDE in the final WHERE
Declare #YourData table (ID int,Active bit,First_Name varchar(50),Last_Name varchar(50),EMail varchar(50),Salary decimal(10,2))
Insert into #YourData values
(1,1,'John','Smith','john.smith#email.com',85600),
(2,0,'Jane','Doe' ,'jane.doe#email.com',83200)
;with cte as (
-- Replace with your Complex Query
Select * from #YourData
)
Select A.ID
,A.Active
,C.*
From cte A
Cross Apply (Select XMLData=cast((Select A.* for XML RAW) as xml)) B
Cross Apply (
Select Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From XMLData.nodes('/row') C1(n)
Cross Apply C1.n.nodes('./#*') C2(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('ID','Active')
) C
Returns