How to use a variable AS a where clause? - sql

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.
)

Related

How to extract a json value inside an array inside a json in SQL

I have a JSON being returned in my query called MetaDataJSON, inside which is an array of JSONs called Results. Inside each JSON in Results are two values, Chronic and Probability. There are a couple other tables that have been joined too. Is there a way to get Chronic in a column by itself? Right now I have gotten this far (table and variable names have been made generic):
SELECT DISTINCT
JSON_QUERY(mdj.value, '$.Results[0]') [Results]
FROM table1 t1
JOIN table2 t2 ON t2.parameter1 = t1.parameter1
AND t2.parameter2 = 'ASDF'
JOIN table3 t3 ON oad.parameter3 = oa.parameter3
AND t3.parameter4 = 11
CROSS APPLY OPENJSON(t3.MetaDataJSON) as mdj
This gets me a column called Results where each entry looks like:
{"Chronic": 0, "Probability": 0.0016}
Is there an efficient way to get Chronic in a column by itself? Thanks!
You can do it like this,
WITH jsons (x)
AS
(
-- replace this part with your query
Select a.x from (select '{"Chronic": 0, "Probability": 0.0016}' x ) as a, (SELECT 1 as y union select 2 as y ) as b
)
select
(SELECT value FROM OPENJSON(x,'$') where [key]='Chronic') as "Chronic",
(SELECT value FROM OPENJSON(x,'$') where [key]='Probability') as "Probability"
from jsons;
I think you can change the #json equation to use your query. But I cannot try since I don't have your tables...
BTW, I assume you are using MSSQL...

Difference between combination of AND and OR when placed inside the parentheses vs when placed stand alone in a conditional statement

I am getting different number of results when I have the script like the following:
select count(distinct(t1.ticketid)),t1.TicketStatus from ticket as t1
inner join Timepoint as t2 on t1.TicketID=t2. ticketid
where
t2.BuilderAnalystID=10 and t1.SubmissionDT >='04-01-2018' AND
(t1.TicketBuildStatusID<>12 OR
t1.TicketBuildStatusID<>11 OR
t1.TicketBuildStatusID<>10
)
And when I use it like this:
select count(distinct(t1.ticketid)),t1.TicketStatus from ticket as t1
inner join Timepoint as t2 on t1.TicketID=t2. ticketid
where
t2.BuilderAnalystID=10 and t1.SubmissionDT >='04-01-2018' AND
t1.TicketBuildStatusID<>12 AND
t1.TicketBuildStatusID<>11 AND
t1.TicketBuildStatusID<>10
Can someone tell me why there is a difference, to me the logic is the same!
Thanks,
In your example, it won't matter because you have all AND clauses. That said, you need to be aware of precedence (ie order of operations) where NOT comes before AND, AND comes before OR and so on.
So just like 3 + 3 x 0 means 3 + (3 x 0), A or B and C means A or (B and C), even if that's not what you meant.
So in cases where you have mixed AND and OR clauses, it matters a lot.
Consider this example:
select *
from A, B
where A.id = B.id and A.family_code = 'ABC' or A.family_code = 'DEF'
It's horrible code, I admit, but for illustrative purposes, bear with me.
You may have meant this:
select *
from A, B
where A.id = B.id and (A.family_code = 'ABC' or A.family_code = 'DEF')
but you said this:
select *
from A, B
where (A.id = B.id and A.family_code = 'ABC') or A.family_code = 'DEF'
Which in the construct above completely blows away your join, resulting in a cartesian product for all cases where the family code is DEF.
So bottom line: when you mix clauses (AND, OR, NOT), it's best to use parentheses to be explicit about what you mean, even when it's not necessary.
Food for thought.
-- EDIT --
The question was changed after I wrote this so that the queries were NOT the same (ands were changed to ors).
Hopefully my explanation still helps.
After the edited to your question there will now be a difference.
t2.BuilderAnalystID=10 and t1.SubmissionDT >='04-01-2018' AND
(t1.TicketBuildStatusID<>12 OR
t1.TicketBuildStatusID<>11 OR
t1.TicketBuildStatusID<>10
)
This query will return values where t1.TicketBuildStatusID is 10, 11 and 12. It states that it should not be 10 (so 11 and 12), or not be 11 (so 10 and 11), or not be 12 (so 10 and 11).
Yes, those queries will produce different results. In fact, the first query will return every value of TicketBuildStatusID unless it has a value of NULL.
When TicketBuildStatusID has a value or 12 it doesn't have a value of 11 or 12 so the expression (t1.TicketBuildStatusID<>12 OR t1.TicketBuildStatusID<>11 OR t1.TicketBuildStatusID<>10), is true. If it has a value of 11, then the same applies again, and for every other possible value, apart from NULL (as {expression}<>NULL = NULL which is not true).
when you do this
AND
(t1.TicketBuildStatusID<>12 OR
t1.TicketBuildStatusID<>11 OR
t1.TicketBuildStatusID<>10)
you are basically doing no filter because any of the condition evaluated to true will make all the condition true e.i.
true AND (true or false or false) = true
when you do this all conditions should match like status should not be 12,11,10
AND
t1.TicketBuildStatusID<>12 AND
t1.TicketBuildStatusID<>11 AND
t1.TicketBuildStatusID<>10
OR isn't the logic that you want. Because if x = 12, then it is not 11. So, all values match x <> 12 and x <> 11.
So, just simply the logic and use not in:
select count(distinct t1.ticketid), t1.TicketStatus
from ticket t1 inner join
Timepoint t2
on t1.TicketID = t2.ticketid
where t2.BuilderAnalystID = 10 and
t1.SubmissionDT >= '2018-04-01' and
t1.TicketBuildStatusID not in (12, 11, 10)
Notes:
distinct is not a function, so there is no need to place the following expression in parentheses.
Use standard date formats. Either 'YYYYMMDD' or 'YYYY-M-DD'.

single-row subquery returns more than one row. Query not working with main query

I hve to display several cell values into one cell. So I am using this query:
select LISTAGG(fc.DESCRIPTION, ';'||chr(10))WITHIN GROUP (ORDER BY fc.SWITCH_NAME) AS DESCRIP from "ORS".SWITCH_OPERATIONS fc
group by fc.SWITCH_NAME
It is working fine. But when I am merging this with my main(complete) query then I am getting the error as: Error code 1427, SQL state 21000: ORA-01427: single-row subquery returns more than one row
Here is my complete query:
SELECT
TRACK_EVENT.LOCATION,
TRACK_EVENT.ELEMENT_NAME,
(select COUNT(*) from ORS.TRACK_EVENT b where (b.ELEMENT_NAME = sw.SWITCH_NAME)AND (b.ELEMENT_TYPE = 'SWITCH')AND (b.EVENT_TYPE = 'I')AND (b.ELEMENT_STATE = 'NORMAL' OR b.ELEMENT_STATE = 'REVERSE'))as COUNTER,
(select COUNT(*) from ORS.SWITCH_OPERATIONS fc where TRACK_EVENT.ELEMENT_NAME = fc.SWITCH_NAME and fc.NO_CORRESPONDENCE = 1 )as FAIL_COUNT,
(select MAX(cw.COMMAND_TIME) from ORS.SWITCH_OPERATIONS cw where ((TRACK_EVENT.ELEMENT_NAME = cw.SWITCH_NAME) and (cw.NO_CORRESPONDENCE = 1)) group by cw.SWITCH_NAME ) as FAILURE_DATE,
(select LISTAGG(fc.DESCRIPTION, ';'||chr(10))WITHIN GROUP (ORDER BY fc.SWITCH_NAME) AS DESCRIP from "ORS".SWITCH_OPERATIONS fc
group by fc.SWITCH_NAME)
FROM
ORS.SWITCH_OPERATIONS sw,
ORS.TRACK_EVENT TRACK_EVENT
WHERE
sw.SEQUENCE_ID = TRACK_EVENT.SEQUENCE_ID
Not only are subqueries in the SELECT list required to return exactly one row (or any time they're used for a singular comparison, like <, =, etc), but their use in that context tends to make the database execute them RBAR - Row-by-agonizing-row. That is, they're slower and consume more resources than they should.
Generally, unless the result set outside the subquery contains only a few rows, you want to construct subqueries as part of a table-reference. Ie, something like:
SELECT m.n, m.z, aliasForSomeTable.a, aliasForSomeTabe.bSum
FROM mainTable m
JOIN (SELECT a, SUM(b) AS bSum
FROM someTable
GROUP BY a) aliasForSomeTable
ON aliasForSomeTable.a = m.a
This benefits you in other ways to - it's easier to get multiple columns out of the same table-reference, for example.
Assuming that LISTAGG(...) can be included with other aggregate functions, you can change your query to look like this:
SELECT Track_Event.location, Track_Event.element_name,
Counted_Events.counter,
Failure.fail_count, Failure.failure_date, Failure.descrip
FROM ORS.Track_Event
JOIN ORS.Switch_Operations
ON Switch_Operations.sequence_id = Track_Event.sequence_id
LEFT JOIN (SELECT element_name, COUNT(*) AS counter
FROM ORS.Track_Event
WHERE element_type = 'SWITCH'
AND event_type = 'I'
AND element_state IN ('NORMAL', 'REVERSE')
GROUP BY element_name) Counted_Events
ON Counted_Events.element_name = Switch_Operations.swicth_name
LEFT JOIN (SELECT switch_name,
COUNT(CASE WHEN no_correspondence = 1 THEN '1' END) AS fail_count,
MAX(CASE WHEN no_correspondence = 1 THEN command_time END) AS failure_date,
LISTAGG(description, ';' || CHAR(10)) WITHIN GROUP (ORDER BY command_time) AS descrip
FROM ORS.Switch_Operations
GROUP BY switch_name) Failure
ON Failure.switch_name = Track_Event.element_name
This query was written to (attempt to) preserve the semantics of your original query. I'm not completely sure that's what you actually need but without sample starting data and desired results, I have no way to tell how else to improve this. For instance, I'm a little suspicious of the need of Switch_Operations in the outer query, and the fact that LISTAGG(...) is run over row where no_correspondence <> 1. I did change the ordering of LISTAGG(...), because the original column would not have done anything (because the order way the same as the grouping), so would not have been a stable sort.
Single-row subquery returns more than one row.
This error message is self descriptive.
Returned field can't have multiple values and your subquery returns more than one row.
In your complete query you specify fields to be returned. The last field expects single value from the subquery but gets multiple rows instead.
I have no clue about the data you're working with but either you have to ensure that subquery returns only one row or you have to redesign the wrapping query (possibly using joins when appropriate).

Set limit to array_agg()

I have the following Postgres query:
SELECT array_agg("Esns".id )
FROM public."Esns",
public."PurchaseOrderItems"
WHERE
"Esns"."PurchaseOrderItemId" = "PurchaseOrderItems".id
AND "PurchaseOrderItems"."GradeId"=2
LIMIT 2;
The limit will affect the rows. I want it to limit the array_agg() to 2 items. The following query works but I get my output with each entry in quotes:
SELECT array_agg ("temp")
FROM (
SELECT "Esns".id
FROM public."Esns",
public."PurchaseOrderItems"
WHERE
"Esns"."PurchaseOrderItemId" = "PurchaseOrderItems".id
AND "PurchaseOrderItems"."GradeId"=2
LIMIT 4
) as "temp" ;
This give me the following output
{(13),(14),(15),(12)}
Any ideas?
select id[1], id[2]
from (
SELECT array_agg("Esns".id ) as id
FROM public."Esns",
public."PurchaseOrderItems"
WHERE
"Esns"."PurchaseOrderItemId" = "PurchaseOrderItems".id
AND "PurchaseOrderItems"."GradeId"=2
) s
or if you want the output as array you can slice it:
SELECT (array_agg("Esns".id ))[1:2] as id_array
FROM public."Esns",
public."PurchaseOrderItems"
WHERE
"Esns"."PurchaseOrderItemId" = "PurchaseOrderItems".id
AND "PurchaseOrderItems"."GradeId"=2
The parentheses (not "quotes") in the result are decorators for the row literals. You are building an array of whole rows (which happen to contain only a single column). Instead, aggregate only the column.
Also, direct array construction from a query result is typically simpler and faster:
SELECT ARRAY (
SELECT e.id
FROM public."Esns" e
JOIN public."PurchaseOrderItems" p ON p.id = e."PurchaseOrderItemId"
WHERE p."GradeId" = 2
-- ORDER BY ???
LIMIT 4 -- or 2?
)
You need to ORDER BY something if you want a stable result and / or pick certain rows. Otherwise the result is arbitrary and can change with every next call.
While being at it I rewrote the query with explicit JOIN syntax, which is generally preferable, and used table aliases to simplify.

Define Column Aliases?

I have a query like this:
SELECT `*`
FROM (`threads` t, `members` m)
WHERE `m`.`id` = t.author
AND `t`.`type` = '0'
AND `t`.`category` = '1'
And basically what happens is that there is an ID field in both tables (members and threads) so what's happening is that the results array is getting messed up. IE: There is only one ID field which is being populated from the members table.
What I need to do is make the results with a prefix infront of their key name so I can distinguish between the two:
IE: Add 't.' to all thread fields and 'm.' to all members fields.
So results should be like: m.id = x, t.id = y
Instead, results at the moment are like: id = x (the id field from the thread table is completely overwritten by the one from the members table)
You could do it by naming each column one by one:
SELECT m.`id` AS "m_id", `t`.`id` AS "t_id", t.`username` AS "t_username"
FROM (`threads` t, `members` m)
WHERE `m`.`id` = t.author
AND `t`.`type` = '0'
AND `t`.`category` = '1'
Use:
SELECT m.id AS x,
t.id 'y'
FROM MEMBERS m
JOIN THREADS t ON t.id = m.author
AND t.type = '0'
AND t.category = '1'
Column aliases are defined on a column by column basis - you can't use wildcards/etc.
You can use the AS keyword, or simply enclose the column alias within single quotes if the column name doesn't contain special characters - use double quotes if it does. You can combine single/double quote usage with the AS keyword.
Do I have to List all the Columns?
Yes, you have to list all the columns unless you like duplicate columns because you choose to use m.* or t.*. There is no convention in SQL that supports what you ask. SELECT * is not an ideal practice - read this answer for details beyond this situation why.
Addendum
I took the liberty of rewriting your query to use ANSI-92 JOIN syntax - your example used ANSI-89. There's no performance difference.
You can set an alias for columns that have the same name:
SELECT t.ID as threadsID, m.ID as membersID, <...> WHERE `m`.`id` = t.author AND `t`.`type` = '0' AND `t`.`category` = '1'
If you just want the id's to be separate, you can do
SELECT `*`, t.id as t_id, m.id as m_id
FROM (`threads` t, `members` m)
WHERE `m`.`id` = t.author
AND `t`.`type` = '0'
AND `t`.`category` = '1'
As the others said, it's a column alias.