SQL - Update Rows from a list of values - sql

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.

Related

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

SQL - Two Columns into One Distinct Ordered Column

If I have a table like this:
Col 1 | Col 2
-------------
A | 1
A | 2
B | 1
C | 1
C | 2
C | 3
How can I write a query to pull one column that looks like this --
Col 1
------
A
1
2
B
1
C
1
2
3
SELECT col1
FROM Some_Table_You_Did_Not_Name
UNION ALL
SELECT col2
FROM Some_Table_You_Did_Not_Name
If the order matters in your example then you want this:
WITH data AS
(
SELECT col1, col2, ROW_NUMBER() OVER (ORDER BY col1, col2) as RN
FROM Some_Table_You_Did_Not_Name
)
SELECT col
FROM (
SELECT DISTINCT col1 as col, RN, 1 as O
FROM data
UNION ALL
SELECT DISTINCT col2 as col, RN, 2 as O
FROM data
) JC_IS_THAT_GUY
ORDER BY RN ASC, O ASC, col ASC
You can use a query like the following:
SELECT Col1
FROM (
SELECT DISTINCT Col1, Col1 AS Col2, 0 AS grp
FROM mytable
UNION ALL
SELECT Col2 AS Col1, Col1 AS Col2, 1 AS grp
FROM mytable) AS t
ORDER BY Col2, grp, Col1
Demo here
There is absolutely no need to do a UNION, UNION ALL or reference the table more than once to unpivot data...
-- if Col2 is always a well ordered sequense like the test data...
SELECT
Col1 = x.Value
FROM
#TestData td
CROSS APPLY ( VALUES (IIF(td.Col2 = 1, td.Col1, NULL)), (CAST(td.Col2 AS CHAR(1))) ) x (Value)
WHERE
x.Value IS NOT NULL;
-- if it isn't...
WITH
cre_Add_RN AS (
SELECT
td.Col1,
td.Col2,
RN = ROW_NUMBER() OVER (PARTITION BY td.Col1 ORDER BY td.Col2)
FROM
#TestData td
)
SELECT
x.Value
FROM
cre_Add_RN arn
CROSS APPLY ( VALUES (IIF(arn.RN = 1, arn.Col1, NULL)), (CAST(arn.Col2 AS CHAR(1))) ) x (Value)
WHERE
x.Value IS NOT NULL;
HTH,
Jason

SQL Server : join all left table and repeat right

I have to join tables in a following way:
Table A:
1
2
3
4
5
Table B:
A
B
The result table should be:
1 A
2 B
3 A
4 B
5 A
Do you have any ideas how to do this?
Assuming worst case, the column in table A is not a sequence without gaps and the number of rows in table B is not known in advance, you must apply a ROW_NUMBER on both tables and then join on a MODULO:
SELECT col1, col2
FROM
(
SELECT col1,
ROW_NUMBER() OVER (ORDER BY col1) -1 AS rn
FROM tableA
) AS A
JOIN
(
SELECT col2,
ROW_NUMBER() OVER (ORDER BY col2) -1 AS rn
FROM tableB
) AS B
ON A.rn % (SELECT COUNT(*) FROM tableB) = B.rn
Maybe something like this:
select A.nr, case when (A.nr%2=0) then b2.chr else b3.chr end letter
from A, B b2, B b3
where b2.chr = 'A' and b3.chr = 'B'

How to find adjacent values in a column based on another column value in Sql

I have a table like below
From the above table I Need to select adjacent values from val3 column based on Number column
I Need output like below :
WITH T1
AS (SELECT *,
DENSE_RANK() OVER (PARTITION BY [Number] ORDER BY [Val3]) - [Val3] AS Grp
FROM YourTable),
T2
AS (SELECT *,
COUNT(*) OVER (PARTITION BY [Number], Grp) AS Cnt
FROM T1)
SELECT [Number],
[Val1],
[Val2],
[Val3]
FROM T2
WHERE Cnt > 1
An ugly solution would be:
select d.number, d.val1, d.val2, d.val3 from table d
where exists
(select * from table t
where t.number = d.number
--and t.val1 = d.val1
--and t.val2 = d.val2
and (t.val3 = d.val3 - 1 or t.val3 = d.val3 + 1))
You can identify a sequential set of numbers by the fact that the number - its position in the set will be constant, e.g.
Val3 | RowNumber | Val - RowNumber
4 | 1 | 3
5 | 2 | 3
6 | 3 | 3
8 | 4 | 4
9 | 5 | 4
As you can see the Val - RowNumber column remains constant for each sequential set. So to apply this to your query:
WITH GroupedT AS
( SELECT Number,
Val1,
Val2,
Val3,
GroupingSet = Val3 - DENSE_RANK() OVER(PARTITION BY Number, Val1, Val2 ORDER BY Val3)
FROM T
), SequentialT AS
( SELECT Number,
Val1,
Val2,
Val3,
SequenceCount = COUNT(*) OVER(PARTITION BY Number, Val1, Val2, GroupingSet)
FROM GroupedT
)
SELECT Number, Val1, Val2, val3
FROM SequentialT
WHERE SequenceCount > 1
ORDER BY Number, Val1, Val2, Val3;
Example on SQL Fiddle
It looks like what you're trying to do is eliminate any records that contain a Val3 value that doesn't have any adjacent Val3 values in other records. To do that, I think you can use something like this:
SELECT t1.*
FROM myTable t1
JOIN myTable t2 ON t1.Number = t2.Number
AND t1.Val1 = t2.Val1
AND t1.Val2 = t2.Val2
AND (t2.Val3 IN (t1.Val3 - 1, t1.Val3 + 1))

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