update row same as row above if condition meet with number - sql

i have data like this
id type
1. a
2. b
3. c
4. c
5. d
6. c
7. c
target
id type. group
1. a. 1
2. b. 2
3. c. 2
4. c. 2
5. d. 3
6. c. 3
7. c. 3
if type c, group value take value from above row.
i can goal this with loop condition and update but that take to much time because looping update many row
how can i achiev this with single update statement with sql server 2008

This should work
declare #t table (id int primary key, val char(1));
insert into #t values
(1, 'a')
, (2, 'b')
, (3, 'c')
, (4, 'c')
, (5, 'd')
, (6, 'c')
, (7, 'c');
select *
, sum(case when val = 'c' then 0 else 1 end) over (order by id) as grp
from #t t
order by id;
id val grp
----------- ---- -----------
1 a 1
2 b 2
3 c 2
4 c 2
5 d 3
6 c 3
7 c 3
Follow the link below for a running demo.
Demo

I am posting this as an alternative to the answer given by #palarazzi which may already be sufficient. This answer is robust to type letters occurring in any amount.
WITH cte AS (
SELECT t1.id AS id1, t1.type AS type1, t2.id AS id2, t2.type AS type2
FROM yourTable t1
INNER JOIN yourTable t2
ON (t1.id = t2.id AND t1.type <> 'c') OR
(t1.id > t2.id AND t1.type = 'c' AND t2.type <> 'c')
),
cte2 AS (
SELECT id1, type2,
ROW_NUMBER() OVER (PARTITION BY id1 ORDER BY id2 DESC) rn
FROM cte
)
SELECT
id1 AS id, type2 AS type,
DENSE_RANK() OVER (ORDER BY type2) [group]
FROM cte2
WHERE rn = 1;
Demo

Related

SQL - Update Rows from a list of values

So Table Setup is:
Column1 Column2 Column3
A 1 Null
B 2 Null
C 1 Null
D 2 Null
E 1 Null
F 2 Null
G 1 Null
H 2 Null
I would like to update Column3 with an array of values (Value1, Value2, Value3) and cycle through that list until the update is complete
The ultimate goal is for the table to look like this:
Column1 Column2 Column3
A 1 Value1
B 2 Value2
C 1 Value3
D 2 Value1
E 1 Value2
F 2 Value3
G 1 Value1
H 2 Value2
I originally tried in powershell but it was not working as I would have liked because of how the data is being imported, so now I am looking towards SQL. Any suggestions would be great!
You could try an update join here. The approach below is to assign an ordered sequence to both your original table and the "array" of values for updating. We join using modulus logic, such that your table's sequence ordering will match up the values in the array and will wrap around until all values have been assigned.
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY Column1) rn
FROM yourTable
)
UPDATE t1
SET Column3 = t2.val
FROM cte t1
INNER JOIN
(
SELECT 1 AS id, 'Value1' AS val UNION ALL
SELECT 2, 'Value2' UNION ALL
SELECT 3, 'Value3'
) t2
ON t2.id = 1 + ((t1.rn - 1) % 3);
Demo
Assuming you can put the "array" in a table, you can use something like this:
with vals as (
select v.*,
row_number() over (order by (select null)) - 1 as seqnum,
count(*) over () as cnt
from (values ('Value1'), ('Value2'), ('Value3')) v(val)
)
update t
set t.column3 = v.val
from (select t.*,
row_number() over (order by column1) - 1 as seqnum
from t
) t join
vals v
on t.seqnum % v.cnt = v.seqnum;
The basic idea is to enumerate the rows in each table and then use modulo arithmetic to match them.

Case when duplicate add one more letter

For example: I have a table with these records below
1 A
2 A
3 B
4 C
...
and I need to migrate these record in to another table
1 AA
2 AB
3 B
4 C
...
Meaning if the record is duplicate, it will automatically add one more letter alphabetically.
Just a slightly different approach
Example
Declare #YourTable Table (ID int,[SomeCol] varchar(50))
Insert Into #YourTable Values
(1,'A')
,(2,'A')
,(3,'B')
,(4,'C')
Select *
,NewVal = concat(SomeCol,IIF(sum(1) over (partition by SomeCol)=1,'',char(64+row_number() over ( partition by SomeCol order by ID ))) )
From #YourTable
Returns
ID SomeCol NewVal
1 A AA
2 A AB
3 B B
4 C C
EDIT - Requested UPDATE
Declare #YourTable Table (ID int,[SomeCol] varchar(50))
Insert Into #YourTable Values
(1,'A')
,(2,'A')
,(3,'B')
,(4,'C')
Select *
,NewVal = concat(SomeCol,IIF(sum(1) over (partition by SomeCol)=1,'',replace(char(63+row_number() over ( partition by SomeCol order by ID )),'#','')) )
From #YourTable
Returns
ID SomeCol NewVal
1 A A
2 A AA
3 B B
4 C C
We might be able to handle this requirement with the help of a calendar table mapping secondary letters to duplicate sequence counts:
WITH letters AS (
SELECT 1 AS seq, 'A' AS let UNION ALL
SELECT 2, 'B' UNION ALL
SELECT 3, 'C' UNION ALL
...
SELECT 26, 'Z' UNION ALL
...
),
cte AS (
SELECT id, let, ROW_NUMBER() OVER (PARTITION BY let ORDER BY id) rn,
COUNT(*) OVER (PARTITION BY let) cnt
FROM yourTable
)
SELECT t1.id, t1.let + CASE WHEN t1.cnt > 1 THEN t2.let ELSE '' END AS let
FROM cte t1
LEFT JOIN letters t2
ON t1.id = t2.seq
ORDER BY t1.id;
Demo

combining strings from muliple rows in sql server

How can I obtain the below output in sql server 2012.
Table
ID | Values|
1 a
1 b
1 c
2 d
2 e
The output should be such that the first row has a fixed number of values(2) seperated by comma and the next row has the remaining values seperated by comma
ID
ID | Values|
1 a,b
1 c
2 d,e
Each id should contain maximum two values in a single row.The remaining values should come in the next row.
Try to use my code:
use db_test;
create table dbo.test567
(
id int,
[values] varchar(max)
);
insert into dbo.test567
values
(1, 'a'),
(1, 'b'),
(1, 'c'),
(2, 'd'),
(2, 'e')
with cte as (
select
id,
[values],
row_number() over(partition by id order by [values] asc) % 2 as rn1,
(row_number() over(partition by id order by [values] asc) - 1) / 2 as rn2
from dbo.test567
), cte2 as (
select
id, max(case when rn1 = 1 then [values] end) as t1, max(case when rn1 = 0 then [values] end) as t2
from cte
group by id, rn2
)
select
id,
case
when t2 is not null then concat(t1, ',', t2)
else t1
end as [values]
from cte2
order by id, [values]

Filter unique records from a database while removing double not-null values

This is kind of hard to explain in words but here is an example of what I am trying to do in SQL. I have a query which returns the following records:
ID Z
--- ---
1 A
1 <null>
2 B
2 E
3 D
4 <null>
4 F
5 <null>
I need to filter this query so that each unique record (based on ID) appears only once in the output and if there are multiple records for the same ID, the output should contain the record with the value of Z column being non-null. If there is only a single record for a given ID and it has value of null for column Z the output still should return that record. So the output from the above query should look like this:
ID Z
--- ---
1 A
2 B
2 E
3 D
4 F
5 <null>
How would you do this in SQL?
You can use GROUP BY for that:
SELECT
ID, MAX(Z) -- Could be MIN(Z)
FROM MyTable
GROUP BY ID
Aggregate functions ignore NULLs, returning them only when all values on the group are NULL.
If you need to return both 2-B and 2-E rows:
SELECT *
FROM YourTable t1
WHERE Z IS NOT NULL
OR NOT EXISTS
(SELECT * FROM YourTable t2
WHERE T2.ID = T1.id AND T2.z IS NOT NULL)
SELECT ID
,Z
FROM YourTable
WHERE Z IS NOT NULL
DECLARE #T TABLE ( ID INT, Z CHAR(1) )
INSERT INTO #T
( ID, Z )
VALUES ( 1, 'A' ),
( 1, NULL )
, ( 2, 'B' ) ,
( 2, 'E' ),
( 3, 'D' ) ,
( 4, NULL ),
( 4, 'F' ),
( 5, NULL )
SELECT *
FROM #T
; WITH c AS (SELECT ID, r=COUNT(*) FROM #T GROUP BY ID)
SELECT t.ID, Z
FROM #T t JOIN c ON t.ID = c.ID
WHERE c.r =1
UNION ALL
SELECT t.ID, Z
FROM #T t JOIN c ON t.ID = c.ID
WHERE c.r >=2
AND z IS NOT NULL
This example assumes you want two rows returned for ID = 2.
with tmp (id, cnt_val) as
(select id,
sum(case when z is not null then 1 else 0 end)
from t
group by id)
select t.id, t.z
from t
inner join tmp on t.id = tmp.id
where tmp.cnt_val > 0 and t.z is not null
or tmp.cnt_val = 0 and t.z is null
WITH CTE
AS (
SELECT id
,z
,ROW_NUMBER() OVER (
PARTITION BY id ORDER BY coalesce(z, '') DESC
) rn
FROM #T
)
SELECT id
,z
FROM CTE
WHERE rn = 1

How to select row based on existance of value in other column

I realise the title to this question may be vague but I am not sure how to phrase it. I have the following table:
i_id option p_id
---- ------ ----
1 A 4
1 B 8
1 C 6
2 B 3
2 C 5
3 A 7
3 B 3
4 E 11
How do I select a row based on the value of the option column for each unique i_id: if 'C' exists, select the row, else select row with 'B' else with 'A' so that result set is:
i_id option p_id
---- ------ ----
1 C 6
2 C 5
3 B 3
select i_id, option, p_id
from (
select
i_id,
option,
p_id,
row_number() over (partition by i_id order by case option when 'C' then 0 when 'B' then 1 when 'A' then 2 end) takeme
from thetable
where option in ('A', 'B', 'C')
) foo
where takeme = 1
This will give you the values ordered by C, B, A, while removing any i_id record that does not have one of these values.
WITH ranked AS
(
SELECT i_id, [option], p_id
, ROW_NUMBER() OVER (PARTITION BY i_id ORDER BY CASE [option]
WHEN 'C' THEN 1
WHEN 'B' THEN 2
WHEN 'A' THEN 3
ELSE 4
END) AS rowNumber
FROM yourTable
WHERE [option] IN ('A', 'B', 'C')
)
SELECT r.i_id, r.[option], r.p_id
FROM ranked AS r
WHERE r.rowNumber = 1
create table t2 (
id int,
options varchar(1),
pid int
)
insert into t2 values(1, 'A', 4)
insert into t2 values(1, 'B', 8)
insert into t2 values(1, 'C', 6)
insert into t2 values(1, 'E', 7)
select t2.* from t2,
(select id, MAX(options) as op from t2
where options <> 'E'
group by id) t
where t2.id = t.id and t2.options = t.op
Well, I would suggest that this problem can be made easier if you can assign a numeric "score" to each letter, such that "better" letters have higher scores. Then you can use MAX to find, for each group, the row with the highest "score" for the option. Since 'A' < 'B' < 'C', we could cheat here and use option as the score, and thus:
SELECT t1.i_id, t1.option, t1.p_id
FROM thetable t1
INNER JOIN (SELECT t2.i_id, MAX(option)
FROM thetable t2
GROUP BY t2.i_id) AS maximums
ON t1.i_id = maximums.i_id
WHERE option != 'D'
This assumes that {i_id, option} is a natural key of the table (i.e., that no two rows will have the same combination of values for those two columns; or, alternatively, that you have an uniqueness constraint on that pair of columns).