SQL: Return a count of 0 with count(*) - sql

I am using WinSQL to run a query on a table to count the number of occurrences of literal strings. When trying to do a count on a specific set of strings, I still want to see if some values return a count of 0. For example:
select letter, count(*)
from table
where letter in ('A', 'B', 'C')
group by letter
Let's say we know that 'A' occurs 3 times, 'B' occurs 0 times, and 'C' occurs 5 times. I expect to have a table returned as such:
letter count
A 3
B 0
C 5
However, the table never returns a row with a 0 count, which results like so:
letter count
A 3
C 5
I've looked around and saw some articles mentioning the use of joins, but I've had no luck in correctly returning a table that looks like the first example.

You can create an in-line table containing all letters that you look for, then LEFT JOIN your table to it:
select t1.col, count(t2.letter)
from (
select 'A' AS col union all select 'B' union all select 'C'
) as t1
left join table as t2 on t1.col = t2.letter
group by t1.col

on many platforms you can now use the values statement instead of union all to create your "in line" table - like this
select t.letter, count(mytable.letter)
from ( values ('A'),('B'),('C') ) as t(letter)
left join mytable on t.letter = mytable.letter
group by t.letter

I'm not that familiar with WinSQL, but it's not pretty if you don't have the values that you want in the left most column in a table somewhere. If you did, you could use a left join and a conditional. Without it, you can do something like this:
SELECT all_letters.letter, IFNULL(letter_count.letter_count, 0)
FROM
(
SELECT 'A' AS letter
UNION
SELECT 'B' AS letter
UNION
SELECT 'C' AS letter
) all_letters
LEFT JOIN
(SELECT letter, count(*) AS letter_count
FROM table
WHERE letter IN ('A', 'B', 'C')
GROUP BY letter) letter_count
ON all_letters.letter = letter_count.letter

Related

PostgreSQL: Select unique rows where distinct values are in list

Say that I have the following table:
with data as (
select 'John' "name", 'A' "tag", 10 "count"
union all select 'John', 'B', 20
union all select 'Jane', 'A', 30
union all select 'Judith', 'A', 40
union all select 'Judith', 'B', 50
union all select 'Judith', 'C', 60
union all select 'Jason', 'D', 70
)
I know there are a number of distinct tag values, namely (A, B, C, D).
I would like to select the unique names that only have the tag A
I can get close by doing
-- wrong!
select
distinct("name")
from data
group by "name"
having count(distinct tag) = 1
however, this will include unique names that only have 1 distinct tag, regardless of what tag is it.
I am using PostgreSQL, although having more generic solutions would be great.
You're almost there - you already have groups with one tag, now just test if it is the tag you want:
select
distinct("name")
from data
group by "name"
having count(distinct tag) = 1 and max(tag)='A'
(Note max could be min as well - SQL just doesn't have single() aggregate function but that's different story.)
You can use not exists here:
select distinct "name"
from data d
where "tag" = 'A'
and not exists (
select * from data d2
where d2."name" = d."name" and d2."tag" != d."tag"
);
This is one possible way of solving it:
select
distinct("name")
from data
where "name" not in (
-- create list of names we want to exclude
select distinct name from data where "tag" != 'A'
)
But I don't know if it's the best or most efficient one.

How to write redshift aws query to search for a value in comma delimited values

table1
user_id
country_code
1
'IN,AU,AC'
2
'MX,IN'
table2
user_id
valid_country
1
'IN'
1
'AU'
2
'MX'
3
'YT'
4
'RU'
As you can see, some entries in the country_code column are multiple codes separated by commas. I would like to print user_id in table1 and their corresponding country_code only if they are valid. To check for validity here I need to use table2 which has user_id and valid_country.
The desired output is:
user_id
country_code
1
'IN'
1
'AU'
2
'MX'
Query looks like
select tb1.user_id, country_code from table1 tb1, table2 tb2 where
tb1.user_id=tb2.user_id and <Here I need to check if tb2.country_code
is there in tb1.country_code (codes separated by commas)>
Are there any simple solution that I could check valid_country in the comma separated values.
The simple way isn't always the best. There are a number of corner cases that can arise here (like are all country codes 2 letters). That said a LIKE clause would be simple:
select tb1.user_id, valid_country as country_code
from table1 tb1, table2 tb2
where tb1.user_id=tb2.user_id
and tb1.country_code like '%'||tb2.valid_country||'%'
Or if we are to put this in modern SQL syntax:
select tb1.user_id, valid_country as country_code
from table1 tb1 join table2 tb2
on tb1.user_id=tb2.user_id
and tb1.country_code like '%'||tb2.valid_country||'%'
Try this:
a) Verticalise tb1 by CROSS JOINing it with a series of consecutive integers (which I supply in a Common Table Expression), and applying the SPLIT_PART() function to break the comma delimited list into single element.
b) INNER JOIN the verticalised result with the valid user_id/country code combinations table on an equi-join on both columns.
WITH
-- your table 1, don't use in end query ...
tb1(user_id,country_code) AS (
SELECT 1,'IN,AU,AC'
UNION ALL SELECT 2,'MX,IN'
)
,
-- your table 2, don't use in end query ...
tb2(user_id,valid_country) AS (
SELECT 1,'IN'
UNION ALL SELECT 1,'AU'
UNION ALL SELECT 2,'MX'
UNION ALL SELECT 3,'YT'
UNION ALL SELECT 4,'RU'
)
-- real query starts here, replace following comma with "WITH" ...
,
i(i) AS ( -- need a series of integers ...
SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
)
,
vertical AS (
SELECT
tb1.user_id
, i
, SPLIT_PART(country_code,',',i) AS valid_country
FROM tb1 CROSS JOIN i
WHERE SPLIT_PART(country_code,',',i) <> ''
)
SELECT
vertical.user_id
, vertical.valid_country
FROM vertical
JOIN tb2 USING(user_id,valid_country)
ORDER BY vertical.user_id,vertical.i
;
-- out user_id | valid_country
-- out ---------+---------------
-- out 1 | IN
-- out 1 | AU
-- out 2 | MX

How do i create a DB2 UNION query using variables from a list

So i have a union query like:
select count(id)
from table 1
where membernumber = 'x'
and castnumber = 'y'
union
select count(id)
from table 1
where membernumber = 'x'
and castnumber = 'y'
union
etc...
There will be over 200 unions coming from a list 2x 200 table with values for x and y in each row. So each union query has to get the value of x and y from the corresponding row (not in any particular order).
How can i achieve that ?
Thanks
Try this:
DECLARE GLOBAL TEMPORARY TABLE
SESSION.PARAMETERS
(
MEMBERNUMBER INT
, CASTNUMBER INT
) DEFINITION ONLY WITH REPLACE
ON COMMIT PRESERVE ROWS NOT LOGGED;
-- Insert all the the constants in your application with
INSERT INTO SESSION.PARAMETERS
(MEMBERNUMBER, CASTNUMBER)
VALUES (?, ?);
-- I don't know the meaning of the result you want to get
-- but it's equivalent
select distinct count(t.id)
from table1 t
join session.parameters p
on p.membernumber = t.membernumber
and p.castnumber = t.castnumber
group by t.membernumber, t.castnumber;

Count for a list of items with zero for does not exist

If I have a table t1 with:
my_col
------
foo
foo
bar
And I have a list with foo and hello
How can I get:
my_col | count
-------|-------
foo | 2
hello | 0
If I just do
SELECT my_col, COUNT(*)
FROM t1
WHERE my_col in ('foo', 'hello')
GROUP BY my_col
I get
my_col | count
-------|------
foo | 2
without any value for hello.
I'm specifically wanting this to be in reference to a list of items because this will be called in a program where the list is a variable.
Ideally you should maintain a separate table with all the possible column values which you want to appear in your report. In the absence of that, we can try using a CTE here:
WITH cte AS (
SELECT 'foo' AS my_col UNION ALL
SELECT 'bar' UNION ALL
SELECT 'hello'
)
SELECT
a.my_col,
COUNT(b.my_col) AS count
FROM cte a
LEFT JOIN t1 b
ON a.my_col = b.my_col
WHERE
a.my_col IN ('foo', 'hello')
GROUP BY
a.my_col;
Demo
Here's yet another way, using values:
select
t2.my_col, count (t1.my_col)
from
(values ('foo'), ('hello')) as t2 (my_col)
left join t1 on t1.my_col = t2.my_col
group by
t2.my_col
Note that count (t1.my_col) returns a 0 for "hello" since nulls are not counted. count (*) by contast would have returned 1 for "hello" because it was counting the row.
You can turn your list into a set of rows and use a LEFT JOIN, like :
SELECT x.val, COUNT(t.my_col)
FROM
(SELECT 'foo' val UNION SELECT 'hello') x
LEFT JOIN t ON t.my_col = x.val
GROUP BY x.val
Postgres solution:
One way is to place the 'list' into an ARRAY, and then convert the ARRAY into a column using unnest. Then perform a left join on that column with the other table and perform a count.
WITH t1 AS (
SELECT 'foo' AS my_col UNION ALL
SELECT 'foo' UNION ALL
SELECT 'bar'
)
SELECT
a.my_col,
COUNT(b.my_col) AS count
FROM unnest(ARRAY['foo', 'hello']) a (my_col)
LEFT JOIN t1 b
ON a.my_col = b.my_col
GROUP BY
a.my_col;
The issue I had with the other answers is that (while they they helped me get to the solution) they did not provide a solution where the items of interest were in a single list (which isn't an actual sql term, so the fault is on me).
However, my real use case is to perform a native query using java and hibernate, and unfortunately the above does not work because the typing cannot be determined. Instead I converted my list into a single string and used string_to_array in place of the ARRAY function.
So the solution that worked best for my use case is below (but at this point, the other answers would be just as correct since I'm now having to do manual string manipulation, but I'm leaving this here for the sake of posterity)
WITH t1 AS (
SELECT 'foo' AS my_col UNION ALL
SELECT 'foo' UNION ALL
SELECT 'bar'
)
SELECT
a.my_col,
COUNT(b.my_col) AS count
FROM unnest(string_to_array('foo, hello', ',')) a (my_col)
LEFT JOIN t1 b
ON a.my_col = b.my_col
GROUP BY
a.my_col;

SQL - Return a default value when my search returns no results along with search criteria

I am searching with a query
--Code Format
SELECT COLA,COLB,COLC from MYTABLE where SWITCH IN (1,2,3);
If MYTABLE does not contain rows with SWITCH 1,2 or 3 I need default values returned along with the SWITCH value. How do I do it?
Below is my table format
COLA | COLB | COLC | SWITCH
------------------------------
A B C 1
a b c 2
i want a query when I search with
select * from MYTABLE where switch in (1,2,3)
That gets results like this --
COLA | COLB | COLC | SWITCH
------------------------------
A B C 1
a b c 2
NA NA NA 3
--Check to see if any row exists matching your conditions
IF NOT EXISTS (SELECT COLA,COLB,COLC from MYTABLE where SWITCH IN (1,2,3))
BEGIN
--Select your default values
END
ELSE
BEGIN
--Found rows, return them
SELECT COLA,COLB,COLC from MYTABLE where SWITCH IN (1,2,3)
END
if not exists( SELECT 1 from MYTABLE where SWITCH IN (1,2,3))
select default_value
How about:
SELECT COLA,COLB,COLC from MYTABLE where SWITCH IN (1,2,3)
union select 5555, 6666, 7777 where not exists (
SELECT COLA,COLB,COLC from MYTABLE where SWITCH IN (1,2,3)
);
5555, 6666, 7777 being the default row in case there aren't any rows matching your criteria.
Here is one way to tackle this. You need a table of the SWITCH values you want to look at. Then a simple left join makes this super easy.
select ColA
, ColB
, ColC
v.Switch
from
(
values
(1)
, (2)
, (3)
)v (Switch)
left join YourTable yt on yt.Switch = v.Switch
You can Use a Split Function And Left Join As Shown Below:
Select ISNULL(ColA,'NA') As ColA,ISNULL(ColB,'NA') As ColB,ISNULL(ColC,'NA') As ColC,ISNULL(Switch,a.splitdata)
from [dbo].[fnSplitString]('1,2,3',',') a
LEFT JOIN #MYTABLE t on a.splitdata=t.Switch
[dbo].[fnSplitString] is a Split Function with 2 arguments - Delimeter Separated String and Delimeter and Output a Table.
EDIT:
Given the new explanation, I changed the answer completely. I think I got your question now:
SELECT * FROM MYTABLE AS mt
RIGHT JOIN (SELECT 1 AS s UNION SELECT 2 AS s UNION SELECT 3 AS s) AS st
ON st.s = mt.SWITCH
You could change the SELECT 1 AS s UNION SELECT 2 AS s UNION SELECT 3 AS spart to a subquery that results in all possible values SWITCH could assume. E.g.:
SELECT DISTINCT SWITCH FROM another_table_with_all_switches
If all want is the value of switch that is not in MYTABLE, not the whole table with null values, you could try:
SELECT * FROM
(SELECT 1 AS s UNION SELECT 2 AS s UNION SELECT 3) AS st
WHERE st.s NOT IN (SELECT DISTINCT SWITCH FROM MYTABLE)