Related
I have a huge SQL table and need to filter several columns (called Field) if they are in this list:
AAA, BBB, CCC, DDD, EEE
If this is the case, set respective value column to NULL.
Let me give you an example table:
Field1 Value1 Field2 Value2 ...... Field10 Value10
AAA 9A AAA 9A CCC World
BBB 1 KKK 0
ZZZ 9 AAA 9A CCC 42
CCC 7
LLL 9 AAA 3 III 98
KKK 3 AAA 4
DDD 100 AAA Hello CCC 6
Not all Field Value combinations are filled up until Value10. However every column that is named FieldXX must be checked for the list entries. If a cell is equal to a list entry the respective value field should be set NULL. Otherwise the value field remains untouched.
I struggle to figure out the correct CASE statement for this specific issue. I tried the following code:
SELECT CAST(
CASE
WHEN Field* = AAA or BBB or CCC or DDD or EEE
THEN Value* = 0
ELSE Value* = Value*
END AS bit)
FROM dbo.Log
But this query totally fails and it might not be for specific field-value pairs.
Expected Output
Field1 Value1 Field2 Value2 ...... Field10 Value10
AAA 0 AAA 0 CCC 0
BBB 0 KKK 0
ZZZ 9 AAA 0 CCC 0
CCC 0
LLL 9 AAA 0 III 98
KKK 3 AAA 0
DDD 0 AAA 0 CCC 0
Table name is dbo.Log.
If I understood correctly, you are trying to update ValueX columns based on FieldX data.
In that case you need to write Case condition for every column.
You can directly write 10 Case conditions like below
UPDATE A_LOG
SET VALUE1 = CASE WHEN FIELD1 IN ('AAA','BBB','CCC','DDD','EEE' ) THEN 0 ELSE VALUE1 END,
VALUE2 = CASE WHEN FIELD2 IN ('AAA','BBB','CCC','DDD','EEE' ) THEN 0 ELSE VALUE2 END,
VALUE3 = CASE WHEN FIELD3 IN ('AAA','BBB','CCC','DDD','EEE' ) THEN 0 ELSE VALUE3 END
.
.
VALUE10 = CASE WHEN FIELD10 IN ('AAA','BBB','CCC','DDD','EEE' ) THEN 0 ELSE VALUE10 END
Or you can generate Update statement with Dynamic query like below
DECLARE #IN VARCHAR(100)=' IN (''AAA'',''BBB'',''CCC'',''DDD'',''EEE'' )'
, #SQL VARCHAR(MAX) = 'UPDATE A_LOG SET '
SELECT #SQL = #SQL+ VAL FROM
(
SELECT COLUMN_NAME + ' = CASE WHEN FIELD'+ REPLACE(COLUMN_NAME,'VALUE','')
+ #IN+ ' THEN 0 ELSE '+ COLUMN_NAME + ' END,
' VAL
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'LOG'
AND COLUMN_NAME LIKE 'VALUE%'
)A
SELECT #SQL = SUBSTRING(#SQL,1,LEN(#SQL)-3)
SELECT #SQL
That will give you update statement for every column with a case condition.
I'm pretty certain the wildcard you're trying to use for your CASE WHEN isn't a valid way of using it.
You can use variables to create a SQL string that you can then copy and execute - this way, if you need to increase the number of fields you're checking, you only have to change one part of your code :)
The code below creates the SQL string then prints it out in the console window:
DECLARE #cnt INT = 1;
DECLARE #sql VARCHAR(MAX) = 'SELECT ';
WHILE #cnt <= 10
BEGIN
DECLARE #COLUMN VARCHAR(MAX) = 'Field' + CAST(#cnt AS varchar(max))
DECLARE #VALUE VARCHAR(MAX) = 'Value' + CAST(#cnt AS varchar(max))
SET #sql = #sql + #COLUMN + ', CASE WHEN ' + #COLUMN + '= ''AAA'' OR ' + #COLUMN + '= ''BBB'' OR ' + #COLUMN + '= ''CCC'' OR ' + #COLUMN + '= ''DDD'' OR ' + #COLUMN + '= ''EEE'' THEN 0 ELSE ' + #VALUE + ' END AS ' + #VALUE;
IF #cnt != 10
(SELECT #sql = #sql + ',')
SET #cnt = #cnt +1
END
SET #sql = #sql + ' INTO [dbo].[LogFilter] FROM [dbo].[Log]';
PRINT #sql;
This code below is what is printed out (I have styled it so you can easily read it and truncated it so it doesn't take up the whole screen) :)
SELECT
Field1,
CASE WHEN Field1= 'AAA' OR Field1= 'BBB' OR Field1= 'CCC' OR Field1= 'DDD' OR Field1= 'EEE'
THEN 0 ELSE Value1 END AS Value1,
Field2,
CASE WHEN Field2= 'AAA' OR Field2= 'BBB' OR Field2= 'CCC' OR Field2= 'DDD' OR Field2= 'EEE'
THEN 0 ELSE Value2 END AS Value2,
Field3,
CASE WHEN Field3= 'AAA' OR Field3= 'BBB' OR Field3= 'CCC' OR Field3= 'DDD' OR Field3= 'EEE'
THEN 0 ELSE Value3 END AS Value3,
...
INTO [dbo].[LogFilter] FROM [dbo].[Log]
This SELECT INTO statement above places your filtered information into a brand new table :)
Try this:
Declare #Table Table(Field1 varchar(10), Value1 varchar(10), Field2 varchar(10), Value2 varchar(10) ,Field10 varchar(10) , Value10 varchar(10))
Declare #NewTable Table(Field1 varchar(10), Value1 varchar(10), Field2 varchar(10), Value2 varchar(10) ,Field10 varchar(10) , Value10 varchar(10))
insert into #Table
SELECT 'AAA','9A','AAA','9A','CCC','World' Union All
SELECT 'BBB','1','KKK','0',NULL,NULL Union All
SELECT 'ZZZ','9','AAA','9A','CCC','42' Union All
SELECT 'CCC','7',NULL,NULL,NULL,NULL Union All
SELECT 'LLL','9','AAA','3','III','98' Union All
SELECT 'KKK','3','AAA','4',NULL,NULL Union All
SELECT 'DDD','100','AAA','Hello','CCC','6'
INSERT into #NewTable
Select Field1,CASE WHEN Field1 in ('AAA','BBB','CCC','DDD','EEE') THEN 0 else Value1 end As Value1
,Field2,CASE WHEN Field2 in ('AAA','BBB','CCC','DDD','EEE') THEN 0 else Value2 end As Value2
,Field10,CASE WHEN Field10 in ('AAA','BBB','CCC','DDD','EEE') THEN 0 else Value10 end As Value10
from #Table
Select * from #NewTable
The Case statement wouldn't work in the way that you've written. You need to individually compare the values in each Field and set the value of 'Value' column.
SELECT CAST(
CASE
WHEN Field1 = AAA or BBB or CCC or DDD or EEE
THEN Value1 = 0
ELSE Value1
END AS bit) Value1,
CAST(
CASE
WHEN Field2 = AAA or BBB or CCC or DDD or EEE
THEN Value2 = 0
ELSE Value2
END AS bit) Value2,
.
.
.
.
<need to write separate case statement for each column which is to be compared>
FROM dbo.Log
I have a query like below:
DECLARE #t TABLE
(
EmpName VARCHAR(10)
, Qty INT
, Item VARCHAR(12)
)
INSERT INTO #t
VALUES ('Jane',3,'Dog')
, ('Carle',1,'Cat')
, ('Abay',5,'Goat')
, ('Jane',1,'Dog')
, ('Carle',10,'Cat')
, ('Jane',2,'Dog')
, ('Jane',8,'Goat')
, ('Jane',3,'Ram')
, ('Carle',2,'Dog')
--SELECT * FROM #t
SELECT
EmpName, [Dog], [Cat], [Goat], [Ram]
FROM
(SELECT
EmpName, Qty, Item
FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN ([Dog], [Cat], [Goat], [Ram])) AS p
And the result is as seen in the screenshot below:
I want to calculate the average Qty across Item without ignoring null values in the calculation. For example, in row 1, EmpName Abay should be 5 divided by 4 (number of columns), as seen in this screenshot:
How do I get the average column?
I'm not really familiar with the PIVOT query, so here is an alternative using conditional aggregation:
SELECT
Empname,
Dog = SUM(CASE WHEN Item = 'Dog' THEN Qty ELSE 0 END),
Cat = SUM(CASE WHEN Item = 'Cat' THEN Qty ELSE 0 END),
Goat = SUM(CASE WHEN Item = 'Goat' THEN Qty ELSE 0 END),
Ram = SUM(CASE WHEN Item = 'Ram' THEN Qty ELSE 0 END),
Average = SUM(ISNULL(Qty, 0))/ 4.0
FROM #t
GROUP BY EmpName;
Note that this will only work if you only have 4 Items. Otherwise, you need to resort to dynamic crosstab.
ONLINE DEMO
For dynamic crosstab, I used a temporary table instead of a table variable:
DECLARE #sql NVARCHAR(MAX) = '';
SELECT #sql =
'SELECT
Empname' + CHAR(10);
SELECT #sql = #sql +
' , SUM(CASE WHEN Item = ''' + Item + ''' THEN Qty ELSE 0 END) AS ' + QUOTENAME(Item) + CHAR(10)
FROM (
SELECT DISTINCT Item FROM #t
) t;
SELECT #sql = #sql +
' , SUM(ISNULL(Qty, 0)) / (SELECT COUNT(DISTINCT Item) * 1.0 FROM #t) AS [Average]' + CHAR(10) +
'FROM #t
GROUP BY EmpName;';
ONLINE DEMO
Try a combination of AVG and ISNULL, i.e. AVG(ISNULL(Dog, 0)).
One simple method is:
select empname, goat, cat, dog, ram,
(coalesce(goat, 0) + coalesce(cat, 0) + coalesce(dog, 0) + coalesce( ram, 0)
) / 4.0 as average
from t;
Another simple method uses outer apply:
select t.*, v.average
from t outer apply
(select avg(coalesce(x, 0))
from (values (t.goat), (t.cat), (t.dog), (t.ram)
) v(x)
) v(average);
DECLARE #t TABLE
(
EmpName VARCHAR(10)
, Qty INT
, Item VARCHAR(12)
)
INSERT INTO #t
VALUES ('Jane',3,'Dog')
, ('Carle',1,'Cat')
, ('Abay',5,'Goat')
, ('Jane',1,'Dog')
, ('Carle',10,'Cat')
, ('Jane',2,'Dog')
, ('Jane',8,'Goat')
, ('Jane',3,'Ram')
, ('Carle',2,'Dog')
SELECT EmpName
, [Dog]
, [Cat]
, [Goat]
, [Ram]
,p.total/4.0 as av
FROM (SELECT EmpName, Qty, Item,SUM(qty)OVER(PARTITION BY EmpName) AS total FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN([Dog],[Cat],[Goat],[Ram])) AS p
EmpName Dog Cat Goat Ram av
---------- ----------- ----------- ----------- ----------- ---------------------------------------
Abay NULL NULL 5 NULL 1.250000
Carle 2 11 NULL NULL 3.250000
Jane 6 NULL 8 3 4.250000
V2: Dynamic script:
CREATE TABLE #t
(
EmpName VARCHAR(10)
, Qty INT
, Item VARCHAR(12)
)
INSERT INTO #t
VALUES ('Jane',3,'Dog')
, ('Carle',1,'Cat')
, ('Abay',5,'Goat')
, ('Jane',1,'Dog')
, ('Carle',10,'Cat')
, ('Jane',2,'Dog')
, ('Jane',8,'Goat')
, ('Jane',3,'Ram')
, ('Carle',2,'Dog')
INSERT #t ( EmpName, Qty, Item )VALUES('Abay',100,'abc')
DECLARE #cols VARCHAR(max),#sql VARCHAR(MAX),#cnt INT
SELECT #cols=ISNULL(#cols+',[','[')+Item+']',#cnt=ISNULL(#cnt+1,1) FROM #t GROUP BY Item
PRINT #cols
PRINT #cnt
SET #sql='SELECT EmpName, '+#cols+',p.total*1.0/'+LTRIM(#cnt)+' as av'+CHAR(13)
+' FROM (SELECT EmpName, Qty, Item,SUM(qty)OVER(PARTITION BY EmpName) AS total FROM #t) AS b'+CHAR(13)
+' PIVOT(SUM(Qty) FOR Item IN('+#cols+')) AS p'
EXEC(#sql)
EmpName abc Cat Dog Goat Ram av
---------- ----------- ----------- ----------- ----------- ----------- ---------------------------------------
Carle NULL 11 2 NULL NULL 2.600000
Jane NULL NULL 6 8 3 3.400000
Abay 100 NULL NULL 5 NULL 21.000000
Avoid NULL from your pivot sentence and compute AVG.
;with ct as
(
SELECT EmpName
, ISnull([Dog],0) Dog
, ISnull([Cat],0) Cat
, ISnull([Goat],0) Goat
, ISnull([Ram],0) Ram
FROM (SELECT EmpName, Qty, Item FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN([Dog],[Cat],[Goat],[Ram])) AS p
)
select empname, avg(dog) dog, avg(cat) cat, avg(goat) goat, avg(ram) ram
from ct
group by empname;
+---------+-----+-----+------+-----+
| empname | dog | cat | goat | ram |
+---------+-----+-----+------+-----+
| Abay | 0 | 0 | 5 | 0 |
+---------+-----+-----+------+-----+
| Carle | 2 | 11 | 0 | 0 |
+---------+-----+-----+------+-----+
| Jane | 6 | 0 | 8 | 3 |
+---------+-----+-----+------+-----+
SELECT EmpName
, [Dog]
, [Cat]
, [Goat]
, [Ram]
,(isnull(p.cat,0)+isnull(p.dog,0)+isnull(p.Goat,0)+isnull(p.Ram,0))/4.0 as average
FROM (SELECT EmpName, Qty, Item FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN([Dog],[Cat],[Goat],[Ram])) AS p
First of all, I am new to SQL. Here is the sample (for both table1 and table2, I have created a SNO as primary key and it's also identity column)
Table1:
PID PNAME PartID
--- ----- ------
0 Length 1
1 Breadth 1
2 Height 1
0 Area 2
1 Volume 2
Table2:
SampleID PID Pvalue PartID ModifiedDate Operator
-------- --- ------ ------ ------------ --------
0 0 10 1 10-Mar-14 Test
0 1 10 1 10-Mar-14 Test
0 2 Fail 1 10-Mar-14 Test
1 0 20 1 12-Mar-14 Test
1 1 Fail 1 12-Mar-14 Test
1 2 Fail 1 12-Mar-14 Test
0 0 10 2 13-Mar-14 Test1
0 1 10 2 13-Mar-14 Test1
Depending upon the PartID, I must get the following results
PARTID: 1
PNAME 0 1
------------ --------- ---------
Length 10 20
Breadth 10 Fail
Height Fail Fail
ModifiedDate 10-Mar-14 12-Mar-14
Operator Test Test
PARTID: 2
PNAME 0
------------ ---------
Area 10
Volume 10
ModifiedDate 13-Mar-14
Operator Test1
How to achieve the desired output as mentioned above in SQL Server 2008?
You can use PIVOT to get the result but you will also need to unpivot the ModifiedDate and Operator columns so you can display them in a single column with the PName. Your final result will need a dynamic solution but it would be much easier to write this static first, then convert to dynamic sql.
The basic syntax will be:
select pname, [0], [1]
from
(
select t2.sampleid, pname = c.col, c.value
from table1 t1
inner join table2 t2
on t1.partid = t2.partid
and t1.pid = t2.pid
cross apply
(
select Pname, pvalue union all
select 'ModifiedDate', convert(varchar(10), ModifiedDate, 120) union all
select 'Operator', Operator
) c (col, value)
where t1.partid = 1
) d
pivot
(
max(value)
for sampleid in ([0], [1])
) p;
See SQL Fiddle with Demo. You'll see that I used CROSS APPLY to convert the 3 columns PName, ModifiedDate and Operator into a single column. This is necessary so you can easily get to the values for each SampleId. The above version is a static version meaning you are hard-coding the values for the final columns, but if you want to have this adjust based on the PartId, you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#partid int,
#paramdef nvarchar(max)
set #partid = 1
set #paramdef = '#partid int'
select #cols = STUFF((SELECT ',' + QUOTENAME(sampleid)
from Table2
where partid = #partid
group by sampleid
order by sampleid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT pname,' + #cols + '
from
(
select t2.sampleid, pname = c.col, c.value
from table1 t1
inner join table2 t2
on t1.partid = t2.partid
and t1.pid = t2.pid
cross apply
(
select Pname, pvalue union all
select ''ModifiedDate'', convert(varchar(10), ModifiedDate, 120) union all
select ''Operator'', Operator
) c (col, value)
where t1.partid = #partid
) x
pivot
(
max(value)
for sampleid in (' + #cols + ')
) p '
exec sp_executesql #query, #paramdef, #partid = #partid;
See SQL Fiddle with Demo. Both give a result:
| PNAME | 0 | 1 |
|--------------|------------|------------|
| Breadth | 10 | Fail |
| Height | Fail | Fail |
| Length | 10 | 20 |
| ModifiedDate | 2014-03-10 | 2014-03-12 |
| Operator | Test | Test |
I have table:
ID Note
1 1 aaa
2 1 bbb
3 1 ccc
4 2 ddd
5 2 eee
6 2 fff
I need to return it as:
ID Note1 Note2 Note3
1 1 aaa bbb ccc
2 2 ddd eee fff
Thank you!
You can use the PIVOT function for this type of query. If you have a known number of columns, then you can hard-code the values:
select *
from
(
select id, note,
'Note' +
cast(row_number() over(partition by id order by id) as varchar(10)) col
from yourtable
) x
pivot
(
max(note)
for col in ([Note1], [Note2], [Note3])
) p
See SQL Fiddle with Demo
If you are going to have an unknown number of notes that you want to turn into columns, then you can use dynamic sql:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ','
+ QUOTENAME('Note' +
cast(row_number() over(partition by id order by id) as varchar(10)))
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id,' + #cols + ' from
(
select id, note,
''Note'' +
cast(row_number() over(partition by id order by id) as varchar(10)) col
from yourtable
) x
pivot
(
max(note)
for col in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
Both will produce the same results.
| ID | NOTE1 | NOTE2 | NOTE3 |
------------------------------
| 1 | aaa | bbb | ccc |
| 2 | ddd | eee | fff |
Or if you do not want to use the PIVOT function, then you can use an aggregate function with a CASE statement:
select id,
max(case when rn = 1 then note else '' end) Note1,
max(case when rn = 2 then note else '' end) Note2,
max(case when rn = 3 then note else '' end) Note3
from
(
select id, note,
row_number() over(partition by id order by id) rn
from yourtable
) src
group by id
See SQL Fiddle with Demo
Here's a simplified example of my problem. I have a table where there's a "Name" column with duplicate entries:
ID Name
--- ----
1 AAA
2 AAA
3 AAA
4 BBB
5 CCC
6 CCC
7 DDD
8 DDD
9 DDD
10 DDD
Doing a GROUP BY like SELECT Name, COUNT(*) AS [Count] FROM Table GROUP BY Name results in this:
Name Count
---- -----
AAA 3
BBB 1
CCC 2
DDD 4
I'm only concerned about the duplicates, so I'll add a HAVING clause, SELECT Name, COUNT(*) AS [Count] FROM Table GROUP BY Name HAVING COUNT(*) > 1:
Name Count
---- -----
AAA 3
CCC 2
DDD 4
Trivial so far, but now things get tricky: I need a query to get me all the duplicate records, but with a nice incrementing indicator added to the Name column. The result should look something like this:
ID Name
--- --------
1 AAA
2 AAA (2)
3 AAA (3)
5 CCC
6 CCC (2)
7 DDD
8 DDD (2)
9 DDD (3)
10 DDD (4)
Note row 4 with "BBB" is excluded, and the first duplicate keeps the original Name.
Using an EXISTS statement gives me all the records I need, but how do I go about creating the new Name value?
SELECT * FROM Table AS T1
WHERE EXISTS (
SELECT Name, COUNT(*) AS [Count]
FROM Table
GROUP BY Name
HAVING (COUNT(*) > 1) AND (Name = T1.Name))
ORDER BY Name
I need to create an UPDATE statement that will fix all the duplicates, i.e. change the Name as per this pattern.
Update:
Figured it out now. It was the PARTITION BY clause I was missing.
With Dups As
(
Select Id, Name
, Row_Number() Over ( Partition By Name Order By Id ) As Rnk
From Table
)
Select D.Id
, D.Name + Case
When D.Rnk > 1 Then ' (' + Cast(D.Rnk As varchar(10)) + ')'
Else ''
End As Name
From Dups As D
If you want an update statement you can use pretty much the same structure:
With Dups As
(
Select Id, Name
, Row_Number() Over ( Partition By Name Order By Id ) As Rnk
From Table
)
Update Table
Set Name = T.Name + Case
When D.Rnk > 1 Then ' (' + Cast(D.Rnk As varchar(10)) + ')'
Else ''
End
From Table As T
Join Dups As D
On D.Id = T.Id
Just update the subquery directly:
update d
set Name = Name+'('+cast(r as varchar(10))+')'
from ( select Name,
row_number() over (partition by Name order by Name) as r
from [table]
) d
where r > 1
SELECT ROW_NUMBER() OVER(ORDER BY Name) AS RowNum,
Name,
Name + '(' + ROW_NUMBER() OVER(PARTITION BY Name ORDER BY Name) + ')' concatenatedName
FROM Table
WHERE Name IN
(
SELECT Name
FROM Table
GROUP BY Name
HAVING COUNT(*) > 1
)
This will get you what you originally asked for. For the update statement, you'll want to do a while and update the top 1
DECLARE #Pointer VARCHAR(20), #Count INT
WHILE EXISTS(SELECT Name FROM Table GROUP BY Name HAVING COUNT(1) > 1)
BEGIN
SELECT TOP 1 #Pointer = Name, #Count = COUNT(1) FROM Table GROUP BY Name HAVING COUNT(1) > 1
UPDATE TOP (1) TABLE
SET Name = Name + '(' + #Count + ')'
WHERE Name = #Pointer
END
There's no need to do an UPDATE at all. The following will create the table for INSERT as desired
SELECT
ROW_NUMBER() OVER(ORDER BY tb2.Id) Id,
tb2.Name + CASE WHEN COUNT(*) > 1 THEN ' (' + CONVERT(VARCHAR, Count(*)) + ')' ELSE '' END [Name]
FROM
tb tb1,
tb tb2
WHERE
tb1.Name = tb2.Name AND
tb1.Id <= tb2.Id
GROUP BY
tb2.Name,
tb2.Id
Here's an even simpler UPDATE statement:
UPDATE
tb
SET
[Name] = [Name] + ' (' + CONVERT(VARCHAR, ROW_NUMBER () OVER (PARTITION BY [Name] ORDER BY Id)) + ')'
WHERE
ROW_NUMBER () OVER (PARTITION BY [Name] ORDER BY Id) > 1