SQL: When and why are two on conditions allowed? - sql

Question:
I recently had an interesting SQL problem.
I had to get the leasing contract for a leasing object.
The problem was, there could be multiple leasing contracts per room, and multiple leasing object per room.
However, because of bad db tinkering, leasing contracts are assigned to the room, not the leasing object. So I had to take the contract number, and compare it to the leasing object number, in order to get the right results.
I thought this would do:
SELECT *
FROM T_Room
LEFT JOIN T_MAP_Room_LeasingObject
ON MAP_RMLOBJ_RM_UID = T_Room.RM_UID
LEFT JON T_LeasingObject
ON LOBJ_UID = MAP_RMLOBJ_LOBJ_UID
LEFT JOIN T_MAP_Room_LeasingContract
ON T_MAP_Room_LeasingContract.MAP_RMCTR_RM_UID = T_Room.RM_UID
LEFT JOIN T_Contracts
ON T_Contracts.CTR_UID = T_MAP_Room_LeasingContract.MAP_RMCTR_CTR_UID
AND T_Contracts.CTR_No LIKE ( ISNULL(T_LeasingObject.LOBJ_No, '') + '.%' )
WHERE ...
However, because the mapping table gets joined before I have the contract number, and I cannot get the contract number without having the mapping table, i have doubled entries.
The problem is a little more complicated, as rooms having no leasing contract needed also to show up, so I couldn't just use an inner join.
With a little bit experimenting, I found that this works as expected:
SELECT *
FROM T_Room
LEFT JOIN T_MAP_Room_LeasingObject
ON MAP_RMLOBJ_RM_UID = T_Room.RM_UID
LEFT JON T_LeasingObject
ON LOBJ_UID = MAP_RMLOBJ_LOBJ_UID
LEFT JOIN T_MAP_Room_LeasingContract
LEFT JOIN T_Contracts
ON T_Contracts.CTR_UID = T_MAP_Room_LeasingContract.MAP_RMCTR_CTR_UID
ON T_MAP_Room_LeasingContract.MAP_RMCTR_RM_UID = T_Room.RM_UID
AND T_Contracts.CTR_No LIKE ( ISNULL(T_LeasingObject.LOBJ_No, '') + '.%' )
WHERE ...
I now see why the two on conditions in one join, which usually are courtesy of query designer, can be useful, and what difference it makes.
I was wondering whether this is a MS-SQL/T-SQL specific thing, or whether this is standard sql.
So I tried in PostgreSQL with another 3 tables.
So I wrote this query on 3 other tables:
SELECT *
FROM t_dms_navigation
LEFT JOIN t_dms_document
ON NAV_DOC_UID = DOC_UID
LEFT JOIN t_dms_project
ON PJ_UID = NAV_PJ_UID
and tried to turn it into one with two on conditions
SELECT *
FROM t_dms_navigation
LEFT JOIN t_dms_document
LEFT JOIN t_dms_project
ON PJ_UID = NAV_PJ_UID
ON NAV_DOC_UID = DOC_UID
So I thought it's t-sql specific, but quickly tried in MS-SQL too, just to find to my surprise that it doesn't work there either.
I thought it might be because of missing foreign keys, so i removed them on all tables in my room query, but it still did not work.
So here my question:
Why are 2 on conditions even legal, does this have a name, and why does it not work on my second example ?

It's standard SQL. Each JOIN has to have a corresponding ON clause. All you're doing is shifting around the order that the joins happen in1 - it's a bit like changing the bracketing of an expression to get around precedence rules.
A JOIN B ON <cond1> JOIN C ON <cond2>
First joins A and B based on cond1. It then takes that combined rowset and joins it to C based on cond2.
A JOIN B JOIN C ON <cond1> ON <cond2>
First joins B and C based on cond1. It then takes A and joins it to the previous combined rowset, based on cond2.
It should work in PostgreSQL - here's the relevant part of the documentation of the SELECT statement:
where from_item can be one of:
[ ONLY ] table_name [ * ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
( select ) [ AS ] alias [ ( column_alias [, ...] ) ]
with_query_name [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
function_name ( [ argument [, ...] ] ) [ AS ] alias [ ( column_alias [, ...] | column_definition [, ...] ) ]
function_name ( [ argument [, ...] ] ) AS ( column_definition [, ...] )
from_item [ NATURAL ] join_type from_item [ ON join_condition | USING ( join_column [, ...] ) ]
It's that last line that's relevant. Notice that it's a recursive definition - what can be to the left and right of a join can be anything - including more joins.
1As always with SQL, this is the logical processing order - the system is free to perform physical processing in whatever sequence it feels will work best, provided the result is consistent.

Related

postgreSQL query empty array fields within jsonb column

device_id | device
-----------------------------
9809 | { "name" : "printer", "tags" : [] }
9810 | { "name" : "phone", "tags" : [{"count": 2, "price" : 77}, {"count": 3, "price" : 37} ] }
For the following postgres SQL query on a jsonb column "device" that contains array 'tags':
SELECT t.device_id, elem->>'count', elem->>'price'
FROM tbl t, json_array_elements(t.device->'tags') elem
where t.device_id = 9809
device_id is the primary key.
I have two issues that I don't know how to solve:
tags is an array field that may be empty, in which case I got 0 rows. I want output no matter tags is empty or not. Dummy values are ok.
If tags contain multiple elements, I got multiple rows for the same device id. How to aggregate those multiple elements into one row?
Your first problem can be solved by using a left outer join, that will substitute NULL values for missing matches on the right side.
The second problem can be solved with an aggregate function like json_agg, array_agg or string_agg, depending on the desired result type:
SELECT t.device_id,
jsonb_agg(elem->>'count'),
jsonb_agg(elem->>'price')
FROM tbl t
LEFT JOIN LATERAL jsonb_array_elements(t.device->'tags') elem
ON TRUE
GROUP BY t.device_id;
You will get a JSON array containing just null for those rows where the array is empty, I hope that is ok for you.

Optimize SQL query and process result

I am looking for advice on optimizing the following sample query and processing the result. The SQL variant in use is the internal FileMaker ExecuteSQL engine which is limited to the SELECT statement with the following syntax:
SELECT [DISTINCT] {* | column_expression [[AS] column_alias],...}
FROM table_name [table_alias], ...
[ WHERE expr1 rel_operator expr2 ]
[ GROUP BY {column_expression, ...} ]
[ HAVING expr1 rel_operator expr2 ]
[ UNION [ALL] (SELECT...) ]
[ ORDER BY {sort_expression [DESC | ASC]}, ... ]
[ OFFSET n {ROWS | ROW} ]
[ FETCH FIRST [ n [ PERCENT ] ] { ROWS | ROW } {ONLY | WITH TIES } ]
[ FOR UPDATE [OF {column_expression, ...}] ]
The query:
SELECT item1 as val ,interval, interval_next FROM meddata
WHERE fk = 12 AND active1 = 1 UNION
SELECT item2 as val ,interval, interval_next FROM meddata
WHERE fk = 12 AND active2 = 1 UNION
SELECT item3 as val ,interval, interval_next FROM meddata
WHERE fk = 12 AND active3 = 1 UNION
SELECT item4 as val ,interval, interval_next FROM meddata
WHERE fk = 12 AND active4 = 1 ORDER BY val
This may give the following result as a sample:
val,interval,interval_next
Artelac,0,1
Artelac,3,6
Celluvisc,1,3
Celluvisc,12,24
What I am looking to achieve (in addition to suggestions for optimization) is a result formatted like this:
val,interval,interval_next,interval,interval_next,interval,interval_next,interval,interval_next ->etc
Artelac,0,1,3,6
Celluvisc,1,3,12,24
Preferably I would like this processed result to be produced by the SQL engine.
Possible?
Thank you.
EDIT: I included the column names in the result for clarity, though they are not part of the result. I wish to illustrate that there may be an arbitrary number of 'interval' and 'interval_next' columns in the result.
I do not think you need to optimise you query, looks fine to me.
You are looking for something like PIVOT in TSQL, which is not supported in FQL. You biggest issue is going to be a variable number of columns returned.
I think the best approach is to get your intermediate result and use a FileMaker script or Custom Function to pivot it.
An alternative is to get the list of distinct val and loop through them (with CF or script) with FQL Statement for each row. You will not be able to combine them with union as it needs the same number of columns.

SQL Developer AND OR Statements

Got a task at my job to get several fields from different tables into one sheet, using SQL Developer. Im a noob to SQL, however managed to build something. Taking a look at my output learns me that the restrictions I built in do not work. Short description below. Can someone please help me?! In my output I still see values other than 1006 in the ATINN field, values other tham Empty in the BWTAR field.. What am I doing wrong?
5 tables linked together
Restriction on Users (DMSTRAATL, etc)
Restriction on Productgroup (006*, etc)
Restriction on some individual products
Restriction on product type (HERB, etc)
Restriction on specific data field (All products should have atinn = 1006)
Restiction on specific data field (all products should have bwtar = empty)
SELECT
dmssap.mara.matnr, dmssap.mara.mtart, dmssap.mara.matkl,
dmssap.mara.ersda, dmssap.mara.ernam, dmssap.mara.bismt,
dmssap.marc.werks, dmssap.inob.cuobj,
LPAD(INOB.CUOBJ, 18, '0') AS CUOBJ_18, dmssap.ZMM_MATNR_MPO.matnr, dmssap.ZMM_MATNR_MPO.pname,
dmssap.ZMM_MATNR_MPO.stat, dmssap.mbew.bwkey, dmssap.mbew.bklas,
dmssap.mbew.bwtar, dmssap.mbew.vprsv, dmssap.mbew.bwtty, dmssap.mbew.verpr,
dmssap.mbew.stprs, dmssap.ZMM_MATNR_MPO.matnr, dmssap.ZMM_MATNR_MPO.pname,
dmssap.ZMM_MATNR_MPO.stat, dmssap.ausp.atinn, dmssap.ausp.atwrt
FROM dmssap.mara
LEFT OUTER JOIN dmssap.marc
ON (dmssap.marc.matnr) = (dmssap.mara.matnr)
LEFT OUTER JOIN dmssap.ZMM_MATNR_MPO
ON (dmssap.ZMM_MATNR_MPO.matnr) = (dmssap.mara.matnr)
LEFT OUTER JOIN dmssap.mbew
ON CONCAT(dmssap.mbew.matnr, dmssap.mbew.bwkey) = CONCAT(dmssap.marc.matnr, dmssap.marc.werks)
LEFT OUTER JOIN dmssap.inob
ON (dmssap.inob.objek) = (dmssap.mara.matnr)
LEFT OUTER JOIN dmssap.ausp
ON dmssap.ausp.objek = LPAD(INOB.CUOBJ, 18, '0')
WHERE (dmssap.mara.ernam) IN (
'DMSTRAATL', 'V0342628', 'V0343809',
'V0336003', 'V0009830', 'V0309577', 'V0010144'
)
AND (dmssap.mara.matkl) IN (
'006*', '007120', '007130', '007140', '007170',
'007180', '007210', '007220', '007230',
'007250', '007270', '007280', '007290',
'007300', '007320', '007340',
'007370', '007380', '007400', '007420'
)
OR (dmssap.mara.matnr) IN (
'000000010001767697', '000000010001870117', '000000010001870116', '000000010001870115',
'000000010001870114', '000000010001870113', '000000010001870112'
)
AND (dmssap.mara.mtart) IN ('HERB', 'HALB', 'ZSTP')
AND (dmssap.ausp.atinn) = '1006'
AND (dmssap.mbew.bwtar) IS NULL;
AND operator has an higher precedence order than OR . to make your query easily readable use braces () around filter clauses.
Let's say you want to select records which have certain values in dmssap.mara.matkl or certain values in dmssap.mara.matnr then you can use braces between these blocks to be precise as shown below.
AND (
(dmssap.mara.matkl) IN (
'006*', '007120', '007130', '007140', '007170',
'007180', '007210', '007220', '007230',
'007250', '007270', '007280', '007290',
'007300', '007320', '007340',
'007370', '007380', '007400', '007420'
)
OR (dmssap.mara.matnr) IN (
'000000010001767697', '000000010001870117', '000000010001870116', '000000010001870115',
'000000010001870114', '000000010001870113', '000000010001870112'
)
)
WHERE (dmssap.mara.ernam) IN (
'DMSTRAATL', 'V0342628', 'V0343809',
'V0336003', 'V0009830', 'V0309577', 'V0010144'
)
AND (dmssap.mara.matkl) IN (
'006*', '007120', '007130', '007140', '007170',
'007180', '007210', '007220', '007230',
'007250', '007270', '007280', '007290',
'007300', '007320', '007340',
'007370', '007380', '007400', '007420'
)
OR (dmssap.mara.matnr) IN (
'000000010001767697', '000000010001870117', '000000010001870116', '000000010001870115',
'000000010001870114', '000000010001870113', '000000010001870112'
)
AND (dmssap.mara.mtart) IN ('HERB', 'HALB', 'ZSTP')
AND (dmssap.ausp.atinn) = '1006'
AND (dmssap.mbew.bwtar) IS NULL;
The crux is in your WHERE statement. The evaluation order for keywords in the WHERE statement is NOT -> AND -> OR
What you seem to want is this instead:
WHERE (dmssap.mara.ernam) IN (
'DMSTRAATL', 'V0342628', 'V0343809',
'V0336003', 'V0009830', 'V0309577', 'V0010144'
)
AND (dmssap.mara.matkl) IN ((
'006*', '007120', '007130', '007140', '007170',
'007180', '007210', '007220', '007230',
'007250', '007270', '007280', '007290',
'007300', '007320', '007340',
'007370', '007380', '007400', '007420'
)
OR (dmssap.mara.matnr) IN (
'000000010001767697', '000000010001870117', '000000010001870116', '000000010001870115',
'000000010001870114', '000000010001870113', '000000010001870112'
))
AND (dmssap.mara.mtart) IN ('HERB', 'HALB', 'ZSTP')
AND (dmssap.ausp.atinn) = '1006'
AND (dmssap.mbew.bwtar) IS NULL;
Notice the two extra brackets around the OR on the AND

Why does count(distinct signatory_id) in my query result in an ORA-00600 error? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
CREATE OR REPLACE FORCE VIEW "EXPCPU_SVS"."TEMP_VW_ZZZ_SVS_DATA_IMPORT" ("ACCOUNT_TYPE", "TOTAL_ACCOUNTS", "TOTAL_SIGNATURES", "TOTAL_SIGNCARDS", "TOTAL_GROUPS", "TOTAL_RULES") AS
SELECT flag_name AS ACCOUNT_TYPE,
NVL(TOTAL_ACCOUNTS,0) AS TOTAL_ACCOUNTS,
NVL(TOTAL_SIGNATURES,0) AS TOTAL_SIGNATURES,
NVL(TOTAL_SIGNCARDS,0) AS TOTAL_SIGNCARDS,
NVL(TOTAL_GROUPS,0) AS TOTAL_GROUPS,
NVL(TOTAL_RULES,0) AS TOTAL_RULES
FROM
(
SELECT flag_name,TOTAL_ACCOUNTS, TOTAL_SIGNATURES,TOTAL_SIGNCARDS,TOTAL_GROUPS,FLAG_VALUE FROM
(
SELECT flag_name,TOTAL_ACCOUNTS, TOTAL_SIGNATURES,TOTAL_SIGNCARDS,FLAG_VALUE FROM
(
SELECT flag_name,TOTAL_ACCOUNTS, TOTAL_SIGNATURES,flag_value
FROM
(
SELECT flag_name,TOTAL_ACCOUNTS,flag_value
FROM
(
SELECT count(*) AS TOTAL_ACCOUNTS ,
A.HISTORY_FLAG AS a_history_flag
FROM tbl_sign_account a WHERE a.history_flag IN(0,1,2) GROUP BY a.history_flag
)
FULL OUTER JOIN
(
SELECT flag_value,flag_name FROM temp_zz_tbl_flag_mapping
)
ON flag_value=a_history_flag
)
FULL OUTER JOIN
(
SELECT COUNT(*) AS TOTAL_SIGNATURES,
b.history_flag AS b_history_flag
FROM tbl_signatory b
WHERE b.history_flag IN (0,1,2)
GROUP BY b.history_flag
)
ON b_history_flag=flag_value )
FULL OUTER JOIN
(
SELECT count(distinct signatory_id) AS TOTAL_SIGNCARDS ,
c.history_flag AS c_history_flag
FROM tbl_signature_card c
WHERE c.history_flag IN (0,1,2) GROUP BY c.history_flag
)
ON flag_value=c_history_flag
)FULL OUTER JOIN
(
SELECT count(*) AS TOTAL_GROUPS,
d.history_flag AS d_history_flag
FROM tbl_sign_group d
WHERE d.history_flag IN (0,1,2) GROUP BY d.history_flag
)
ON flag_value=d_history_flag
)
FULL OUTER JOIN
(
SELECT count(*) AS TOTAL_RULES,
e.history_flag AS e_history_flag
FROM tbl_sign_rule e WHERE e.history_flag IN (0,1,2) GROUP BY e.history_flag
)
ON e_history_flag=flag_value;
And this is what the error is:
ORA-00600: internal error code, arguments: [kkqcscpopn_Int: 0], [], [], [], [], [], [], [], [], [], [], []
00600. 00000 - "internal error code, arguments: [%s], [%s], [%s], [%s], [%s], [%s], [%s], [%s]"
*Cause: This is the generic internal error number for Oracle program
exceptions. This indicates that a process has encountered an
exceptional condition.
*Action: Report as a bug - the first argument is the internal error number
The ORA-00600 means you're hitting an internal error, essentially a bug. You can look up the details of the error on My Oracle Support; there's a generic document about where to start with ORA-00600 errors, and searching for the argument kkqcscpopn_Int will show various known issues - see document ID 1267257.1 for a summary. You will need to know the exact version and patch level to narrow down the possibilities, and to raise a service request if you can't find a match or have any doubt you're seeing the same thing. As the error message says, reporting a bug is the expected action when an internal error occurs.
Sometimes obscure bugs can be avoided by rewriting or redesigning the query you're using. In this case what you have seems overly complicated - not sure why you've put in so many levels of query, or if full outer joins are really needed. If you want to stick to joins to computed counts (rather than, say, having subqueries in the select list) you could try just simplifying it a bit to see if that helps the parser. As a quick first stab, and assuming you will always have records in temp_zz_tbl_flag_mapping for the three flag values you're using, I think this is roughly the same:
CREATE OR REPLACE VIEW temp_vw_zzz_svs_data_import AS
SELECT z.flag_name AS account_type,
NVL(a.total_accounts, 0) AS total_accounts,
NVL(b.total_signatures, 0) AS total_signatures,
NVL(c.total_signcards, 0) AS total_signcards,
NVL(d.total_groups, 0) AS TOTAL_GROUPS,
NVL(e.total_rules, 0) AS TOTAL_RULES
FROM temp_zz_tbl_flag_mapping z
LEFT JOIN (
SELECT history_flag, COUNT(*) AS total_accounts
FROM tbl_sign_account
GROUP BY history_flag
) a ON a.history_flag = z.flag_value
LEFT JOIN (
SELECT history_flag, COUNT(*) AS total_signatures
FROM tbl_signatory
GROUP BY history_flag
) b ON b.history_flag = z.flag_value
LEFT JOIN (
SELECT history_flag, COUNT(DISTINCT signatory_id) AS total_signcards
FROM tbl_signature_card
GROUP BY history_flag
) c ON c.history_flag = z.flag_value
LEFT JOIN (
SELECT history_flag, COUNT(*) AS total_groups
FROM tbl_sign_group
GROUP BY history_flag
) d ON d.history_flag = z.flag_value
LEFT JOIN (
SELECT history_flag, COUNT(*) AS total_rules
FROM tbl_sign_rule
GROUP BY history_flag
) e ON e.history_flag = z.flag_value
WHERE z.flag_value in (0,1,2);
If you might not have all the flag values in that table/view you might need to go back to full outer joins, but even so removing the nested inline views may make a difference.
Or it may not; I can't produce the error in any of the DB versions I have available, so I can't check if this avoids it. You'll have to do some testing and experimentation. If you get it down to the simplest form you can that makes sense for your data and rules, and you still get an error, you'll have to get Oracle's help.

SQL JOIN clause: substituting a bunch of flags with one Enum

I'm trying to implement a DSL containing some parts of SQL SELECT queries.
The JOIN syntax between two tables is specified (e.g. for PostgreSQL) like this:
// one of theese:
[ INNER ] JOIN
LEFT [ OUTER ] JOIN
RIGHT [ OUTER ] JOIN
FULL [ OUTER ] JOIN
CROSS JOIN
Note the optional keywords.
The following Xtext grammar works (sort of):
Join:
'INNER'? inner?='JOIN'
| left?='LEFT' 'OUTER'? 'JOIN'
| right?='RIGHT' 'OUTER'? 'JOIN'
| full?='FULL' 'OUTER'? 'JOIN'
| cross?='CROSS' 'JOIN'
;
The model inference will of course create a bunch of flags which cannot be handled nicely later.
What I really want is an enum like this:
enum JoinType: INNER_JOIN | LEFT_JOIN | RIGHT_JOIN | FULL_JOIN | CROSS_JOIN;
I want an enum because:
The generator et al. can use a simple switch statement.
The processing of the optional keywords and the embedded whitespace is grammar work.
Is there any reasonable way to connect that enum to the rest of the grammar?
You can define them individually, it may not be as elegant as enum although it is a way around;
Join:
(joins += JoinType)+ // or however you wish
;
JoinTypes:
INNER_JOIN | LEFT_JOIN | RIGHT_JOIN | FULL_JOIN | CROSS_JOIN
;
Then define each of them as well of you want.
INNER_JOIN:
// whatever you want, optional keywords etc.
;
LEFT_JOIN:
...