Aggregating data from multiple columns (similar to a PIVOT table) - sql-server

I have a table that has the following data (shortened for this example):
C1 C2 C3 C4
=========================
1 0 1 1 0
2 1 1 0 1
3 1 0 1 1
4 1 1 1 1
5 0 0 1 1
6 0 0 0 1
I want to create a create a query that gives me the following result:
C3 C4
=============
C1 2 3
C2 2 2
That is, the combination of those four columns when:
C1 = 1 & C3 = 1
C1 = 1 & C4 = 1
C2 = 1 & C3 = 1
C2 = 1 & C4 = 1
I've been able to get make some progress, but not ultimately what I want. I've managed to get this far: http://sqlfiddle.com/#!3/7d3d4/2/0
but I can't figure out how to get my query to format like my desired output above. I was going to try to use a PIVOT table, but quickly abandoned that idea because it seemed to get extremely convoluted once I needed to add a 2nd PIVOT statement (and I'd end up having to add more as my example above is a really basic example of the number of columns I need to perform this on).
What am I missing?
Thank you.

try this:
SELECT 'C1',SUM(CASE WHEN C1=1 AND C3=1 THEN 1 END)AS C3,
SUM(CASE WHEN C1=1 AND C4=1 THEN 1 END)AS C4
FROM T1
UNION ALL
SELECT 'C2',SUM(CASE WHEN C2=1 AND C3=1 THEN 1 END)AS C3,
SUM(CASE WHEN C2=1 AND C4=1 THEN 1 END)AS C4
FROM T1
SQL Fiddle Demo

I think this gives you the result set as asked
SELECT COL1, C3, C4
FROM (
SELECT SUBSTRING(col,1,2) col1, SUBSTRING(col,3,2) col2, value
FROM (
SELECT SUM(c1*c3) as c1c3, SUM(c1*c4) as c1c4,
SUM(c2*c3) as c2c3, SUM(c2*c4) as c2c4
FROM t1
) t
UNPIVOT (value FOR col IN (c1c3, c1c4, c2c3, c2c4)) AS u
) data
PIVOT (SUM(value) FOR col2 IN ([c3], [c4])) pvt
I started writing a query that gives the totals, the resultset looks like this
c1c3 c1c4 c2c3 c2c4
2 3 2 2
At this moment you could probably just use this query and will get the result as wanted
SELECT 'c1' AS col, c1c3 AS C3, c1c4 AS C4
UNION ALL
SELECT 'c2' AS col, c2c3 AS C3, c2c4 AS C4
The first solution is just an example of how to UNPIVOT and then PIVOT again to order the columns in the way you want

Related

For Each loop in Stored Procedure

I have a stored procedure with three parameters:
Parameter 1 - has 200 data
Parameter 2 - has 6 data
Parameter 3 - 150 data
I want to dump all possible combinations of the result into a table. Any ideas please?
I want to do something like the following
For each Parameter 1
For each parameter 2
For each parameter 3
Assuming that the parameters are table valued parmeters, and by "data" you mean rows - to get all possible combinations you use a cross join, just like with regular tables:
-- It's better practice to specify the columns but I don't know what your columns are...
SELECT *
FROM #TVP1
CROSS JOIN #TVP2
CROSS JOIN #TVP3
The cross join will return every possible combination between the rows of both tables - here is another example to help you visualize this:
DECLARE #T as table
(
Col int
);
INSERT INTO #T (Col) VALUES (0), (1);
SELECT t1.Col As C1,
t2.Col As C2,
t3.Col As C3
FROM #T t1
CROSS JOIN #T t2
CROSS JOIN #T t3
ORDER BY C1, C2, C3
C1 C2 C3
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1

How to derive a column based on multiple columns values for a single row

I currently have an issue with a calculation I'm trying to create.
So I have 8 columns, all with the same 3 values:
1 = pass , 2 = borderline , 3 = Fail.
I need to be able to calculate a new column which will state the following:
Pass = All 8 fields equals '1'
Borderline = 7 Fields that equal '1' with one field that equals '2'
Else Fail
It seems a easy calculation but I keep tripping up on the borderline logic, as any of the 8 columns could have the '2'?
Any help would be much appreciated.
If your columns will have only 1,2 or 3 then you can try this
case Col1 + Col2 + Col3 + Col4 + Col5 + Col6 + Col7 + Col8 When 8 Then 'Pass'
when 9 Then 'Borderline'
Else 'Fail'
End
You could simple calculate the total of the 8 columns, and if the value is greater than 9 it's a fail. This assumes no NULL or 0 values are in the 8 columns.
SELECT Col1 + Col2 + Col3 + Col4 + Col5 + Col6 + Col7 + Col8 AS Total
You can use simple addition with a CASE statement to work this out.
Rules:
8 passes: total always = 8
7 passes and 1 borderline: always = 9
Any other combination = fail
Sample code:
-- dummy table
CREATE TABLE #temp
(
c1 INT , c2 INT , c3 INT , c4 INT ,
c5 INT , c6 INT , c7 INT , c8 INT
)
-- dummy data
INSERT INTO #temp
( c1 , c2 , c3 , c4 ,
c5 , c6 , c7 , c8
)
SELECT 1,1,1,1,1,1,1,1
UNION ALL
SELECT 1,1,1,1,1,1,2,1
UNION ALL
SELECT 1,2,3,1,1,1,1,2
-- query to calculate totals and pass/fail
SELECT *,
CASE t.Total WHEN 8 THEN 'Pass'
WHEN 9 THEN 'Border Line'
ELSE 'Fail' END Overall
FROM ( SELECT * ,
c1 + c2 + c3 + c4 + c5 + c6
+ c7 + c8 Total
FROM #temp
) t
-- clean up
DROP TABLE #temp
Produces:
c1 c2 c3 c4 c5 c6 c7 c8 Total Overall
1 1 1 1 1 1 1 1 8 Pass
1 1 1 1 1 1 2 1 9 Border Line
1 2 3 1 1 1 1 2 12 Fail

How to group a couple of rows in SQL Server?

I have this query:
SELECT
Table1.ID, Table1.Code1, Table1.Code2, Table1.Details,
Table1.IDS, Table2.Name
FROM
Table1
INNER JOIN
Table2 ON Table1.Code1 = Table2.Code1
WHERE
Table1.IDS = 1
ORDER BY
Table1.Code1, Table1.Code2
This is my result for query:
ID Code1 Code2 Details IDS Name
1 1001 01 D1 1 N1
2 1001 01 D2 1 N1
3 1001 02 D3 1 N1
4 1001 05 D4 1 N1
5 1002 11 D5 1 N2
6 1002 12 D6 1 N2
7 1005 21 D7 1 N3
8 1005 21 D8 1 N3
But I want this result:
ID Code1 Code2 Details IDS Name
1 1001 01 D1 1 N1
2 01 D2 1
3 02 D3 1
4 05 D4 1
5 1002 11 D5 1 N2
6 12 D6 1
7 1005 21 D7 1 N3
8 21 D8 1
How do I get this result? Please help me. Thanks a lot
Embedding presentation logic in your query isn't ideal. I recommend you process the query results programmatically, either to detect when groups change as you iterate, or to transform the query results into a nested table. The latter can be generalized as a reusable function.
If you can rely on the ID column for ordering the groups (or a combination of other rows, like code1,code2) then you can do this in a few different ways.
If your server is 2012+ then you can use the LAG() window function to access previous rows and if the previous rows Code1 is the same as the current rows Code1 replace it with null (or an empty string if that suits you better). However, if you're using a version < 2012 then you can accomplish it using a self join.
This kind of formatting might be better to handle on the client side (or reporting layer) though if can.
The query below includes both versions, but I commented out the self-join stuff:
SELECT
Table1.ID,
-- CASE WHEN Table1.Code1 = t1.Code1 THEN NULL ELSE Table1.Code1 END AS Code1,
CASE WHEN LAG(Table1.Code1) OVER (ORDER BY Table1.ID) = Table1.Code1 THEN NULL ELSE Table1.Code1 END AS Code1,
Table1.Code2, Table1.Details, Table1.IDS,
-- CASE WHEN Table1.Name = t1.Name THEN NULL ELSE Table1.Name END AS Name,
CASE WHEN LAG(Table2.Name) OVER (ORDER BY Table1.ID) = Table2.Name THEN NULL ELSE Table2.Name END AS Name
FROM
Table1
INNER JOIN
Table2 ON Table1.Code1 = Table2.Code1
-- LEFT JOIN Table1 t1 ON Table1.ID = t1.ID + 1
WHERE
Table1.IDS = 1
ORDER BY
Table1.Code1, Table1.Code2
Sample SQL Fiddle
Morteza,
This is a clear case of a presentation/UI layer requirement. Databases are made for a particular purpose and that is to crunch data and present you with results. I'd highly recommend you to turn to the front end logic for achieving your purpose.
Using ROW_NUMBER() within CTE or a subquery, here is one way to get your expected output:
;WITH q1 as
(
SELECT
t1.ID,
t1.Code1,
t1.Code2,
t1.Details,
t1.IDS,
t2.Name,
ROW_NUMBER() OVER (PARTITION BY t1.Code1 ORDER BY t1.ID) as rn
FROM
table1 t1
INNER JOIN
Table2 t2 ON t1.Code1 = t2.Code1
)
SELECT
q1.ID,
CASE
WHEN rn = 1 THEN q1.Code1
ELSE ''
END as Code1, --only populate first row for each code1
q1.Code2,
q1.Details,
q1.IDS,
CASE
WHEN rn = 1 THEN q1.Name
ELSE ''
END as Name --only populate first row for each name
FROM
q1
WHERE
q1.IDS = 1
ORDER BY
q1.Code1, q1.Code2
SQL Fiddle Demo

How to count column values in table row?

In SQL Server, i have one table in that there 15 columns are there, the columns contain the data SL,PL,CL or 8,4 like that.
empid D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 D14 D15
==================================================================
sam PL 8 PL 4 8 SL 8 SL 4 CL 8 CL 8 8 8
sunny 8 CL 4 CL SL 8 4 SL 4 PL 8 8 8 8 8
zimmy 4 4 4 8 8 8 4 4 8 8 8 8 8 8 8
Like the above my table is there , now i want to calculate PL or CL or SL of every empid, please help me.
The need to aggregate across columns in a single row is a good indicator you're storing the data wrong. I would change it to the following:
empid D_number val
sam 1 PL
sam 2 8
sam 3 PL
In which case you can just do a count or conditional sum. For example, a conditional sum to get both PL and SL counts in one go:
select empid, sum(case when val = 'PL' then 1 else 0 end),
sum(case when val = 'SL' then 1 else 0 end)
from myTable
group by empid
Or if you're just interested in the PL count, you can simply COUNT:
select empid, count(*)
from myTable
where val = 'PL'
group by empid
However, assuming there's nothing you can do about your table schema, you have two options:
Conditional sum across each column explicitly (boy does this get ugly fast):
select empid,
case when d1 = 'PL' then 1 else 0 end +
case when d2 = 'PL' then 1 else 0 end +
case when d3 = 'PL' then 1 else 0 end +
...,
case when d1 = 'SL' then 1 else 0 end +
case when d2 = 'SL' then 1 else 0 end +
case when d3 = 'SL' then 1 else 0 end +
...
from myTable
UNPIVOT the data to make it look like the form above, then use a conditional sum:
with cte as (
select empid, D_number, val
from (
select empid, d1, d2, d3, d4, d5, ...
from myTable
) x
unpivot (val for D_number in
(d1, d2, d3, d4, d5, ...)
)
)
select empid, sum(case when val = 'PL' then 1 else 0 end),
sum(case when val = 'SL' then 1 else 0 end)
from cte
group by empid
Use UNPIVOT
Test data and table:
DECLARE #t table
(empid varchar(10), D1 char(2), D2 char(2), D3 char(2), D4 char(2),D5 char(2),
D6 char(2),D7 char(2), D8 char(2), D9 char(2),D10 char(2), D11 char(2),
D12 char(2), D13 char(2), D14 char(2), D15 char(2))
INSERT #t values
('sam', 'PL','8','PL','4','8','SL','8','SL','4','CL','8','CL','8','8','8'),
('sunny','8','CL','4','CL','SL','8','4','SL','4','PL','8','8','8','8','8'),
('zimmy','4','4','4','8','8','8','4','4','8','8','8','8','8','8','8')
Query:
SELECT empid, value, count(*) count
FROM #t as p
UNPIVOT
(value FOR col IN
([D1],[D2],[D3],[D4],[D5],[D6],[D7],[D8],[D9],
[D10],[D11],[D12],[D13],[D14],[D15]) ) AS unpvt
WHERE value in ('PL','CL','SL')
GROUP BY empid, value
Result:
empid value count
sam CL 2
sam PL 2
sam SL 2
sunny CL 2
sunny PL 1
sunny SL 2

SQL Server + Select top 1 record of all the distinct records

I am struggling to write a query to result in the following records.
I have a table with records as
c1 c2 c3 c4 c5 c6
1 John 2.3.2010 12:09:54 4 7 99
2 mike 2.3.2010 13:09:59 8 6 88
3 ahmad 2.3.2010 14:09:59 1 9 19
4 Jim 23.3.2010 16:35:14 4 5 99
5 run 23.3.2010 12:09:54 3 8 12
I want to fetch only the records :-
3 ahmad 2.3.2010 14:09:59 1 9 19
4 Jim 23.3.2010 16:35:14 4 5 99
I mean the records that are sort by column c3 and the one which is latest for that day. here i have 1, 2, 3 records that are at different times of the day. there i need the records that are sort by date desc and then only top 1 record. similarly for 4 and 5. can you please help me in writing a query.
If you're on SQL Server 2008 or 2008 R2, you can try this:
WITH TopPerDay AS
(
SELECT
c1, c2, c3, c4, c5, C6,
ROW_NUMBER() OVER
(PARTITION BY CAST(c3 AS DATE) ORDER BY c3 DESC) 'RowNum'
FROM dbo.YourTable
)
SELECT *
FROM TopPerday
WHERE RowNum = 1
I basically partition the data by day (using the DATE type in SQL Server 2008 and up), and order by the c3 column in a descending order. This means, for every day, the oldest row will have RowNum = 1 - so I just select those rows from the Common Table Expression and I'm done.
Tried this on a SQL Server 2005 database.
SELECT *
FROM dbo.YourTable t1
WHERE (t1.c3) =
(
SELECT MAX(t2.c3)
FROM dbo.YourTable t2
WHERE DATEDIFF(dd,t2.c3, t1.c3) = 0
)
ORDER BY t1.c3 ASC
Thanks for the responses!
I have found the solution too.
select * from
(select convert(varchar(10),c3,104) as date, max(c3) as date1 from MYTABLE
group by convert(varchar(10),c3,104)) as T1 innerjoin MYTABLE as T2 on
convert(varchar(10),T2.c3,104) = T1.date and t2.c3 = T2.date1

Resources