Need a tweak in SQL Server - sql

Tbl 1
ID Name Email
1 A a#b.c
2 B b#c.d
Tbl2
ID Related_ID Value Name
1 1 Z Name1
2 1 Y Name2
3 2 X Name1
4 2 W Name2
5 2 G Name3
I can write a query to show
ID Name Email Value1
1 A a#b.c Z
1 A a#b.c Y
How can I write it so it will become like
ID Name Email Value1 Value2
1 A a#b.c Z Y
2 B b#c.d X W

Here is an example to get what you want using dynamic sql.
Note that the CTE test and the temporary table #tmp2 should map to your original tables.
create table #tmp (ID int, Related_ID int, Value nvarchar(50), Name nvarchar(50), rn int)
delete #tmp
create table #tmp2 (ID int, Name nvarchar(50), Email nvarchar(50))
delete #tmp2
;with test(ID, Related_ID, Value, Name)
as
(
select 1, 1, 'Z', 'Name1'
union all
select 2, 1, 'Y', 'Name2'
union all
select 3, 2, 'X', 'Name1'
union all
select 4, 2, 'W', 'Name2'
union all
select 5, 2, 'G', 'Name3'
)
insert into #tmp
select *, row_number() OVER (partition by Related_ID order by ID) as rn
from test
insert into #tmp2
select 1, 'A', 'a#b.c'
union all
select 2, 'B', 'b#c.d'
declare #d nvarchar(MAX)
,#e nvarchar(MAX)
SET #d = 'select a.ID, a.Name, a.Email '
SET #e = ',min(case b.rn when >rn< then Value else null end) as [Value>rn<]'
select #d = #d + (
select t from(
select distinct replace(#e,'>rn<', b.rn) as [text()]
from #tmp b
for xml path('')) c(t)
)
set #d = #d + '
from #tmp2 a
join #tmp b
on a.ID = b.Related_ID
group by a.ID, a.Name, a.Email'
exec sp_executesql #d
drop table #tmp
drop table #tmp2

declare #t1 table(id int, name varchar(20), email varchar(32))
declare #t2 table(id int, related_id int, value varchar(10), name varchar(10))
insert #t1 values(1,'A', 'a#b.c')
insert #t1 values(2,'B', 'b#c.d')
insert #t2 values(1, 1, 'Z', 'Name1')
insert #t2 values(2, 1, 'Y', 'Name2')
insert #t2 values(3, 2, 'X', 'Name1')
insert #t2 values(4, 2, 'W', 'Name2')
insert #t2 values(5, 2, 'G', 'Name3')
;with a as
(
select value, related_id, ROW_NUMBER() over(PARTITION BY related_id order by value desc) rn
from #t2
), b as
(
select value, related_id, rn from a where rn in (1,2)
)
select t5.ID, t5.Name, t5.Email, t3.value Value1, t4.value Value2
from #t1 t5
join b t3
on t3.related_id = t5.id and t3.rn = 1
join b t4
on t4.related_id = t5.id and t4.rn = 2
Result:
ID Name Email Value1 Value2
------ ------ ---------- -------- ----------
1 A a#b.c Z Y
2 B b#c.d X W

It can be done using PIVOT Statement.
Here are a couple of links you can have a look at.
http://blog.sqlauthority.com/2008/06/07/sql-server-pivot-and-unpivot-table-examples/

Related

How can I override rows from another table?

I have two tables:
TableA
ID Name
-- ----
1 aaa
2 bbb
3 ccc
4 ddd
TableB
ID Name
-- --------
3 WWXXYYZZ
I want to select from both tables, but skip the rows which exist in TableB. The result should look like this:
ID Name
-- --------
1 aaa
2 bbb
3 WWXXYYZZ
4 ddd
I have tried union and join but did not figure out how to achieve this.
-- Did not work
select *
from TableA
union
select *
from TableB
-- Did not work
select *
from
(
select *
from TableA
) x
join
(
select *
from TableB
) y
on x.ID = y.ID
You could left join b on to a, and use coalesce to prefer b's rows:
SELECT a.id, COALESCE(b.name, a.name) AS name
FROM a
LEFT JOIN b ON a.id = b.id
You can do:
select a.id, coalesce(b.name, a.name)
from a left join b on a.id = b.id
One method is union all:
select b.*
from b
union all
select a.*
from a
where not exists (select 1 from a where a.id = b.id);
You can also choose from a and override with values from b:
select a.id, coalesce(b.name, a.name) as name
from a left join
b
on a.id = b.id;
A more complex method uses ROW_NUMBER which might be necessary if your query is significantly more complex than shown. It also handled the case where a row exists in TableB but not TableA (which is not clear from your question).
DECLARE #TableA TABLE (id INT, [Name] VARCHAR(12));
DECLARE #TableB TABLE (id INT, [Name] VARCHAR(12));
INSERT INTO #TableA (id, [Name])
VALUES
(1, 'aaa'),
(2, 'bbb'),
(3, 'ccc'),
(4, 'ddd');
INSERT INTO #TableB (id, [Name])
VALUES
(3, 'WWXXYYZZ'),
(5, 'TTTGGG');
SELECT id, [Name]
FROM (
SELECT id, [Name]
, ROW_NUMBER() OVER (PARTITION BY id ORDER BY [Priority] DESC) Rn
FROM (
SELECT id, [Name], 0 [Priority]
FROM #TableA
UNION ALL
SELECT id, [Name], 1 [Priority]
FROM #TableB
) X
) Y
WHERE Rn = 1;
Returns:
1 aaa
2 bbb
3 WWXXYYZZ
4 ddd
5 TTTGGG

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

Collect all Similar Persons to One Group

I have a person with several Id's.
Some of them in Column Id1 and Some of them in Id2.
I want to collect all the same persons Id's to one group.
If id1=10, is in the same row with id2=20. so it's mean that person with id1=10 he is the same person like id2=20.
The Input and Output example:
Input
Id1 Id2
--- ---
10 20
10 30
30 30
10 40
50 70
60 50
70 70
Output
NewId OldId
----- -----
1 10
1 20
1 30
1 40
2 50
2 60
2 70
For recursive tasks you should use recursive CTE.
with cq as
(
select distinct Id2, Id1 from #Tmp -- get your table
union
select distinct Id1, Id2 from #Tmp -- get your table (or sort output)
union
select distinct Id1, Id1 from #Tmp -- add root from Id1
union
select distinct Id2, Id2 from #Tmp -- add root from Id2
), cte (Id1, Id2, lvl)
as (
select t.Id1, t.Id2, 0 lvl
from cq t
union all
select t2.Id2, c.Id1, lvl + 1 lvl
from cq t2, cte c
where t2.Id1 = c.Id2
and t2.Id1 != c.Id1
and c.lvl < 5 -- maximum level of recursion
)
select
Id1,
min(Id2) FirstId1,
dense_rank() over(order by min(Id2)) rn
from cte
group by Id1
Max lvl and condition with != isn't necessary if your table will be well ordered.
I suspect this could be done with recursive CTEs, but here is a less elegent solution.
-- CREATE Temps
CREATE TABLE #Table (id1 INT, id2 INT)
CREATE TABLE #NewTable (NewID INT, OldID INT)
CREATE TABLE #AllIDs (ID INT)
-- Insert Test data
INSERT #Table
( id1, id2 )
VALUES ( 10, 20 ),
( 10, 30 ),
( 30, 20 ),
( 10, 40 ),
( 50, 70 ),
( 60, 50 ),
( 70, 70 ),
( 110, 120 ),
( 120, 130 ),
( 140, 130 )
-- Assemble all possible OldIDs
INSERT INTO #AllIDs
SELECT id1 FROM #Table
UNION
SELECT id2 FROM #Table
DECLARE #NewID INT = 1,
#RowCnt int
-- Insert seed OldID
INSERT #NewTable
SELECT TOP 1 #NewID, id
FROM #AllIDs
WHERE id NOT IN (SELECT OldID FROM #NewTable)
ORDER BY 2
SET #RowCnt = ##ROWCOUNT
WHILE #RowCnt > 0
BEGIN
WHILE #RowCnt > 0
BEGIN
-- Check for id2 that match current OldID
INSERT #NewTable
SELECT DISTINCT #NewID, id2
FROM #Table t
INNER JOIN #NewTable nt ON t.id1 = nt.OldID
WHERE nt.[NewID] = #NewID
AND t.id2 NOT IN (SELECT OldID FROM #NewTable WHERE [NewID] = #NewID)
SELECT #RowCnt = ##ROWCOUNT
-- Check for id1 that match current OldID
INSERT #NewTable
SELECT DISTINCT #NewID, id1
FROM #Table t
INNER JOIN #NewTable nt ON t.id2 = nt.OldID
WHERE nt.[NewID] = #NewID
AND t.id1 NOT IN (SELECT OldID FROM #NewTable WHERE [NewID] = #NewID)
SELECT #RowCnt = #RowCnt + ##ROWCOUNT
END
SET #NewID = #NewID + 1
-- Add another seed OldID if any left
INSERT #NewTable
SELECT TOP 1 #NewID, id
FROM #AllIDs
WHERE id NOT IN (SELECT OldID FROM #NewTable)
ORDER BY 2
SELECT #RowCnt = ##ROWCOUNT
END
-- Get Results
SELECT * FROM #NewTable ORDER BY [NewID], OldID
Anna, is that a good example?
This is a connected components issue.
Input
Id1 Id2
--- ---
10 20
10 30
30 30
10 40
50 70
60 50
70 70
Output
NewId OldId
----- -----
1 10
1 20
1 30
1 40
2 50
2 60
2 70
The CTE version. Note that I have added a few more data points to simulate duplicates and lone Ids.
--create test data
declare #table table (Id1 int, Id2 int);
insert #table values
(10, 20),
(10, 30),
(30, 30),
(10, 40),
(40, 45),
(20, 40),
(50, 70),
(60, 50),
(70, 70),
(80, 80);
select *
from #table;
--join related IDs with recursive CTE
;with min_first_cte as (
select case when Id1 <= Id2 then Id1 else Id2 end Id1,
case when Id1 <= Id2 then Id2 else Id1 end Id2
from #table
), related_ids_cte as (
--anchor IDs
select distinct Id1 BaseId, Id1 ParentId, Id1 ChildId
from min_first_cte
where Id1 not in ( select Id2
from min_first_cte
where Id2 <> Id1)
union all
--related recursive IDs
select r.BaseId, m.Id1 ParentId, M.Id2 ChildId
from min_first_cte m
join related_ids_cte r
on r.ChildId = m.Id1
and m.Id1 <> m.Id2
), distinct_ids_cte as (
select distinct r.BaseId, r.ChildId
from related_ids_cte r
)
select dense_rank() over (order by d.BaseId) [NewId],
d.ChildId OldId
from distinct_ids_cte d
order by BaseId, ChildId;
Conceptually, it's about finding connected components given a list of connected pairs. And then, assign each of the groups a new id. The following implementation works:
CREATE TABLE #pairs (a int, b int)
CREATE TABLE #groups (a int, group_id int)
INSERT INTO #pairs
VALUES (1, 2), (3, 4), (5, 6), (5, 7), (3, 9), (8, 10), (11, 12), (1, 3)
-- starting stage - all items belong to their own group
INSERT INTO #groups(a, group_id)
SELECT a, a
FROM #pairs
UNION
SELECT b, b
FROM #pairs
DECLARE #a INT
DECLARE #b INT
DECLARE #cGroup INT
SET ROWCOUNT 0
SELECT * INTO #mytemp FROM #pairs
SET ROWCOUNT 1
SELECT #a = a, #b = b FROM #mytemp
WHILE ##rowcount <> 0
BEGIN
SET ROWCOUNT 0
DECLARE #aGroup INT, #bGroup INT, #newGroup INT
SELECT #aGroup = group_id FROM #groups WHERE a = #a
SELECT #bGroup = group_id FROM #groups WHERE a = #b
SELECT #newGroup = MIN(group_id) FROM #groups WHERE a IN (#a, #b)
-- update the grouping table with the new group
UPDATE #groups
SET group_id = #newGroup
WHERE group_id IN (#aGroup, #bGroup)
DELETE FROM #mytemp
WHERE a = #a
AND b = #b
SET ROWCOUNT 1
SELECT #a = a, #b = b FROM #mytemp
END
SET ROWCOUNT 0
SELECT * FROM #groups
DROP TABLE #mytemp
DROP TABLE #pairs
DROP TABLE #groups
Here's the explanation:
initially, assign each number a group of it's own value
iterate over the pairs
for each pair
find the minimum as set it as the new group id
set the group id to all the numbers where the current group id is the same as for the numbers in the current pair
In terms of a procedure, these are 2 iterations continuously updating the group ids to the minimum in the group - O(n2).

SQL server, how to get a number of distinct items

I am using SQL server,
id 3 | 4 | 5 | 6
items 1 2 3 | 2 3 5| 6 | 1 2 5
-------------------------
# of items 3 | 4 | 5 | 5
so, each id has items (ex, 3 has 3 items - 1,2,3)
and for each item, I'd like to get the number of distinct items accrued.
so, 3 has 3 distinct items - 1, 2, 3
4 has 4 distinct items - 1, 2, 3, 5
5 has 5 distinc items - 1, 2, 3, 5, 6
6 has 5 distinct items - 1, 2, 3, 5, 6
I can do this by running, 1 through 2, 1 though 3, 1 through 5 and 1 through 6 by doing count(distinct items)
But I want to automate this process and get the same results in one run.
The idea is to create a temp table and put an item in it while checking if the item is already in the temp table and print number of distinct items for each id.
CREATE TABLE TEST
(
id int, items int
)
INSERT INTO TEST
VALUES
(3, 1),
(3, 2),
(3, 3),
(4, 2),
(4, 3),
(4, 5),
(5, 6),
(6, 1),
(6, 2),
(6, 5)
SELECT B.id, COUNT(DISTINCT(A.ITEMS)) AS itemCount
FROM TEST A
INNER JOIN TEST B ON A.id <= B.id
GROUP BY B.ID
DROP TABLE TEST
Output:
id itemCount
3 3
4 4
5 5
6 5
Assuming your data in below format:
Declare #table table
(
id int,
items varchar(10)
)
insert into #table values (3, '1 2 3');
insert into #table values (4, '2 3 5');
insert into #table values (5, '6');
insert into #table values (6, '1 2 5');
with cte as
(
Select id, b.Item
from #table a
cross apply [dbo].[Split] (items, ' ') b
)
Select y.id, count(distinct(x.Item)) AS [# of items]
from cte x
join cte y on x.id <= y.id
group by y.id
Use the table valued function [dbo].[Split] from LINK.
You can as the below:
DECLARE #Tbl TABLE (Id VARCHAR(10), Column3 VARCHAR(100), Column4 VARCHAR(100), Column5 VARCHAR(100), Column6 VARCHAR(100))
INSERT #Tbl
VALUES
('items', '1 2 3', '2 3 5', '6', '1 2 5')
;WITH CTE1
AS
(
SELECT T.Id, T.Column3 AS ColumnId, CAST('<X>' + REPLACE(T.Column3,' ','</X><X>') + '</X>' as XML) AS FilterColumn FROM #Tbl T UNION ALL
SELECT T.Id, T.Column4 AS ColumnId, CAST('<X>' + REPLACE(T.Column4,' ','</X><X>') + '</X>' as XML) AS FilterColumn FROM #Tbl T UNION ALL
SELECT T.Id, T.Column5 AS ColumnId, CAST('<X>' + REPLACE(T.Column5,' ','</X><X>') + '</X>' as XML) AS FilterColumn FROM #Tbl T UNION ALL
SELECT T.Id, T.Column6 AS ColumnId, CAST('<X>' + REPLACE(T.Column6,' ','</X><X>') + '</X>' as XML) AS FilterColumn FROM #Tbl T
), CTE2
AS
(
SELECT
A.*,
B.SplitData
FROM
CTE1 A CROSS APPLY
(SELECT fdata.D.value('.','varchar(50)') AS SplitData FROM A.FilterColumn.nodes('X') as fdata(D)) B
)
SELECT
T.Id ,
(SELECT COUNT(DISTINCT C.SplitData) FROM CTE2 C WHERE C.Id = T.Id AND C.ColumnId IN (T.Column3)) Column3OfDistinct,
(SELECT COUNT(DISTINCT C.SplitData) FROM CTE2 C WHERE C.Id = T.Id AND C.ColumnId IN (T.Column3, T.Column4)) Column4OfDistinct,
(SELECT COUNT(DISTINCT C.SplitData) FROM CTE2 C WHERE C.Id = T.Id AND C.ColumnId IN (T.Column3, T.Column4, T.Column5)) Column5OfDistinct,
(SELECT COUNT(DISTINCT C.SplitData) FROM CTE2 C WHERE C.Id = T.Id AND C.ColumnId IN (T.Column3, T.Column4, T.Column5, T.Column6)) Column6OfDistinct
FROM
#Tbl T
Result:
Id Column3OfDistinct Column4OfDistinct Column5OfDistinct Column6OfDistinct
---------- ----------------- ----------------- ----------------- -----------------
items 3 4 5 5
This should help you:
select
id,
count(items)
from table_name
group by id

Update table in Sql

Table tblProductStock:
ID Name Qyt
1 X 50
2 Y 40
3 Z 30
Table tblStockMinus:
Id Name Qty
1 X 10
1 X 20
2 Y 30
3 Z 20
I want to update tblProductStock so that it will have:
ID Name Qyt
1 X 20
2 Y 10
3 Z 10
I have tried to use the following SQL request, but it didn't work correctly:
UPDATE ProductStock_Show
SET Quantity = ProductStock_Show.Quantity - ProductStock_Show_Minus.Quantity
FROM ProductStock_Show JOIN ProductStock_Show_Minus
ON ProductStock_Show_Minus.ProductId=ProductStock_Show.ProductId
This will give you required result.
select t1.id, t1.qty-t2.qty as qty from tblProductStock as t1 inner join
(
select id, sum(qty) as qty from tblStockMinus group by id
) as t2 on t1.id=t2.id
You need to update this (but it depends in the RDBMS. This is for MS SQL Server)
Update t
set
t.qty=t3.qty from tblProductStock as t inner join
(
select t1.id, t1.qty-t2.qty as qty from tblProductStock as t1 inner join
(
select id, sum(qty) as qty from tblStockMinus group by id
) as t2 on t1.id=t2.id
) as t3 on t.id=t3.id
DECLARE #Table1 TABLE
( ID int, Name varchar(1), Qyt int)
;
INSERT INTO #Table1
( ID , Name , Qyt )
VALUES
(1, 'X', 50),
(2, 'Y', 40),
(3, 'Z', 30)
;
DECLARE #Table2 TABLE
( Id int, Name varchar(1), Qty int)
;
INSERT INTO #Table2
( Id , Name , Qty )
VALUES
(1, 'X', 10),
(1, 'X', 20),
(2, 'Y', 30),
(3, 'Z', 20)
;
UPDATE #Table1 SET QYT =
t.Qyt - s from (
select
t.id,
t.Name,
t.Qyt ,
SUM(tt.Qty)S
FROM #Table1 t
INNER JOIN #Table2 tt
ON t.Name = tt.Name
GROUP BY t.id,t.Name,t.Qyt)T
SELECT * from #Table1