Updates and Self Joins and Case Statements - oh my - mysql

I think I may be trying to do too much with one query, and it's been driving me batty.
I have two tables, Source and Zip_Code. The Source table has a zip code field that can either be 3 or 5 digits.
If it's 3 digits, I need to join to the zip_code table's 3-digit field and set the Source's new_zip field to the zip_code table's corresponding 5-digit entry.
If it's 5 digits, I need to link to the zip_code table's 5-digit field (to make sure it's a valid zip code), then put that 5-digit number in the Source's new_zip field.
I already understand that you can't manipulate something you're updating, hence using a self-join to manipulate a copy. So now I need to create Source2, and join Source1, Source2, and Zip_Code tables.
UPDATE SOURCE1
INNER JOIN SOURCE2 ON SOURCE1_UID = SOURCE2_UID
INNER JOIN ZIP_CODE ON SOURCE1_ZIP =
(
SELECT
(
CASE WHEN LENGTH(SOURCE1.ZIP <4
THEN ZIP_CODE.3_DIGIT
ELSE ZIP_CODE.5_DIGIT
END
)
FROM SOURCE2
)
SET SOURCE1.NEW_ZIP =
(
CASE WHEN LENGTH(SOURCE1.ZIP <4
THEN ZIP_CODE.3_DIGIT
ELSE ZIP_CODE.5_DIGIT
END
)
I know I'm doing something majorly wrong, but I'm having a block as to where.

You're not naming the Source table in the query. You need to name the table and then give it an alias like Source1 if you have to refer to it twice.
But I don't think there's any need for the self-join.
There's no need for the subqueries, you can use conditionals in an ON clause.
UPDATE Source AS s
JOIN Zip_Code AS z ON
CASE WHEN LENGTH(s.zip) = 3
THEN z.3_digit
ELSE z.5_digit
END
SET s.new_zip = z.5_digit
Your description says that you always want to set new_zip to the 5-digit code, so there's no need for a CASE in the SET clause.

Related

SELECT statement inside a CASE statement in SNOWFLAKE

I have a query where i have "TEST"."TABLE" LEFT JOINED to PUBLIC."SchemaKey". Now in my final select statement i have a case statement where i check if c."Type" = 'FOREIGN' then i want to grab a value from another table but the table name value i am using in that select statement is coming from the left joined table column value. I've tried multiple ways to get to work but i keep getting an error, although if i hard code the table name it seems to work. i need the table name to come from c."FullParentTableName". Is what i am trying to achieve possible in snowflake and is there a way to make this work ? any help would be appreciated !
SELECT
c."ParentColumn",
c."FullParentTableName",
a."new_value",
a."column_name"
CASE WHEN c."Type" = 'FOREIGN' THEN (SELECT "Name" FROM TABLE(c."FullParentTableName") WHERE "Id" = 'SOME_ID') ELSE null END "TestColumn" -- Need assistance on this line...
FROM "TEST"."TABLE" a
LEFT JOIN (
select s."Type", s."ParentSchema", s."ParentTable", s."ParentColumn", concat(s."ParentSchema",'.','"',s."ParentTable",'"') "FullParentTableName",s."ChildSchema", s."ChildTable", trim(s."ChildColumn",'"') "ChildColumn"
from PUBLIC."SchemaKey" as s
where s."Type" = 'FOREIGN'
and s."ChildTable" = 'SOMETABLENAME'
and "ChildSchema" = 'SOMESCHEMANAME'
) c
on a."column_name" = c."ChildColumn"
Thanks !
In Snowflake you cannot dynamically use the partial results as tables.
You can use a single bound value via identifier to bind a value to table name
But you could write a Snowflake Scripting but it would need to explicitly join the N tables. Thus if you N is fixed, you should just join those.

Use ValueA when JOIN returns a row, otherwise ValueB (like a default)

I have four tables for a form-builder in my databse.
fields (fieldID(PK), typeID, fieldName, ...) - This table is a row by row list of all fields to be in the form
fields_types (typeID(PK), htmlType, ...) - This is a table that links fields to html types (and other settings)
fields_meta (FieldMetaID(PK), FieldID, mName, mValue) - Additional settings for fields, but more specific. A textarea field might have a height attribute, but almost no other field would use that.
fields_tyeps_meta (TypeMetaID(PK), typeID, tmName, tmValue) - Defines what extraneous settings a field can have, and also supplies default values if it's not explicitly set)
So my Query currently looks something like this
SELECT *
FROM Fields F
JOIN Field_Types FT
on FT.FieldID = F.FieldID
LEFT
JOIN Field_Meta FM
on FM.FieldID = F.FieldID
I was wondering if there's a way to join Fields_Types_Meta so that when the row's JOIN to Fields_Meta doesn't return a row (no mValue), it returns tmValue
I realize I can use something like (CASE WHEN mValue = "" THEN tmValue ELSE mValue END) AS UseValue, but I might have fields where I want to allow the value to be set to empty.
Edit: I could probably do something with a subquery and COUNT, using a CASE decision based on that. It might not be the healthiest performance-wise, but this query runs and caches itself til server restart, or until it's told to run again (updates to form design)
It looks like you just want ¢oalesce():
coalesce(FM.mValue, FT.tmValue) as UseValue
When FM.mValue is null, coalesce() returns FT.tmValue instead.
If you have null values in FM that you want to preserve in the result set, then use a case expression instead:
case when FM.FieldID IS NULL THEN FT.tmValue ELSE FM.mValue END as UseValue
This phrases as: when the left join did find a match in FM, use mValue from that row (even if it is null), else use FT.tmValue.

MySQL: How do I use Load Data Infile and replace existing rows' fields if it is not empty field in the file

I have a file with some empty fields like this:
(first column being primary key- a1,b1,b2)
a1,b,,d,e
b1,c,,,,e
b2,c,c,,
I have already present in table like
a1,c,f,d,e
Now for this key a1 using replace option and lad data infile I want final output like:
a1,b,f,d,e
Here c in second column has been replaced by b,
but f has not been replaced by empty string.
To make it clear: Replace field if an actual value is present in file
if an empty field is present, retain the old value.
Let consider 2 tables having 5 columns present
in t1 table -columns are c1,c2,c3,c4,c5
in t2 table -columns are d1,d2,d3,d4,d5
so query will become like this:
select c1 as e1
ifnull(c2,d2) as e2,
ifnull(c3,d3) as e3,
ifnull(c4,d4) as e4,
ifnull(c5,d5) as e5
from t1
inner join t2 on c1 = d1;
hope it will helpful to you.
Please try the following...
CREATE TABLE tempTblDataIn LIKE tblTable;
/* Read your data into tempTblDataIn here */
UPDATE tblTableName
JOIN tempTblDataIn ON tblTableName.fldID = tempTblDataIn.fldID
SET tblTableName.fldField1 = COALESCE( tempTblDataIn.fldField1, tblTableName.fldField1 ),
tblTableName.fldField2 = COALESCE( tempTblDataIn.fldField2, tblTableName.fldField2 ),
tblTableName.fldField3 = COALESCE( tempTblDataIn.fldField3, tblTableName.fldField3 ),
tblTableName.fldField4 = COALESCE( tempTblDataIn.fldField4, tblTableName.fldField4 );
DROP TABLE tempTblDataIn;
This Answer is based on Eric's Answer at MySQL - UPDATE query based on SELECT Query.
It is also based on the assumption that the data file will contain update data only rather than update data and new records.
Yes, you will need to do a COALESCE() line for each field. You will probably have to code each line yourself. You could use a PROCEDURE if there are many fields with a repeated structure to programmatically produce the above statements, but you may find the above simpler.
If you have any questions or comments, then please feel free to post a Comment accordingly.
Further Reading
https://dev.mysql.com/doc/refman/5.7/en/create-table-like.html (on MySQL's CREATE TABLE ... LIKE)
https://dev.mysql.com/doc/refman/5.7/en/update.html (on MySQL's UPDATE statement)

Joining and filtering one-to-many relationship

I need some help about optimal structuring of SQL query. I have model like this:
I'm trying some kind of join between tables NON_NATURAL_PERSON and NNP_NAME. Because I have many names in table NNP_NAME for one person I can't do one-to-one SELECT * from NON_NATURAL_PERSON inner join NNP_NAME etc. That way I'll get extra rows for every name one person has.
Data in tables:
How to extend this query to get rows marked red on picture shown below? My wannabe query criteria is: Always join name of typeA only if exists. If not, join name of typeB. If neither exists join name of typeC.
SELECT nnp.ID, name.NAME, name.TYPE
FROM NON_NATURAL_PERSON nnp
INNER JOIN NNP_NAME name ON (name.NON_NATURAL_PERSON = nnp.ID)
If type is spelled exactly as it's written (typeA, typeB, typeC) then you can use MIN() function:
SELECT NON_NATURAL_PERSON, MIN(type) AS min_type
FROM NNP_NAME
GROUP BY NON_NATURAL_PERSON
if you also want the username you can use this query:
SELECT
n1.NON_NATURAL_PERSON AS ID,
n1.Name,
n1.Type
FROM
NNP_NAME n1 LEFT JOIN NNP_NAME n2
ON n1.NON_NATURAL_PERSON = n2.NON_NATURAL_PERSON
AND n1.Type > n2.type
WHERE
n2.type IS NULL
Please see this fiddle. If Types are not literally sorted, change this line:
AND n1.Type > n2.type
with this:
AND FIELD(n1.Type, 'TypeA', 'TypeB', 'TypeC') >
FIELD(n2.type, 'TypeA', 'TypeB', 'TypeC')
MySQL FIELD(str, str1, str2, ...) function returns the index (position) of str in the str1, str2, ... list, and 0 if str is not found. You want to get the "first" record, ordered by type, for every NON_NATURAL_PERSON. There are multiple ways to get this info, I chose a self join:
ON n1.NON_NATURAL_PERSON = n2.NON_NATURAL_PERSON
AND n1.Type > n2.type -- or filed function
with the WHERE condition:
WHERE n2.type IS NULL
this will return all rows where the join didn't succeed - the join won't succeed when there is not n2.type that is less than n1.type - it will return the first record.
Edit
If you want a platform independent solution, avoiding the creation of new tables, you could use CASE WHEN, just change
AND n1.Type > n2.Type
with
AND
CASE
WHEN n1.Type='TypeA' THEN 1
WHEN n1.Type='TypeB' THEN 2
WHEN n1.Type='TypeC' THEN 3
END
>
CASE
WHEN n2.Type='TypeA' THEN 1
WHEN n2.Type='TypeB' THEN 2
WHEN n2.Type='TypeC' THEN 3
END
There is a piece of information missing. You say:
Always join name of typeA only if exists. If not, join name of typeB. If neither exists join name of typeC.
But you do not indicate why you prefer typeA over typeB. This information is not included in your data.
In the answer of #fthiella, either lexicographical is assumed, or an arbitrary order is given using FIELD. This is also the reason why two joins with the table nnp_name is necessary.
You can solve this problem by adding a table name_type (id, name, order) and changing the type column to contain the id. This will allow you to add the missing information in a clean way.
With an additional join with this new table, you will be able get the preferred nnp_name for each row.

WHere clause taking much longer time

HI i am using this query below its taking hell lot of a time around like 5 mins .. its crazy .. any better way ? or reason fr such long time .. even cutting it into smaller query n getting the value n then finding the common values is much much faster then this.
SELECT Product_ItemID
FROM Product_ItemProperties
LEFT JOIN Product_Items USING (Product_ItemID)
WHERE
Product_CatalogueID = 'xx' AND
Field = 'brandname' AND
MATCH (Value) AGAINST ('xx' IN BOOLEAN MODE) AND
Product_ItemID IN (Select Product_ItemID
FROM Product_ItemProperties
Where Field = 'xx' AND
Match (Value) AGAINST ('xx' IN BOOLEAN MODE)
);
i dont know why in first where clause you are making Field = 'brandname' and in second inner where clause you are filtering by Field = 'xx' . anyway you are double selecting Product_ItemProperties while you should use it once.
try this:
SELECT
Product_ItemID
FROM
Product_ItemProperties
LEFT JOIN
Product_Items USING (Product_ItemID)
WHERE
Product_CatalogueID = 'xx'
AND Field = 'brandname'
AND MATCH (Value) AGAINST ('xx' IN BOOLEAN MODE)
AND Field = 'xx';
It looks like your itemProperties table has multiple entries for the same "Product_ItemID", and you are looking for something that is both "BrandX", AND has some "OtherProperty" of a different value. To handle this, you can use that table TWICE (or more if more property values you are interested in... I would suggest having an index on your Product_ItemProperties table by (Product_ItemID, Field, Value) to be best optimization of the query.
For example, you are looking for a Car Brand "X", and secondly, it is a "Blue" car (not considering the catalog component).
Also, notice in this query, I give simplified aliases, and qualify each field with the alias.field so there is no ambiguity which field is coming from where.
The outer WHERE clause is your first criteria, only get those items that have a minimum of the brand name field, and it matches the value you are expecting... From those, join again to the product item properties table, but for the "other" field value you are interested in, and its value.
I was unsure where the cataglog component was, but I suspect that's from the product table and should have ITs alias adjusted.
SELECT
ByBrand.Product_ItemID,
P.NameOfProduct, (just an example to get this too)
FROM
Product_ItemProperties ByBrand
JOIN Product_Items P
ON ByBrand.Product_ItemID = P.Product_ItemID
JOIN Product_ItemProperties ByOtherField
ON ByBrand.Product_ItemID = ByOtherField.Product_ItemID
AND ByOtherField.Field = 'otherPropertyInterestedIn'
AND MATCH (ByOtherField.Value) against ( 'otherValueLookingFor' IN BOOLEAN MODE )
WHERE
ByBrand.Product_CatalogueID = 'someCatalogID' (or is this from product_items table)
AND ByBrand.Field = 'brandname'
AND MATCH (ByBrand.Value) against ( 'brandValueLookingFor' IN BOOLEAN MODE )