SQL Server Group By in an UPDATE statement - sql

How is it possible to use GROUP BY in this statement?
UPDATE LoanMaster
SET LeadsID1 = NEXT VALUE FOR LM
WHERE PrdAcctId IS NOT NULL
GROUP BY LBrCode, CustNo

I am going to speculate that you want a unique id for the pair LBrCode and CustNo. You can do this as:
with nums as (
SELECT t.*, (NEXT VALUE FOR LM) as newval
FROM (SELECT DISTINCT LBrCode, CustNo
FROM LoanMaster
WHERE PrdAcctId IS NOT NULL
) t
)
update lm
set LeadsId1 = newval
from LoanMaster lm JOIN
nums
on lm.LBrCode = nums.LBrCode and lm.CustNo = nums.CustNo;
Note: Although this should work, you should really create a Leads table with one row per value. It seems like you want a foreign key relationship, and you should have an entity for that relationship.

Related

For each selected row insert another?

I have a single table TableA. It has columns id, type, relatedId, another1, another2. Column type can have values 1, 2 or 3.
What I need is, for each row in TableA, where type = 1, insert another row in the same table and update the original row (column relatedId) with id of newly inserted row. Also, values for some columns in newly inserted row should be copied from the original one.
So for current state:
id|type|relatedId|another1
10| 1 |null|"some text"
11| 2 |null|"somthing"
12| 1 |null|"somthing else"
result should be following:
id|type|relatedId|another1
10| 1 |13 |"some text" - now has relationship to 13
11| 2 |null|"somthing"
12| 1 |14 |"somthing else" - now has relationship to 13
13| 3 |null|"some text" - inserted, "another1" is copied from 10
14| 3 |null|"somthing else" - inserted, "another1" is copied from 12
Assuming the texts are unique you can do this:
demo:db<>fiddle
WITH ins AS (
INSERT INTO tablea(type, related_id, another1)
SELECT 3, null, another1
FROM tablea
WHERE type = 1
RETURNING id, another1
)
UPDATE tablea t
SET related_id = s.id
FROM (
SELECT * FROM ins
) s
WHERE s.another1 = t.another1 AND t.type = 1
The WITH clause allows to execute two separate statements sequentially. So first inserting the new data. With the new generated ids you can update the old data afterwards. Because you have to match the original data, the text is helpful as identifier.
This only works if you do not have to datasets with (1, 'something'). Then it would be hard to identify which of both records is the original for each copy.
Another way could be to store the type1-ids in the new type3-columns as well. If this would be ok for you, you could do this:
demo:db<>fiddle
WITH ins AS (
INSERT INTO tablea(type, related_id, another1)
SELECT 3, id, another1
FROM tablea
WHERE type = 1
RETURNING id, related_id, another1
)
UPDATE tablea t
SET related_id = s.id
FROM (
SELECT * FROM ins
) s
WHERE s.related_id = t.id
This stores the original type1-ids in the related_id column of the new ones. So in every case the original id can be found over this value.
Unfortunately, you cannot NULL out these columns in another WITH clause because the WITH clauses only work with existing data. At this moment the query itself is not done yet. So the new records do not exist physically.
This one could work...
demo:db<>fiddle
WITH to_be_copied AS (
SELECT id, another1
FROM tablea
WHERE type = 1
), ins AS (
INSERT INTO tablea(type, related_id, another1)
SELECT 3, null, another1
FROM to_be_copied
ORDER BY id -- 1
RETURNING id, another1
)
UPDATE tablea t
SET related_id = s.type3_id
FROM (
SELECT
*
FROM
(SELECT id as type1_id, row_number() OVER (ORDER BY id) FROM to_be_copied) tbc
JOIN
(SELECT id as type3_id, row_number() OVER (ORDER BY id) FROM ins) i
ON tbc.row_number = i.row_number
) s
WHERE t.id = s.type1_id
This solution assumes that the given order at (1) ensures the inserting order of the new records. In fact, I am not quite sure about it. But if so: First all type1 records are queried. After that there are copied (in the same order!). After that the old and the new records ids are taken. The row_number() window function adds a consecutive row count to the records. So if both data sets have the same order, the old ids should get the same row number as their corresponding new ids. In that case an identification is possible. For the small example this works...
--> Edit: This seems to say: Yes, the order will be preserved since Postgres 9.6 https://stackoverflow.com/a/50822258/3984221
According to this question Postgres retains the order of row inserted via a SELECT with explicit ORDER BY as of 9.6. We can use this to connect the inserted rows with those they come from using row_number().
WITH
"cte1"
AS
(
SELECT "id",
3 "type",
"related_id",
"another1",
row_number() OVER (ORDER BY "id") "rn"
FROM "tablea"
WHERE "type" = 1
),
"cte2"
AS
(
INSERT INTO "tablea"
("type",
"another1")
SELECT "type",
"another1"
FROM "cte1"
ORDER BY "id"
RETURNING "id"
),
"cte3"
AS
(
SELECT "id",
row_number() OVER (ORDER BY "id") "rn"
FROM "cte2"
)
UPDATE "tablea"
SET "related_id" = "cte3"."id"
FROM "cte1"
INNER JOIN "cte3"
ON "cte3"."rn" = "cte1"."rn"
WHERE "cte1"."id" = "tablea"."id";
In the first CTE we get all the rows, that should be insert along with their row_number() ordered by their ID. In the second one we insert them by selecting from the first CTE explicitly ordering by the ID. We return the inserted ID in the second CTE, so that we can select it in the third CTE where we again add a row_number() ordered by the ID. We can now join the first and the third CTE via the row number to get pairs of original ID and newly inserted IDs. Base on that we can update the table setting the related IDs.
db<>fiddle

Update rows only when a join is unambiguous

I've got two tables, a and b, both with product_name and value. The value column in a is null.
I'd like to update the a table with values from the b table. Because of a quirk in the data, product_name is not unique in either table.
I only want to set the value when there is one unambiguous match on product_name between the two. When more than one row in a has the same product name, or more than one row matches from b, I'd like to keep the value empty. Is there an efficient way to do this in Postgres?
A simpler version of this would be to first identify unique product names in a. Then, update rows where only a single row in b matches -- but I'm also not sure how to write that constraint.
The simple way:
update a
set value = (select min(b.value) from b where b.product_name = a.product_name)
where product_name in (select product_name from a group by product_name having count(*) = 1)
and product_name in (select product_name from b group by product_name having count(*) = 1)
;
You can use aggregation:
update a
set value = b.value
from (select b.product_name, max(b.value) as value
from b
group by b.product_name
having min(b.value) = max(b.value) -- there is only one value
) b
where b.product_name = a.product_name;
Note that this assumes that b.value is not null. It is easy to include logic for null values, if that is needed.
Just put together [How to Select Every Row Where Column Value is NOT Distinct and #Gordon Linoff's answer:
create table a (
id serial primary key
,product_name text
,value int
);
create table b (
id serial primary key
,product_name text
,value int not null
);
insert into a (product_name) values
('A')
,('B')
,('C')
,('D')
,('E')
,('E');
insert into b (product_name,value) values
('A',1)
,('A',1)
,('B',42)
,('C',1)
,('C',2)
,('E',1)
;
update a
set value = b.value
from (select product_name, min(value) as value
from b
group by b.product_name
having 1 = count(*)
) b
where b.product_name = a.product_name
and a.product_name not in
(select product_name
from a
group by product_name
having 1 < count(*));
#Gordon Lindof your answer fails if product_name and value both dupilcated in b (example product_name=A) and misses the requirement product_name not duplicated in a.

How to fill Joining date and id based on following requirement?

I want to fill the joining date and id by creating a new view and output should be like second image
you might be looking for something like:
UPDATE mytable
SET tofill.ID = fillvalues.ID
,tofill.JOININGDATE = fillvalues.JOININGDATE
FROM mytable tofill
INNER JOIN
( SELECT DISTINCT ID, JOININGDATE, NAME
FROM mytable
WHERE ID IS NOT NULL
AND JOININGDATE IS NOT NULL
) fillvalues
ON tofill.NAME = fillvalues.NAME
WHERE tofill.ID IS NULL
OR tofill.JOININGDATE IS NULL
;
I am not familiar with Oracle, but statement should be teh same or similiar

Check for same character value in column

I'm trying to verify when a column has all the same values for the same group. Here is a sample of my table data:
So using this data, for example. I want to check to see if all values of Status is the same for every row with the same TPID. So TPID 60210 should result with True since both items have a Status of A. However, TPID 60061 should result in false since two of the Line_Item show A and the rest P.
I intend to update a different table using this information, setting its status using a CASE statement. But I'm at a loss how to check against this column to find the values I desire.
;WITH CTE_Count
AS
(
SELECT TPID, COUNT(DISTINCT Status) CNT
FROM TableName
GROUP BY TPID
)
UPDATE AnotherTableName
SET ColumnName = (
CASE WHEN CTE_Count.CNT = 1 -- all row has same status
THEN SomeValue
ELSE SomeOtherValue END
)
FROM AnotherTableName
INNER JOIN CTE_Count ON ...

Tricky MS Access SQL query to remove surplus duplicate records

I have an Access table of the form (I'm simplifying it a bit)
ID AutoNumber Primary Key
SchemeName Text (50)
SchemeNumber Text (15)
This contains some data eg...
ID SchemeName SchemeNumber
--------------------------------------------------------------------
714 Malcolm ABC123
80 Malcolm ABC123
96 Malcolms Scheme ABC123
101 Malcolms Scheme ABC123
98 Malcolms Scheme DEF888
654 Another Scheme BAR876
543 Whatever Scheme KJL111
etc...
Now. I want to remove duplicate names under the same SchemeNumber. But I want to leave the record which has the longest SchemeName for that scheme number. If there are duplicate records with the same longest length then I just want to leave only one, say, the lowest ID (but any one will do really). From the above example I would want to delete IDs 714, 80 and 101 (to leave only 96).
I thought this would be relatively easy to achieve but it's turning into a bit of a nightmare! Thanks for any suggestions. I know I could loop it programatically but I'd rather have a single DELETE query.
See if this query returns the rows you want to keep:
SELECT r.SchemeNumber, r.SchemeName, Min(r.ID) AS MinOfID
FROM
(SELECT
SchemeNumber,
SchemeName,
Len(SchemeName) AS name_length,
ID
FROM tblSchemes
) AS r
INNER JOIN
(SELECT
SchemeNumber,
Max(Len(SchemeName)) AS name_length
FROM tblSchemes
GROUP BY SchemeNumber
) AS w
ON
(r.SchemeNumber = w.SchemeNumber)
AND (r.name_length = w.name_length)
GROUP BY r.SchemeNumber, r.SchemeName
ORDER BY r.SchemeName;
If so, save it as qrySchemes2Keep. Then create a DELETE query to discard rows from tblSchemes whose ID value is not found in qrySchemes2Keep.
DELETE
FROM tblSchemes AS s
WHERE Not Exists (SELECT * FROM qrySchemes2Keep WHERE MinOfID = s.ID);
Just beware, if you later use Access' query designer to make changes to that DELETE query, it may "helpfully" convert the SQL to something like this:
DELETE s.*, Exists (SELECT * FROM qrySchemes2Keep WHERE MinOfID = s.ID)
FROM tblSchemes AS s
WHERE (((Exists (SELECT * FROM qrySchemes2Keep WHERE MinOfID = s.ID))=False));
DELETE FROM Table t1
WHERE EXISTS (SELECT 1 from Table t2
WHERE t1.SchemeNumber = t2.SchemeNumber
AND Length(t2.SchemeName) > Length(t1.SchemeName)
)
Depend on your RDBMS you may use function different from Length (Oracle - length, mysql - length, sql server - LEN)
delete ShortScheme
from Scheme ShortScheme
join Scheme LongScheme
on ShortScheme.SchemeNumber = LongScheme.SchemeNumber
and (len(ShortScheme.SchemeName) < len(LongScheme.SchemeName) or (len(ShortScheme.SchemeName) = len(LongScheme.SchemeName) and ShortScheme.ID > LongScheme.ID))
(SQL Server flavored)
Now updated to include the specified tie resolution. Although, you may get better performance doing it in two queries: first deleting the schemes with shorter names as in my original query and then going back and deleting the higher ID where there was a tie in name length.
I'd do this in multiple steps. Large delete operations done in a single step make me too nervous -- what if you make a mistake? There's no sql 'undo' statement.
-- Setup the data
DROP Table foo;
DROP Table bar;
DROP Table bat;
DROP Table baz;
CREATE TABLE foo (
id int(11) NOT NULL,
SchemeName varchar(50),
SchemeNumber varchar(15),
PRIMARY KEY (id)
);
insert into foo values (714, 'Malcolm', 'ABC123' );
insert into foo values (80, 'Malcolm', 'ABC123' );
insert into foo values (96, 'Malcolms Scheme', 'ABC123' );
insert into foo values (101, 'Malcolms Scheme', 'ABC123' );
insert into foo values (98, 'Malcolms Scheme', 'DEF888' );
insert into foo values (654, 'Another Scheme ', 'BAR876' );
insert into foo values (543, 'Whatever Scheme ', 'KJL111' );
-- Find all the records that have dups, find the longest one
create table bar as
select max(length(SchemeName)) as max_length, SchemeNumber
from foo
group by SchemeNumber
having count(*) > 1;
-- Find the one we want to keep
create table bat as
select min(a.id) as id, a.SchemeNumber
from foo a join bar b on a.SchemeNumber = b.SchemeNumber
and length(a.SchemeName) = b.max_length
group by SchemeNumber;
-- Select into this table all the rows to delete
create table baz as
select a.id from foo a join bat b where a.SchemeNumber = b.SchemeNumber
and a.id != b.id;
This will give you a new table with only records for rows that you want to remove.
Now check these out and make sure that they contain only the rows you want deleted. This way you can make sure that when you do the delete, you know exactly what to expect. It should also be pretty fast.
Then when you're ready, use this command to delete the rows using this command.
delete from foo where id in (select id from baz);
This seems like more work because of the different tables, but it's safer probably just as fast as the other ways. Plus you can stop at any step and make sure the data is what you want before you do any actual deletes.
If your platform supports ranking functions and common table expressions:
with cte as (
select row_number()
over (partition by SchemeNumber order by len(SchemeName) desc) as rn
from Table)
delete from cte where rn > 1;
try this:
Select * From Table t
Where Len(SchemeName) <
(Select Max(Len(Schemename))
From Table
Where SchemeNumber = t.SchemeNumber )
And Id >
(Select Min (Id)
From Table
Where SchemeNumber = t.SchemeNumber
And SchemeName = t.SchemeName)
or this:,...
Select * From Table t
Where Id >
(Select Min(Id) From Table
Where SchemeNumber = t.SchemeNumber
And Len(SchemeName) <
(Select Max(Len(Schemename))
From Table
Where SchemeNumber = t.SchemeNumber))
if either of these selects the records that should be deleted, just change it to a delete
Delete
From Table t
Where Len(SchemeName) <
(Select Max(Len(Schemename))
From Table
Where SchemeNumber = t.SchemeNumber )
And Id >
(Select Min (Id)
From Table
Where SchemeNumber = t.SchemeNumber
And SchemeName = t.SchemeName)
or using the second construction:
Delete From Table t Where Id >
(Select Min(Id) From Table
Where SchemeNumber = t.SchemeNumber
And Len(SchemeName) <
(Select Max(Len(Schemename))
From Table
Where SchemeNumber = t.SchemeNumber))