0

Well, the SQL statement that I have written works fine, but I would like to make it less bulky and use only one 'ACCEPTANCE_DATE' since it is the same in all tables.

I am trying to join the results of multiple tables by using UNION ALL statement. The below example works perfectly fine.

SEL COUNT(*)FROM
MY_DATABASE.HUMAN_RESOURCES
WHERE ACCEPTANCE_DATE='2015-08-09'
UNION ALL
SEL COUNT(*)FROM
MY_DATABASE.FINANCIAL_RESOURCES
WHERE ACCEPTANCE_DATE='2015-08-09'
UNION ALL
SEL COUNT(*)FROM
MY_DATABASE.INFRASTRUCTURE_RESOURCES
WHERE ACCEPTANCE_DATE='2015-08-09';

All the tables have the same type of columns, e.g. each table has a column called ‘ACCEPTANCE_DATE’. The result I get is correct. Nevertheless, I am combining a lot of tables in one query (using UNION ALL) and am wondering whether there is a way to transform this query so I do not have to update the ACCEPTANCE_DATE='2015-08-09' in each select statement. Ideally, I would like to define it just once especially when I use over 30 UNION ALL clauses, e.g.

SEL * FROM
    (SEL COUNT(*)FROM
    MY_DATABASE.HUMAN_RESOURCES
    UNION ALL
    SEL COUNT(*)FROM
    MY_DATABASE.FINANCIAL_RESOURCES
    UNION ALL
    SEL COUNT(*)FROM
    MY_DATABASE.INFRASTRUCTURE_RESOURCES) AS T1
WHERE ACCEPTANCE_DATE='2015-08-09'; 

The below statement fetches the data.

SEL COUNT(*)FROM
MY_DATABASE.HUMAN_RESOURCES
WHERE ACCEPTANCE_DATE='2015-08-09'
UNION ALL
SEL COUNT(*)FROM
MY_DATABASE.FINANCIAL_RESOURCES
WHERE ACCEPTANCE_DATE='2015-08-09'
UNION ALL
SEL COUNT(*)FROM
MY_DATABASE.INFRASTRUCTURE_RESOURCES
WHERE ACCEPTANCE_DATE='2015-08-09';

As described above, I would like it to be something like the below one:

SEL * FROM
    (SEL COUNT(*)FROM
    MY_DATABASE.HUMAN_RESOURCES
    UNION ALL
    SEL COUNT(*)FROM
    MY_DATABASE.FINANCIAL_RESOURCES
    UNION ALL
    SEL COUNT(*)FROM
    MY_DATABASE.INFRASTRUCTURE_RESOURCES) AS T1
WHERE ACCEPTANCE_DATE='2015-08-09';
alexherm
  • 1,362
  • 2
  • 18
  • 31
Andy
  • 37
  • 1
  • 6

7 Answers7

2

Maybe this would be what you are looking for:

SEL COUNT(*) FROM
    (SEL ACCEPTANCE_DATE, 1 AS ORIGIN FROM
    MY_DATABASE.HUMAN_RESOURCES
    UNION ALL
    SEL ACCEPTANCE_DATE, 2 AS ORIGIN FROM
    MY_DATABASE.FINANCIAL_RESOURCES
    UNION ALL
    SEL ACCEPTANCE_DATE, 3 AS ORIGIN FROM
    MY_DATABASE.INFRASTRUCTURE_RESOURCES) AS T1
WHERE ACCEPTANCE_DATE='2015-08-09'
GROUP BY ORIGIN;

You could even give the ORIGIN some more meaningful names and show them afterwards:

SEL ORIGIN, COUNT(*) FROM
    (SEL ACCEPTANCE_DATE, 'HUMAN' AS ORIGIN FROM
    MY_DATABASE.HUMAN_RESOURCES
    UNION ALL
    SEL ACCEPTANCE_DATE, 'FINANCIAL' AS ORIGIN FROM
    MY_DATABASE.FINANCIAL_RESOURCES
    UNION ALL
    SEL ACCEPTANCE_DATE, 'INFRASTRUCTURE' AS ORIGIN FROM
    MY_DATABASE.INFRASTRUCTURE_RESOURCES) AS T1
WHERE ACCEPTANCE_DATE='2015-08-09'
GROUP BY ORIGIN;

Which results in two columns. Now, it still doesn't solve the problem with the third value not being displayed, but this way you know which values are missing and can easily distinguish which should be zeros. If this is not sufficient for you than the code gets nastier. I might think of a solution later.


To address the problem of a missing entry for empty tables I thought of two possible solutions. The choice depends on whether the operation would be performed just once or is this a reoccurring action or just one time event. If you plan to do it multiple times it might be a good idea to create a table with all the origin tables names (or some shortcuts, you'll get the idea) on the database. For this minimal example let's consider such a thing exists under name SOURCE_TABLES:

SELECT RESOURCE FROM SOURCE_TABLES
/*
    RESOURCE:
    HUMAN
    FINANCIAL
    INFRASTRUCTURE
*/

In this case the previously provided script needs just a little modification:

SEL ST.RESOURCE, COUNT(T1.ACCEPTANCE_DATE) FROM SOURCE_TABLES ST
    LEFT JOIN (SEL ACCEPTANCE_DATE, 'HUMAN' AS ORIGIN FROM
    MY_DATABASE.HUMAN_RESOURCES
    UNION ALL
    SEL ACCEPTANCE_DATE, 'FINANCIAL' AS ORIGIN FROM
    MY_DATABASE.FINANCIAL_RESOURCES
    UNION ALL
    SEL ACCEPTANCE_DATE, 'INFRASTRUCTURE' AS ORIGIN FROM
    MY_DATABASE.INFRASTRUCTURE_RESOURCES) AS T1
    ON ST.RESOURCE = T1.ORIGIN
WHERE ACCEPTANCE_DATE='2015-08-09'
GROUP BY ST.RESOURCE;

Here, by using LEFT JOIN you ensure that every entry from the table is present in the output, even if in T1 there are no rows with specified origin. COUNT(T1.ACCEPTANCE_DATE) utilises the fact, that NULLs do not add up to the counter.

Now, if for any reason you do not like the idea of creating the table (you can't create an object on database or it is too much of a hassle for a single action) you could stick to the idea of numbers which are easier generated on the fly. The solution below exploits the same idea as above, but is more flexible in terms of the number of tables it reads from and obviously does not require you to create additional table. Considering you mentioned 30 tables, this could be a better option. One can argue that it is less readable though:

WITH numbers AS (
SEL 1 AS number
UNION ALL
SEL number + 1 FROM numbers WHERE number + 1 <= 3 -- Change 3 to the number of sourcing tables
), input_merged AS ( -- if we already use the WITH clause we can do so for merging input. It's more readable
SEL ACCEPTANCE_DATE, 1 AS ORIGIN FROM MY_DATABASE.HUMAN_RESOURCES
    UNION ALL
SEL ACCEPTANCE_DATE, 2 AS ORIGIN FROM MY_DATABASE.FINANCIAL_RESOURCES
    UNION ALL
SEL ACCEPTANCE_DATE, 3 AS ORIGIN FROM MY_DATABASE.INFRASTRUCTURE_RESOURCES
-- add further sources accordingly...
)
SEL COUNT(ACCEPTANCE_DATE) FROM numbers n
    LEFT JOIN input_merged im ON n.number = im.ORIGIN
WHERE ACCEPTANCE_DATE='2015-08-09'
GROUP BY n.number;

This should produce the first asked and desired output.

As for the numbers part in WITH statement you might want to refer to this, note that in this solution I used WITH to also merge input as Christoph did. If you use ORACLE database, utilising CONNECT BY LEVEL could be a better option to create a sequence of numbers.

Hopefully now you can achieve what you desired!

TheDecks
  • 86
  • 3
  • Thanks a lot, this is really interesting one, but I am missing the last result here. My query provides three rows (Results: 63, 226, 0) whereas yours only two (63, 226). Zero (0) is missing. – Andy Aug 09 '19 at 19:33
  • Please see the edited answer, it might be sufficient for you. If not, let me know. – TheDecks Aug 09 '19 at 20:06
  • Many thanks, TheDecks, still it gives me the below result (2 rows): FINAN 226 HUMAN 63. I really need it to show INFRUSTRUCTURE 0. – Andy Aug 09 '19 at 20:15
  • Sorry for the delayed answer, please see the (yet again) edited answer and check whether that is what you wanted. – TheDecks Aug 09 '19 at 23:06
2

You can either use a Macro as @ravioli suggested:

REPLACE MACRO my_counts(inDate DATE) AS
 (
   SELECT 'HUMAN_RESOURCES' AS tab, Count(*)
   FROM MY_DATABASE.HUMAN_RESOURCES
   WHERE ACCEPTANCE_DATE=:inDate
   UNION ALL
   SELECT 'FINANCIAL_RESOURCES', Count(*)
   FROM MY_DATABASE.FINANCIAL_RESOURCES
   WHERE ACCEPTANCE_DATE=:inDate
   UNION ALL
   SELECT 'INFRASTRUCTURE_RESOURCES', Count(*)
   FROM MY_DATABASE.INFRASTRUCTURE_RESOURCES
   WHERE ACCEPTANCE_DATE=:inDate;
 );

EXEC my_counts(DATE '2015-08-09');

Create this macro either in a database where you have Create Macro rights or within your own user (but then nobody else but you can use it).

Or you use a Common Table Expression for defining the date:

WITH cte AS 
 (
   SELECT DATE '2015-08-09' AS ACCEPTANCE_DATE
 )
SELECT 'HUMAN_RESOURCES' AS tab, Count(*)
FROM MY_DATABASE.HUMAN_RESOURCES
WHERE ACCEPTANCE_DATE=(SELECT ACCEPTANCE_DATE FROM cte)
UNION ALL
SELECT 'FINANCIAL_RESOURCES', Count(*)
FROM MY_DATABASE.FINANCIAL_RESOURCES
WHERE ACCEPTANCE_DATE=(SELECT ACCEPTANCE_DATE FROM cte)
UNION ALL
SELECT 'INFRASTRUCTURE_RESOURCES', Count(*)
FROM MY_DATABASE.INFRASTRUCTURE_RESOURCES
WHERE ACCEPTANCE_DATE=(SELECT ACCEPTANCE_DATE FROM cte);

The (SELECT ACCEPTANCE_DATE FROM cte) will be executed once and then passed as parameter to each Select.

dnoeth
  • 59,503
  • 4
  • 39
  • 56
1

Not sure if this is standard SQL or just Microsoft syntax, but in Microsoft SQL Server you could do it like this:

WITH PreSelect AS (
  SELECT ACCEPTANCE_DATE FROM HUMAN_RESOURCES
  UNION ALL 
  SELECT ACCEPTANCE_DATE FROM FINANCIAL_RESOURCES
  UNION ALL 
  SELECT ACCEPTANCE_DATE FROM INFRASTRUCTURE_RESOURCES
)
SELECT COUNT(*) FROM PreSelect WHERE ACCEPTANCE_DATE = '2015-08-09';

Or with an origin as TheDecks suggests if you need each value separately.

Christoph
  • 3,322
  • 2
  • 19
  • 28
  • Really appreciate your advice, Christoph. Quite interesting one, but it does sums it up so I get (289), whereas I would like to get three separate rows: 63, 226, 0. – Andy Aug 09 '19 at 19:51
1

2nd try:

WITH PreSelect AS (
  SELECT 'Infrastructure Resources' AS Origin, ACCEPTANCE_DATE FROM INFRASTRUCTURE_RESOURCES
  UNION ALL 
  SELECT 'Human Resources' AS Origin, ACCEPTANCE_DATE FROM HUMAN_RESOURCES
  UNION ALL 
  SELECT 'Financial Resources' AS Origin, ACCEPTANCE_DATE FROM FINANCIAL_RESOURCES
)
SELECT Origin, COUNT(*) FROM PreSelect 
WHERE ACCEPTANCE_DATE = '2015-08-09' 
GROUP BY Origin
ORDER BY 2 DESC;

This version does not sum it up and provides speaking labels and orders it by the highest number descending.

Christoph
  • 3,322
  • 2
  • 19
  • 28
  • Thanks, Christoph. This is pretty useful, but I am afraid the third row is still missing in the output Infrastructure Resources =0. – Andy Aug 09 '19 at 20:36
1

3rd try:

WITH PreSelect AS (
  SELECT 'Infrastructure Resources' AS Origin, ACCEPTANCE_DATE FROM INFRASTRUCTURE_RESOURCES
  UNION ALL 
  SELECT 'Human Resources' AS Origin, ACCEPTANCE_DATE FROM HUMAN_RESOURCES
  UNION ALL 
  SELECT 'Financial Resources' AS Origin, ACCEPTANCE_DATE FROM FINANCIAL_RESOURCES
), 
Categories AS (
  SELECT DISTINCT Origin FROM PreSelect
),
ReferenceDate AS (
    SELECT Origin, COUNT(*) RecordCount FROM PreSelect 
    WHERE ACCEPTANCE_DATE = '2015-08-09' 
    GROUP BY Origin
)
SELECT c.Origin, ISNULL(rd.RecordCount, 0) AS RecordCount FROM Categories c
LEFT OUTER JOIN ReferenceDate rd ON  c.Origin = rd.Origin 
ORDER BY 2 DESC;

Like this also rows with 0 entries appear...

Christoph
  • 3,322
  • 2
  • 19
  • 28
  • Oh, ```ISNULL``` is probably Microsoft syntax. That would be ```CASE WHEN rd.RecordCount IS NULL THEN 0 ELSE rd.RecordCount END``` in standard SQL. – Christoph Aug 10 '19 at 19:43
  • If a DBMS supports `CASE` it also supports the `COALESCE(rd.RecordCount, 0)` shortcut as replacement for T-SQL `ISNULL`. – dnoeth Aug 11 '19 at 11:12
  • Yes, it worked out with COALESCE(rd.RecordCount, 0). – Andy Aug 16 '19 at 20:59
0

Yes cte is the best option for this... In CTE , it filter the data using where clause from the result (union all)

vijay sahu
  • 765
  • 1
  • 7
  • 29
0

If your issue is that you only want to define the ACCEPTANCE_DATE one time, then you can keep your original SQL and use a macro or an SP and parameterize ACCEPTANCE_DATE as an input value.

If you want to re-write the SQL, maybe try something like this:

SELECT MyCount FROM (
  SELECT ACCEPTANCE_DATE, MyCount
  FROM (
    SELECT ACCEPTANCE_DATE, COUNT(*) AS MyCount
    FROM MY_DATABASE.HUMAN_RESOURCES
    GROUP BY ACCEPTANCE_DATE
  )

  UNION ALL

  SELECT ACCEPTANCE_DATE, MyCount
  FROM (
    SELECT ACCEPTANCE_DATE, COUNT(*) AS MyCount
    FROM MY_DATABASE.FINANCIAL_RESOURCES
    GROUP BY ACCEPTANCE_DATE
  )

  UNION ALL

  SELECT ACCEPTANCE_DATE, MyCount
  FROM (
    SELECT ACCEPTANCE_DATE, COUNT(*) AS MyCount
    FROM MY_DATABASE.INFRASTRUCTURE_RESOURCES
    GROUP BY ACCEPTANCE_DATE
  )
) src
WHERE ACCEPTANCE_DATE = '2015-08-09';

This will likely not perform very well if you have lots of rows in these tables, unless you have some optimization like PPI defined on ACCEPTANCE_DATE fields.

I haven't tested this, so you may have some syntax errors to work through, but it should get you what you want.

ravioli
  • 3,749
  • 3
  • 14
  • 28