Count number of rows with distinct value in column in T-SQL - tsql

In T-SQL, how can I query this table to show me record counts based on how many times a distinct value appears in a column?
For example, I have a table defined as:
ControlSystemHierarchy
----------------------
ParentDeviceID int
ChildDeviceID int
Instrument bit
I want to display the number of records that match each distinct ParentDeviceID in the table so that this table
ParentDeviceID | ChildDeviceID | Instrument
1 | 1 | 0
1 | 2 | 0
1 | 2 | 1
2 | 3 | 0
would return
ParentDeviceID | Count
1 | 3
2 | 1

select ParentDeviceID, count(*) as [Count]
from ControlSystemHierarchy
group by ParentDeviceID

Related

How to group by counted rows in Postgres?

If I have a table:
id | status
----+--------
2 | 200
1 | 0
4 | 100
3 | 200
5 | 200
I want to count the number of occurrences of each status. I have tried to use the COUNT/OVER function
SELECT status, COUNT(*) OVER () AS all, COUNT(*) OVER (PARTITION by status) as count FROM my_table;
The results are what is expected per the postgres docs on windows "However, window functions do not cause rows to become grouped into a single output row like non-window aggregate calls would. Instead, the rows retain their separate identities"
status | all | count
--------+-------+-------
0 | 5 | 1
100 | 5 | 1
200 | 5 | 3
200 | 5 | 3
200 | 5 | 3
How instead can get an output that combines the rows, so that I only get 1 row per unique status if the partition is required?
status | all | count
--------+-------+-------
0 | 5 | 1
100 | 5 | 1
200 | 5 | 3
No window function necessary in the first stage of the query, i.e. getting the counts per status. Window functions work on the result of the non-windowing part of the query, thus you can have a window function referring the aggregate & non-aggregate columns in a query. To get all_counts, it is sufficient to SUM the status_count over all the rows.
SELECT
status
, COUNT(*) status_count
, SUM(COUNT(*)) OVER () all_count
FROM my_table
GROUP BY status

PostgresQL for each row, generate new rows and merge

I have a table called example that looks as follows:
ID | MIN | MAX |
1 | 1 | 5 |
2 | 34 | 38 |
I need to take each ID and loop from it's min to max, incrementing by 2 and thus get the following WITHOUT using INSERT statements, thus in a SELECT:
ID | INDEX | VALUE
1 | 1 | 1
1 | 2 | 3
1 | 3 | 5
2 | 1 | 34
2 | 2 | 36
2 | 3 | 38
Any ideas of how to do this?
The set-returning function generate_series does exactly that:
SELECT
id,
generate_series(1, (max-min)/2+1) AS index,
generate_series(min, max, 2) AS value
FROM
example;
(online demo)
The index can alternatively be generated with RANK() (example, see also #a_horse_­with_­no_­name's answer) if you don't want to rely on the parallel sets.
Use generate_series() to generate the numbers and a window function to calculate the index:
select e.id,
row_number() over (partition by e.id order by g.value) as index,
g.value
from example e
cross join generate_series(e.min, e.max, 2) as g(value);

T-SQL. HOW to create a table with a sequence of values

I have a table with a list of names and indices. For example like this:
ID | Name | Index
1 | Value 1 | 3
2 | Value 2 | 4
...
N | Value N | NN
I need to create a new table, where every value from field "Name" will be repeat repeated as many times as the "Index" field is specified. For example like this:
ID | Name_2 | ID_2
1 | Value 1 | 1
2 | Value 1 | 2
3 | Value 1 | 3
4 | Value 2 | 1
5 | Value 2 | 2
6 | Value 2 | 3
7 | Value 2 | 4
...
N | Value N | 1
N+1| Value N | 2
...
I have no idea how to write a cycle to get such result. Please, give me an advice.
Here is solution to repeat the rows based on a column value
declare #order table ( Id int, name varchar(20), indx int)
Insert into #order
(Id, name, indx)
VALUES
(1,'Value1',3),
(2,'Value2',4),
(3,'Value3',2)
;WITH cte AS
(
SELECT * FROM #order
UNION ALL
SELECT cte.[ID], cte.name, (cte.indx - 1) indx
FROM cte INNER JOIN #order t
ON cte.[ID] = t.[ID]
WHERE cte.indx > 1
)
SELECT ROW_NUMBER() OVER(ORDER BY name ASC) AS Id, name as [name_2], 1 as [Id_2]
FROM cte
ORDER BY 1

Select rows that satisfy a certain group condition in psql

Given the following table:
id | value
---+---------
1 | 1
1 | 0
1 | 3
2 | 1
2 | 3
2 | 5
3 | 2
3 | 1
3 | 0
3 | 1
I want the following table:
id | value
---+---------
1 | 1
1 | 0
1 | 3
3 | 2
3 | 1
3 | 0
3 | 1
The table contains ids that have a minimum value of 0.
I have tried using exist and having but to no success.
try this :
select * from foo where id in (SELECT id FROM foo GROUP BY id HAVING MIN(value) = 0)
or that ( with window functions)
select * from
(select *,min(value) over (PARTITION BY id) min_by_id from foo) a
where min_by_id=0
If I'm understanding correctly, it's a fairly simple having clause:
=# SELECT id, MIN(value), MAX(value) FROM foo GROUP BY id HAVING MIN(value) = 0;
id | min | max
----+-----+-----
1 | 0 | 3
3 | 0 | 2
(2 rows)
Did I miss something that is making it more complicated?
It looks it is not possible to use window function in WHERE or HAVING. Below is solution based on JOINs.
JOIN every row with all rows of the same id.
Filter based on second set.
Show result from first set.
The SQL looks like this.
SELECT a.*
FROM a_table AS a
INNER JOIN a_table AS value ON a.id = b.id
WHERE b.value = 0;

Traversing Gaps in sequential data

I have a table [ContactCallDetail] which stores call data for each leg of a call from our phone system. The data is stored with a 4 part primary key: ([SessionID], [SessionSeqNum], [NodeID], [ProfileID]). The [NodeID], [ProfileID] , and [SessionID] together make up a call, and the [SessionSeqNum] defines each leg of the call as the caller is transferred from one department/rep to the next.
I need to look at each leg of a call and, if a transfer occured, find the next leg of the call so I can report on where the transfered call went.
The problems I am facing are 1) the session sequence does not always start with the same number 2) there can be gaps in the sequence number 3) The table has 15,000,000 rows and is added to via data import every night, so I need a non cursor based solution.
Sample data
| sessionid | sessionseqnum | nodeid | profileid |
| 170000459184 | 0 | 1 | 1 |
| 170000459184 | 1 | 1 | 1 |
| 170000459184 | 3 | 1 | 1 |
| 170000229594 | 1 | 1 | 1 |
| 170000229594 | 2 | 1 | 1 |
| 170000229598 | 0 | 1 | 1 |
| 170000229598 | 2 | 1 | 1 |
| 170000229600 | 0 | 1 | 1 |
| 170000229600 | 1 | 1 | 1 |
| 170000229600 | 3 | 1 | 1 |
| 170000229600 | 5 | 1 | 1 |
I think what I need to do is create a lookup table using an identity column or rownum() or the like to get a new sequence number for the call legs that will have no gaps. How would I do this? Or if there is a different, best practices solution you could point me to that would be great.
You can use the lead() analytic function to identify the next session sequence number.
SELECT sessionid ,
nodeid ,
profileid ,
sessionseqnum ,
lead(sessionseqnum) OVER ( PARTITION BY sessionid, nodeid, profileid ORDER BY sessionseqnum ) AS next_seq_num
FROM ContactCallDetail
ORDER BY sessionid ,
nodeid ,
profileid ,
sessionseqnum;
sessionid nodeid profileid sessionseqnum next_seq_num
--
170000229594 1 1 1 2
170000229594 1 1 2
170000229598 1 1 0 2
170000229598 1 1 2
170000229600 1 1 0 1
170000229600 1 1 1 3
170000229600 1 1 3 5
170000229600 1 1 5
170000459184 1 1 0 1
170000459184 1 1 1 3
170000459184 1 1 3
The ORDER BY clause isn't strictly necessary; it just makes it easier for humans to read the output.
Now you can join the original table to produce a row that shows relevant pairs of rows. There are several different ways to express that in standard SQL. Here, I'm using a common table expression.
WITH next_seq_nums
AS ( SELECT * ,
lead(sessionseqnum) OVER ( PARTITION BY sessionid, nodeid, profileid ORDER BY sessionseqnum ) AS next_seq_num
FROM ContactCallDetail
)
SELECT t1.sessionid ,
t1.nodeid ,
t1.profileid ,
t1.sessionseqnum ,
t2.sessionseqnum next_sessionseqnum ,
t2.nodeid next_nodeid ,
t2.profileid next_profileid
FROM next_seq_nums t1
LEFT JOIN ContactCallDetail t2 ON t1.sessionid = t2.sessionid
AND t1.nodeid = t2.nodeid
AND t1.profileid = t2.profileid
AND t1.next_seq_num = t2.sessionseqnum
ORDER BY t1.sessionid ,
t1.nodeid ,
t1.profileid ,
t1.sessionseqnum;
The LEFT JOIN will leave NULLs in the rows for the last session sequence numbers in each session. That makes sense--on the last row, there isn't a "next leg of the call". But it's easy enough to exclude those rows if you need to.
If your dbms doesn't support the lead() analytic function, you can replace the common table expression above with this one.
WITH next_seq_nums
AS ( SELECT t1.* ,
( SELECT MIN(sessionseqnum)
FROM contactcalldetail
WHERE sessionid = t1.sessionid
AND nodeid = t1.nodeid
AND profileid = t1.profileid
AND sessionseqnum > t1.sessionseqnum
) next_seq_num
FROM contactcalldetail t1
)
...
with cte
as
(SELECT *,
rank() OVER
(partition BY sessionid,profileid,nodeid
ORDER BY sessionseqnum ) AS Rank
FROM dbo.Table_1)
SELECT
cte.sessionid,cte.nodeid,cte.profileid,cte.sessionseqnum,cte_1.sessionseqnum
FROM cte LEFT JOIN
cte AS cte_1
ON cte.sessionid = cte_1.sessionid
and cte.profileid= cte_1.profileid
and cte.nodeid= cte_1.nodeid
and cte.rank= cte_1.rank-1