LIKE clause INNER JOIN - sql

I have a problem with 2 tables:
table A
IDA | Description
1 | BIG_MAN
2 | BIG_MANCA
3 | WEB_BrowserCOM
4 | WEB_BrowserCO
5 | Other
Table B
IDB | FileName
1 | BIG_MAN_98267828_29292
2 | BIG_MANCA_8282836662_92992
3 | WEB_BrowserCO_7263562_82828
4 | WEB_Browser_28828288_826662
5 | WEB_BrowserCOM_9374664_9933
What I would like to do is get the IDA doing a inner join with table B:
;WITH FileDetails AS
(
SELECT
FL.IDA,
FL.Description
FROM TableA FL
WHERE IsActive = 1
)
SELECT
FD.IDA,
FLL.FileName
FD.Description
FROM TableB FLL
INNER JOIN TableA FD
ON FLL.FileName LIKE (FD.Filename)+'%'
However I get:
IDA | FileName |Description
1 | BIG_MAN_98267828_29292 |BIG_MAN
1 | BIG_MANCA_8282836662_92992 |BIG_MAN
4 | WEB_BrowserCO_7263562_82828 |WEB_BrowserCO
4 | WEB_Browser_28828288_826662 |WEB_BrowserCO
4 | WEB_BrowserCOM_9374664_9933 |WEB_BrowserCO
Any idea to solve this and get the correct IDA?

The problem you have is that you're not including a trailing underscore in your like clause. Try this:
WITH FileDetails AS
(
SELECT
FL.IDA,
FL.Description
FROM TableA FL
WHERE IsActive = 1
)
SELECT
FD.IDA,
FLL.FileName
FD.Description
FROM TableB FLL
INNER JOIN TableA FD
ON FLL.FileName LIKE (FD.Filename)+'$_%' escape '$'

I suspect that you just need a better like pattern:
SELECT FD.IDA, FLL.FileName, FD.Description
FROM TableB FLL INNER JOIN
TableA FD
ON FLL.FileName LIKE FD.Filename + '%$_%' ESCAPE '$';
This looks for the name followed by an underscore. The ESCAPE is needed because '_' is a wildcard.

Depending on your data you may find the answers already provided are sufficient, but I wasn't keen on them ...
They make two assumptions which may not hold:
The underscores within your "descriptions" will not create a match with other data by being interpreted as a wildcard.
Add to TableB a filename of 'BIGAMAN_123456_123456' to illustrate the issue.
When one description matches another except for a "tail" that tail doesn't begin with an underscore.
Add to TableA a description of 'BIG_MAN_7' to illustrate the issue.
If SQL-Server had good native handling of RegEx objects, I'd make a suggestion along those lines, but since it doesn't:...
SELECT
FD.IDA,
FLL.FileName,
FD.Description
FROM TableB FLL
INNER JOIN #TableA FD ON
left(FLL.FileName,len(fd.description)+1) = fd.description + '_' AND
len(replace(fll.FileName,fd.description + '_','')) -
len(replace(replace(fll.FileName,fd.description + '_',''),'_','')) = 1
The first part of the join does the same job as the like versions but avoids treating any subtrings as wildcards. The second part takes the remainder of the filename, and checks that it contains only one underscore.

Related

Update inner join result Oracle

In my java code I have foreach loop which iterates though list
foreach(MyObject obj:list){
String status = obj.getStatus();
String is = obj.getId();
// DB call
1. To update Status in Table A
jdbcobj.updtastatus(status,id);
2. Get status from table B
String tableBStatu= jdbcobj.getstatufromtableB(status,id):
obj.setStatus(tableBStatus):
}
To avoid 2 dB calls in for loop I am using inner join and trying to achieve same output as above
I am using inner-join and get the new result set based on common field.I want to update the result set but I am unable to figure out how?
I have two tables "A" and "B".
Table "A" has columns id,name,statusA
Table "B" has columns id,city,statusB
As stated at start, I am using inner-join and my query looks like this.
Select A.id A.statusA,B.statusB FROM A INNER JOIN ON B where A.id=B.id
Which gives me result as "id", status from table "A" and status from table "B".
Now i want use the inner-join result, to update statusA column from table "A" and set value ="DONE"
And want to use the statusB column value in java object.
String statusfromColumnB = get statusB col value
and set in my java object like this
myObj.setStatus(statusfromColumnB)
Sample Data
Suggest a solution.
If I understand you correctly, an Oracle MERGE query could properly respond to your need :
Consider :
MERGE INTO A
USING B ON (A.id = B.id)
WHEN MATCHED THEN UPDATE SET A.statusA = B.statusB
This query will update the status in table A from that of the corresponding record in table B.
Oracle merge is a vendor-specific statement that is optimized for multi-rows upserts (inserts/updates).
Demo on DB Fiddle :
Select A.id, A.statusA, B.statusB FROM A INNER JOIN B ON A.id=B.id
ID | STATUSA | STATUSB
-: | :------ | :--------
1 | Pending | Initiated
2 | Pending | Completed
MERGE INTO A
USING B ON (A.id = B.id)
WHEN MATCHED THEN UPDATE SET A.statusA = B.statusB
2 rows affected
Select A.id, A.statusA, B.statusB FROM A INNER JOIN B ON A.id=B.id
ID | STATUSA | STATUSB
-: | :-------- | :--------
1 | Initiated | Initiated
2 | Completed | Completed
If you want to set statusA to a fixed value instead, then you could go :
MERGE INTO A
USING B ON (A.id = B.id)
WHEN MATCHED THEN UPDATE SET A.statusA = 'Finished'
Do you want something like this?
update a
set (status, somewhereelsecolumn) =
(select 'DONE', <whatever>
from b
where A.id = B.id
)
where exists (select 1 from b where a.id = b.id);

SQL query with two EXISTS statements behaving differently than expected

The following SQL query is intended to find label_item_lists which have label_items with given names.
SELECT lils.id FROM label_item_lists AS lils
INNER JOIN label_items AS items ON lils.id = items.label_item_list_id
WHERE EXISTS(SELECT * FROM label_item_lists WHERE items.name=?)
OR EXISTS(SELECT * FROM label_item_lists WHERE items.name=?)
It properly returns the ids of label_item_lists having an item with either name. However, the same query using the AND operator rather than OR returns no results.
SELECT lils.id FROM label_item_lists AS lils
INNER JOIN label_items AS items ON lils.id = items.label_item_list_id
WHERE EXISTS(SELECT * FROM label_item_lists WHERE items.name=?)
AND EXISTS(SELECT * FROM label_item_lists WHERE items.name=?)
There are definitely label_item_list entries that have label_items matching both names provided. In fact the OR SQL query returns the id twice for these entries, but the AND query returns no results. For this reason I think I might be missing an important piece of info on how these JOINed queries with EXISTS work. Thanks for any assistance!
----------------------------------------------------------------
| label_items | id | name | label_item_list_id |
----------------------------------------------------------------
| Row1 | 1 | foo | 1 |
----------------------------------------------------------------
| Row2 | 2 | bar | 1 |
----------------------------------------------------------------
| Row3 | 3 | bar | 2 |
----------------------------------------------------------------
--------------------------------
| label_item_lists | id |
--------------------------------
| Row1 | 1 |
--------------------------------
| Row2 | 2 |
--------------------------------
I want to return the first label_item_list but not the second, as it only has one of the two names I am searching for, 'foo' and 'bar'.
try changing the where condition from
WHERE EXISTS(SELECT * FROM label_item_lists WHERE items.name=?)
AND EXISTS(SELECT * FROM label_item_lists WHERE items.name=?)
to
WHERE EXISTS(SELECT * FROM label_item_lists lst WHERE lst.name=?)
AND EXISTS(SELECT * FROM label_item_lists lst WHERE lst.name=?)
In your query AND will not return anything because on same output row it will apply both filters which will never happen hence it is giving blank output.
And Or operator will never check condition after OR operator until first condition is false.
Try something like this, # is just a place holder to distinguish between two searches:
select * from label_items lil
where label_item_list_id
in (
select li.label_item_list_id from
label_items li
inner join label_items l1
on li.label_item_list_id = l1.label_item_list_id
and li.name <> l1.name
where concat(li.name,'#',l1.name) = 'foo#bar')
This is what I eventually came up with! I'm not 100% confident yet, but it has worked so far. I added a bit of functionality in Ruby and ActiveRecord to allow for as many necessary matches as desired and to return only those which match exactly (without any extra names not in the list).
items = ["foo", "bar"]
db = ActiveRecord::Base.connection
query = <<-EOS
SELECT lils.id FROM label_item_lists AS lils
JOIN label_items AS items ON items.label_item_list_id = lils.id
WHERE lils.id IN (
SELECT label_item_list_id FROM label_items AS items
WHERE items.name IN (#{(['?'] * items.length).join(',')})
) AND lils.id NOT IN (
SELECT label_item_list_id FROM label_items AS items
WHERE items.name NOT IN (#{(['?'] * items.length).join(',')})
)
GROUP BY lils.id
HAVING COUNT(DISTINCT items.name) = #{items.length}
EOS
query = ActiveRecord::Base.send(:sanitize_sql_array, [query, *(items*2)])
Basically it checks that a name is both IN the list of given names (items array) AND it also checks that the name IS NOT outside (or NOT IN) the list of given names. Any list of label_items that has a non-matching name is excluded by the latter IN query. This is helpful so that a label_item_list with both "foo" and "bar" but also "lorem" is not included.

SQL - Fallback to default language when translate does not exist

Recording texts in different languages in a table (tbl_i18n) with the following structure:
text_FK | language_FK | value
-----------------------------
1 | 1 | hello
1 | 2 | hallo
2 | 1 | world
3 | 1 | test
gives texts of a specific language (id = 2) with a simple join like:
SELECT [value] FROM tbl_i18n i
JOIN tbl_products p ON p.text_id = i.text_FK
JOIN tbl_languages l ON l.id = i.language_FK AND i.language FK = 2;
and the result is:
value
-------
hallo
How could aforementioned select statement changed so we could have got a default language and when translate for a text fields does not exist their fallback text will be shown and the result will became:
value
-------
hallo
world
test
LEFT JOIN the language table twice. The first time for wanted language, the second time for fallback value. Use COALESCE to pick wanted language if available, otherwise fallback language.
SELECT coalesce(l1.[value], l2.[value])
FROM tbl_i18n i
JOIN tbl_products p ON p.text_id = i.text_FK
LEFT JOIN tbl_languages l1 ON l.id = i.language_FK AND i.language_FK = 2
LEFT JOIN tbl_languages l2 ON l.id = i.language_FK AND i.language_FK = 1;
I think in simple english you want the highest available language_FK for each text_FK.
WITH X AS (
SELECT *
,ROW_NUMBER() OVER (PARTITION BY text_FK ORDER BY language_FK DESC)rn
FROM TableName
)
SELECT *
FROM X
WHERE X.rn = 1

How to use one table as a filter from other and generate a new one in sql

say that I have a table that needs to be used as a filter table(table A) vs other one which reads data from a linkedserver (table b). The result of the filtering will generate a third table, the thing is I can not figure it out how to apply this filtering correctly. I found reference from this link: Reference but it does not onboard what I am looking for.
See as following:
Table A - data type in columns is Bit
|Field1 |Field2 |Field3 |Field4 |CustomerCode|
|-------|-------|-------|-------|-------------|
| 1 | 0 | 0 | 1 | c001 |
Table B - data type in columns is varchar
|FieldA |FieldB |FieldC |FieldD |CustomerCode |
|------- |-------|-------|-------|-------------|
| aaaaa | null | ccccc |ddddd | c001 |
Then what I seeking is something like this:
IF tableA.Field1 = 1 THEN NOT EMPTY tableB.FieldA
Do an INSERT INTO tableC FieldI VALUES(tableB.FieldA)
ELSE INSERT INTO tableC FieldI VALUES ('No Value Found')
So this will ensure that for all fields from table A are equal 1,
then for table B data can not be null, if data is null just insert that message: 'No Value Found'; if data is not null, then just place the data from table B to that table C.
I am starting with SQL and mostly I am missing something that could make it easier to digest this issue.
Thanks
Use INSERT INTO tableC ... SELECT FROM. Something like this should work:
INSERT INTO tableC
SELECT COALESCE(t1.FieldA, 'No Value Found'),
COALESCE(t1.FieldB, 'No Value Found'),
COALESCE(t1.FieldC, 'No Value Found'),
COALESCE(t1.FieldD, 'No Value Found')
FROM TableB t1
INNER JOIN TableA t2
ON t1.CustomerCode = t2.CustomerCode
WHERE t2.Field1 = 1 AND
t2.Field2 = 1 AND
t2.Field3 = 1 AND
t2.Field4 = 1

Is there a simpler way to write this query? [MS SQL Server]

I'm wondering if there is a simpler way to accomplish my goal than what I've come up with.
I am returning a specific attribute that applies to an object. The objects go through multiple iterations and the attributes might change slightly from iteration to iteration. The iteration will only be added to the table if the attribute changes. So the most recent iteration might not be in the table.
Each attribute is uniquely identified by a combination of the Attribute ID (AttribId) and Generation ID (GenId).
Object_Table
ObjectId | AttribId | GenId
32 | 2 | 3
33 | 3 | 1
Attribute_Table
AttribId | GenId | AttribDesc
1 | 1 | Text
2 | 1 | Some Text
2 | 2 | Some Different Text
3 | 1 | Other Text
When I query on a specific object I would like it to return an exact match if possible. For example, Object ID 33 would return "Other Text".
But if there is no exact match, I would like for the most recent generation (largest Gen ID) to be returned. For example, Object ID 32 would return "Some Different Text". Since there is no Attribute ID 2 from Gen 3, it uses the description from the most recent iteration of the Attribute which is Gen ID 2.
This is what I've come up with to accomplish that goal:
SELECT attr.AttribDesc
FROM Attribute_Table AS attr
JOIN Object_Table AS obj
ON obj.AttribId = obj.AttribId
WHERE attr.GenId = (SELECT MIN(GenId)
FROM(SELECT CASE obj2.GenId
WHEN attr2.GenId THEN attr2.GenId
ELSE(SELECT MAX(attr3.GenId)
FROM Attribute_Table AS attr3
JOIN Object_Table AS obj3
ON obj3.AttribId = attr3.AttribId
WHERE obj3.AttribId = 2
)
END AS GenId
FROM Attribute_Table AS attr2
JOIN Object_Table AS obj2
ON attr2.AttribId = obj2.AttribId
WHERE obj2.AttribId = 2
) AS ListOfGens
)
Is there a simpler way to accomplish this? I feel that there should be, but I'm relatively new to SQL and can't think of anything else.
Thanks!
The following query will return the matching value, if found, otherwise use a correlated subquery to return the value with the highest GenId and matching AttribId:
SELECT obj.Object_Id,
CASE WHEN attr1.AttribDesc IS NOT NULL THEN attr1.AttribDesc ELSE attr2.AttribDesc END AS AttribDesc
FROM Object_Table AS obj
LEFT JOIN Attribute_Table AS attr1
ON attr1.AttribId = obj.AttribId AND attr1.GenId = obj.GenId
LEFT JOIN Attribute_Table AS attr2
ON attr2.AttribId = obj.AttribId AND attr2.GenId = (
SELECT max(GenId)
FROM Attribute_Table AS attr3
WHERE attr3.AttribId = obj.AttribId)
In the case where there is no matching record at all with the given AttribId, it will return NULL. If you want to get no record at all in this case, make the second JOIN an INNER JOIN rather than a LEFT JOIN.
Try this...
Incase the logic doesn't find a match for the Object_table GENID it maps it to the next highest GENID in the ON clause of the JOIN.
SELECT AttribDesc
FROM object_TABLE A
INNER JOIN Attribute_Table B
ON A.AttrbId = B.AttrbId
AND (
CASE
WHEN A.Genid <> B.Genid
THEN (
SELECT MAX(C.Genid)
FROM Attribute_Table C
WHERE A.AttrbId = C.AttrbId
)
ELSE A.Genid
END
) -- Selecting the right GENID in the join clause should do the job
= B.Genid
This should work:
with x as (
select *, row_number() over (partition by AttribId order by GenId desc) as rn
from Attribute_Table
)
select isnull(a.attribdesc, x.attribdesc)
from Object_Table o
left join Attribute_Table a
on o.AttribId = a.AttribId and o.GenId = a.GenId
left join x on o.AttribId = x.AttribId and rn = 1