I have a table as below:
Name ValueA1 ValueA2 ValueA3 ValueB1 ValueB2 ValueB3 QtyA1 QtyA2 QtyA3 QtyB1 QtyB2 QtyB3
John 1 2 3 4 5 6 100 200 300 150 250 350
Dave 11 12 13 14 15 16 100 200 300 150 250 350
I am able to use unpivot to get the values:
select [Name]
, Replace(u.[Period],'Value','') as [Period]
, u.[Value]
from Table1
unpivot
(
[Value]
for [Period] in ([ValueA1], [ValueA2], [ValueA3], [ValueB1], [ValueB2], [ValueB3])
) u;
SQL Fiddle
However I'm trying to get both the Value and Qty columns on a single row, what I want to end up with is:
Name Number Value Qty
John A1 1 100
John A2 2 200
John A3 3 300
John B1 4 150
John B2 5 250
John B3 6 350
Dave A1 11 100
Dave A2 12 200
Dave A3 13 300
Dave B1 14 150
Dave B2 15 250
Dave B3 16 350
What I have so far is (which doesn't work at all):
select [Name]
, Replace(u.[Period],'Value','') as [Period]
, u.[Value]
, u2.[Value]
from Table1
unpivot
(
[Value]
for [Period] in ([ValueA1], [ValueA2], [ValueA3], [ValueB1], [ValueB2], [ValueB3])
) u
unpivot
(
[Qty]
for [Period] in ([QtyA1], [QtyA2], [QtyA3], [QtyB1], [QtyB2], [QtyB3])
) u2;
Is what I am trying to do even possible with unpivot?
You can use a simple apply for this by specifying the pairs in a values clause:
declare #table table (Name varchar(10), ValueA1 int, ValueA2 int, QtyA1 int, QtyA2 int);
insert into #table
select 'John', 1, 2, 100, 200 union all
select 'Dave', 11, 12, 100, 200;
select Name, Number, Value, Qty
from #table
cross
apply ( values
('A1', ValueA1, QtyA1),
('A2', ValueA2, QtyA2)
) c (number, value, qty);
If you're using an older edition of MSSQL, you might need to use this
instead of the values clause above:
cross
apply ( select 'A1', ValueA1, QtyA1 union all
select 'A2', ValueA2, QtyA2
) c (number, value, qty);
Returns:
Name Number Value Qty
John A1 1 100
John A2 2 200
Dave A1 11 100
Dave A2 12 200
Related
Consider a table having data as shown. I want to find the top 3 marks and combine the rest of the values of column marks as a single value 0.
name age marks height
-----------------------------
anil 25 67 5
ashish 23 75 6
ritu 22 0 4
manish 25 0 6
kirti 23 97 5
Output
name age marks height
-----------------------------
kirti 23 97 5
ashish 23 75 6
anil 25 67 5
OTHERS 0 0 0
With TOP 3 and UNION ALL for the last row:
select t.* from (
select top 3 * from tablename
order by marks desc
) t
union all
select 'OTHERS', 0, 0, 0
See the demo.
Results:
> name | age | marks | height
> :----- | --: | ----: | -----:
> kirti | 23 | 97 | 5
> ashish | 23 | 75 | 6
> anil | 25 | 67 | 5
> OTHERS | 0 | 0 | 0
I would use a CTE (Common Table Expression) and the ROW_NUMBER() function:
;WITH cte AS (SELECT
[Name],
Age,
Marks,
Height,
ROW_NUMBER() OVER (ORDER BY Marks DESC) AS [Rank]
FROM
Test
)
SELECT
[Name],
Age,
Marks,
Height
FROM
cte
WHERE
[Rank] <= 3
UNION ALL SELECT 'OTHERS', 0, 0, 0
You can use select top 3 or row_number()
You can use row_number() as follows
declare #mytable as table(name varchar(50),age int,marks int,height int)
insert into #mytable values('anil', 25, 67, 5),('ashish', 23, 75, 6),('ritu', 22, 0, 4),('manish', 25, 0, 6),('kirti', 23, 97, 5),
('other',0,0,0);
with cte as(
select name,age,marks,height,row_number() over(partition by 1 order by marks desc) row# from #mytable )
select name,age,marks,height from cte where row#<4 or name='other'
order by row#
Another way, using union without inserting ('other',0,0,0) to the table, you can the same result
declare #mytable as table(name varchar(50),age int,marks int,height int)
insert into #mytable values('anil', 25, 67, 5),('ashish', 23, 75, 6),('ritu', 22, 0, 4),('manish', 25, 0, 6),('kirti', 23, 97, 5)
--,('other',0,0,0)
;
with cte as(
select name,age,marks,height,row_number() over(partition by 1 order by marks desc) row# from #mytable )
select name,age,marks,height,row# from cte where row#<4
union select 'others',0,0,0,4
order by row#
I have an existing database where some logic is made by the front end application.
Now I have to make reports from that database and I'm facing to a proble of missing records which are covered on a record basis in the frontend but have issues in the report
Given the following tables:
create table #T (id int, id1 int, label varchar(50))
create table #T1 (id int, T_id1 int, A int, B int, C int)
With the values:
insert into #T values (10, 1, 'label1'), (10, 2, 'label2'), (10, 3, 'label3'), (10, 15, 'label15'), (10, 16, 'label16'), (20, 100, 'label100'), (20, 101, 'label101')
insert into #T1 values (10, 1, 100, 200, 300), (10, 15, 150, 250, 350), (20, 100, 151, 251, 351), (20, 101, 151, 251, 351)
if I make a report we can see some missing records:
select #T.id, #T.id1, #T1.A, #T1.B, #T1.C
from #T left join #T1 on #T.id1 = #T1.T_id1
result:
id id1 A B C
10 1 100 200 300
10 2 NULL NULL NULL
10 3 NULL NULL NULL
10 15 150 250 350
10 16 NULL NULL NULL
20 100 151 251 351
20 101 151 251 351
Expected result would be:
id id1 A B B
10 1 100 200 300
10 2 100 200 300
10 3 100 200 300
10 15 150 250 350
10 16 150 250 350
20 100 151 251 351
20 101 151 251 351
As you can see here the missing data is filled out of the the first (in id, id1 order) previous existing record for a given id. For a given id there can be any number of "missing" records and for the given id there can be any number of existing records after a not existing ones.
I can do this with a cursor but I'm looking for a solution without cursor
You can use subquery (to find groups with same values) + window function
WITH Grouped AS (
SELECT #T.id, #T.id1, #T1.A, #T1.B, #T1.C,
GroupN = SUM(CASE WHEN #T1.A IS NULL THEN 0 ELSE 1 END) OVER(/* PARTITION BY id ? */ ORDER BY id1 ROWS UNBOUNDED PRECEDING)
FROM #T
LEFT JOIN #T1 ON #T.id1 = #T1.T_id1
)
SELECT Grouped.id, Grouped.id1,
A = MAX(A) OVER(PARTITION BY GroupN),
B = MAX(B) OVER(PARTITION BY GroupN),
C = MAX(C) OVER(PARTITION BY GroupN)
FROM Grouped
You can use below sql for thedesired output:
with cte (id, id1, A, B, C)
as
(
select #T.id, #T.id1, #T1.A, #T1.B, #T1.C
from #T left join #T1 on #T.id1 = #T1.T_id1
)
select cte.id, cte.id1,
coalesce(cte.A,TT.A) A,
coalesce(cte.B,TT.B) B,
coalesce(cte.C,TT.C) C
from cte
left join
(
select p.id1,max(q.id1) id1_max
from cte p
inner join cte q on p.id1 > q.id1 and p.a is null and q.a is not null
group by p.id1
) T on cte.id1 = T.id1
left join cte TT on T.id1_max = TT.id1
I have this table.
ID Date Value
___ ____ _____
3241 9/17/12 5
3241 9/16/12 100
3241 9/15/12 20
4355 9/16/12 12
4355 9/15/12 132
4355 9/14/12 4
1001 NULL 89
1001 9/16/12 125
5555 NULL 89
1234 9/16/12 45
2236 9/15/12 128
2236 9/14/12 323
2002 9/17/12 45
I would like to select the maximum date grouped by id and including NULL as maximum value that should be in the result to get something like that.
ID Date Value
___ ____ _____
3241 9/17/12 5
4355 9/16/12 12
1001 9/16/12 125
5555 NULL 89
1234 9/16/12 45
2236 9/15/12 128
2002 9/17/12 45
I found a solution but that not include NULL as maximum value
the solution by #bluefeet
Return value at max date for a particular id
SELECT t1.id,
t2.mxdate,
t1.value
FROM yourtable t1
INNER JOIN
( SELECT max(date) mxdate,
id
FROM yourtable
GROUP BY id) t2 ON t1.id = t2.id
AND t1.date = t2.mxdate
I also search for a solution to see how can we select NULL maximum value in t-sql so i found this solution by #Damien_The_Unbeliever How can I include null values in a MIN or MAX?
SELECT recordid,
MIN(startdate),
CASE
WHEN MAX(CASE
WHEN enddate IS NULL THEN 1
ELSE 0
END) = 0 THEN MAX(enddate)
END
FROM tmp
GROUP BY recordid
But i m stuck i don't know how to merge between this two solution to get what i want.
PS: I m using SQL SERVER 2008
You can use this
SELECT
ID
,[Date]
,[Value]
FROM(
SELECT
*
, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ISNULL([Date],'9999-12-31') DESC) AS Row#
FROM yourtable
) A WHERE Row# = 1
I have a table:
Project_Id Period Value
123 Jan-15 0
123 Feb-15 34
123 Mar-15 78
123 Apr-15 56
456 Jan-15 0
456 Feb-15 0
456 Mar-15 0
456 Apr-15 0
789 Jan-15 45
789 Feb-15 4
789 Mar-15 18
789 Apr-15 26
I need to retrieve Project data only when i do not have 0 for Value field in all the months like:
Project_Id Period Value
123 Jan-15 0
123 Feb-15 34
123 Mar-15 78
123 Apr-15 56
789 Jan-15 45
789 Feb-15 4
789 Mar-15 18
789 Apr-15 26
Project no 456 should not come in my result because for all the months the value is 0 for that particular project.
Can someone help me with the query?
Use SUM and COUNT to determine the number of 0 Values:
SELECT *
FROM tbl
WHERE project_id IN(
SELECT project_id
FROM tbl
GROUP BY project_id
HAVING SUM(CASE WHEN Value = 0 THEN 1 ELSE 0 END) <> COUNT(*)
)
SQL Fiddle
Another solution is to use EXISTS:
SELECT *
FROM tbl t1
WHERE EXISTS(
SELECT 1 FROM tbl t2 WHERE t2.project_id = t1.project_id AND t2.Value > 0
)
SQL Fiddle
The inner select gets all project_ids that have a least one value that is not 0.
select * from your_table
where project_id in
(
select project_id
from your_table
group by project_id
having sum(case when value <> 0 then 1 else 0 end) > 0
)
Some test data but idea remains the same
create table #test123
(
pid int,
value int
)
insert into #test123
select 1,0
union all
select 1,1
union all
select 2,0
union all
select 2,0
union all
select 3,2
select * from #test123 t2 where exists (select 1 from #test123 t1
where t1.pid=t2.pid
group by pid
having sum(value)>0
)
For performance, I prefer not making a join to check for repeating values:
;WITH CTE as
(
SELECT
Project_Id,
Period,
Value,
max(abs(value)) over (Partition by Period) value
FROM YourTable
)
SELECT
Project_Id,
Period,
Value
FROM CTE
WHERE value > 0
*using abs to check for negative values. If all values are positive, the abs can be omitted.
Is there a way to have something like:
id Name value
--------------------
1 sex m
2 age 12
3 weight 200
4 height 200
5 rx 34
from a known table:
sex age weight height rx
--------------------------
m 12 200 200 34
If I do:
Select
[id] = ORDINAL_POSITION,
[Name] = COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'known'
I get:
id Name
-----------
1 sex
2 age
3 weight
4 height
5 rx
how to changethe query to get:
id Name value
--------------------
1 sex m
2 age 12
3 weight 200
4 height 200
5 rx 34
If they were 2 rows:
sex age weight height rx
--------------------------
m 12 200 200 34
f 34 245 111 67
id Name value
--------------------
1 sex m
2 age 12
3 weight 200
4 height 200
5 rx 34
6 sex f
7 age 34
8 weight 240
9 height 111
10 rx 67
-----------------EDIT--------------------
Thanks for your answers, but I am wondering if this can be possible intead of getting
id value
-------------------
1 m
2 12
3 200
4 200
5 34
from:
sex age weight height rx
--------------------------
m 12 200 200 34
using
Select
[id] = ORDINAL_POSITION,
[Value] ...
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'known'
The problem you're going to run into whatever method you choose is that all data in a particular column must be of the same type. In your case you have sex, which is a character, and a bunch of numbers (probably integers). This stops you being able to do this neatly as you cant use UNPIVOT. However there is always a way...
Given this setup code:
CREATE TABLE test(sex char(1), age int, weight int, height int, rx int)
INSERT INTO test
SELECT 'm', 12 , 200, 200, 34
union select 'f',34,245,111,67
You can do this, which is just a small addition to your query:
Select
[id] = ROW_NUMBER() OVER(ORDER BY isc.ORDINAL_POSITION),
[Name] = COLUMN_NAME,
[Value] = CASE LOWER(COLUMN_NAME)
WHEN 'sex' THEN CAST(d.sex AS VARCHAR(20))
WHEN 'age' then CAST(d.age AS VARCHAR(20))
WHEN 'weight' THEN CAST(d.weight AS VARCHAR(20))
WHEN 'height' THEN CAST(d.height AS VARCHAR(20))
WHEN 'rx' THEN CAST(d.rx AS VARCHAR(20))
END
from INFORMATION_SCHEMA.COLUMNS isc
CROSS JOIN dbo.test d
where TABLE_NAME = 'test'
Output:
1 sex m
2 sex f
3 age 12
4 age 34
5 weight 200
6 weight 245
7 height 200
8 height 111
9 rx 34
10 rx 67
You''ll notice this output is in a slightly different order to your own. This is because you have not described any key on your "known" table. If you did have a key on that table, you simply change this line:
[id] = ROW_NUMBER() OVER(ORDER BY isc.ORDINAL_POSITION),
to
[id] = ROW_NUMBER() OVER(ORDER BY d.yourKeyField, isc.ORDINAL_POSITION),
You can do it with PIVOT/UNPIVOT (see Books Online).
You can try this.
declare #T table
(
sex char(1),
age int,
weight int,
height int,
rx int
)
insert into #T values
('m', 12, 200, 200, 34),
('f', 34, 245, 111, 67)
select row_number() over(order by (select 1)) as ID,
T2.X.value('local-name(.)', 'varchar(128)') as Name,
T2.X.value('.', 'varchar(10)') as Value
from (select *
from #T
for xml path(''), type
) as T1(X)
cross apply T1.X.nodes('/*') as T2(X)
This part order by (select 1) makes the assignment of ID's somewhat unpredictable. If you had a primary key (ID int identity) or a datetime to use in order by you could change that to order by ID instead.