Using case in a aggregate sql query without splitting rows - sql

In ms-sql i am using an aggregate group to filter results by user, i would also like to use case to filter by a row not contained in the aggregate so all the user results are on 1 line. I'm not sure this is possible.
Here is the query below which splits the results into two lines.
select case when col_A='1' then sum(col b) End as Sum1_results,
case when col_A='2' then sum(col_b) End as Sum2_Results, Username from tbl1
group by Username, col_A
example of results is.
Sum1_results | Sum2_results | Username
5499 null John
null 3400 John
Ideally, i would like to just have these results merged into one line for each username if possible.
Any help would be appreciated

You could use:
select Username ,
SUM(case when col_A='1' then col_b End) as Sum1_results,
SUM(case when col_A='2' then col_b End) as Sum2_Results,
from tbl1
group by Username

below Query can do the job
Create table #tmp (col_A CHAR(1),col_b int,Username VARCHAR(10))
INSERT INTO #tmp VALUES('1',5000,'John')
INSERT INTO #tmp VALUES('2',400,'John')
INSERT INTO #tmp VALUES('1',499,'John')
INSERT INTO #tmp VALUES('2',3000,'John')
SELECT * FROM #tmp
select SUM(case when col_A='1' then col_b End) as Sum1_results,
SUM(case when col_A='2' then col_b End) as Sum2_Results,Username
from #tmp
group by col_A,UserName
DROP TABLE #tmp

Results merged into one line for each username.
Create table #tmp (col_A CHAR(1),col_b int,Username VARCHAR(10))
INSERT INTO #tmp VALUES('1',5000,'John')
INSERT INTO #tmp VALUES('2',400,'John')
INSERT INTO #tmp VALUES('1',499,'John')
INSERT INTO #tmp VALUES('2',3000,'John')
SELECT * FROM #tmp
select SUM(case when col_A='1' then col_b End) as Sum1_results,
SUM(case when col_A='2' then col_b End) as Sum2_Results,Username
from #tmp
group by UserName
DROP TABLE #tmp

Related

Need to return an ID which has start and END in sql server

I have a scenario wherein I need to find the ID which only has start and END in it. Below is the table for reference.
Declare #T Table ( ID int, Name varchar(100))
Insert into #T values (1,'Start')
Insert into #T values (1,'END')
Insert into #T values (1,'Stuart')
Insert into #T values (1,'robin')
Insert into #T values (2,'Start')
Insert into #T values (2,'END')
Insert into #T values (3,'Start')
Insert into #T values (4,'END')
I want the Output as:
ID Name
2 Start
2 END
I want those ID which only has start and end in it.
What I tried so far:
SELECT * FROM #T t
WHERE EXISTS (SELECT * FROM #T WHERE id = t.id AND name = 'start')
AND EXISTS (SELECT * FROM #T WHERE id = t.id AND name = 'END')
But my query is giving ID 1 as well.
Can someone please help me rectify the problem.
I presume your issue is that record 1 has a 'Stuart' in it too?
As such, you can do a similar check in the WHERE e.g.,
SELECT * FROM #T t
WHERE EXISTS (SELECT * FROM #T WHERE id = t.id AND name = 'start')
AND EXISTS (SELECT * FROM #T WHERE id = t.id AND name = 'END')
AND NOT EXISTS (SELECT * FROM #T WHERE id = t.id AND name NOT IN ('start','END'))
Note that you may want to consider
What happens if you have two 'start' rows or two 'end' rows (e.g., start-start-end)? Can you even have two 'start' rows (e.g., start-start)?
What happens if you have a blank/NULL (e.g., start-NULL-end)?
EDIT: removed 'What happens if they're out of order (e.g., end-start)?' as a question as there is no sorting in the data at all (e.g., not even an implicit sort).
You can go for CTE to get group wise count and total count as 2.
Declare #T Table ( ID int, Name varchar(100))
Insert into #T values (1,'Start')
Insert into #T values (1,'END')
Insert into #T values (1,'Stuart')
Insert into #T values (1,'robin')
Insert into #T values (2,'Start')
Insert into #T values (2,'END')
Insert into #T values (3,'Start')
Insert into #T values (4,'END')
;WITH CTE_Total_StartEnd AS
(
select id, count(*) AS Total_Cnt
, COUNT( case when Name IN ('Start') THEN 1 END) as start_cnt
, COUNT( case when Name IN ('End') THEN 1 END) as end_cnt
from #t
group by id
having COUNT( case when Name IN ('Start') THEN 1 END) =1 and
COUNT( case when Name IN ('End') THEN 1 END) = 1 and
count(*) = 2
)
SELECT t.* from #t t
inner join CTE_Total_StartEnd as c
ON c.id = t.id
+----+-------+
| ID | Name |
+----+-------+
| 2 | Start |
| 2 | END |
+----+-------+
You can do this by using group by function also like below
WITH cte AS
(
SELECT 1 AS id , 'Start' AS name
UNION ALL
SELECT 1 AS id ,'END' AS name
UNION ALL
SELECT 1 AS id ,'Stuart' AS name
UNION ALL
SELECT 1 AS id ,'robin' AS name
UNION ALL
SELECT 2 AS id ,'Start' AS name
UNION ALL
SELECT 2 AS id ,'END' AS name
UNION ALL
SELECT 3 AS id ,'Start' AS name
UNION ALL
SELECT 4 AS id ,'END' AS name
)
SELECT T.ID,SUM(T.VAL)AS SUM
FROM
(
SELECT id,name , CASE WHEN name='Start' THEN 1
WHEN name='END' THEN 2
ELSE 3
END AS VAL
FROM cte
)T
GROUP BY T.ID
HAVING SUM(T.VAL) =3
could you please try this? Pls note i added collate command in the end of sql.
SQL Server check case-sensitivity?
SELECT * FROM #T t
WHERE EXISTS (SELECT * FROM #T WHERE id = t.id AND name = 'start' COLLATE SQL_Latin1_General_CP1_CS_AS)
AND EXISTS (SELECT * FROM #T WHERE id = t.id AND name = 'END' COLLATE SQL_Latin1_General_CP1_CS_AS)

How to get the each record with some condition

I have following data:
DECLARE #temp TABLE (
ID int
,sn varchar(200)
,comment varchar(2000)
,rownumber int
)
insert into #temp values(1,'sn1',NULL,1)
insert into #temp values(2,'sn1','aaa',2)
insert into #temp values(3,'sn1','bbb',3)
insert into #temp values(4,'sn1',NULL,4)
insert into #temp values(5,'sn2',NULL,1)
insert into #temp values(6,'sn2',NULL,2)
insert into #temp values(7,'sn2',NULL,3)
select * from #temp
And I want to output like this:
2 sn1 aaa 2
5 sn2 NULL 1
same sn, if comment have value, get this lower rownumber's record. For sn1, have two records with comment value, so here, get the the record with rownumber=2
If comment doesn't have value, get the lower rownumber's record. For sn2, get the record with rownumber=1
May I know how to write this SQL?
This is a prioritization query. I think row_number() is the simplest method:
select t.*
from (select t.*,
row_number() over (partition by sn
order by (case when comment is not null then 1 else 2 end),
rownumber
) as seqnum
from #temp t
) t
where seqnum = 1;
Here is a db<>fiddle.

Constructing single select statement that returns order depends on the value of a column in SQL Server

Table1
Id bigint primary key identity(1,1)
Status nvarchar(20)
Insert dummy data
Insert into Table1 values ('Open') --1
Insert into Table1 values ('Open') --2
Insert into Table1 values ('Grabbed') --3
Insert into Table1 values ('Closed') --4
Insert into Table1 values ('Closed') --5
Insert into Table1 values ('Open') --6
How would I construct a single select statement which orders the data where records with 'Grabbed' status is first, followed by 'Closed', followed by 'Open' in SQL Server
Output:
Id Status
3 Grabbed
4 Closed
5 Closed
1 Open
2 Open
6 Open
I think you need something like this:
select *
from yourTable
order by case when Status = 'Grabbed' then 1
when Status = 'Closed' then 2
when Status = 'Open' then 3
else 4 end
, Id;
[SQL Fiddle Demo]
Another way is to using CTE like this:
;with cte as (
select 'Grabbed' [Status], 1 [order]
union all select 'Closed', 2
union all select 'Open', 3
)
select t.*
from yourTable t
left join cte
on t.[Status] = cte.[Status]
order by cte.[order], Id;
[SQL Fiddle Demo]
This could be done much better with a properly normalized design:
Do not store your Status as a textual content. Just imagine a typo (a row with Grabed)...
Further more a lookup table allows you to add side data, e.g. a sort order.
CREATE TABLE StatusLookUp(StatusID INT IDENTITY PRIMARY KEY /*you should name your constraints!*/
,StatusName VARCHAR(100) NOT NULL
,SortRank INT NOT NULL)
INSERT INTO StatusLookUp VALUES
('Open',99) --ID=1
,('Closed',50)--ID=2
,('Grabbed',10)--ID=3
CREATE TABLE Table1(Id bigint primary key identity(1,1) /*you should name your constraints!*/
,StatusID INT FOREIGN KEY REFERENCES StatusLookUp(StatusID));
Insert into Table1 values (1) --1
Insert into Table1 values (1) --2
Insert into Table1 values (3) --3
Insert into Table1 values (2) --4
Insert into Table1 values (2) --5
Insert into Table1 values (1) --6
SELECT *
FROM Table1 AS t1
INNER JOIN StatusLookUp AS s ON t1.StatusID=s.StatusID
ORDER BY s.SortRank;
I find that the simplest method uses a string:
order by charindex(status, 'Grabbed,Closed,Open')
or:
order by charindex(',' + status + ',', ',Grabbed,Closed,Open,')
If you are going to put values in the query, I think the easiest way uses values():
select t1.*
from t1 left join
(values ('Grabbed', 1), ('Closed', 2), ('Open', 3)) v(status, priority)
on t1.status = v.status
order by coalesce(v.priority, 4);
Finally. This need suggests that you should have a reference table for statuses. Rather than putting the string name in other tables, put an id. The reference table can have the priority as well as other information.
Try this:
select Id,status from tablename where status='Grabbed'
union
select Id,status from tablename where status='Closed'
union
select Id,status from tablename where status='Open'

SQL: How to Transform rows to column in TSQL

Here is table structure, and sample data
create table #tmp ( Id int, Name varchar(100))
insert into #tmp (Id,Name)
Values (1,'Add')
insert into #tmp (Id,Name)
Values (2,'Update')
insert into #tmp (Id,Name)
Values (3,'Delete')
and expected result should be.
Add Update Delete
=== ====== ======
1 2 3
There are several ways that you can transform the data from rows into columns.
If your database has a PIVOT function, then you could use the following code to pivot the data:
select [Add], [Update], [Delete]
from
(
select id, name
from #tmp
) src
pivot
(
max(id)
for name in ([Add], [Update], [Delete])
) piv
See SQL Fiddle with Demo.
Or you could use an aggregate function with a CASE expression:
select
max(case when name = 'Add' then id end) [Add],
max(case when name = 'Update' then id end) [Update],
max(case when name = 'Delete' then id end) [Delete]
from #tmp
See SQL Fiddle with Demo
Please try:
SELECT [Add], [Update], [Delete]
FROM (select * from #tmp) up
PIVOT (sum(id) FOR Name IN ([Add], [Update], [Delete])) AS pvt

Convert Rows Into Columns in SQL

Table A
ID COLA
-----------------------
A value1
B value1
C value1
Table B
ID DETAIL_ID COL_X COL_Y
A 0 foo foo
A 1 bar bar
B 0 foo foo
My expected out is something like
ID COLA COL_X_0 COL_X_1 COL_Y_0 COL_Y_1
A value1 foo bar foo bar
B value1 foo NULL foo NULL
C value1 NULL NULL NULL NULL
It means the rows of table B will be column values based on DETAIL_ID column.
I tried to write queries for this , but can't succeed due to following.
Number of DetailID values will NOT be fixed-length.It means I can't hard-coded the name of the columns.
This will give the exact output you described and you can add more columns if needed
DECLARE #a table (id char, cola varchar(10))
DECLARE #b table (id char, detail_id int, colx char(3), coly char(3))
INSERT #a values('A', 'value1'),('B', 'value2'),('C','value3')
INSERT #b values('A', 0, 'foo', 'foo'),('A', 1, 'bar', 'bar'),
('B',0, 'foo','foo')--,('A', 2, 'bar', 'bar') -- add this for extra columns
CREATE TABLE ##t(id char, detail_id tinyint, colvalue char(3), col varchar(8), cola varchar(10))
DECLARE #columns varchar(max)=''
DECLARE #sqlstring varchar(1000)
;WITH a as (
SELECT a.id, a.cola, b.detail_id, colx, coly,
'col_x_' + cast(detail_id as varchar) col_a,
'col_y_' + cast(detail_id as varchar) col_b
FROM #a a LEFT JOIN #b b on a.id = b.id
)
INSERT ##t
SELECT id, detail_id, colx, col_a, cola FROM a
UNION
SELECT id, detail_id, coly, col_b, cola FROM a
ORDER BY 4,2
SELECT #columns = coalesce(#columns, '') +',[' + col + ']'
FROM (
SELECT DISTINCT col, detail_id FROM ##t where not col is null
) a
SET #columns = stuff(#columns, 1,1,'')
SET #sqlstring =
'SELECT * FROM (
SELECT id, cola, col, colvalue FROM ##t
) b
PIVOT(max(colvalue) FOR col
in(
'+#columns+'))AS p order by 1'
EXEC(#sqlstring)
DROP TABLE ##t
SQL queries must specify the columns of the result set. That's fundamental to SQL. Even PIVOT requires that your query specify the columns before you send it to the RDBMS.
For that reason, it's difficult and error-prone to create a query that returns rows as columns as you describe, and can adapt as needed to any number of columns.
Handling dynamic columns must be a two-stage procedure.
One option is to make the two stages be:
Write application code to build the SQL query dynamically, based on the distinct values found in the data. This requires an extra query to discover what values exist so you can build the query.
Execute the SQL query and retrieve the results.
The other option is to make the two stages be:
Run a more plain SQL query, that fetches rows as rows, as they are stored in the database.
Write application code to post-process the results, collecting individual values from rows into an expanding set of columns based on the values found. This does not require an extra query as the first design does.
Just join table A and B on B.DETAIL_ID == A.ID ?? Or is that too simple?