PostgreSQL 3 Table Join Multiplying - sql

I have 3 tables. The first has the records I want. The other two have categories to be applied to the first table. If the lookup value from table3 is found in the description, I want to return that category. Else, return the category in table2. I think I have that logic correct, but the results are being multiplied. How can I limit the results to just the table1 records I want, but apply the correct category?
Here is my query with an example schema. It should only return the first 6 rows in table1 with whichever category is correct, but it returns 10. http://sqlfiddle.com/#!15/fc6fa/49/0
SELECT table1.product_code, table1.date_signed, table1.description,
CASE
WHEN lower(table1.description) LIKE ('%' || lower(table3.lookup_value) || '%')
THEN table3.category
ELSE table2.category
END
FROM table1
LEFT JOIN table2 ON table2.psc_code = table1.product_code
LEFT JOIN table3 ON table3.psc_code = table1.product_code
WHERE date_signed = '2017-02-01';
create table table1 (
product_code int,
date_signed timestamp,
description varchar(20)
);
insert into table1
(product_code, date_signed, description)
values
(1, '2017-02-01', 'i have a RED car'),
(2, '2017-02-01', 'i have a blue boat'),
(3, '2017-02-01', 'i have a dark cat'),
(1, '2017-02-01', 'i have a green truck'),
(2, '2017-02-01', 'i have a blue rug'),
(3, '2017-02-01', 'i have a dark dog'),
(1, '2017-02-02', 'i REd NO SHOW'),
(2, '2017-02-02', 'i blue NO SHOW'),
(3, '2017-02-02', 'i dark NO SHOW');
create table table2 (
psc_code int,
category varchar(20)
);
insert into table2
(psc_code, category)
values
(1, 'vehicle'),
(2, 'vehicle');
create table table3 (
psc_code int,
lookup_value varchar(20),
category varchar(20)
);
insert into table3
(psc_code, lookup_value, category)
values
(1, 'fox', 'animal'),
(1, 'red', 'color'),
(1, 'box', 'shipping'),
(2, 'cat', 'animal');

You're trying to join 1 to many, and you only want one value.
SELECT table1.product_code, table1.date_signed, table1.description,
CASE
WHEN EXISTS (select 1 from table3
where table3.psc_code = table1.product_code and
lower(table1.description) LIKE ('%' || lower(table3.lookup_value) || '%'))
THEN (select table3.category from table3
where table3.psc_code = table1.product_code and
lower(table1.description) LIKE ('%' || lower(table3.lookup_value) || '%') limit 1)
ELSE (select table2.category
from table2
where table2.psc_code = table1.product_code
limit 1)
END
FROM table1
WHERE date_signed = '2017-02-01';
http://rextester.com/TQIY93378
+--------------+---------------------+----------------------+----------+
| product_code | date_signed | description | category |
+--------------+---------------------+----------------------+----------+
| 1 | 01.02.2017 00:00:00 | i have a RED car | color |
| 2 | 01.02.2017 00:00:00 | i have a blue boat | vehicle |
| 3 | 01.02.2017 00:00:00 | i have a dark cat | NULL |
| 1 | 01.02.2017 00:00:00 | i have a green truck | vehicle |
| 2 | 01.02.2017 00:00:00 | i have a blue rug | vehicle |
| 3 | 01.02.2017 00:00:00 | i have a dark dog | NULL |
+--------------+---------------------+----------------------+----------+

Yep you will get a cartesian product on this one.
Your problem is that you have multiple rows of each product_code that match fro table1. So when you join on table3, you get six records out with an id of 1. The other join conditions don't pose a situation where you have multiple matches on both sides, so that is how you get six product code 1 rows, 2 product code 2 rows and 2 product code 3 rows.
The solution is to join in ways in which the foreign key is targetting a unique row in the target table.
This really ought to be a useful example of why normalization and key awareness matter. Where you break the basic rules of functional dependencies, bad problems ted to multiply.

Related

How records can be retrieved based on three conditions?

I'm new to HQL. I need to fetch all the records from table A based on the following 2 condition using HQL/SQL query:
Person ID which satisfies both these conditions "(Music < 100) and (Dance != Normal)" (in Table B) and whose Place and Country is A and AAA (in Table C).
Tables below:
[
[
[
How can I fetch these records based on this three conditions. Could someone help me.
The output should be
Record having ID as 100 in Table A since it has place and value as 'A' and 'AA'. And it also has both Music and Dance skills with Music value greater than 100 and Dance value is not like 'Normal'
select
*
from a
inner join b as music on a.id = music.person_id and music.skills = 'Music'
inner join b as dance on a.id = dance.person_id and dance.skills = 'Dance'
inner join c on a.id = c.id
where c.place = 'A' and c.country = 'AAA'
and music.score < '100'
and dance.score <> 'Normal'
You will have a problem attempting to use less than on the column "score" which is text and not numeric, see test cases below
CREATE TABLE mytable(
Value VARCHAR(6) NOT NULL PRIMARY KEY
);
INSERT INTO mytable(Value) VALUES ('100');
INSERT INTO mytable(Value) VALUES ('a');
INSERT INTO mytable(Value) VALUES ('aa');
INSERT INTO mytable(Value) VALUES ('bbb');
INSERT INTO mytable(Value) VALUES ('cccc');
INSERT INTO mytable(Value) VALUES ('99');
INSERT INTO mytable(Value) VALUES ('9');
INSERT INTO mytable(Value) VALUES ('1');
INSERT INTO mytable(Value) VALUES ('19');
select
*
from mytable where value < '100'
| value |
| :---- |
| 1 |
select
*
from mytable where value > '100'
| value |
| :---- |
| a |
| aa |
| bbb |
| cccc |
| 99 |
| 9 |
| 19 |
select
*
from mytable where cast(value as integer) > 100
ERROR: invalid input syntax for integer: "a"
db<>fiddle here

How to return columns from nested SELECT queries in the final table?

I have three layered nested query which works.
select PARTNER, BIRTHDT, XSEXM, XSEXF from "schema"."platform.view/table2" where partner IN
(select SID from "schema"."platform.view/table1" where TYPE='BB' and CLASS='yy' and ID IN
(select SID from "schema"."platform.view/table1" where TYPE='AA' and CLASS='zz' and ID IN ("one", "two")
))
I want the values ( "one", "two") from table1 in the inner most query to be present in the final Table returned.
I have tried to get it like this:
select t1.ID, t2.SID from "schema"."platform.view/table1" t1
OUTER APPLY (
select SID from "schema"."platform.view/table1" t2
where t2.TYPE='BB' and t2.CLASS='yy' and t2.ID IN t1.SID
)
where t1.TYPE='AA' and t1.CLASS='zz' and t1.ID IN ("one", "two")
There are three three identifiers:
1. ID ( ONE, TWO, etc.)
2. intermediate SID ( 123, 124, etc) which is again searched as ID
3. Partner ID (P12, P13, etc) which maps to table2.
Sample Data:
table1:
| ID | SID | TYPE | CLASS |
|------|-----|------|-------|
| ONE | 123 | AA | zz |
| TWO | 124 | AA | zz |
| 123 | P12 | BB | yy |
| THRE | 125 | AA | zz |
| 124 | P13 | BB | yy |
| 125 | P14 | BB | yy |
| FOUR | 123 | AA | zz |
table2:
| PARTNER | BIRTHDT | XSEXM | XSEXF |
|---------|----------|-------|-------|
| P12 | 19900214 | X | |
| P13 | 19900713 | X | |
| P14 | 19900407 | | X |
Desired Output for Input ("ONE", "TWO", "THRE"):
| ID | PARTNER | BIRTHDT | XSEXM | XSEXF |
|-----|---------|----------|-------|-------|
| ONE | P12 | 19900214 | X | |
| TWO | P13 | 19900713 | X | |
| THRE| P14 | 19900407 | | X |
How to map this initial search value with its final result rows in this three layer nested statement?
Since you want to "carry" information from your "inner" SELECTs you can either "join back" the data at the final projection step which requires that you have a 1:1 relationship that you could use for the join.
This is not the case here.
Instead, don't use the WHERE ... IN (SELECT ID...) approach, but INNER JOINs instead.
These allow the same kind of filtering/selection but also give the option to project any column of the two involved tables.
For your rather abstract statement (the column names really need a lot of context knowledge in order to make sense... - that's something you may want to fix by adding useful column aliases) this can look like so:
drop table tab1;
drop table tab2;
CREATE TABLE TAB1
("ID" varchar(6)
, "SID" varchar(5)
, "TYPE" varchar(6)
, "CLASS" varchar(7))
;
INSERT INTO TAB1
VALUES ('ONE', '123', 'AA', 'zz');
INSERT INTO TAB1
VALUES ('TWO', '124', 'AA', 'zz');
INSERT INTO TAB1
VALUES ('123', 'P12', 'BB', 'yy');
INSERT INTO TAB1
VALUES ('THRE', '125', 'AA', 'zz');
INSERT INTO TAB1
VALUES ('124', 'P13', 'BB', 'yy');
INSERT INTO TAB1
VALUES ('125', 'P14', 'BB', 'yy');
INSERT INTO TAB1
VALUES ('FOUR', '123', 'AA', 'zz');
select * from tab1;
CREATE TABLE TAB2
("PARTNER" varchar(9)
, "BIRTHDT" varchar(10)
, "XSEXM" varchar(7)
, "XSEXF" varchar(7))
;
INSERT INTO TAB2
VALUES ('P12', '19900214', 'X', NULL);
INSERT INTO TAB2
VALUES ('P13', '19900713', 'X', NULL);
INSERT INTO TAB2
VALUES ('P14', '19900407', NULL, 'X');
with id_sel as (
select SID, ID
from TAB1
where
TYPE='AA'
and CLASS='zz'
and ID IN ('ONE', 'TWO', 'THRE')
),
part_sel as (
select
t1.SID, id.ID orig_id
from
TAB1 t1
inner join id_sel id
on t1.id = id.sid
where
t1.TYPE='BB'
and t1.CLASS='yy'
)
select
part_sel.orig_id, t2.PARTNER, t2.BIRTHDT, t2.XSEXM, t2.XSEXF
from
TAB2 t2
inner join part_sel
on t2.partner = part_sel.sid;
ORIG_ID PARTNER BIRTHDT XSEXM XSEXF
ONE P12 19900214 X ?
TWO P13 19900713 X ?
THRE P14 19900407 ? X

Keep null relations on WHERE IN() or with SELECT and LEFT JOIN

I have table like there:
table:
| id | fkey | label | amount |
|----|------|-------|--------|
| 1 | 1 | aaa | 10 |
| 2 | 1 | bbb | 15 |
| 3 | 1 | fff | 99 |
| 4 | 1 | jjj | 33 |
| 5 | 2 | fff | 10 |
fkey is a foreign key to other table.
Now I need to query for all amounts asociated with some labels ('bbb', 'eee', 'fff') and with specifed fkey, but i need to keep all unexisting labels with NULL.
For simple query with WHERE IN ('bbb', 'eee', 'fff') I got, of course, only two rows:
SELECT label, amount FROM table WHERE label IN ('bbb', 'eee', 'fff') AND fkey = 1;
| label | amount |
|-------|--------|
| bbb | 15 |
| fff | 99 |
but excepted result should be:
| label | amount |
|-------|--------|
| bbb | 15 |
| eee | NULL |
| fff | 99 |
I tried also SELECT label UNION ALL label (...) LEFT JOIN which should work on MySQL (Keep all records in "WHERE IN()" clause, even if they are not found):
SELECT T.label, T.amount FROM (
SELECT 'bbb' AS "lbl"
UNION ALL 'eee' AS "lbl"
UNION ALL 'fff' AS "lbl"
) LABELS
LEFT OUTER JOIN table T ON (LABELS."lbl" = T."label")
WHERE T.fkey = 1;
and also with WITH statement:
WITH LABELS AS (
SELECT 'bbb' AS "lbl"
UNION ALL 'eee' AS "lbl"
UNION ALL 'fff' AS "lbl"
)
SELECT T.label, T.amount FROM LABELS
LEFT OUTER JOIN table T ON (LABELS."lbl" = T."label")
WHERE T.fkey = 1;
but always this LEFT JOIN give me 2 rows istead 3.
Create temporary table doesn't work at all (I got 0 rows from it and I cannot use this in join):
CREATE TEMPORARY TABLE __ids (
id VARCHAR(9) PRIMARY KEY
) ON COMMIT DELETE ROWS;
INSERT INTO __ids (id) VALUES
('bbb'),
('eee'),
('fff');
SELECT
*
FROM __ids
Any idea how to enforce Postgres to keep empty relation? Or even any other idea to get label 'eee' with NULL amount if there is not row for this in table?
List of labels can be different on every request.
This case online: http://rextester.com/CRQY46630
------ EDIT -----
I extended this question with filter where, because answer from a_horse_with_no_name is great, but not cover my whole case (I supposed this where no matter there)
Your approach with the outer join does work. You just need to take the label value from the "outer" joined table, not from the "table":
with labels (lbl) as (
values ('bbb'), ('eee'), ('fff')
)
select l.lbl, --<< this is different to your query
t.amount
from labels l
left outer join "table" t on l.lbl = t.label;
Online example: http://rextester.com/LESK82163
Edit after the scope of the question was extended.
If you want to filter on the base table, you need to move the into the JOIN condition, not the where clause:
with labels (lbl) as (
values ('bbb'), ('eee'), ('fff')
)
select l.lbl, --<< this is different to your query
t.amount
from labels l
left outer join "table" t
on l.lbl = t.label
and t.fkey = 1; --<<
Online example: http://rextester.com/XDO76971

How to the write SQL to show the data in my case in Oracle?

I have a table like this -
create table tbl1
(
id number,
role number
);
insert into tbl1 values (1, 1);
insert into tbl1 values (2, 3);
insert into tbl1 values (1, 3);
create table tbl2
(
role number,
meaning varchar(50)
);
insert into tbl2 values (1, 'changing data');
insert into tbl2 values (2, 'move file');
insert into tbl2 values (3, 'dance');
I want the sql result like the following -
id role_meaning is_permitted
1 changing data yes
1 move file no
1 dance yes
2 changing data no
2 move file no
2 dance yes
Please help how can I do this? I have tried several methods but not sure how to do this.
You can use partitioned outer join here.
SQL Fiddle
Query 1:
select tbl1.id,
tbl2.meaning,
case when tbl1.role is NULL then 'no' else 'yes' end is_permitted
from tbl1
partition by (id) right outer join tbl2
on tbl1.role = tbl2.role
order by tbl1.id, tbl2.role
Results:
| ID | MEANING | IS_PERMITTED |
|----|---------------|--------------|
| 1 | changing data | yes |
| 1 | move file | no |
| 1 | dance | yes |
| 2 | changing data | no |
| 2 | move file | no |
| 2 | dance | yes |

SQL Ignore duplicate primary keys

Imagine you have a string of results from a SELECT statement:
ID (pk) Name Address
1 a b
1 c d
1 e f
2 a b
3 a d
2 a d
Is it possible to alter the SQL statement to get one record ONLY for the record with ID 1?
I have a SELECT statement that displays multiple values which can have the same primary key. I want to only take one of those records, if say, I have 5 records with the same primary key.
SQL: http://pastebin.com/cFCBA2Uy
Screenshot: http://i.imgur.com/UlMBZhC.png
What I want is to show only one file which is for e.g. File Number: 925, 890
You stated that no matter which row to choose when there are more than one row for the same Id, you just want one row for each id.
The following query does what you asked for:
DECLARE #T table
(
id int,
name varchar(50),
address varchar(50)
)
INSERT INTO #T VALUES
(1, 'a', 'b'),
(1, 'c', 'd'),
(1, 'e', 'f'),
(2, 'a', 'b'),
(3, 'a', 'd'),
(2, 'a', 'd');
WITH A AS
(
SELECT
t.id, t.name, t.address,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY (SELECT NULL)) AS RowNumber
FROM
#T t
)
SELECT
A.id, A.name, A.address
FROM
A
WHERE
A.RowNumber = 1
But I think there should be a criteria. If you find one, express your criteria as the ORDER BY inside the OVER clause.
EDIT:
Here you have the result:
+----+------+---------+
| id | name | address |
+----+------+---------+
| 1 | a | b |
| 2 | a | b |
| 3 | a | d |
+----+------+---------+
Disclaimer: the query I wrote is non-deterministic, different conditions (indexes, statistics, etc) might lead to different results.