I have an SQL query joined on multiple tables (all INNER JOINS).
The below is an example of the query I am trying to run (the ? is to illustrate the position in which I presume the answer to my question will be rectified).
Case
(
SELECT Count(ID)
FROM CPD_Candidates cpdCan
WHERE
cpdCan.CandidateID = can.CandidateID
AND
(
cpdCan.DateEnded >= GETDATE()
OR
coalesce(cpdCan.DateEnded, '') = N'1-Jan-1900'
)
AND
cpdCan.Deleted <> 1
)
When ? > 0 then 'Bigger' else 'Equal or Smaller' End
)
The idea with the above is that instead of the ? the actual value I want to compare against would be Count(ID), if it's greater than 0 I want it to SELECT 'Bigger', otherwise it should SELECT 'Equal or Smaller'. So a more-accurate depiction of what I wish to run would be the below.
Case
(
SELECT Count(ID)
FROM CPD_Candidates cpdCan
WHERE
cpdCan.CandidateID = can.CandidateID
AND
(
cpdCan.DateEnded >= GETDATE()
OR
coalesce(cpdCan.DateEnded, '') = N'1-Jan-1900'
)
AND
cpdCan.Deleted <> 1
)
When
Count(cpdCan.ID) > 0 then 'Bigger' else 'Equal or Smaller' End
)
Of course there is a syntax error above but I am enquiring as to whether it is possible to compare like in the above SQL query structure but replacing Count(cpdCan.ID) > 0 with some other means to achieve that value & logic?
If this is un-achievable in SQL Server 2016 what other means would be a better solution to this XY?
I think that you mean:
case when
(
SELECT Count(ID)
FROM CPD_Candidates cpdCan
WHERE
cpdCan.CandidateID = can.CandidateID
AND (cpdCan.DateEnded >= GETDATE() OR coalesce(cpdCan.DateEnded, '') = N'1-Jan-1900')
AND cpdCan.Deleted <> 1
) > 0
then 'Bigger'
else 'Equal or Smaller'
End
How can i count columns that hold particular values - but have it as a grand-total.
table data:
Code No
1 *
2 -
3 4
4
If for example i wanted to count how many rows had * and - and space
I could do
Case when No = '*'
Then COUNT(No)
when No = '-' then count(No)
when No = '' then count(No)
else 0 end as 'Count'
but this returns 4
http://sqlfiddle.com/#!9/f73409/4
I would want this to return 3
Any help would be appreciated
Use IN:
select Sum(Case when No IN ('*', '-', '') then 1 else 0 end) as Count
from Table1
See Fiddle.
Standard SQL has a particular feature for that: the filter clause that follows aggregates.
Unfortunately, it is not very widely supported (mainly PostgreSQL).
There is an easy workaround using case however:
COUNT(CASE WHEN <condition> THEN 1 END)
This works because the implied else null clause of case and because count does not count null.
More about the filter clause and ways to mimic it: http://modern-sql.com/feature/filter
Let's imagine I have a query like the following:
SELECT
CASE WHEN ONE = 1 THEN 1 ELSE 0 END,
CASE WHEN JUST_ONE = 1 THEN 1 ELSE 0 END,
CASE WHEN ANOTHER_ONE = 1 THEN 1 ELSE 0 END,
CASE WHEN TWO = 1 THEN 1 ELSE 0 END,
CASE WHEN JUST_TWO = 1 THEN 1 ELSE 0 END,
CASE WHEN ANOTHER_TWO = 1 THEN 1 ELSE 0 END
-- 20 more things like that where changes only columns name
FROM
SOME_TABLE;
As you can see, the only difference between these two groups is that in the first one I use columns that have 'ONE' and in the second the ones that have 'TWO' and I have around 30 groups like this in my actual query so I wonder if there is a way to shorten it somehow?
Since they are different columns, you must explicitly mention them separately in the SELECT list. You cannot do it dynamically in pure SQL.
I would suggest, using a good text editor, it would hardly take a minute or two to write the entire SQL.
You could use DECODE which will have some less syntax instead of CASE expression which is verbose.
For example,
DECODE(ONE, 1, 1, 0) AS col1,
DECODE(JUST_ONE, 1, 1, 0) AS col2,
DECODE(ANOTHER_ONE, 1, 1, 0) AS col3,
DECODE(TWO, 1, 1, 0) AS col4,
DECODE(JUST_TWO, 1, 1, 0) AS col5,
DECODE(ANOTHER_TWO, 1, 1, 0) as col6
I would suggest to stick to SQL, and not use PL/SQL. They are not the same, they are different engines. PL --> Procedural Language.
But if you insist, then you could use a cursor for loop to loop through all the columns in [DBA|ALL|USER]_TAB_COLS. You could use a SYS_REFCURSOR to see the data. First you will have to build the dynamic SQL.
Below is an example of dynamically creating query. You can put this in e.g. cursor variable if you want more queries created.
select 'SELECT ' || listagg('CASE WHEN '||column_name||' = 1 THEN 1 ELSE 0 END ' || column_name,',') within group(order by column_name) || ' FROM YOUR_TABLE_NAME'
from cols
where data_type in ('NUMBER')
and table_name = 'YOUR_TABLE_NAME';
You can use COLS view to get all column names and their datatypes for all your tables.
Oracle SQL metaprogramming is possible by combining Oracle Data Cartridge with the ANY types.
Even using an existing package for metaprogramming, building a query inside a query is complicated and should be avoided when possible. The other answers are generally better, even though they may require an extra step and are not "pure" SQL.
If you really need to do everything in a single SQL statement, try my open source project, Method4. After installing it, create a sample schema:
create table some_table(
one number, just_one number, another_one number,
two number, just_two number, another_two number);
insert into some_table values(1,1,1,2,2,2);
Run this query that builds the real SELECT statement based on the data dictionary:
select * from table(method4.dynamic_query(
q'[
--Find columns that match pattern and aggregate into SELECT list.
select
'SELECT'||chr(10)||
rtrim(listagg(' CASE WHEN '||column_name||' = 1 THEN 1 ELSE 0 END '||column_name||',', chr(10))
within group (order by order_by1, order_by2), ',')||chr(10)||
'FROM some_table' sql_statement
from user_tab_columns
join
(
--Column names that might match the pattern [null|JUST_|ANOTHER]SPELLED_NUMBER.
select prefix||spelled_number possible_column_names
,order_by1, order_by2
from
(
--Numbers 1-10.
select upper(to_char(to_date(level, 'j'), 'jsp')) spelled_number
,level order_by1
from dual
--Increase this number up to the maximum possible number.
connect by level <= 10
)
cross join
(
--Possible prefixes.
select null prefix, 1 order_by2 from dual union all
select 'JUST_' prefix, 2 order_by2 from dual union all
select 'ANOTHER_' prefix, 3 order_by2 from dual
)
) column_names
on user_tab_columns.column_name = column_names.possible_column_names
where table_name = 'SOME_TABLE'
]'
));
The same query will return different columns based on the table:
ONE JUST_ONE ANOTHER_ONE TWO JUST_TWO ANOTHER_TWO
---------- ---------- ----------- ---------- ---------- -----------
1 1 1 0 0 0
That's some seriously complicated coding to avoid typing a few lines. This is the kind of solution that a manager would dream up when he first hears that hard-coding is always bad.
This literally answers the question about metaprogramming an Oracle SQL SELECT statement. There are a few rare cases where this approach is a life-saver. But 99.9% of the time it's better to do things the simple way even if it is a bit less automated.
The generative approach (in SQL limited with manuall post processing) could be
with src as (
select 'ONE' col_name from dual union all
select 'TWO' col_name from dual
)
select
'CASE WHEN '||col_name||' = 1 THEN 1 ELSE 0 END,'||chr(10)||
'CASE WHEN JUST_'||col_name||' = 1 THEN 1 ELSE 0 END,'||chr(10)||
'CASE WHEN ANOTHER_'||col_name||' = 1 THEN 1 ELSE 0 END,'||chr(10)
from src;
giving
CASE WHEN ONE = 1 THEN 1 ELSE 0 END,
CASE WHEN JUST_ONE = 1 THEN 1 ELSE 0 END,
CASE WHEN ANOTHER_ONE = 1 THEN 1 ELSE 0 END,
CASE WHEN TWO = 1 THEN 1 ELSE 0 END,
CASE WHEN JUST_TWO = 1 THEN 1 ELSE 0 END,
CASE WHEN ANOTHER_TWO = 1 THEN 1 ELSE 0 END,
Note that you provide the list of your column names and gets the expanded syntax.
Typically you need to remove the last delimiter and add the wrapping SELECT frame.
I am attempting to create a row called Flag that will keep a count of when Value is above 2. Later I will need to sum flag as a count.
I currently have:
CASE
WHEN Value > 2
THEN 1
ELSE 0
END AS 'Flag',
CASE
WHEN 'Flag' = 1
THEN 1
ELSE 0
END AS 'FollowedUpCorrectly'
I receive the error:
Conversion failed when converting the varchar value 'Flag' to data
type int.
How can I force the 1 or 0 to be an INT in order to do later math?
I've looked around and I can't seem to find a way that fits.
To be able to use previously created columns in the select, you'll need to use for example outer apply, with something like this:
select
*
from table1
outer apply (
select CASE WHEN Value > 2 THEN 1 ELSE 0 END AS Flag
) X
outer apply (
select CASE WHEN X.Flag = 1 THEN 1 ELSE 0 END AS FollowedUpCorrectly
) Y
Test this in SQL Fiddle
You could use CTE or a subquery to create a flag and then do your case statement as needed in the outer query like this:
;WITH q1
AS (
SELECT
col1
,col2
,col3
,CASE
WHEN Value > 2
THEN 1
ELSE 0
END AS 'Flag'
FROM your_table --change this to match your table and column name
)
SELECT q1.col1
,q1.col2
,q1.col3
,CASE
WHEN q1.Flag = 1
THEN 1
ELSE 0
END AS 'FollowedUpCorrectly'
FROM q1;
I might misunderstand what you are after.
CASE
WHEN Value > 2
THEN 1
ELSE 0
END AS 'Flag',
CASE
WHEN 'Flag' = 1
THEN 1
ELSE 0
END AS 'FollowedUpCorrectly'
If these two lines are in the same code block, 'Flag' is unknown in the second Case Statement.
Update: As Siyual has pointed out, Flag is a string literal. Try changing the name to something that is not a reserved word.
You are comparing a string ('Flag') to an int (1). Perhaps you meant to refer to the first case that you named 'Flag'. If so, try referring to it without using the single quotes. Then the analyzer will recognize it and accept it as an int, which it is. But 'Flag' is a string. Flag is an int.
EDIT: damien the unbeliever, my apologies, trying to be terse I omitted saying that the design of the table is not under my control; this table is a "dump" of data we receive from another vendor, and I have to convert it from their format to ours. The reason I need a query is to find out if the data is consistent with assumptions in other parts of code. The solutions proposed looking for length or exact match of the concatenated strings are better than my pair of queries for the problem I described.
I have a working pair of queries for my problem, but I wondered if there's something a bit prettier. Exactly one of taxidflag1, taxidflag2, taxidflag3 should be filled in with * in each row. So I confirm they all have two blanks and one * like this. All fields are are non nullable.
select * from acct where 2 <>
(case when taxidFlag1 <> '' then 1 else 0 end) +
(case when taxidFlag2 <> '' then 1 else 0 end) +
(case when taxidFlag3 <> '' then 1 else 0 end)
select * from acct where 1 <>
(case when taxidFlag1 = '*' then 1 else 0 end) +
(case when taxidFlag2 = '*' then 1 else 0 end) +
(case when taxidFlag3 = '*' then 1 else 0 end)
You could do this:
select * from acct where taxidFlag1 + taxidFlag2 + taxidFlag3 = '*';
This condition is only true if two are empty ('') and one is a asterisk (*).
select *
from
acct a1
where
(select count(*) from acct unpivot (foo for taxidFlag in (taxidFlag1, taxidFlag2, taxidFlag3)) as unp where unp.row_id = a1.row_id and foo = '*') <> 1
;
where row_id is your primary key field.