I have the input as
123 1 Y Active
123 1 Y Idle
109 1 Y Active
109 1 Y Away
165 1 Y Active
145 1 Y Idle
I need the output as :
123 1 Y Active and Idle
109 1 Y Active and Away
165 1 Y Active Only
145 1 Y Idle Only
SELECT distinct COl1,Col2,Col3 ,
case when Col4 = 'Active' and Col4 = 'Idle' then 'Active and Idle'
when Col4 = 'Active' and Col4 = 'Away' then 'Active and Away'
when Col4 = 'Active' then 'Active Only'
when Col4 = 'Idle' then 'Idle Only'
end as category
from Person
I tried the above query but it is not working and it wont work because i am
trying to convert multiple rows to 1 column, which is not possible with
Case.
I have to try something like PIVOT, but not able to implement . Any help?
You need FOR XML PATH() approach :
SELECT DISTINCT p.col1, STUFF(pp.Col4, 1, 4, '') AS category
FRPM Person p CROSS APPLY
( SELECT ' AND ' + p1.Col4
FROM Person p1
WHERE P1.col1 = p.col1
FOR XML PATH('')
) pp(Col4);
By this way FOR XML PATH('') would return xml element for each col1 & outer query STUFF() will replace AND with '' at the start of position of string.
try the following:
declare #table table(
col1 int,col2 int,col3 nvarchar(10),col4 nvarchar(50)
)
insert into #table values(123, 1, 'Y' ,'Active')
insert into #table values(123, 1, 'Y' ,'Idle')
insert into #table values(109, 1, 'Y' ,'Active')
insert into #table values(109, 1, 'Y' ,'Away')
insert into #table values(165, 1, 'Y' ,'Active')
insert into #table values(145, 1, 'Y' ,'Idle')
select col1,col2,col3, stuff((SELECT ' AND ' + t2.col4 + CASE WHEN count(t1.col1) = 1 THEN ' ONLY' ELSE '' END
FROM #table t2
WHERE t2.col1 = t1.col1
ORDER BY t2.col1
FOR XML PATH(''), TYPE).value('.', 'varchar(max)')
,1,5,'') AS col4
from #table t1
group by col1,col2,col3
I'm using the stuff to concat values that does not include in the group.
And when count = 1 its mean that there is only one item in this group so you need to add the Only suffix.
That's not such an obvious question, even in SQL Server 2017 which provides STRING_AGG. A check is needed to append Only if there is only one row per group.
Given this table :
declare #table table (id int, flag1 int, flag2 char(1), status varchar(40))
insert into #table
values
(123,1,'Y','Active'),
(123,1,'Y','Idle'),
(109,1,'Y','Active'),
(109,1,'Y','Away'),
(165,1,'Y','Active'),
(145,1,'Y','Idle')
The following query will return the aggregated values. If there's only one value, it will append Only :
select id,flag1,flag2,
string_agg(status,' and ') + IIF(count(*)>1,'',' Only')
from #table
group by id,flag1,flag2
This produces :
109 1 Y Active and Away
123 1 Y Active and Idle
145 1 Y Idle Only
165 1 Y Active Only
Related
My data looks like this
ID Text IsParent ParentID
-------------------------
1 A 1 NULL
2 B 0 NULL
3 C 0 NULL
4 D 0 NULL
5 E 1 NULL
6 F 0 NULL
7 G 1 NULL
8 H 0 NULL
I want to fill ParentID with the previous parentID.
Data is ordered so
ID : 2,3,4 have parentID : 1
ID : 6 has parentID : 5
ID : 8 has parentID : 7
How to do this with SQL?
I have tried with a cursor, but it is way too slow.
Here is my code:
DECLARE cur1 CURSOR FOR
SELECT ID Text IsParent ParentID
FROM x2
ORDER BY ID
OPEN cur1
FETCH NEXT FROM cur1 INTO #ID, #Text, #IsParent, #ParentID
WHILE ##FETCH_STATUS = 0
BEGIN
IF #IsParent = 1
BEGIN
SET #LastParentID = #ID
END
ELSE
BEGIN
UPDATE X2
SET ParentID = #LastParentID
WHERE ID = #ID
END
FETCH NEXT FROM cur1 INTO #ID, #Text, #IsParent, #ParentID
END;
CLOSE cur1;
DEALLOCATE cur1;
You can do this with APPLY. The premise is to find the parent record with the highest ID, where the ID is lower than the child record.
Example
DECLARE #x2 TABLE (ID INT NOT NULL, Text CHAR(1), IsParent BIT, ParentID INT);
INSERT #x2 (ID, Text, IsParent)
VALUES
(1, 'A', 1), (2, 'B', 0), (3, 'C', 0), (4, 'D', 0),
(5, 'E', 1), (6, 'F', 0), (7, 'G', 1), (8, 'H', 0);
UPDATE c
SET ParentID = p.ID
FROM #x2 AS c
CROSS APPLY
( SELECT TOP 1 ID
FROM #x2 AS p
WHERE p.IsParent = 1 -- Is a parent record
AND p.ID < c.ID -- ID is lower than child record
ORDER BY p.ID DESC -- Order descending to get the highest ID
) AS p
WHERE c.IsParent = 0
AND c.ParentID IS NULL;
SELECT *
FROM #x2;
OUTPUT
ID Text IsParent ParentID
---------------------------------
1 A 1 NULL
2 B 0 1
3 C 0 1
4 D 0 1
5 E 1 NULL
6 F 0 5
7 G 1 NULL
8 H 0 7
You can use CTE and window function to achieve that.
First we are creating continuous id(cid) using sum and secondly picking the minimum ID using the cid created in the first step and then finally updating the table where IsParent is 0.
try the following:
;WITH cte AS
(
SELECT *, sum(t.IsParent) OVER (ORDER BY id) cid
FROM #t t
),
cte2 AS
(
SELECT *, min(id) OVER (PARTITION BY cid ORDER BY id) pid
FROM cte c
)
UPDATE t
SET
t.ParentID = pid
FROM #t t
JOIN cte2 c ON c.id = t.ID
WHERE c.IsParent = 0
db<>fiddle demo.
The quickest way would be to insert child records with the parent id. Instead of populating the parent ids after the fact. From code you would first insert parent records and get back the newly generated parent ids. Then insert the child records with those newly generated parent ids.
Trying to maintain a query's speed like the ones suggested would just get gross over time as the data grows. Just because you can, doesn't mean you should.
Also as a side note, if you plan on having child of child records with an unknown depth. To avoid recursion I would recommend looking into having a hierarchyid data type column.
I have a query with the columns 'Name', 'Amount', and 'ReasonId'. I want to sum the amount and put the reasons on one row to keep every name to a single line. There are about 50 distinct ReasonId's so I do not want to name the column the name of the ReasonId's. Instead, I would like to name the columns 'Reason1', 'Reason2', 'Reason3', and 'Reason4'. One single name can have up to 4 different reasons.
I have this:
Name Amount ReasonId
-------------------------
Bob $5 7
Bob $8 6
John $2 8
John $5 9
John $3 9
John $8 4
I want to produce the following:
Name Amount Reason1 Reason2 Reason3 Reason4
-----------------------------------------------------
Bob $13 7 6 NULL NULL
John $18 8 9 4 NULL
One way to do this is to use the dense_rank window function to number the rows, and then use conditional aggregation to put the reason in the correct columns.
I can't see anything that would give the specific order of the reason columns though, maybe there is some column missing that provides the order?
with cte as (
select
name,
reasonid,
amount,
dense_rank() over (partition by name order by reasonid) rn
from your_table
)
select
name,
sum(amount) amount,
max(case when rn = 1 then reasonid end) reason1,
max(case when rn = 2 then reasonid end) reason2,
max(case when rn = 3 then reasonid end) reason3,
max(case when rn = 4 then reasonid end) reason4
from cte
group by name
If you have some column that gives the order you want then change the order by clause used in the dense_rank function.
Sample SQL Fiddle (using PG as MSSQL seems to be offline).
The output from the query above would be:
| name | amount | reason1 | reason2 | reason3 | reason4 |
|------|--------|---------|---------|---------|---------|
| Bob | 13 | 6 | 7 | (null) | (null) |
| John | 18 | 4 | 8 | 9 | (null) |
You could also use a pivot to achieve this; if you know the columns you can enter them in the script, but if not, you can use dynamic sql (there are reasons why you might want to avoid the dynamic solution).
The advantage of this route is that you can enter the column list in a table and then changes to that table will result in changes to your output with change to the script involved. The disadvantages are all those associated with dynamic SQL.
In the interests of variation, here is a dynamic SQL solution using temp tables to hold your data, since a different possibility has been provided:
-- set up your data
CREATE TABLE #MyTab (Name VARCHAR(4), Amount INT, ReasonId INT)
CREATE TABLE #AllPossibleReasons (Id INT,Label VARCHAR(10))
INSERT #AllPossibleReasons
VALUES
(1,'Reason1')
,(2,'Reason2')
,(3,'Reason3')
,(4,'Reason4')
,(5,'Reason5')
,(6,'Reason6')
,(7,'Reason7')
,(8,'Reason8')
,(9,'Reason9')
INSERT #MyTab
VALUES
('Bob',7,7)
,('Bob',8,6)
,('John',2,8)
,('John',5,9)
,('John',3,9)
,('John',8,4)
-----------------------------------------------------------------------------
-- The actual query
DECLARE #ReasonList VARCHAR(MAX) = ''
DECLARE #SQL VARCHAR(MAX)
SELECT #ReasonList = #ReasonList + ',' + QUOTENAME(Label)
FROM #AllPossibleReasons
SET #ReasonList = SUBSTRING(#ReasonList,2,LEN(#ReasonList))
SET #SQL =
'SELECT Name,Value,' + #ReasonList + ' FROM
(SELECT
M.Name,SUM(Amount) AS This, Label, SUM(Total.Value) AS Value
FROM
#MyTab AS M
INNER JOIN #AllPossibleReasons AS Reason ON M.ReasonId = Reason.Id
INNER JOIN(SELECT T.Name, SUM(Amount)Value
FROM #MyTab T GROUP BY T.Name) AS Total ON M.Name = Total.Name
GROUP BY M.Name, Reason.Label) AS Up
PIVOT (SUM(THis) FOR Label IN (' + #ReasonList + ')) AS Pvt'
EXEC (#SQL)
DROP TABLE #AllPossibleReasons
DROP TABLE #MyTab
Working from the information in ListAGG in SQLSERVER, I came up with this somewhat ugly example:
with tbl1 as (
-- Set up initial data set
select 'Bob' name, 5 amount, 7 ReasonId
union all select 'Bob' , 3, 4
union all select 'Bob', 2, 1
union all select 'Brian', 8, 2
union all select 'Bob', 6, 4
union all select 'Brian', 1, 3
union all select 'Tim', 2, 2)
, TBL2 AS ( -- Add a blank to separate the concatenation
SELECT NAME
, AMOUNT
, CAST(ReasonId as varchar) + ' ' ReasonId from tbl1
)
select ta.name
, Total
, ReasonIds from (
(select distinct name, stuff((select distinct '' + t2.ReasonId from tbl2 t2
where t1.name = t2.name
for xml path(''), type).value('.','NVARCHAR(MAX)'),1,0,' ') ReasonIds from tbl2 t1) ta
inner join ( select name, sum(amount) Total from tbl1 group by name) tb on ta.name = tb.name) ;
This converts TBL1 to the following:
name Total ReasonIds
Bob 16 1 4 7
Brian 9 2 3
Tim 2 2
My data is like below:
ClassId ClassName StudentId Subject SubjectId
-----------------------------------------------------
1 ESL 12 English 20
1 ESL 13 Science 30
1 ESL 12 Social 40
1 ESL 12 Maths 50
Required output: parameters are Subject column values
ClassId ClassName TotalStudents SubjectIds
-----------------------------------------------
1 ESL 2 20, 40, 50, 30
When one student takes multiple subjects then count student only once, so in the above data 12 is one student id takes multiple subjects so counted only once. TotalStudents value is 2 (1 from student id 12 and 1 from student id 13)
I am not looking for how to display subjectIds column value in comma separated string.
Thanks in advance
COUNT DISTINCT then use STUFF for combined the subject
declare #temp table
(ClassId int,ClassName nvarchar(max),StudentId int,Subject nvarchar(max), SubjectId int)
insert into #temp values (1,'ESL',12,'English' , 20 )
insert into #temp values (1,'ESL',13,'Science' , 30 )
insert into #temp values (1,'ESL',12,'Social ' , 40 )
insert into #temp values (1,'ESL',12,'Maths ' , 50 )
select ClassId,ClassName,COUNT(DISTINCT StudentId) CNT,
STUFF( (SELECT ',' + CAST(t1.SubjectId AS NVARCHAR)
FROM #temp t1
WHERE StudentId = t1.StudentId
FOR XML PATH('')),
1, 1, '') SubjectIdS
from #temp
GROUP BY ClassId,ClassName
OUTPUT
DISTINCT can be applied inside aggregate functions.
SELECT COUNT(DISTINCT column_name) FROM table_name;
If you don't need to display the SubjectIds, then you need to use a GROUP BY clause to group the resultset by ClassId and ClassName.
SELECT ClassId, ClassName, COUNT(distinct StudentId) as TotalStudents
FROM MyTable
GROUP BY ClassId, ClassName
See this example at SqlFiddle
I have the following rows.I dont want to select those columns whose entire value is null
col1 col2 col3
1 2 NULL
2 3 NULL
3 4 NULL
. . NULL
. . NULL
. . NULL
100 101 NULL
For example,
I want to select only col1 and col2 since all the values of col3 is null ie if col3 contains any value other than null then col1 col2 and col3 should be selected.Other wise col1 and col2 should only be selected.
How to acheive the above scenaria in sqlserver
There could be a better/efficient way present but you can try the below which is mixed o bit TSQL script and dynamic SQL. teasted and it works fine. A sample code below.
create table test1(col1 int,col2 int);
insert into test1(col1) values(2),(3),(4)
declare #col1sum int, #col2sum int, #countrow int;
-- This query will get you NULL count for all columns
select #col1sum = sum(case when col1 is null then 1 else 0 end),
#col2sum = sum(case when col2 is null then 1 else 0 end),
#countrow = count(1)
from test1;
select #col1sum, #col2sum, #countrow; --for display
declare #sql varchar(100);
-- check for column null count < total count of rows
if(#col1sum < #countrow)
set #sql = 'select col1 from test1';
else if(#col2sum < #countrow)
set #sql = 'select col2 from test1';
else
set #sql = 'select col1,col2 from test1';
exec(#sql); -- Finally execute the dynamic sql
If you want to go the way that David suggests in the comments and do the hiding in the UI, but have the database do the work to help you out, you can use aggregate functions with windows to add some extra columns:
declare #t table (col1 int,col2 int,col3 int)
insert into #t(col1,col2,col3) values
(1 ,2 ,NULL),
(2 ,3 ,NULL),
(3 ,4 ,NULL),
(9 ,NULL ,NULL),
(100 ,101 ,NULL)
select
col1,CASE WHEN MAX(col1) OVER () IS NULL THEN 0 ELSE 1 END as col1_HasValues,
col2,CASE WHEN MAX(col2) OVER () IS NULL THEN 0 ELSE 1 END as col2_HasValues,
col3,CASE WHEN MAX(col3) OVER () IS NULL THEN 0 ELSE 1 END as col3_HasValues
from #t
Which produces the result set:
col1 col1_HasValues col2 col2_HasValues col3 col3_HasValues
----------- -------------- ----------- -------------- ----------- --------------
1 1 2 1 NULL 0
2 1 3 1 NULL 0
3 1 4 1 NULL 0
9 1 NULL 1 NULL 0
100 1 101 1 NULL 0
The _HasValues columns will be identical across all rows and tells you if any row has a non-NULL value in the preceding column. If it's 0, you should hide the column in the UI.
I have a sql server database table with columns as shown below :
Table1
Id Name ErrorId
1 AB
2 CD
3 AB 3
4 AB 4
I want to get an output something like this :
Name IdCount ErrorIdCount ErrorIds
AB 3 2 4,3
CD 1 0 0
I wrote a query which looks like this currently :
select Name, Count(Id) as IdCount,
Count(Distinct case when ErrorId != ' ' then Id END) as ErrorIdCount
from Table1
group by Name;
It gives me something like this below :
Name IdCount ErrorIdCount.
AB 3 2
CD 1 0
I cannot figure out how I can include the ErrorIds too in my query ?
Can anyone point me out how I can solve this ?
Declare #a table (Id int, Name varchar(10),ErrorId int)
insert into #a Values (1,'AB',null),(2,'CD',null),(3,'AB',3),(4,'AB',4);
Select Name, Count(Id) as IdCount,
Count(Distinct case when ErrorId != ' ' then Id END) as ErrorIdCount
,[ErrorIds]=
STUFF((SELECT ', ' + Cast(ErrorId as Varchar(10))
FROM #a iup
WHERE iup.Name = a.Name
order by ErrorId
FOR XML PATH('')), 1, 1, '')
from #a a
Group by Name