SQL Server 2008 - Fill the blank value - sql-server

I am having a problem write a query that fill in the blank value. I know it sounds confusing so let me demonstrate and explain.
Says, I have a query "Select Name, Serial, TrackNum From Table1" and the result is below:
Table1
Name Serial TrackNum
AAA
AAA 222 T1
BBB 333 T1
BBB 444 T2
BBB 555
BBB 666
CCC 777 T3
CCC 888
CCC 999
DDD 998
EEE 997 T4
EEE 996
FFF T5
I am having problem to form a query to produce this result below. You see that Serial# 555 and 666 were inherited "T2" from the row above, and same thing for Serial# 888 and 999. However, part# DDD has serial but not track#. And FFF has not serial but track#.
Name Serial TrackNum
AAA
AAA 222 T1
BBB 333 T1
BBB 444 T2
BBB 555 T2
BBB 666 T2
CCC 777 T3
CCC 888 T3
CCC 999 T3
DDD 998
EEE 997 T4
EEE 996 T4
FFF T5
Please help and appreciate if you provide sample code.

Would this work for your needs? The caveat to this technique is that the first row for AAA will also display T1 for the TrackNum rather than an empty string/NULL.
DECLARE #Table1 TABLE (Name VARCHAR(3),Serial VARCHAR(3),TrackNum VARCHAR(3))
INSERT #Table1
VALUES ('AAA','','')
,('AAA','222','T1')
,('BBB','333','T1')
,('BBB','444','T2')
,('BBB','555','')
,('BBB','666','')
,('CCC','777','T3')
,('CCC','888','')
,('CCC','999','')
;WITH c AS (SELECT Name,
MAX(TrackNum) AS TrackNum
FROM #Table1
GROUP BY Name)
SELECT t.Name, t.Serial, CASE
WHEN t.TrackNum IS NULL OR t.TrackNum = ''
THEN CASE
WHEN t.Serial IS NULL OR t.Serial = ''
THEN ''
ELSE c.TrackNum
END
ELSE t.TrackNum
END AS TrackNum
FROM #Table1 t
JOIN c ON t.Name = c.Name
If you want to UPDATE anything this can give you an idea.
;WITH c AS (SELECT Name,
MAX(TrackNum) AS TrackNum
FROM #Table1
GROUP BY Name)
UPDATE t
SET t.TrackNum = c.TrackNum
FROM #Table1 t
JOIN c ON t.Name = c.Name
WHERE t.TrackNum IS NULL OR t.TrackNum = ''
SELECT *
FROM #Table1

Assuming those are NULL values:
SELECT Name, Serial,
CAST(
SUBSTRING(
MAX( CAST(Name AS BINARY(4)) + CAST(Serial AS BINARY(4)) )
OVER( ORDER BY Name
ROWS UNBOUNDED PRECEDING ),
5, 4)
AS varchar(5)) AS TrackNum
FROM table
Courtesy of Mr. Ben-Gan, http://sqlmag.com/t-sql/last-non-null-puzzle

Related

Update(Rename) values of a column based on the values of another column in same table - T-SQL

I'm trying to rename a column based on another column in the same table. Example - I have a table as below
Row# Name Date id
-------------------------------------
1 aaa 2018-03-02 Null
2 aaa 2018-03-02 123
3 aaa 2018-03-02 456
4 bbb 2019-07-05 Null
5 bbb 2019-07-05 Null
6 bbb 2019-07-05 345
Here, I would like to check if the name and sent date match - if both the condition match and the Id is NULL, no changes in the name but if the id is not NULL then I want to rename the 'Name' field as 'aaa (temp)' in the same table.
I'm not sure how to compare the id field and rename the table if its not NULL (irrespective of what the value is) below is the solution I'm expecting (Since the name & Date are same but the Ids in row 2 & 3 are not NULL)
Row# Name Date id
-------------------------------------------
1 aaa 2018-03-02 Null
2 aaa(Temp) 2018-03-02 123
3 aaa(Temp) 2018-03-02 456
4 bbb 2019-07-05 Null
5 bbb 2019-07-05 Null
6 bbb(Temp) 2019-07-05 345
You can made a update with inner join
UPDATE e1 SET name = CONCAT(e1.name,' ( Temp )')
FROM #example e1
INNER JOIn #example e2
ON e2.name = e1.name
AND e2.date = e1.date
WHERE e1.id IS NOT NULL
But because you dont have a unique identifier for each row is going to also update the row that dont have a match, bc each rows is going to match with itself.
If you have uid is pretty easy
UPDATE e1 SET name = CONCAT(e1.name,' ( Temp )')
FROM #example e1
INNER JOIN #example e2
ON e2.name = e1.name
AND e2.date = e1.date
AND e2.id_row <> e1.id_row
WHERE e1.id is not null
if you don't really have a unique identifier you can easily add it with this query
ALTER TABLE #example ADD id_row INT IDENTITY NOT NULL
You can use exists and case:
select t.*,
(case when id is not null and
exists (select 1 from t t2 where t2.name = t.name and t2.date = t.date and t2.id is null)
then concat(name, '(Temp)')
else name
end) as new_name
from t;
If you want to update the value, then you can use an updatable CTE:
with toupdate as (
select t.*,
(case when id is not null and
exists (select 1 from t t2 where t2.name = t.name and t2.date = t.date and t2.id is null)
then concat(name, '(Temp)')
else name
end) as new_name
from t
)
update toudpate
set name = new_name
where name <> new_name;
You can also phrase this as:
update t
set name = concat(name, '(Temp)')
where t.id is not null and
exists (select 1 from t t2 where t2.name = t.name and t2.date = t.date and t2.id is null);

Sqlserver minus with unmatched columns

I have two tables with different columns and want intersect records.
Table1:
Id name age
1 AAA 20
2 AAA 30
3 BBB 25
4 BBB 30
Table2:
name age
AAA 20
BBB 30
Expect Output:(Table2 - Table1)
Id name age
2 AAA 30
3 BBB 25
You can use NOT EXISTS with a correlated subquery to check that no tuple (name, age) with identical values exists.
SELECT *
FROM table1 t1
WHERE NOT EXISTS (SELECT *
FROM table2 t2
WHERE t2.name = t1.name
AND t2.age = t1.age);
Use Right Join
SELECT DISTINCT Table1.Id, Table1.Name, Table1.Age
FROM Table2 RIGHT JOIN Table1 ON Table1.Name = Table2.Name AND Table1.Age = Table2.Age
WHERE Table2.Name IS NULL
FIDDLE DEMO

Data population

I have a scenario like
Table1
Id. Name. Age. City
1. Aaa. 20. Ccc
2. BBB. 12. Ccc
Table 2
Id. Name. Age. City
1. FFF Ccc
Now all I need is based on the city (ccc is common in both )
I will have to change age value in table b and the expected output is
Id. Name. Age. City
1. FFF 20. Ccc
2. FFF 12. Ccc
Just Use a simple INNER JOIN :
SELECT t1.ID, t2.name, t1.Age, t1.City
FROM Table1 t1 JOIN Table2 t2 on ( t1.City = t2.City );
SQL Fiddle Demo
SELECT Table1.Id, Table2.Name, Table1.Age, Table1.City
FROM Table1
INNER JOIN Table2
ON Table1.City=Table2.City
It's easier if you tabulate the table name and the values in your question.
(use code block)

Mark all rows that meet two conditions and has the lowest value on a third

I need to mark all the first occurences (lowest ID in table_a) where two conditions (customer and user) match the conditions in another table (customer and user in table_b). A very simplified version of the problem is here:
table_a
Id Customer Users
----- -------- ----
100 1001 abc
101 1001 abc
102 1001 xyz
103 1001 xyz
104 1002 abc
105 1002 abc
106 1002 xyz
107 1002 xyz
table_b
Customer Users
-------- -----
1001 abc
1002 xyz
What I want:
Id Customer User include
----- -------- ---- -------
100 1001 abc 1
101 1001 abc 0
102 1001 xyz 0
103 1001 xyz 0
104 1002 abc 0
105 1002 abc 0
106 1002 xyz 1
107 1002 xyz 0
this what I tried:
select a.*, case when exists(
select 1
from table_a a1, table_b b
where a.customer=b.customer
and a.user=b.user
having min(a1.id)=a.id
)
then 1 else 0 end as include
but only the first row (lowest ID) in the whole list is marked. If the conditions are not met in the first row (user and customer combo don´t match the one on table_b) none is marked.
There is some logic I miss here. Any suggestion? The real table_a has million of rows so speed is a concern. So besides logic I probably also need some speed magic.
The full code is here:
DROP TABLE IF EXISTS #table_a
DROP TABLE IF EXISTS #table_b
create table #table_a (Id char(3),Customer char(4),Users char(3))
insert into #table_a (Id,Customer,Users) values
('100','1001','abc'),
('101','1001','abc'),
('102','1001','xyz'),
('103','1001','xyz'),
('104','1002','abc'),
('105','1002','abc'),
('106','1002','xyz'),
('107','1002','xyz')
create table #table_b (Customer char(4),Users char(3))
insert into #table_b (Customer,Users) values
('1001','abc'),
('1002','xyz')
select a.*
, case when exists(
select *
from #table_a a1, #table_b b
where a.customer=b.customer
and a.users=b.users
having min(a1.id)=a.id
)
then 1 else 0 end as include
from #table_a a
This would do the trick, if you can use windowed functions in your version of SQL Server:
WITH Includes AS (
SELECT
a.*,
CASE WHEN b.Customer IS NOT NULL THEN 1 ELSE 0 END AS [include],
ROW_NUMBER() OVER (PARTITION BY a.Customer, a.Users ORDER BY a.Id) AS include_id
FROM
#table_a a
LEFT JOIN #table_b b ON b.Customer = a.Customer AND b.Users = a.Users)
SELECT
a.*,
CASE WHEN i.include_id = 1 THEN i.[include] ELSE 0 END AS [include]
FROM
#table_a a
LEFT JOIN Includes i ON i.Id = a.Id;
Basically it builds up a list of matches, then uses ROW_NUMBER() to pick the first one from each group.
You can try using the following query:
SELECT a.Id, a.Customer, a.Users,
CASE
WHEN SUM(IIF(b.Customer IS NOT NULL, 1, 0))
OVER (PARTITION BY a.Customer ORDER BY a.Id) = 1 THEN 1
ELSE 0
END AS include
FROM #table_a AS a
LEFT JOIN #table_b AS b ON a.Customer = b.Customer AND a.Users = b.Users
The query assumes that there is at most one match between #table_a and #table_b.
Explanation:
The query uses SUM() OVER() with an ORDER BY clause in order to caculate the running total of records having a match. So, this query:
SELECT a.Id, a.Customer, a.Users,
SUM(IIF(b.Customer IS NOT NULL, 1, 0))
OVER (PARTITION BY a.Customer ORDER BY a.Id) AS cnt
FROM table_a AS a
LEFT JOIN table_b AS b ON a.Customer = b.Customer AND a.Users = b.Users
produces this output:
Id Customer Users cnt
-----------------------
100 1001 abc 1
101 1001 abc 2
102 1001 xyz 2
103 1001 xyz 2
104 1002 abc 0
105 1002 abc 0
106 1002 xyz 1
107 1002 xyz 2
The record having cnt=1 is the one we are looking for.
Demo here
SELECT a.*,
CASE WHEN a.Id = a1.Id THEN 1 ELSE 0 END AS [include]
FROM #table_a a
LEFT JOIN #table_b b ON a.Customer = b.Customer
AND a.Users = b.Users
OUTER APPLY (
SELECT TOP 1 Id
FROM #table_a a
WHERE a.Customer = b.Customer
AND a.Users = b.Users
) a1

TSQL: Displaying multiple rows of results as 1 row

All,
Not quite sure how to do the following. Teaching myself SQL, working with SQL Server 2008 R2. Note that while I can perform all the select queries I like, I do not have the permissions to create drop tables in the database.
In my database, there's a table called "messages." Each message is a three letter code (e.g., 'AAA', 'AAB', etc.). Each primary key can have an arbitrary number of messages. So, for purposes of this exercise, say the table looks like this:
1 AAA
1 AAB
1 AAC
2 AAA
2 CCC
etc,
The output I would like to get is to convert this horizontal data to vertical data so I can get this:
1 AAA AAB AAC
2 AAA CCC
If relevant, the database also contains a list of all the possible message codes on a different table.
I suspect the correct answer involves PIVOT, but I am not quite sure how to get there. The closest I found is this: How to pivot table with T-SQL? However, (a) I wasn't sure how to adapt it to my situation and (b) it appears to require creating a table.
Thank you in advance.
Since your question has been edited, including both queries:
Query for expected result in Original question:
;WITH CTE AS (
SELECT T2.ID, STUFF(
(SELECT ' '+ T1.Code
FROM TableName T1
WHERE T1.ID = T2.ID
FOR XML PATH('')),1,1,'') AS CSV
FROM TableName AS T2
GROUP BY T2.ID)
SELECT TOP 1 STUFF(
(SELECT ' ' + s.Temp
FROM (SELECT CONVERT(varchar(10),ID)+' '+CSV as Temp
FROM CTE) s
FOR XML PATH('')),1,1,'') AS Result
Result:
RESULT
1 AAA AAB AAC 2 AAA CCC
See result in SQL Fiddle.
Query for expected result in Edited question:
SELECT T2.ID, STUFF(
(SELECT ' '+ T1.Code
FROM TableName T1
WHERE T1.ID = T2.ID
FOR XML PATH('')),1,1,'') AS Codes
FROM TableName AS T2
GROUP BY T2.ID
Result:
ID CODES
1 AAA AAB AAC
2 AAA CCC
See result in SQL Fiddle.
Test Data
DECLARE #TABLE TABLE(MessageID INT, Body VARCHAR(100))
INSERT INTO #TABLE VALUES
(1, 'AAA'),
(1, 'AAB'),
(1, 'AAC'),
(2, 'AAA'),
(2, 'CCC')
Query
SELECT t.MessageID,
STUFF((SELECT ' ' + Body
FROM #TABLE
WHERE MessageID = t.MessageID
FOR XML PATH(''),TYPE)
.value('.','NVARCHAR(MAX)'),1,1,'')
AS FullMessage
FROM #TABLE t
GROUP BY t.MessageID
Result Set
╔═══════════╦═════════════╗
║ MessageID ║ FullMessage ║
╠═══════════╬═════════════╣
║ 1 ║ AAA AAB AAC ║
║ 2 ║ AAA CCC ║
╚═══════════╩═════════════╝

Resources