Update multiple rows in same query using PostgreSQL - sql

I'm looking to update multiple rows in PostgreSQL in one statement. Is there a way to do something like the following?
UPDATE table
SET
column_a = 1 where column_b = '123',
column_a = 2 where column_b = '345'

You can also use update ... from syntax and use a mapping table. If you want to update more than one column, it's much more generalizable:
update test as t set
column_a = c.column_a
from (values
('123', 1),
('345', 2)
) as c(column_b, column_a)
where c.column_b = t.column_b;
You can add as many columns as you like:
update test as t set
column_a = c.column_a,
column_c = c.column_c
from (values
('123', 1, '---'),
('345', 2, '+++')
) as c(column_b, column_a, column_c)
where c.column_b = t.column_b;
sql fiddle demo

Based on the solution of #Roman, you can set multiple values:
update users as u set -- postgres FTW
email = u2.email,
first_name = u2.first_name,
last_name = u2.last_name
from (values
(1, 'hollis#weimann.biz', 'Hollis', 'Connell'),
(2, 'robert#duncan.info', 'Robert', 'Duncan')
) as u2(id, email, first_name, last_name)
where u2.id = u.id;

Yes, you can:
UPDATE foobar SET column_a = CASE
WHEN column_b = '123' THEN 1
WHEN column_b = '345' THEN 2
END
WHERE column_b IN ('123','345')
And working proof: http://sqlfiddle.com/#!2/97c7ea/1

For updating multiple rows in a single query, you can try this
UPDATE table_name
SET
column_1 = CASE WHEN any_column = value and any_column = value THEN column_1_value end,
column_2 = CASE WHEN any_column = value and any_column = value THEN column_2_value end,
column_3 = CASE WHEN any_column = value and any_column = value THEN column_3_value end,
.
.
.
column_n = CASE WHEN any_column = value and any_column = value THEN column_n_value end
if you don't need additional condition then remove and part of this query

Let's say you have an array of IDs and equivalent array of statuses - here is an example how to do this with a static SQL (a sql query that doesn't change due to different values) of the arrays :
drop table if exists results_dummy;
create table results_dummy (id int, status text, created_at timestamp default now(), updated_at timestamp default now());
-- populate table with dummy rows
insert into results_dummy
(id, status)
select unnest(array[1,2,3,4,5]::int[]) as id, unnest(array['a','b','c','d','e']::text[]) as status;
select * from results_dummy;
-- THE update of multiple rows with/by different values
update results_dummy as rd
set status=new.status, updated_at=now()
from (select unnest(array[1,2,5]::int[]) as id,unnest(array['a`','b`','e`']::text[]) as status) as new
where rd.id=new.id;
select * from results_dummy;
-- in code using **IDs** as first bind variable and **statuses** as the second bind variable:
update results_dummy as rd
set status=new.status, updated_at=now()
from (select unnest(:1::int[]) as id,unnest(:2::text[]) as status) as new
where rd.id=new.id;

Came across similar scenario and the CASE expression was useful to me.
UPDATE reports SET is_default =
case
when report_id = 123 then true
when report_id != 123 then false
end
WHERE account_id = 321;
Reports - is a table here, account_id is same for the report_ids mentioned above. The above query will set 1 record (the one which matches the condition) to true and all the non-matching ones to false.

The answer provided by #zero323 works great on Postgre 12. In case, someone has multiple values for column_b (referred in OP's question)
UPDATE conupdate SET orientation_status = CASE
when id in (66934, 39) then 66
when id in (66938, 49) then 77
END
WHERE id IN (66934, 39, 66938, 49)
In the above query, id is analogous to column_b; orientation_status is analogous to column_a of the question.

In addition to other answers, comments and documentation, the datatype cast can be placed on usage. This allows an easier copypasting:
update test as t set
column_a = c.column_a::number
from (values
('123', 1),
('345', 2)
) as c(column_b, column_a)
where t.column_b = c.column_b::text;

#Roman thank you for the solution, for anyone using node, I made this utility method to pump out a query string to update n columns with n records.
Sadly it only handles n records with the same columns so the recordRows param is pretty strict.
const payload = {
rows: [
{
id: 1,
ext_id: 3
},
{
id: 2,
ext_id: 3
},
{
id: 3,
ext_id: 3
} ,
{
id: 4,
ext_id: 3
}
]
};
var result = updateMultiple('t', payload);
console.log(result);
/*
qstring returned is:
UPDATE t AS t SET id = c.id, ext_id = c.ext_id FROM (VALUES (1,3),(2,3),(3,3),(4,3)) AS c(id,ext_id) WHERE c.id = t.id
*/
function updateMultiple(table, recordRows){
var valueSets = new Array();
var cSet = new Set();
var columns = new Array();
for (const [key, value] of Object.entries(recordRows.rows)) {
var groupArray = new Array();
for ( const [key2, value2] of Object.entries(recordRows.rows[key])){
if(!cSet.has(key2)){
cSet.add(`${key2}`);
columns.push(key2);
}
groupArray.push(`${value2}`);
}
valueSets.push(`(${groupArray.toString()})`);
}
var valueSetsString = valueSets.join();
var setMappings = new String();
for(var i = 0; i < columns.length; i++){
var fieldSet = columns[i];
setMappings += `${fieldSet} = c.${fieldSet}`;
if(i < columns.length -1){
setMappings += ', ';
}
}
var qstring = `UPDATE ${table} AS t SET ${setMappings} FROM (VALUES ${valueSetsString}) AS c(${columns}) WHERE c.id = t.id`;
return qstring;
}

I don't think the accepted answer is entirely correct. It is order dependent. Here is an example that will not work correctly with an approach from the answer.
create table xxx (
id varchar(64),
is_enabled boolean
);
insert into xxx (id, is_enabled) values ('1',true);
insert into xxx (id, is_enabled) values ('2',true);
insert into xxx (id, is_enabled) values ('3',true);
UPDATE public.xxx AS pns
SET is_enabled = u.is_enabled
FROM (
VALUES
(
'3',
false
,
'1',
true
,
'2',
false
)
) AS u(id, is_enabled)
WHERE u.id = pns.id;
select * from xxx;
So the question still stands, is there a way to do it in an order independent way?
---- after trying a few things this seems to be order independent
UPDATE public.xxx AS pns
SET is_enabled = u.is_enabled
FROM (
SELECT '3' as id, false as is_enabled UNION
SELECT '1' as id, true as is_enabled UNION
SELECT '2' as id, false as is_enabled
) as u
WHERE u.id = pns.id;

Related

How can convert SQL to lambda or LINQ

How can I convert below SQL to lambda or LINQ?
with cte
as (select * from test1
union all
select * from test2)
select * from cte
union all
select sum(columnA),sum(columnB),sum(columnC) from cte
In Linq UNION ALL is .Concat(), so:
var cte = test1.Concat(test2);
var sums = new MyModel
{
columnA = cte.Sum(c => c.columnA),
columnB = cte.Sum(c => c.columnB),
columnC = cte.Sum(c => c.columnC),
}
return cte.Concat(IEnumerable.Repeat(sums, 1));
You must remember that test1 and test2 must be type MyModel and MyModel contains only columnA, columnB and columnC.
I put two tables together in one datagridvie but in the last row of datagridview I need the total for both tables in the country, I can do one row in total for one table and another row for the other table I also don't need it, like I can only have one line with the total of both tables.
DataContex db = new DataContex();
var query = (
from v1 in db.View1
where shf.Date >= dpDate.Value && shf.Date <= dpDate1.Value
select new
{
v1.Name,
v1.Date,
v1.Quality,
v1.Rat,
v1.Total
}
).Concat
(
from v2 in db.View2
where f.Date >= dpDate.Value && f.Date <= dpDate1.Value
select new
{
v2.Name,
v2.Date,
v2.Quality,
v2.Rat,
v2.Total
}
).Concat
(from View2 in
(from v2 in db.View2
where v2.Date >= dpDate.Value && sh.Date <= dpDate1.Value
select new
{
v2.Name,
v2.Date,
v2.Quality,
v2.Rate,
v2.Total
})
group v2 by new { v2.NRFA } into g
select new
{
Name = "Total:",
Date = dpDate1.Value,
Quality = (decimal?)g.Sum(p => p.Quality),
Rate = (decimal?)g.Sum(p => p.Rate),
Total = (decimal?)g.Sum(p => p.Total)
}
);
Blockquote

Optional parameters in cursor where clause

I have the following sample query which takes values from procedure parameters. The parameter can be either passed or default to null.
SELECT * FROM table
WHERE( table_term_code = '201931'
OR (table_term_code = '201931' and table_DETL_CODE ='CA02')
OR ( table_term_code = '201931' and table_ACTIVITY_DATE = sysdate)
OR ( table_term_code = '201931' and table_SEQNO = NULL));
i.e the user can input term code and not input any other parameter, or can input term code and table_DETL_CODE and not any other input parameter.
Same goes for the other 2 or conditions.
If a term code is passed and table_DETL_CODE is null, the query should return all the values for that term_code, whereas this query returns null.
Is there a way to achieve this without case or if conditions in PL/SQL?
If I understood you correctly, this might be what you're looking for:
select *
from your_table
where (table_term_code = :par_term_code or :par_term_code is null)
and (table_detl_code = :par_detl_code or :par_detl_code is null)
and (table_activity_date = :par_activity_date or :par_activity_date is null)
and (table_seqno = :par_seqno or :par_seqno is null)
The description seems to that you require user to enter table_term_code and then either none or exactly 1 of the other 3. If so then perhaps:
select *
from your_table
where table_term_code = :par_term_code
and ( (table_detl_code = :par_detl_code and :par_activity_date is null and :par_seqno is null)
or (table_activity_date = :par_activity_date and :par_detl_code is null and :par_seqno is null)
or (table_seqno = :par_seqno and :par_detl_code is null and :par_activity_date is null)
or (table_seqno is null and :par_detl_code is null and :par_activity_date is null)
);

select distinct out of three column connectiontable

I have a ef connection table with 3 columns in it. I want to select distinct value out of two of them.
I want to select distinct rows orderby ResourceId and MetaDataId.
So i want it to return row (1, 3 and 4) in this case.
Ive tried this:
ctx.ResourceMetas.Where(a => a.ResourceId == resourceid).Distinct()});
But obviously this gets the distinct values out of all three. Can i somehow choose to get distinct out of just the two?
You may group by your distinct values and then get the max or min of the leftovers.
In your case that you only have one more field you could do it like:
ctx.ResourceMetas
.GroupBy(x=>new{x.ResourceId, x.MetaDataId})
.Select
(
x=>new
{
MetaListId = x.Min(m=>m.MetaListId ),
ResourceId = x.Key.ResourceId,
MetaDataId = x.Key.MetaDataId
}
)
.Where(a => a.ResourceId == resourceid)
But in a scenario that you would like the distinct values of 2 fields out of more than three then you would have to do it like:
ctx.ResourceMetas
.GroupBy(x=>new{x.ResourceId, x.MetaDataId})
.Select
(
x=>new
{
MetaListId = x.Where(i=>i.MetaListId == x.Min(m=>m.MetaListId)).FirstOrDefault().MetaListId ,
OtherField = x.Where(i=>i.MetaListId == x.Min(m=>m.MetaListId)).FirstOrDefault().OtherField ,
ResourceId = x.Key.ResourceId,
MetaDataId = x.Key.MetaDataId
}
)
.Where(a => a.ResourceId == resourceid)

Perl sql query statement

My sql statement is simple as below:
if not exists (select col_a from t_a where co_b = 'v_b')
begin
insert into t_a (col_a ,col_b )
VALUES(v_a,v_b)
end
else
begin
update t_a set col_a = v_a, col_b = v_b where col_b = 'v_b'
end
As I have hundreds of rows to update, how can I do this in Perl for the least time cost?
If I use Prepare + Execute, how to write the statement using the placeholder ? ?
Does the $dbh->prepare($statement); support multiple composite SQL lines like those above? Or do I have to save the lines into an sql file and run it using SQL server?
To make the question more clear, my Perl lines look like those below:
$statement = "if ... VALUES(?,?)...update t_a set col_a = ?, col_b = ?";
# better to use one binding values(v_a, v_b) couplets mapping
# the 2 placeholders of insert and update both?
foreach (#$va_arr) {
my $values_for_one_row = $_;
$dbh->prepare($statement);
$execute->execute($values_for_one_row->{col_a }, $values_for_one_row->{col_b });
}
I forgot one thing: the 'whatever' is also a value in $va_arr to be changed on every iteration: if not exists (select col_a from t_a where co_b = 'v_b'). Also, the update section should be: update t_a set col_a = ?, col_b = ? where col_b = "v_b". Seems no better way then include the prepare into the loop? Sorry I didn't think the example complete. But I think simbabque's answer is good enough.
You can use your SQL without problems. You need to prepare the statement once. I am assuming your $va_arr looks like this:
my $va_arr = [
{
col_a => 1,
col_b => 2,
},
{
col_a => 'foo',
col_b => 'bar',
},
];
Your code to run this could be as follows. Note that you have to pass the col_n params twice as it needs to fill them in two times into each ? with every execute. They get filled in the order of the ? in the query, so we need col_a, col_b for the INSERT and another col_a, col_b for the UPDATE.
my $sql = <<'EOSQL';
if not exists (select col_a from t_a where co_b = 'whatever')
begin
insert into t_a (col_a ,col_b )
VALUES(?, ?)
end
else
begin
update t_a set col_a = ?, col_b = ?
end
EOSQL
my $sth = $dbi->prepare($sql);
foreach ($values = #{ $va_arr }) {
$dbh->execute($values->{col_a }, $values->{col_b },
$values->{col_a }, $values->{col_b });
}
If you have a long list of columns and you know the order, consider this:
my #columns = qw( col_a col_b col_c col_n );
my $va_arr = [
{
col_a => 1,
col_b => 2,
col_n => 99,
},
{
col_a => 'foo',
col_b => 'bar',
col_n => 'baz',
},
];
# build the sql dynamically based on columns
my $sql = q{
if not exists (select col_a from t_a where co_b = 'whatever')
begin
insert into t_a (} . join(',' #columns) . q{)
VALUES(} . join(',', map '?', #columns) . q{)
end
else
begin
update t_a set } . join(',' map { "$_ => ?" } #columns) . q{
end
};
my $sth = $dbi->prepare($sql);
foreach ($values = #{ $va_arr }) {
$dbh->execute(#{$values}{#columns}, #{$values}{#columns});
}
Let's look at what this does. It's helpful if you have a really long list of columns.
You know their names and order, and put that into #columns.
Build the SQL based on these columns. We have to add the column name and a ? to the INSERT and the combination of both to the UPDATE for each of the columns.
Execute it with a hash ref slice
Please note that I have not run this, just hacked it in here.
you should put the prepare statement out of the loop and use a transaction
for example:
my $sql1 = qq(select col_a from t_a where col_b = ?);
my $sql2 = qq(insert into t_a (col_a, col_b) VALUES(?, ?));
my $sql3 = qq(update t_a set col_a = ? where col_b = ?);
my $query = $dbh->prepare($sql1);
$dbh->begin_work();
foreach (#$va_arr) {
my $values_for_one_row = $_;
$query->execute($values_for_one_row->{col_b});
my #out = $query->fetchrow_array();
$query->finish();
if ( not defined $out[0] )
{
$dbh->do($sql2, undef, $values_for_one_row->{col_a}, $values_for_one_row->{col_b});
}
else
{
$dbh->do($sql3, undef, $values_for_one_row->{col_a}, $values_for_one_row->{col_b});
}
}
$dbh->commit();
If upsert is not available, here's how I might do it:
Bulk load the data into a staging table.
Delete all data that joins to the target table.
Insert data from staging to target.
Alternatively you can update from staging to target, delete from the staging data that joins, then insert what's left in staging.
Or, a few hundred rows is not that many, so I might: prepare an insert and an update statement handle outside of the loop. Then in the loop:
my $rows = $upd_sth->execute(...);
$ins_sth->execute(...) if $rows == 0;

How To Split Pipe-Delimited Column and insert each value into new table Once?

I have an old database with a gazillion records (more or less) that have a single tags column (with tags being pipe-delimited) that looks like so:
Breakfast
Breakfast|Brunch|Buffet|Burger|Cakes|Crepes|Deli|Dessert|Dim Sum|Fast Food|Fine Wine|Spirits|Kebab|Noodles|Organic|Pizza|Salad|Seafood|Steakhouse|Sushi|Tapas|Vegetarian
Breakfast|Brunch|Buffet|Burger|Deli|Dessert|Fast Food|Fine Wine|Spirits|Noodles|Pizza|Salad|Seafood|Steakhouse|Vegetarian
Breakfast|Brunch|Buffet|Cakes|Crepes|Dessert|Fine Wine|Spirits|Salad|Seafood|Steakhouse|Tapas|Teahouse
Breakfast|Brunch|Burger|Crepes|Salad
Breakfast|Brunch|Cakes|Dessert|Dim Sum|Noodles|Pizza|Salad|Seafood|Steakhouse|Vegetarian
Breakfast|Brunch|Cakes|Dessert|Dim Sum|Noodles|Pizza|Salad|Seafood|Vegetarian
Breakfast|Brunch|Deli|Dessert|Organic|Salad
Breakfast|Brunch|Dessert|Dim Sum|Hot Pot|Seafood
Breakfast|Brunch|Dessert|Dim Sum|Seafood
Breakfast|Brunch|Dessert|Fine Wine|Spirits|Noodles|Pizza|Salad|Seafood
Breakfast|Brunch|Dessert|Fine Wine|Spirits|Salad|Vegetarian
Is there a way one could retrieve each tag and insert it into a new table tag_id | tag_nm using MySQL only?
Here is my attempt which uses PHP..., I imagine this could be more efficient with a clever MySQL query. I've placed the relationship part of it there too. There's no escaping and error checking.
$rs = mysql_query('SELECT `venue_id`, `tag` FROM `venue` AS a');
while ($row = mysql_fetch_array($rs)) {
$tag_array = explode('|',$row['tag']);
$venueid = $row['venue_id'];
foreach ($tag_array as $tag) {
$rs2 = mysql_query("SELECT `tag_id` FROM `tag` WHERE tag_nm = '$tag'");
$tagid = 0;
while ($row2 = mysql_fetch_array($rs2)) $tagid = $row2['tag_id'];
if (!$tagid) {
mysql_execute("INSERT INTO `tag` (`tag_nm`) VALUES ('$tag')");
$tagid = mysql_insert_id;
}
mysql_execute("INSERT INTO `venue_tag_rel` (`venue_id`, `tag_id`) VALUES ($venueid, $tagid)");
}
}
After finding there is no official split function I've solved the issue using only MySQL like so:
1: I created the function strSplit
CREATE FUNCTION strSplit(x varchar(21845), delim varchar(255), pos int) returns varchar(255)
return replace(
replace(
substring_index(x, delim, pos),
substring_index(x, delim, pos - 1),
''
),
delim,
''
);
Second I inserted the new tags into my new table (real names and collumns changed, to keep it simple)
INSERT IGNORE INTO tag (SELECT null, strSplit(`Tag`,'|',1) AS T FROM `old_venue` GROUP BY T)
Rinse and repeat increasing the pos by one for each collumn (in this case I had a maximum of 8 seperators)
Third to get the relationship
INSERT INTO `venue_tag_rel`
(Select a.`venue_id`, b.`tag_id` from `old_venue` a, `tag` b
WHERE
(
a.`Tag` LIKE CONCAT('%|',b.`tag_nm`)
OR a.`Tag` LIKE CONCAT(b.`tag_nm`,'|%')
OR a.`Tag` LIKE CONCAT(CONCAT('%|',b.`tag_nm`),'|%')
OR a.`Tag` LIKE b.`tag_nm`
)
)