I have a table named Employee_audit with following schema,
emp_audit_id
eid
name
salary
...
1
1
Daniel
1000
...
2
1
Dani
1000
...
3
1
Danny
3000
...
My goal is to write a SQL query which will return in following format, considering the first row also as changed value from null.
columnName
oldValue
newValue
name
null
Daniel
salary
null
1000
name
Daniel
Dani
name
Dani
Danny
salary
1000
3000
...
...
...
Finally reached to below solution
CREATE TABLE Employee_audit (
emp_audit_id int,
eid int,
name varchar(50),
salary int,
department varchar(50)
)
insert into Employee_audit (emp_audit_id, eid, name, salary,department)
values
(1, 1, 'Daniel', 1000,'ROP'),
(2, 1, 'Dani', 1000,'ROP'),
(3, 1, 'Danny', 3000,'ROP');
with diffs as (
select 'name' colName, emp_audit_id, eid, lag(name, 1, null) over (partition by eid order by emp_audit_id) oldValue, name newValue
from Employee_audit
union all
select 'salary', emp_audit_id, eid, cast(lag(salary, 1, null) over (partition by eid order by emp_audit_id) as varchar), cast(salary as varchar) newValue
from Employee_audit
union all
...
)
select *
from diffs
where oldValue <> newValue or oldValue is null
order by emp_audit_id, eid
Returns:
emp_audit_id
columnName
oldValue
newValue
1
name
NULL
Daniel
1
salary
NULL
1000.00
2
name
Daniel
Dani
3
name
Dani
Danny
3
salary
1000.00
3000.00
...
...
...
...
But the problem is, the query is very slow because to track 10 fields we have to write 10 union all.
How can I more optimize the query like in a single scan how can I do it?
Here is an option that will dynamically unpivot your data without actually using Dynamic SQL.
Example
;with cte as (
Select emp_audit_id
,eid
,[key]
,newValue=[value]
,oldvalue = lag(value) over (partition by eid,[key] order by emp_audit_id)
From Employee_audit A
Cross Apply ( Select [key],[value] From OpenJson( (Select A.* For JSON Path,Without_Array_Wrapper ) ) ) B
Where [key] not in ('emp_audit_id','eid')
)
Select emp_audit_id
,columName = [key]
,oldvalue
,newvalue
From cte
Where newvalue<>oldvalue or oldvalue is null
Returns
emp_audit_id columName oldvalue newvalue
1 department NULL ROP
1 name NULL Daniel
2 name Daniel Dani
3 name Dani Danny
1 salary NULL 1000
3 salary 1000 3000
I would use apply:
select t.emp_audit_id, v.columnName, v.newValue,
lag(v.newValue) over (partition by eid, columnName order by emp_audit_id) as oldValue
from some_table t cross apply
(values ('name', t.name),
('salary', t.salary),
. . .
) v (columnName, newValue);
If you need to cast the values so they are strings, that goes in the values clause:
select t.emp_audit_id, v.columnName, v.newValue,
lag(v.newValue) over (partition by eid, ColumnName order by emp_audit_id) as oldValue
from some_table t cross apply
(values ('name', t.name),
('salary', cast(t.salary as varchar(255))),
. . .
) v (columnName, newValue);
Related
I have the following table:
id Prefix FisrtName LastName
--------------------------------------------
123 Mr Lynn Berg
123 Ms Madeline Owen
123 Mrs Zelenia Sellers
101 Mrs Jesse Vincent
101 Mr Chaim Long
The result table should look like this
id name1 name2 name2
-----------------------------------------------------------
123 Mr Lynn Berg Ms MadelineOwen Mrs Zelenia Sellers
101 Mrs Jesse Vincent Mr Chaim Long
How could I achieve this result in SQL Server? Can I use pivot function?
Please help
You can also do conditional aggregation :
select id, max(case when seq = 1 then Name end) as Name1,
max(case when seq = 2 then Name end) as Name2,
max(case when seq = 3 then Name end) as Name3
from (select id, concat(Prefix,' ',FisrtName,' ',LastName) as Name,
row_number() over (partition by id order by (select null)) as seq
from table
) t
group by id;
Try this simple pivot:
declare #tbl table (id int, Prefix varchar(3), FirstName varchar(20), LastName varchar(20));
insert into #tbl values
(123, 'Mr', 'Lynn', 'Berg'),
(123, 'Ms', 'Madeline', 'Owen'),
(123, 'Mrs', 'Zelenia', 'Sellers'),
(101, 'Mrs', 'Jesse', 'Vincent'),
(101, 'Mr', 'Chaim', 'Long');
select id, [1] [Name1], [2] [Name2], [3] [Name3] from (
select id,
ROW_NUMBER() over (partition by id order by (select null)) rn,
Prefix + ' ' + FirstName + ' ' + LastName [FullName]
from #tbl
) a pivot (
max(fullname) for rn in ([1],[2],[3])
) b;
CREATE TABLE #Table1 (
id INT
,Prefix VARCHAR(3)
,FisrtName VARCHAR(8)
,LastName VARCHAR(7)
);
INSERT INTO #Table1
VALUES (
123
,'Mr'
,'Lynn'
,'Berg'
)
,(
123
,'Ms'
,'Madeline'
,'Owen'
)
,(
123
,'Mrs'
,'Zelenia'
,'Sellers'
)
,(
101
,'Mrs'
,'Jesse'
,'Vincent'
)
,(
101
,'Mr'
,'Chaim'
,'Long'
)
SELECT *
FROM #Table1
SELECT id
,[1] [Name1]
,[2] [Name2]
,[3] [Name3]
FROM (
SELECT id
,CONCAT (
PREFIX
,FISRTNAME
,LASTNAME
) AS Namm
,ROW_NUMBER() OVER (
PARTITION BY id ORDER BY (
id
)
) AS rn
FROM #TABLE1
) a
pivot(max(Namm) FOR rn IN (
[1]
,[2]
,[3]
)) b
I have a table of PostalCode, now i want to move all data in single row like give second table
PostalId Country StateId DistrictId
-------- ------- ------- ----------
110051 1 110 10165
Second Table
RowNo Value
----- -----
1 110051
2 1
3 110
4 10165
You can do this with
SELECT V.RowNo,
V.Value
FROM PostalCode
CROSS APPLY (VALUES (1, PostalId),
(2, Country),
(3, StateId),
(4, DistrictId) )V(RowNo, Value);
Please try other one way to achieve the above result using CROSS APPLY with XML Method :
SELECT ROW_NUMBER() OVER(ORDER BY
(
SELECT NULL
)) RowNo,
split.a.value('.', 'VARCHAR(MAX)') Value
FROM
(
SELECT CAST('<X>'+REPLACE(CONVERT(VARCHAR(MAX), PostalId)+','+CONVERT(VARCHAR(MAX), Country)+','+CONVERT(VARCHAR(MAX), StateId)+','+CONVERT(VARCHAR(MAX), DistrictId), ',', '</X><X>')+'</X>' AS XML) AS String
FROM PostalCode
) AS Z
CROSS APPLY String.nodes('/X') AS split(a);
Result :
RowNo Value
----- -----
1 110051
2 1
3 110
4 10165
You can use this.
SELECT ROW_NUMBER() OVER(ORDER BY RowNo) RowNo, Value
FROM PostalCode UNPIVOT( Value FOR
RowNo IN ([PostalId], [Country], [StateId], [DistrictId] ) ) UNPVT
I have a table with the columns below, and I need to get the values if COD is duplicated, get the non NULL on VALUE column. If is not duplicated, it can get a NULL VALUE. Like the example:
I'm using SQL SERVER.
This is what I get:
COD ID VALUE
28 1 NULL
28 2 Supermarket
29 1 NULL
29 2 School
29 3 NULL
30 1 NULL
This is what I want:
COD ID VALUE
28 2 Supermarket
29 2 School
30 1 NULL
What I'm tryin' to do:
;with A as (
(select DISTINCT COD,ID,VALUE from CodId where ID = 2)
UNION
(select DISTINCT COD,ID,NULL from CodId where ID != 2)
)select * from A order by COD
You can try this.
DECLARE #T TABLE (COD INT, ID INT, VALUE VARCHAR(20))
INSERT INTO #T
VALUES(28, 1, NULL),
(28, 2 ,'Supermarket'),
(29, 1 ,NULL),
(29, 2 ,'School'),
(29, 3 ,NULL),
(30, 1 ,NULL)
;WITH CTE AS (
SELECT *, RN= ROW_NUMBER() OVER (PARTITION BY COD ORDER BY VALUE DESC) FROM #T
)
SELECT COD, ID ,VALUE FROM CTE
WHERE RN = 1
Result:
COD ID VALUE
----------- ----------- --------------------
28 2 Supermarket
29 2 School
30 1 NULL
Another option is to use the WITH TIES clause in concert with Row_Number()
Example
Select top 1 with ties *
from YourTable
Order By Row_Number() over (Partition By [COD] order by Value Desc)
Returns
COD ID VALUE
28 2 Supermarket
29 2 School
30 1 NULL
I would use GROUP BY and JOIN. If there is no NOT NULL value for a COD than it should be resolved using the OR in JOIN clause.
SELECT your_table.*
FROM your_table
JOIN (
SELECT COD, MAX(value) value
FROM your_table
GROUP BY COD
) gt ON your_table.COD = gt.COD and (your_table.value = gt.value OR gt.value IS NULL)
If you may have more than one non null value for a COD this will work
drop table MyTable
CREATE TABLE MyTable
(
COD INT,
ID INT,
VALUE VARCHAR(20)
)
INSERT INTO MyTable
VALUES (28,1, NULL),
(28,2,'Supermarket'),
(28,3,'School'),
(29,1,NULL),
(29,2,'School'),
(29,3,NULL),
(30,1,NULL);
WITH Dups AS
(SELECT COD FROM MyTable GROUP BY COD HAVING count (*) > 1 )
SELECT MyTable.COD,MyTable.ID,MyTable.VALUE FROM MyTable
INNER JOIN dups ON MyTable.COD = Dups.COD
WHERE value IS NOT NULL
UNION
SELECT MyTable.COD,MyTable.ID,MyTable.VALUE FROM MyTable
LEFT JOIN dups ON MyTable.COD = Dups.COD
WHERE dups.cod IS NULL
I have table like this
Reg_No Student_Name Subject1 Subject2 Subject3 Subject4 Total
----------- -------------------- ----------- ----------- ----------- ----------- -----------
101 Kevin 85 94 78 90 347
102 Andy 75 88 91 78 332
From this I need to create a temp table or table like this:
Reg_No Student_Name Subject Total
----------- -------------------- ----------- -----------
101 Kevin 85 347
94
78
90
102 Andy 75 332
88
91
78
Is there a way I can do this in SQL Server?
DDL:
DECLARE #temp TABLE
(
Reg_No INT
, Student_Name VARCHAR(20)
, Subject1 INT
, Subject2 INT
, Subject3 INT
, Subject4 INT
, Total INT
)
INSERT INTO #temp (Reg_No, Student_Name, Subject1, Subject2, Subject3, Subject4, Total)
VALUES
(101, 'Kevin', 85, 94, 78, 90, 347),
(102, 'Andy ', 75, 88, 91, 78, 332)
Query #1 - ROW_NUMBER:
SELECT Reg_No = CASE WHEN rn = 1 THEN t.Reg_No END
, Student_Name = CASE WHEN rn = 1 THEN t.Student_Name END
, t.[Subject]
, Total = CASE WHEN rn = 1 THEN t.Total END
FROM (
SELECT
Reg_No
, Student_Name
, [Subject]
, Total
, rn = ROW_NUMBER() OVER (PARTITION BY Reg_No ORDER BY 1/0)
FROM #temp
UNPIVOT
(
[Subject] FOR tt IN (Subject1, Subject2, Subject3, Subject4)
) unpvt
) t
Query #2 - OUTER APPLY:
SELECT t.*
FROM #temp
OUTER APPLY
(
VALUES
(Reg_No, Student_Name, Subject1, Total),
(NULL, NULL, Subject2, NULL),
(NULL, NULL, Subject3, NULL),
(NULL, NULL, Subject4, NULL)
) t(Reg_No, Student_Name, [Subject], Total)
Query Plan:
Query Cost:
Output:
Reg_No Student_Name Subject Total
----------- -------------------- ----------- -----------
101 Kevin 85 347
NULL NULL 94 NULL
NULL NULL 78 NULL
NULL NULL 90 NULL
102 Andy 75 332
NULL NULL 88 NULL
NULL NULL 91 NULL
NULL NULL 78 NULL
PS: In your case query with OUTER APPLY is faster than ROW_NUMBER solution.
The simplest approach would be to use a UNIONclause
select Reg_No, Student_Name, Subject1, Total from YourTable union all
select Reg_No, Student_Name, Subject2, Total from YourTable union all
select Reg_No, Student_Name, Subject3, Total from YourTable union all
select Reg_No, Student_Name, Subject3, Total from YourTable
UNION
Combines the results of two or more queries into a single result set
that includes all the rows that belong to all queries in the union.
The UNION operation is different from using joins that combine columns
from two tables.
The following are basic rules for combining the result sets of two
queries by using UNION:
•The number and the order of the columns must be the same in all
queries.
•The data types must be compatible.
Check this Fiddle
;WITH MyCTE AS
(
SELECT *
FROM (
SELECT Reg_No,
[Subject1],
[Subject2],
[Subject3],
[Subject4]
FROM Table1
)p
UNPIVOT
(
Result FOR SubjectName in ([Subject1], [Subject2], [Subject3], [Subject4])
)unpvt
)
SELECT T.Reg_No,
T.Student_Name,
M.SubjectName,
M.Result,
T.Total
FROM Table1 T
JOIN MyCTE M
ON T.Reg_No = M.Reg_No
If you do want NULL values in the rest, you may try the following:
This is the new Fiddle
And here is the code:
;WITH MyCTE AS
(
SELECT *
FROM (
SELECT Reg_No,
[Subject1],
[Subject2],
[Subject3],
[Subject4]
FROM Table1
)p
UNPIVOT
(
Result FOR SubjectName in ([Subject1], [Subject2], [Subject3], [Subject4])
)unpvt
),
MyNumberedCTE AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY Reg_No ORDER BY Reg_No,SubjectName) AS RowNum
FROM MyCTE
)
SELECT T.Reg_No,
T.Student_Name,
M.SubjectName,
M.Result,
T.Total
FROM MyCTE M
LEFT JOIN MyNumberedCTE N
ON N.Reg_No = M.Reg_No
AND N.SubjectName = M.SubjectName
AND N.RowNum=1
LEFT JOIN Table1 T
ON T.Reg_No = N.Reg_No
You should look after the PIVOT operator :
http://technet.microsoft.com/en-us/library/ms177410(v=sql.100).aspx
> DECLARE #cols AS NVARCHAR(MAX),#query AS NVARCHAR(MAX)
>
> select #cols = STUFF((SELECT ',' + QUOTENAME(designation)
> from MyTable
> group by designation
> order by designation
> FOR XML PATH(''), TYPE
> ).value('.', 'NVARCHAR(MAX)'),1,1,'')
>
> set #query = N'SELECT Row, ' + #cols + N' from
> (
> select ''SS'' Row, SS AS Value , designation from MyTable
> UNION ALL
> select ''AS'' Row, [AS] AS Value , designation from MyTable
> UNION ALL
> select ''Vac'' Row, Vac AS Value , designation from MyTable
> ) x
> pivot
> (
> max(Value) for designation in (' + #cols + N')
> ) p '
> exec sp_executesql #query;
For more details: Convert row into column when number of row is not fixed
I have a table with data
ID Name
1 John
2 Robert
3 John
4 Sam
5 Jack
6 Sam
Now i want ony the the duplicate names ony through query
ie..,
Name
John
Sam
SELECT Name
FROM YourTable
GROUP BY Name
HAVING COUNT(*) > 1
CREATE TABLE MyTable (
ID int
, Name nvarchar(50)
)
INSERT MyTable VALUES ( 1, 'John' )
INSERT MyTable VALUES ( 2, 'Robert' )
INSERT MyTable VALUES ( 3, 'John' )
INSERT MyTable VALUES ( 4, 'Sam' )
INSERT MyTable VALUES ( 5, 'Jack' )
INSERT MyTable VALUES ( 6, 'Sam' )
SELECT
Name
FROM
MyTable
GROUP BY
Name
HAVING
COUNT(*) > 1
DROP TABLE MyTable
Results:
Name
--------------------------------------------------
John
Sam
with temp as (
select Name, count(Name) as countOfNames
from myTable
group by Name
)
select Name from temp
where countOfNames > 1
select columnname,count(column name) from tablename group by column name having count(*)>1