Is there a way to display the total count of rows in a separate row? - sql

I have a table that looks like this:
City_Id
City
41
Athena
39
Beijing
35
London
30
Rio de Janeiro
28
Salt Lake City
18
Sochi
7
Sydney
4
Torino
is there a way to display another row in the bottom that will display the total count of rows?
City_Id
City
41
Athena
39
Beijing
35
London
30
Rio de Janeiro
28
Salt Lake City
18
Sochi
7
Sydney
4
Torino
Total
8

You can actually use GROUPING SETS for this. This avoids having to scan the table twice.
However you still have the data-type mismatch problem. You could solve it by casting, but it's probably easier to just swap the columns around
SELECT
CASE WHEN GROUPING(City) = 0 THEN City ELSE 'Total' END AS City,
CASE WHEN GROUPING(City_Id) = 0 THEN City_Id ELSE COUNT(*) END AS City_Id
FROM Table1
GROUP BY GROUPING SETS (
(City_Id, City),
()
)
ORDER BY GROUPING(City_Id);
SQL Fiddle
What this does is generate separate result-sets, unioned together. You can differentiate between a grouped row and a non-grouped row using the GROUPING function.

I would agree with most of the other comments that acquiring a result set count would be more appropriate from the application code (which usually has a mechanism specifically for this purpose).
However...
If you must have a TSQL solution for your question, an option is to return the count in a separate column. This is different than returning it in a separate row, of course. There are pros & cons with each approach.
DROP TABLE IF EXISTS #Cities;
CREATE TABLE #Cities (
City_Id INT,
City VARCHAR(128)
);
INSERT INTO #Cities
VALUES
(41, 'Athena'),
(39, 'Beijing'),
(35, 'London'),
(30, 'Rio de Janeiro'),
(28, 'Salt Lake City'),
(18, 'Sochi'),
(7 , 'Sydney'),
(4 , 'Torino');
SELECT *, COUNT(*) OVER(ORDER BY (SELECT NULL)) AS Total
FROM #Cities;
--Count is properly reflected based on WHERE clause.
SELECT *, COUNT(*) OVER(ORDER BY (SELECT NULL)) AS Total
FROM #Cities
WHERE City LIKE 'S%';
--Be careful with this one--the COUNT(*) may not be what you expected.
SELECT TOP(4) *, COUNT(*) OVER(ORDER BY (SELECT NULL)) AS Total
FROM #Cities;
NOTE: be aware that this approach may not scale (perform) well for large result sets. Be sure to do some testing!

As you know already, it should be done in the presentation layer. But if you just want to know if there is any way, then I would suggest to use UNION ALL
select cast(City_Id as varchar(10)) City_Id, City from Table1
union all
select 'Total' as City_Id, cast(count(*) as varchar(14)) from Table1
Here is the sql fiddle

Related

Finding all instances where a foreign key appears multiple times grouped by month

I am not too familiar with SQL, and I have been tasked with something that I quite frankly have no clue how to go about it.
I am just going to simplify the tables to the point where only the necessary fields are taken into consideration.
The tables look as follows.
Submission(course(string), student(foreign_key), date-submitted)
Student(id)
What I need to do is produce a table of active students per month, per course with a total. An active student being anyone who has more than 4 submissions in the month. I am only looking at specific courses, so I will need to hard code the values that I need, for the sake of the example "CourseA" and "CourseB"
The result should be as follows
month | courseA | CourseB | Total
------------------------------------------
03/2020 50 27 77
02/2020 25 12 37
01/2020 43 20 63
Any help would be greatly apreciated
You can do this with two levels of aggregation: first by month, course and student (while filtering on students having more than 4 submissions), then by month (while pivoting the dataset):
select
month_submitted,
count(*) filter(where course = 'courseA') active_students_in_courseA,
count(*) filter(where course = 'courseB') active_students_in_courseB,
count(*) total
from (
select
date_trunc('month', date_submitted) month_submitted,
course,
student_id,
count(*) no_submissions
from submission
where course in ('courseA', 'courseB')
group by 1, 2, 3
having count(*) > 4
) t
group by 1
You could do subqueries using the WITH keyword like this:
WITH monthsA AS (
SELECT to_char(date-submitted, "MM/YYYY") as month, course, COUNT(*) as students
FROM Submission
WHERE course = 'courseA'
GROUP BY 1, 2
), monthsB AS (
SELECT to_char(date-submitted, "MM/YYYY") as month, course, COUNT(*) AS students
FROM Submission
WHERE course = 'courseB'
GROUP BY 1, 2
)
SELECT ma.month,
COALESE(ma.students, 0) AS courseA,
COALESCE(mb.students) AS courseB,
COALESCE(ma.students, 0) + COALESCE(mb.students, 0) AS Total
FROM monthsA ma
LEFT JOIN monthsB mb ON ma.month = mb.month
ORDER BY 1 DESC

SQL Query to find Total Occurence of a "number" in a Column

I want to know the SQL Query to find how many times a particular number is repeating(total count) in a column which is of int type.
Example: Coumn_iD = PostalCode
Postalcode
8696
2314
9645
3268
4288
2222
in the above case count of 8 = 4, Query for 2 should return 7
Like that, any help would be appreciated.
You can compute the number of occurrences of the particularSymbol on each row in the table as LENGTH(columnName) - LENGTH(REPLACE(columnName, particularNumber, "")) and then simply sum those over the whole table:
SELECT SUM(LENGTH(columnName) - LENGTH(REPLACE(columnName, particularNumber, '')))
FROM tableName
First count the number of occurrences for each row (the nested SELECT statement), then sum up that count to get one aggregate number.
DECLARE #my_num INT
SET #my_num = 2
SELECT SUM(count_per_row)
FROM
(
SELECT len(Postalcode) - len(replace(Postalcode,#my_num,'')) as count_per_row
FROM table)
If you don't need to generalize it, then just replace #my_num in the SELECT block with your number of interest (and get rid of the SELECT statement).
You didn't mention the database system you are using.
In Postgres you can create one row for each character in the postalcode and then group by that:
with test_data (postalcode) as (
values ('8696'),('2314'),('9645'),('3268'),('4288'),('2222')
)
select c, count(*)
from test_data, unnest(string_to_array(postalcode, null)) as t(c)
group by c
order by c
Ivo solution is probably the best. However, because the postal codes are only 4 digits, you might find this easier to follow:
select sum(case when postalcode like '2222' then 4
when postalcode like '%2%2%2%' then 3
when postalcode like '%2%2%' then 2
when postalcode like '%2%' then 1
else 0
end) as num_2s
from t;

SQL find entire row where only 2 columns values

I'm attempting to
select columns Age, Height, House_number, Street
from my_table
where count(combination of House_number, Street)
occurs more than once.
My table looks like this
Age, Height, House_number, Street
15 178 6 Mc Gill Crst
85 166 6 Mc Gill Crst
85 166 195 Mc Gill Crst
18 151 99 Moon Street
52 189 14a Grimm Lane
My desired outcome looks like this
Age, Height, House_number, Street
15 178 6 Mc Gill Crst
85 166 6 Mc Gill Crst
Stuck!
The best way to do this is with window functions, assuming your database supports them:
select columns Age, Height, House_number, Street
from (select t.*, count(*) over (partition by house_number, street) as cnt
from my_table t
) t
where cnt > 1
This is using a windows function (also called analytic function) in Oracle. The expression count(*) over (partition by house_number, street) is counting the number of rows for each house_number and street combination. It is kind of like doing a group by, but it adds the count to each row rather than combining multiple rows into one.
Once you have that, it is easy to simply choose the rows where the value is greater than 1.
Since you haven't mentioned the RDBMS you are using, the query below will amost work on most RDBMS.
SELECT *
FROM tableName
WHERE (House_number, Street) IN
(
SELECT House_number, STREET
FROM tableName
GROUP BY House_number, STREET
HAVING COUNT(*) >= 2
)
SQLFiddle Demo
Sounds like you need a NOT DISTINCT. The following might give you what you need: Multiple NOT distinct
If you do not have windowing function, then you can use a subquery with a JOIN. The subquery gets the list of the house_number and street that have a count of greater than 1, this result is then used to join back to your table:
select t1.age,
t1.height,
t1.house_number,
t1.street
from my_table t1
inner join
(
select house_number, street
from my_table
group by house_number, street
having count(*) > 1
) t2
on t1.house_number = t2.house_number
and t1.street = t2.street
See SQL Fiddle with Demo

Assistance with SQL Query (Windowing Functions)

Houston Apartment Order 1
Houston Apartment Order 5
Houston TownHouse Order 3
Houston TownHouse Order 4
Austin Condo
Dallas MultiFamily Order 2
All,
I have a result set like the one above.
Using the familiar Customer -> Orders schema as an example,
The first two columns (e.g. Houston, Apartment) come from category1 and category2 fields on the Customer Table.
The third column comes from the Orders Table and will represent the Primary Key of the table. The values in this column were deliberately listed out of order (1...5...3) to show that I cannot guarantee the order the values.
What I want is to have a column that adds a Rank or Row_number (or calculation?) that numbers each combination of Category 1 and 2:
1 Houston Apartment Order 1
1 Houston Apartment Order 5
2 Houston TownHouse Order 3
2 Houston TownHouse Order 4
3 Austin Condo
4 Dallas MultiFamily Order 2
So, Houston-Aparment is 1, Houston-TownHouse is 2, etc...
I would like to avoid any sub/nested queries if possible.
Please note:
The values in the example or just sample data. The real data is not based on a Customer/orders so I respectfully and humbly ask that you please not chastise me for having Cities and Apartment types as categories, etc (I would put these in separate domain tables in this instance) or suggest a change of schema
Can anyone help please?!
Steve
Based on the results that you show, I think you want:
select dense_rank() over (order by Category1, Category2) as rankorder, *
from Customers c join
Orders o
on o.CustomerID = c.CustomerID
You seem to be adding an index based only on the first two categories, and never starting over again (the partition is used to start counting over again). You have ties with no gaps (1, 1, 2 . . .). For this case, you want dense_rank(). If you had ties with gaps (1, 1, 3 . . .), you would use rank(). If you just wanted the ordering (1, 2, 3) you would use row_number().
If your database supports windowing functions, you could use row_number():
select row_number() over (partition by Category1, Category2 order by CustomerID)
from Customers c
join Orders o
on o.CustomerID = c.CustomerID
Something like this should do:
create table Data
(
city varchar(50),
propertyType varchar(50),
anOrder int
)
insert into Data select 'Houston', 'Apartment', 1
insert into Data select 'Houston', 'Apartment', 5
insert into Data select 'Houston', 'TownHouse', 3
insert into Data select 'Houston', 'TownHouse', 4
insert into Data select 'Austin', 'Condo', 1
insert into Data select 'Dallas', 'MultiFamily', 2
select city, propertyType, RANK() OVER
(PARTITION BY Data.city ORDER BY Data.city,Data.propertyType DESC) AS Rank
from Data
group by city, propertyType

Collapse Multiple Records Into a Single Record With Multiple Columns

In a program I'm maintaining we were given a massive (~500 lines) SQL statement by the customer. It is used for generating flat files with fixed length records for transmitting data to another big business. Since its a massive flat file its not relational and the standard normal forms of data are collapsed. So, if you have a record that can have multiple codes associated, in this case upto 19, they all have be written into single line, but seperate fields, in the flat file.
Note: this example is simplified.
The data might look like this, with three tables:
RECORDS
record_id firstname lastname
--------------------------------
123 Bob Schmidt
324 George Washington
325 Ronald Reagan
290 George Clooney
CODE_TABLE
code_id code_cd code_txt
--------------------------------
5 3 President
2 4 Actor
3 7 Plumber
CODES_FOR_RECORDS
record_id code_cd
-------------------
123 7
325 3
290 4
324 3
325 4
123 4
This needs to produce records like:
firstname lastname code1 code2 code3
Bob Schmidt Actor Plumber NULL
George Washington President NULL NULL
Ronald Reagon Actor President NULL
George Clooney Actor NULL NULL
The portion of the current query we were given looks like this, but with 19 code columns instead of the 5:
select
x.record_id,
max(case when x.rankk = 1 then code_txt end) as CodeColumn1,
max(case when x.rankk = 2 then code_txt end) as CodeColumn2,
max(case when x.rankk = 3 then code_txt end) as CodeColumn3,
max(case when x.rankk = 4 then code_txt end) as CodeColumn4,
max(case when x.rankk = 5 then code_txt end) as CodeColumn5,
from
(
select
r.record_id,
ct.code_txt as ctag ,
dense_rank() over (partition by r.record_id order by cfr.code_id) as rankk
from
records as r
codes_for_records as cfr,
code_table as ct
where
r.record_id = cfr.record_id
and ct.code_cd = cfr.code_cd
and cfr.code_cd is not null
and ct.code_txt not like '%V%'
) as x
where
x.record_id is not null
group by
x.record_id
I trimmed down things for simplicties sake, but the actual statment includes an inner query and a join and more where conditions, but that should get the idea across. My brain is telling me there has to be a better way, but I'm not an SQL expert. We are using DB2 v8 if that helps. And the codes have to be in seperate columns, so no coalescing things into a single string. Is there a cleaner solution than this?
Update:
I ended up just refacorting the original query, it sill uses the ugly MAX() business, but overall the query is much more readable due to reworking other parts.
It sounds like what you are looking for is pivoting.
WITH joined_table(firstname, lastname, code_txt, rankk) AS
(
SELECT
r.firstname,
r.lastname,
ct.code_txt,
dense_rank() over (partition by r.record_id order by cfr.code_id) as rankk
FROM
records r
INNER JOIN
codes_for_records cfr
ON r.record_id = cfr.record_id
INNER JOIN
codes_table ct
ON ct.code_cd = cfr.code_cd
),
decoded_table(firstname, lastname,
CodeColumn1, CodeColumn2, CodeColumn3, CodeColumn4, CodeColumn5) AS
(
SELECT
firstname,
lastname,
DECODE(rankk, 1, code_txt),
DECODE(rankk, 2, code_txt),
DECODE(rankk, 3, code_txt),
DECODE(rankk, 4, code_txt),
DECODE(rankk, 5, code_txt)
FROM
joined_table jt
)
SELECT
firstname,
lastname,
MAX(CodeColumn1),
MAX(CodeColumn2),
MAX(CodeColumn3),
MAX(CodeColumn4),
MAX(CodeColumn5)
FROM
decoded_table dt
GROUP BY
firstname,
lastname;
Note that I've never actually done this myself before. I'm relying on the linked document as a reference.
You might need to include the record_id to account for duplicate names.
Edit: Added the GROUP BY.
One of the possible solutions is using of recursive query:
with recursive_view (record_id, rankk, final) as
(
select
record_id,
rankk,
cast (ctag as varchar (100))
from inner_query t1
union all
select
t1.record_id,
t1.rankk,
/* all formatting here */
cast (t2.final || ',' || t1.ctag as varchar (100))
from
inner_query t1,
recursive_view t2
where
t2.rankk < t1.rankk
and t1.record_id = t2.record_id
and locate(t1.ctag, t2.final) = 0
)
select record_id, final from recursive_view;
Can't guarantee that it works, but hope it will be helpful. Another way is using of custom aggregate function.