SQL Column Parameter Bind Variable with either or logic - sql

I apologize if the title is misleading.
I'm trying to avoid using two different queries. With that in mind,
I have the following sample query
SELECT COUNT (*) COUNT,
SUM (AMT) AS DED_AMT,
SUM (SURCOST),
SUM (DEALSUM),
NVL (TO_CHAR (SUM (RETAIL)), 'N/A') AS RETAIL,
MNFCID
FROM (SELECT B.ID, B.CD, A.*
FROM OUTPUTS_A A JOIN OUTPUTS_B B ON A.ID = B.ID
WHERE B.ID = :ID AND B.CD = UPPER (:CD))
GROUP BY ID;
that returns a result you can see in the first screenshot.
Notice, I'm passing two bind variables in the query, :ID, :CD. They have to go together and that's why I'm using AND operator there.
Sometimes, I have MFCID only and not :ID and :CD.
This is the logic I'm thinking about.
I would like to modify the query such a way that I should be able to pass MFCID as a bind variable. Let's say :mfcid is the variable I'm passing.
If I have the values for :ID and :CD handy, I will pass those values and pass nothing for :mfcid. (Nothing in a sense that I won't pass anything. This field CAN'T be null)
If I only have the value for :mfcid handy, I will pass that value and pass nothing for :ID and :CD. (Nothing in a sense that I won't pass anything. This field CAN'T be null)
Either way it should return me the same result.
I have tried putting it this way: AND B.MNFCID = NVL(:MNFCID, B.MNFCID) but it takes forever because it's always true if don't pass anything.

Does this do what you want?
SELECT COUNT(*) COUNT, SUM(AMT) AS DED_AMT, SUM(SURCOST),
SUM(DEALSUM), NVL(TO_CHAR(SUM(RETAIL)), 'N/A') AS RETAIL,
MNFCID
FROM OUTPUTS_A A JOIN
OUTPUTS_B B
ON A.ID = B.ID
WHERE (B.ID = :ID OR :ID IS NULL) AND
(B.CD = UPPER(:CD) OR :CD IS NULL)
GROUP BY B.ID;
If this doesn't work -- and it might not -- you may want to use dynamic SQL so the appropriate indexes can more readily be used.

Related

Snowflake: SQL compilation error: not a valid group by expression

I'm trying to call a window function inside of a case statement, as such:
SELECT
DISTINCT properties.property_id
COALESCE(MAX(CASE
WHEN units_count.unit_type = 'NORMAL' THEN units_count.unit_count
END) OVER (PARTITION BY properties.property_id),
0)::INT AS normal_units_count
FROM units_count
JOIN properties ON units_count.property_id = properties.property_id
I'm receiving the following error:
SQL compilation error: [IFF(UNITS_COUNT.UNIT_TYPE = 'NORMAL', UNITS_COUNT.UNIT_COUNT, SYSTEM$NULL_TO_FIXED(null))] is not a valid group by expression
I've tried adding a qualify clause to remove the MAX() function:
SELECT
DISTINCT properties.property_id
COALESCE(CASE
WHEN units_count.unit_type = 'NORMAL' THEN units_count.unit_count
END OVER (PARTITION BY properties.property_id),
0)::INT AS normal_units_count
FROM units_count
JOIN properties ON units_count.property_id = properties.property_id
QUALIFY units_count.unit_count = MAX(units_count.unit_count) OVER (PARTITION BY properties.property_id)
The code executes, but the qualify clause results in unwanted filtering for other fields. Can I keep the existing logic (with MAX()) or do I need to include a qualify clause?
The DISTINCT is in effect a grouping operation, but your MAX is a for every row with the OVER clause.
which implies as "basic SQL" this should work:
SELECT
p.property_id,
MAX(IFF(uc.unit_type = 'NORMAL', uc.unit_count, 0)) as v1 max_units_count
FROM units_count AS uc
JOIN properties AS p
ON uc.property_id = p.property_id
GROUP BY 1
but as your "other fields" implies you are selecting other things and without knowing what/how you are doing that is hard to see what you are wanting todo.
also your max(unit_count) does not so much feel like it is a normal_unit_count.
But you example needs to pull in a second+ column of what you are wanting to do to see how they should be co-handled. But I would be inclined with zero information to suggest you use a CTE to find the per property_id the MAX unit_count and then join that result to a second read from your data. Because with zero insights to the other operations and how they could re-use that read, I have experienced it is better to GROUP/JOIN then WINDOW(over partition)/ANYVALUE. But that is another option.
Thus (without testing) this might work:
SELECT
DISTINCT p.property_id
ANYVALUE(COALESCE(MAX(CASE
WHEN uc.unit_type = 'NORMAL' THEN uc.unit_count
END) OVER (PARTITION BY p.property_id),
0)::INT) AS normal_units_count
FROM units_count AS uc
JOIN properties AS p
ON uc.property_id = p.property_id

Write an additional column to query result with different values everytime

I've been searching for quite a while now and I haven't been able to find an answer for what I was looking. I have the following query:
SELECT DISTINCT o.titulo, o.fecha_estreno
FROM Obra o
WHERE (o.titulo LIKE '%Barcelona%' AND EXISTS(SELECT p.id_obra FROM Pelicula p WHERE p.id_obra = o.id_obra)) OR EXISTS(SELECT DISTINCT pa.id_obra
FROM Participa pa
WHERE pa.id_obra = o.id_obra AND EXISTS(SELECT DISTINCT l.nombre FROM Lugar l
WHERE l.nombre LIKE '%Barcelona%' AND EXISTS(SELECT DISTINCT tl.id_lugar FROM TieneLugar tl
WHERE tl.id_lugar = l.id_lugar AND tl.id_profesional = pa.id_profesional))) OR EXISTS(SELECT DISTINCT er.id_obra
FROM EstaRelacionado er
WHERE er.id_obra = o.id_obra AND EXISTS(SELECT DISTINCT k.keyword
FROM Keywords k
WHERE k.id_keyword = er.id_keyword AND k.keyword LIKE '%Barcelona%'));
What it basically does is it searches for every movie in my database which is related in some way to the city it gets. I wanted to have a third column showing for every result, with the reason the row is showing as a result (for example: TITLE CONTAINS IT, or ACTOR FROM THE MOVIE BORN THERE, etc.)
Thank you for your patience and help!
EDIT: As suggested, here are some examples of output. The column should show just the first cause related to the movie:
TITULO FECHA_ESTRENO CAUSE
---------- ---------------- ----------
Barcelona mia 1967 TITLE
https://www.postgresql.org/docs/7.4/static/functions-conditional.html
The SQL CASE expression is a generic conditional expression, similar
to if/else statements in other languages:
CASE WHEN condition THEN result
[WHEN ...]
[ELSE result]
END
CASE clauses can be used wherever an expression is valid. condition is an expression that returns a boolean result. If
the result is true then the value of the CASE expression is the result
that follows the condition. If the result is false any subsequent WHEN
clauses are searched in the same manner. If no WHEN condition is true
then the value of the case expression is the result in the ELSE
clause. If the ELSE clause is omitted and no condition matches, the
result is null.
Example for your case:
SELECT (CASE WHEN EXISTS(... l.nombre LIKE '%Barcelona%') THEN 'TITLE CONTAINS IT' WHEN <conditon for actor> THEN 'ACTOR WA BORN THERE' WHEN ... END) as reason
Here is one solution.
Create a subquery for each search condition.
include the reason in the subqueries' projections
outer join the subqueries so it doesn't matter which one hist
filter to make sure that at least one of your subqueries has a positive result
use coalesce() to get one reason.
I haven't done all your conditions, and I've probably mangled your logic but this is the general idea:
SELECT o.titulo
, o.fecha_estreno
, coalesce(t1.reason, t2.reason) as reason
FROM Obra o
left outer join ( select id_obra, 'title contains it' as reason
from Obra
where titulo LIKE '%Barcelona%' ) t1
on t1.id_obra o.id_obra
left outer join ( select distinct pa.id_obra , 'takes place there' as reason
from Participa pa
join TieneLugar tl
on tl.id_profesional = pa.id_profesional
join Lugar l
on tl.id_lugar = l.id_lugar
where l.nombre LIKE '%Barcelona%' ) t2
on t2.id_obra o.id_obra
WHERE t1.id_obra is not null
or t2.id_obra is not null
/
coalesce() just returns the first non-null value which means you won't see multiple reasons if you get more than one hit. So order the arguments to put the most powerful reasons first.
Also, you should consider consider using Oracle Text. It's the smartest way to wrangle this sort of keyword searching. Find out more.

How to use a variable AS a where clause?

I have one where clause which I have to use multiple times. I am quite new to Oracle SQL, so please forgive me for my newbe mistakes :). I have read this website, but could not find the answer :(. Here's the SQL statement:
var condition varchar2(100)
exec :condition := 'column 1 = 1 AND column2 = 2, etc.'
Select a.content, b.content
from
(Select (DBMS_LOB.SUBSTR(ost_bama_vrij_veld.inhoud,3)) as content
from table_name
where category = X AND :condition
group by (DBMS_LOB.SUBSTR(ost_bama_vrij_veld.inhoud,3))
) A
,
(Select (DBMS_LOB.SUBSTR(ost_bama_vrij_veld.inhoud,100)) as content
from table_name
where category = Y AND :condition
group by (DBMS_LOB.SUBSTR(ost_bama_vrij_veld.inhoud,100))) B
GROUP BY
a.content, b.content
The content field is a CLOB field and unfortunately all values needed are in the same column. My query does not work ofcourse.
You can't use a bind variable for that much of a where clause, only for specific values. You could use a substitution variable if you're running this in SQL*Plus or SQL Developer (and maybe some other clients):
define condition = 'column 1 = 1 AND column2 = 2, etc.'
Select a.content, b.content
from
(Select (DBMS_LOB.SUBSTR(ost_bama_vrij_veld.inhoud,3)) as content
from table_name
where category = X AND &condition
...
From other places, including JDBC and OCI, you'd need to have the condition as a variable and build the query string using that, so it's repeated in the code that the parser sees. From PL/SQL you could use dynamic SQL to achieve the same thing. I'm not sure why just repeating the conditions is a problem though, binding arguments if values are going to change. Certainly with two clauses like this it seems a bit pointless.
But maybe you could approach this from a different angle and remove the need to repeat the where clause. Querying the table twice might not be efficient anyway. You could apply your condition once as a subquery, but without knowing your indexes or the selectivity of the conditions this could be worse:
with sub_table as (
select category, content
from my_table
where category in (X, Y)
and column 1 = 1 AND column2 = 2, etc.
)
Select a.content, b.content
from
(Select (DBMS_LOB.SUBSTR(ost_bama_vrij_veld.inhoud,3)) as content
from sub_table
where category = X
group by (DBMS_LOB.SUBSTR(ost_bama_vrij_veld.inhoud,3))
) A
,
(Select (DBMS_LOB.SUBSTR(ost_bama_vrij_veld.inhoud,100)) as content
from sub_table
where category = Y
group by (DBMS_LOB.SUBSTR(ost_bama_vrij_veld.inhoud,100))) B
GROUP BY
a.content, b.content
I'm not sure what the grouping is for - to eliminate duplicates? This only really makes sense if you have a single X and Y record matching the other conditions, doesn't it? Maybe I'm not following it properly.
You could also use a case statement:
select max(content_x), max(content_y)
from (
select
case when category = X
then DBMS_LOB.SUBSTR(ost_bama_vrij_veld.inhoud,3) end as content_x,
case when category = Y
then DBMS_LOB.SUBSTR(ost_bama_vrij_veld.inhoud,100) end as content_y,
from my_table
where category in (X, Y)
and column 1 = 1 AND column2 = 2, etc.
)

Squeryl Select Duplicates

I would like to find overlapping data with a Squeryl query. I can do so by using the method found here with normal SQL, but can't figure out how to do so using Squeryl.
Basically I need to convert this line that finds Non-Distinct rows to Squeryl
SELECT *
FROM myTable L1
JOIN(
SELECT myField1,myField2
FROM myTable
GROUP BY myField1,myField2
HAVING COUNT(*) >= 2
) L2
ON L1.myField1 = L2.myField1 AND L1.myField2 = L2.myField2;
EDIT : More importantly I need to be able to do this dynamically. I have a bit of a complex dynamic query that I call that may rely on different options being passed. If an Option is defined then it should call this, otherwise inhibit if null. But groupBy does not support an inhibitBy method. To see a full explanation of my current method look here
def getAllJoined(
hasFallback:Option[String] = None
showDuplicates:Option[String] = None):List[(Type1,Type2)] = transaction{
join(mainTable,
table2,
table3,
table3,
table4.leftOuter,
table4.leftOuter,
table5,
table6)((main, attr1, attr2, attr3, attr4, attr5, attr6, attr7) =>
where(
main.fallBack.isNotNull.inhibitWhen(!hasFallback.isDefined)
)
//What to do here to only find duplicates when showDuplicates.isDefined? AKA Non-Distinct
select(main,attr1,attr2,attr3,attr4,attr5,attr6,attr7)
on(
(main.attr1Col === attr1.id) ,
(main.attr2Col === attr2.id) ,
(main.attr3Col === attr3.id) ,
(main.attr4Col === attr4.map(_.id)) ,
(main.attr5Col === attr5.map(_.id)) ,
(main.attr6Col === attr6.id) ,
(main.attr7Col === attr7.id)
)
).toList
Check out this discussion on Google Groups. Looks like they had fixed a bug related to inhibited having in 2011, but not sure why it still persists in your case. They also have an example query using the having clause in the same thread.

SQL "Count (Distinct...)" returns 1 less than actual data shows?

I have some data that doesn't appear to be counting correctly. When I look at the raw data I see 5 distinct values in a given column, but when I run an "Count (Distinct ColA)" it reports 4. This is true for all of the categories I am grouping by, too, not just one. E.g. a 2nd value in the column reports 2 when there are 3, a 3rd value reports 1 when there are 2, etc.
Table A: ID, Type
Table B: ID_FK, WorkID, Date
Here is my query that summarizes:
SELECT COUNT (DISTINCT B.ID_FK), A.Type
FROM A INNER JOIN B ON B.ID_FK = A.ID
WHERE Date > 5/1/2013 and Date < 5/2/2013
GROUP BY Type
ORDER BY Type
And a snippet of the results:
4|Business
2|Design
2|Developer
Here is a sample of my data, non-summarized. Pipe is the separator; I just removed the 'COUNT...' and 'GROUP BY...' parts of the query above to get this:
4507|Business
4515|Business
7882|Business
7889|Business
7889|Business
8004|Business
4761|Design
5594|Design
5594|Design
5594|Design
7736|Design
7736|Design
7736|Design
3132|Developer
3132|Developer
3132|Developer
4826|Developer
5403|Developer
As you can see from the data, Business should be 5, not 4, etc. At least that is what my eyes tell me. :)
I am running this inside a FileMaker 12 solution using it's internal ExecuteSQL call. Don't be concerned by that too much, though: the code should be the same as nearly anything else. :)
Any help would be appreciated.
Thanks,
J
Try using a subquery:
SELECT COUNT(*), Type
FROM (SELECT DISTINCT B.ID_FK, A.Type Type
FROM A
INNER JOIN B ON B.ID_FK = A.ID
WHERE Date > 5/1/2013 and Date < 5/2/2013) x
GROUP BY Type
ORDER BY Type
This could be a FileMaker issue, have you seen this post on the FileMaker forum? It describes the same issue (a count distinct smaller by 1) with 11V3 back in 03/2012 with a plug in, then updated with same issue with 12v3 in 11/2012 with ExecuteSQL. It didn't seem to be resolved in either case.
Other considerations might be if there are any referential integrity constraints on the joined tables, or if you can get a query execution plan, you might find it is executing the query differently than expected. not sure if FileMaker can do this.
I like Barmar's suggestion, it would sort twice.
If you are dealing with a bug, directing the COUNT DISTINCT, Join and/or Group By by structuring the query to make them happen at different times might work around it:
SELECT COUNT (DISTINCT x.ID), x.Type
FROM (SELECT A.ID ID, A.Type Type
FROM A
INNER JOIN B ON B.ID_FK = A.ID
WHERE B.Date > 5/1/2013 and B.Date < 5/2/2013) x
GROUP BY Type
ORDER BY Type
you might also try replacing B.ID_FK with A.ID, who knows what context it applies, such as:
SELECT COUNT (DISTINCT A.ID), A.Type