Update column based on other column values - sql-server

I have a table that stores clients that are then grouped under one client who are considered the 'Head' of the group. So a client is considered a 'Head' if they appear in the 'Group' column, even though they may not be in their own 'Group'. The table may appear as follows:
+--------+-------+------+
| Client | Group | Head |
+--------+-------+------+
| ABC | ABC | Yes |
| DEF | ABC | No |
| GHI | GHI | Yes |
| JKL | MNO | Yes |
| MNO | PQR | Yes |
| PQR | MNO | No |
| STU | STU | Yes |
+--------+-------+------+
Here we can see that 'Head' records for client JKL and PQR are incorrect. What I need is a list of just the clients whose 'Head' column is incorrect and what it should be (Yes/1 or No/0). What is the best way to go about doing this?

Declare #a Table (Client Varchar(10),[Group] Varchar(10),Head Varchar(3))
Insert into #a Values ('ABC','ABC','YES')
Insert into #a Values ('DEF','ABC','No')
Insert into #a Values ('GHI','GHI','YES')
Insert into #a Values ('JKL','MNO','YES')
Insert into #a Values ('MNO','PQR','YES')
Insert into #a Values ('PQR','MNO','No')
Insert into #a Values ('STU','STU','YES')
-- uncomment here to get only wrong ones
--Select * from (
Select a.*, Coalesce(h.RealHead,'No') as RealHead
, CASE WHEN Coalesce(h.RealHead,'No')<>a.Head then 'ERROR' else 'OK' end as Info
FROM #a a
LEFT JOIN (Select Distinct [Group], 'YES' as RealHead from #a) h
ON h.[GROUP]=a.Client -- Join real Heads with clients
-- uncomment here to get only wrong ones
--) s where Info='ERROR'

Use the SQL select statement WHERE and double equal sign ==
select * from tableName where Head=="No";
You can update the records to what you like
;
UPDATE tableName
SET Head = 0
WHERE Head="No";
Hope this helps.

declare #t table(Client varchar(50), [Group] varchar(50), Head varchar(50))
insert into #t values( 'ABC','ABC','Yes'),
('DEF','ABC','No'),
('GHI','GHI','Yes'),
('JKL','MNO','Yes'),
('MNO','PQR','Yes'),
('PQR','MNO','No'),
('STU','STU','Yes')
select * from #t t1
where t1.client not in (select distinct [Group] from #t t3 where t3.Head = 'Yes' and t3.Client = t3.[group])
--and Head = 'Yes' --please uncomment this line and check the result, if any issue to desire your result, tell me

Related

SQL Server - Splitting a single, variable length column that is delimited with a / into multiple columns

I'm trying to split this field into columns separating at the '/' character.
The string contains the Company Name/Location/Cost Center/Department/Job
Here is a sample:
JSM MFG/Stearns Blg/Operations/Shipping/Packer
JSM MFG/Birch Lane Blg/Maintenance/Electrical/Electrician II
The desired output is:
JSM MFG, Stearns Blg, Operations, Shipping, Packer
into their respective separate columns.
This would be combined with other columns, too, employee number, rate of pay etc.. which are just direct selects.
I have found a few different SQL excerpts but nothing that I could see that covers multiple delimiters with variable length in one string.
Thanks in advance,
Doug
Here's a solution that maybe you can build on. It uses the SQL Server 2016+ string_split() table value function to split the string into fields, which are then mapped into columns on a table you want to update.
declare #empId int = 123456 -- Let's assume a table Employee you want to update.
-- You get the values you want to split from somewhere.
declare #example varchar(max) = 'JSM MFG/Stearns Blg/Operations/Shipping/Packer'
-- SQL Server 2016+ has a handy string_split() table function we can use.
-- We'll dump the split values into a temp table.
select value into #tmp from string_split(#example, '/')
-- We now retroactively add an identity column to the temp table,
-- so we can tell which positions the values map to.
alter table #tmp add i int identity(1,1)
-- And now, a cursor to iterate over the split values.
declare #i int
declare #value varchar(max)
declare split_cursor cursor for
select i, value from #tmp
open split_cursor
fetch next from split_cursor into #i, #value
while ##fetch_status = 0
begin
-- We know which values correspond to which positions in the Employee table,
-- thanks to the identity integer we added to the temp table above.
if (#i = 1)
begin
update Employee set Employer = #value where EmpId = #empId
end
else if (#i = 2)
begin
update Employee set Building = #value where EmpId = #empId
end
-- ETC...
fetch next from split_cursor into #i, #value
end
close split_cursor
deallocate split_cursor
There's more than 1 way to do this.
Here's a demonstration of some:
create table Test (
id int identity(1,1) primary key,
col nvarchar(100)
);
GO
✓
insert into Test (col) values
('abc/def/ghi/jkl/mno'),
('s/t/u/f/f'),
('w/h/y'),
(null);
GO
4 rows affected
SELECT id,
s1 AS [CompanyName],
s2 AS [Location],
s3 AS [Cost Center],
s4 AS [Department],
s5 AS [Job]
FROM Test t
OUTER APPLY
(
SELECT s1, s2, s3, s4, s5
FROM (VALUES (col+'/')) q(s0)
CROSS APPLY (select case when charindex('/',s0)>0 then left(s0, charindex('/',s0)-1) end s1, case when charindex('/',s0)>0 then charindex('/',s0) end p1) a1
CROSS APPLY (select case when p1>0 and charindex('/',s0,p1+1)>0 then substring(s0, p1+1, charindex('/',s0,p1+1)-p1-1) end s2, case when p1>0 then charindex('/',s0,p1+1) end p2) a2
CROSS APPLY (select case when p2>0 and charindex('/',s0,p2+1)>0 then substring(s0, p2+1, charindex('/',s0,p2+1)-p2-1) end s3, case when p2>0 then charindex('/',s0,p2+1) end p3) a3
CROSS APPLY (select case when p3>0 and charindex('/',s0,p3+1)>0 then substring(s0, p3+1, charindex('/',s0,p3+1)-p3-1) end s4, case when p3>0 then charindex('/',s0,p3+1) end p4) a4
CROSS APPLY (select case when p4>0 and charindex('/',s0,p4+1)>0 then substring(s0, p4+1, charindex('/',s0,p4+1)-p4-1) end s5) a5
) a;
GO
id | CompanyName | Location | Cost Center | Department | Job
-: | :---------- | :------- | :---------- | :--------- | :---
1 | abc | def | ghi | jkl | mno
2 | s | t | u | f | f
3 | w | h | y | null | null
4 | null | null | null | null | null
select id,
[1] AS [CompanyName],
[2] AS [Location],
[3] AS [Cost Center],
[4] AS [Department],
[5] AS [Job]
from Test t
outer apply (
select *
from
(
select value
, row_number() over (order by (select 0)) n
from string_split(t.col,'/') s
) src
pivot (
max(value)
for n in ([1],[2],[3],[4],[5])
) pvt
) a;
GO
id | CompanyName | Location | Cost Center | Department | Job
-: | :---------- | :------- | :---------- | :--------- | :---
1 | abc | def | ghi | jkl | mno
2 | s | t | u | f | f
3 | w | h | y | null | null
4 | null | null | null | null | null
select id,
s1 AS [CompanyName],
s2 AS [Location],
s3 AS [Cost Center],
s4 AS [Department],
s5 AS [Job]
from Test t
outer apply (
select
s1 = x0.value('/x[1]','nvarchar(max)')
, s2 = x0.value('/x[2]','nvarchar(max)')
, s3 = x0.value('/x[3]','nvarchar(max)')
, s4 = x0.value('/x[4]','nvarchar(max)')
, s5 = x0.value('/x[5]','nvarchar(max)')
from
(
select
cast(('<x>'+ replace(col,'/','</x><x>') +'</x>') as xml) x0
) q
) a;
GO
id | CompanyName | Location | Cost Center | Department | Job
-: | :---------- | :------- | :---------- | :--------- | :---
1 | abc | def | ghi | jkl | mno
2 | s | t | u | f | f
3 | w | h | y | null | null
4 | null | null | null | null | null
db<>fiddle here

SQL Server split field and create addition rows with values from main row

I have a table with rows and in one field there are values like this A,B,C
Table 'Mytable':
|ID | Date | MyValue | SplitID |
|1 | 2019-12-17 | A | |
|2 | 2019-12-15 | A,B | |
|3 | 2019-12-16 | B,C | |
Result should be:
|1 | 2019-12-17 | A | 1 |
|2 | 2019-12-15 | A | 2 |
|4 | 2019-12-15 | B | 2 |
|3 | 2019-12-16 | B | 3 |
|5 | 2019-12-16 | C | 3 |
(Sorry, I could not find HOW to format a table in the Stackoverflow help)
I tried a inline table function which splits the Field Myvalue into more lines but could not pass my rows with
charindex(',',[MyValue])>0
from MyTable as input lines.
The code is this:
ALTER function [dbo].[fncSplitString](#input Varchar(max), #Splitter Varchar(99), #ID int)
returns table as
Return
with tmp (DataItem, ix, ID) as
( select LTRIM(#input) , CHARINDEX('',#Input), #ID --Recu. start, ignored val to get the types right
union all
select LTRIM(Substring(#input, ix+1,ix2-ix-1)), ix2, #ID
from (Select *, CHARINDEX(#Splitter,#Input+#Splitter,ix+1) ix2 from tmp) x where ix2<>0
) select DataItem,ID from tmp where ix<>0
Thanks for help
Michael
You can try the following query.
Create table #Temp(
Id int,
DateField Date,
MyValue Varchar(10),
SplitID int
)
CREATE FUNCTION [dbo].[SplitPra] (#Value VARCHAR(MAX), #delimiter CHAR)
RETURNS #DataResult TABLE([Position] TINYINT IDENTITY(1,1),[Value] NVARCHAR(128))
AS
BEGIN
DECLARE #XML xml = N'<r><![CDATA[' + REPLACE(#Value, #delimiter, ']]></r><r><![CDATA[') + ']]></r>'
INSERT INTO #DataResult ([Value])
SELECT RTRIM(LTRIM(T.c.value('.', 'NVARCHAR(128)')))
FROM #xml.nodes('//r') T(c)
RETURN
END
insert into #Temp Values(1, '2019-12-17', 'A', NULL),(2, '2019-12-15', 'A,B', NULL), (3, '2019-12-16', 'B,C', NULL)
Select
#Temp.Id, DateField, b.Value as MyValue, b.Id as SplitValue
from #Temp inner join (
select
Id, f.*
from
#Temp u
cross apply [dbo].[SplitPra](u.MyValue, ',') f
)b on #Temp.Id = b.Id
Drop table #Temp
This will give an output as shown below.
Id DateField MyValue SplitValue
---------------------------------
1 2019-12-17 A 1
2 2019-12-15 A 2
2 2019-12-15 B 2
3 2019-12-16 B 3
3 2019-12-16 C 3
You can find the live demo here.
I found this solution, i hope it will work for you. But i didn't use your function to solve this problem. Instead of that, i used cross apply function.You can find the query below:
-- Creating Test Table
CREATE TABLE #Test
(
ID int,
Date date,
MyValue nvarchar(max),
SplitID int
);
GO
-- Inserting data into test table
INSERT INTO #Test VALUES (1, '2019-12-17', 'A', NULL);
INSERT INTO #Test VALUES (2, '2019-12-15', 'A,B', NULL);
INSERT INTO #Test VALUES (3, '2019-12-16', 'B,C', NULL);
GO
-- Select query
SELECT
*,
(SELECT ID FROM test t1 WHERE t.Date = t1.date) AS SplitID
FROM
(
SELECT
ROW_NUMBER() OVER (ORDER BY ID) AS ID,
Date,
substring(A.value,1,
CASE WHEN charindex(',',rtrim(ltrim(A.value))) = 0 then LEN(A.value)
ELSE charindex(',',rtrim(ltrim(A.value))) -1 end) as MyValue
FROM Test
CROSS APPLY string_split (MyValue, ',') A) AS T
ORDER BY MyValue ASC;
And the result must be like that:
ID Date MyValue SplitID
1 2019-12-17 A 1
2 2019-12-15 A 2
3 2019-12-15 B 2
4 2019-12-16 B 3
5 2019-12-16 C 3

how to copy TreeView's rows from the same table and update with different ID column and copy the parent_id

I want copy the table and put different value on column Type= B and auto_increment id and copy the parent id
Table = Menu
Id | parent_id | order | section | name | url | type
100 | NULL | 7 | web | Tasks | ~/en/Tasks | A
102 | 100 | 1 | web | Pages | ~/en/Pages | A
103 | 100 | 4 | web | Category | ~/en/Category | A
104 | NULL | 3 | web | DLM | ~/en/DLM | A
105 | 104 | 6 | web | ONS | ~/en/ONS | A
106 | 104 | 2 | web | HBO | ~/en/HBO | A
107 | NULL | 7 | web | Tasks | ~/en/Tasks | B
108 | 107 | 1 | web | Pages | ~/en/Pages | B
109 | 107 | 4 | web | Category | ~/en/Category | B
110 | NULL | 3 | web | DLM | ~/en/DLM | B
111 | 110 | 6 | web | ONS | ~/en/ONS | B
112 | 110 | 2 | web | HBO | ~/en/HBO | B
This probably isn't the most efficient, but it gets the job done. It assumes that name is unique. I left out columns unnecessary to the example. Also, you can't put a variable into the identity clause, so that needs to be wrapped in an EXEC
IF OBJECT_ID (N'paths', N'U') IS NOT NULL
DROP TABLE paths
IF OBJECT_ID (N'new_paths', N'U') IS NOT NULL
DROP TABLE new_paths
CREATE TABLE paths (
id INT,
parent_id INT,
name NVARCHAR(20)
)
INSERT INTO dbo.paths
(id,parent_id,name)
VALUES
(100, NULL, 'Tasks'),
(102, 100, 'Pages'),
(103, 100, 'Category'),
(104, NULL, 'DLM'),
(105, 104, 'ONS'),
(106, 104, 'HBO')
DECLARE #start_value INT
SET #start_value = (SELECT MAX(id) FROM paths) + 1
DECLARE #sql nvarchar(1000)
SET #sql = N'
CREATE TABLE new_paths (
id INT IDENTITY(' + CAST(#start_value AS nvarchar) + ',1),
parent_id INT,
name NVARCHAR(20)
)
'
EXEC sp_executesql #stmt = #sql
INSERT INTO new_paths (parent_id,name)
SELECT Parent_id, name FROM dbo.paths
;WITH mappings AS (
SELECT n.*, p.id AS old_id
FROM new_paths n
INNER JOIN paths p
ON p.name = n.name
)
UPDATE n
SET n.parent_id = m.id
FROM new_paths n
INNER JOIN mappings m
ON m.old_id = n.parent_id
--SELECT * FROM new_paths
Please see below approach to resolve an issue, ask questions in the comments if something is unclear, I have added some explanation in code comments
EDITED, to manage GUID (as per comment)
-- declare table var
declare #table table ([Increment] int identity(1,1), Id uniqueidentifier, [parent_id] nvarchar(50), [order] int, [section] nvarchar(50), [name] nvarchar(50), [url] nvarchar(50), [type] nvarchar(50))
-- insert values into this table
insert into #table
select [Id],
[parent_id],
[order],
[section],
[name],
[url],
'B'
from your_table
where [type] = 'A'
-- loop your temp table
declare #max_temp int = (select max(Increment) from #table)
declare #curr int = 1
declare #parent_value uniqueidentifier = null
while (#curr <= #max_temp)
begin
-- do diffrent inserts depend on parent_id value
if (select parent_id from #table) = null
begin
-- set below var, it will be used in next insert where parent_id is not null
set #parent_value = (select Id from #table where Increment = #curr)
insert into your_table ([parent_id], [order], [section], [name], [url], [type])
select
[parent_id],
[order],
[section],
[name],
[url],
[type]
from #table
where Id = #curr
end
else
begin
insert into your_table ([parent_id], [order], [section], [name], [url], [type])
select
isnull(#parent_value, [parent_id]),
[order],
[section],
[name],
[url],
[type]
from #table
where Id = #curr
end
-- update current
set #curr = #curr + 1
end

Alternative for Cross Join with a Table having Single Row (Multiple Columns)

I have a table for Configuration - tblConfig as Shown below
-------------------------------------------
| SiteCode | CompanyCode | CompanyGroup |
-------------------------------------------
| AISH78 | SPWI85 |SFTIT |
-------------------------------------------
And another table tblData With More than 10K rows.
I want to write a query to return tblData Table Rows along with Columns of tblConfig tables.
Eg:
---------------------------------------------------------------------------
| SiteCode | CompanyCode | CompanyGroup | tblDataCol1 | tblDataCol2|.....etc
---------------------------------------------------------------------------
| AISH78 | SPWI85 |SFTIT | 1 | XY | ....etc
----------------------------------------------------------------------------
| AISH78 | SPWI85 |SFTIT | 2 | MN | ....etc
----------------------------------------------------------------------------
| AISH78 | SPWI85 |SFTIT | 3 | PQ | ....etc
----------------------------------------------------------------------------
...
...
...
..etc
I Know i can write a cross Join Query as
Select * from tblConfig
Cross Join tblData
But above Query is affecting performance as tblData is having more than 10K rows. (I have few other queries after this cross Join).
Is there any alternative for cross join for the above scenario.?
Please help.
Hm... you don't need to use a cross join. You can use a regular inner join as well. However, might be that the Optimizer changes it into a cross join after all - you will have to try if performance suits your requirements:
DECLARE #t1 TABLE(
SiteCode NVARCHAR(10),
CompanyCode NVARCHAR(10),
CompanyGroup NVARCHAR(10)
)
INSERT INTO #t1 VALUES ('AISH78','SPWI85', 'SFTIT')
DECLARE #t2 TABLE(
c1 NVARCHAR(10),
c2 NVARCHAR(10),
c3 NVARCHAR(10)
)
INSERT INTO #t2 VALUES ('AAA','BBB', 'CCC'), ('DDD','EEE', 'FFF'), ('GGG','HHH', 'III')
SELECT t1.*, t2.*
FROM #t2 t2
JOIN #t1 t1 ON t1.SiteCode = t1.SiteCode

SQL JOIN with COALESCE in condition is duplicating rows

I am trying to join two tables together. The first table contains data records that I do not want to duplicate. The second table I am joining to the first table to lookup a [value] by a distinct [profileId] and [role]. The [profileId], [role] column in the second table has a unique constraint on the combination, but [role] can sometimes be NULL, in which case I treat that value as the default for that profile.
How can I join these tables together without duplicating the rows, and without using multiple left joins? My actual query is more complex than the example.
See example below.
DECLARE #temp TABLE ([profileId] int, [role] int)
DECLARE #temp2 TABLE ([profileId] int, [role] int, [value] nvarchar(50))
INSERT INTO #temp ([profileId], [role]) VALUES (1, 1)
INSERT INTO #temp ([profileId], [role]) VALUES (1, 2)
INSERT INTO #temp ([profileId], [role]) VALUES (2, 1)
INSERT INTO #temp ([profileId], [role]) VALUES (2, 2)
INSERT INTO #temp2 ([profileId], [role], [value]) VALUES (1, 1, 'MATCH')
INSERT INTO #temp2 ([profileId], [role], [value]) VALUES (1, NULL, 'DEFAULT1')
INSERT INTO #temp2 ([profileId], [role], [value]) VALUES (2, NULL, 'DEFAULT2')
SELECT
T1.[profileId],
T1.[role],
T2.value
FROM
#temp T1
JOIN #temp2 T2 ON T1.profileId = T2.profileId AND COALESCE(T2.[role], T1.[role]) = T1.[role]
This gives me (and I understand why)
================================
| profileId | role | value |
================================
| 1 | 1 | MATCH |
--------------------------------
| 1 | 1 | DEFAULT1 |
--------------------------------
| 1 | 2 | DEFAULT1 |
--------------------------------
| 2 | 1 | DEFAULT2 |
--------------------------------
| 2 | 2 | DEFAULT2 |
================================
While I want
================================
| profileId | role | value |
================================
| 1 | 1 | MATCH |
--------------------------------
| 1 | 2 | DEFAULT1 |
--------------------------------
| 2 | 1 | DEFAULT2 |
--------------------------------
| 2 | 2 | DEFAULT2 |
================================
This SQL works fine:
SELECT
T1.[role],
Value = coalesce(max(nullif(T2.value,'DEFAULT')),'DEFAULT')
FROM
#temp T1
JOIN #temp2 T2 ON COALESCE(T2.[role], T1.[role]) = T1.[role]
group by
T1.[role]
;
You can use APPLY and TOP:
SELECT
t.profileId,
t.role,
x.value
FROM #temp t
OUTER APPLY(
SELECT TOP 1 value
FROM #temp2
WHERE
profileId = t.profileId
AND (role = t.role OR role IS NULL)
ORDER BY
CASE WHEN role IS NOT NULL THEN 0 ELSE 1 END
)x
if you know the DEFAULT value use left join.
SQL Fiddle Demo
SELECT
T1.[role],
COALESCE(T2.value, 'DEFAULT') as value
FROM
temp T1
LEFT JOIN temp2 T2
ON T1.[role] = T2.[role];
Otherwise
SELECT
T1.[role],
COALESCE(T2.value, (SELECT value
FROM temp2
WHERE role is NULL and temp2.profileID = T1.profileID)) as value
FROM
temp T1
LEFT JOIN temp2 T2
ON T1.[role] = T2.[role]
AND T1.[profileID] = T2.[profileID]
;

Resources