Remove leading zero in join - sql

The tables are:
Table A: tag_num (nvarchar(6)) Table B: tag_num (nvarchar(6))
------------------------------ ------------------------------
883 00883
The query:
select * from A test1
inner join B test2 on test2.tag_num = test1.tag_num
return 0 rows due to the leading zeros in Table B.
How can I return the result
A.tag_num | B.tag_num
883 | 00883
even though Table B column contain leading zeros.
Which data type should I use?

You can cast the columns to int
select *
from A test1
inner join B test2
on cast(test2.tag_num as int) = cast(test1.tag_num as int)
Or better use int as data type in the table in the first place.

you should cast the string to int and join on them
SELECT *
FROM A AS test1 INNER JOIN B AS test2
ON CAST(test2.tag_num AS INT) = CAST(test1.tag_num AS INT)

You can keep datatype of NVARCHAR and use SUBSTRING to remove leading zero in following:
select * from A test1
inner join B test2 on test1.tag_num =
SUBSTRING(test2.tag_num, PATINDEX('%[^0]%', test2.tag_num+'.'), LEN(test2.tag_num))

You want to avoid doing a type conversion on both sides, if you want any hope of using an index. One method is to use like:
select *
from A inner join
B
on b.tag_num like '%' + a.tag_num;
However, this is too general, because the wildcards will match too many things. Another approach would be to use in:
select *
from A inner join
B
on b.tag_num in (a.tag_num, '0' + a.tag_num, '00' + a.tag_num)
This has some hope of using an index on b.tag_num. Getting a sargable expression fora.tag_num` is more challenging.

Just a shorten version of casting:
select * from A test1
inner join B test2 on 1*test2.tag_num = test1.tag_num
Multiplying by 1 will automatically cast result to int.

Related

Use WHERE + AND + CASE + IN when crafting T-SQL

I have a stored procedure that has a few variables that may or may not be passed. they are a list of PKs from other tables, so FKs but formatted in as a string of CSVs.
here's what the query essentially looks like
DECLARE #SomeIds VARCHAR(MAX) = ''
CREATE TABLE #TempIds (Id INT)
IF (#SomeIds = '' OR #SomeIds = NULL) INSERT INTO #TempIds VALUES (NULL)
ELSE INSERT INTO #TempIds SELECT * FROM SplitString(#SomeIds,',') -- SplitString() is a user function
SELECT cont.varchar_LastName AS LastName
,cred.varchar_CredentialName AS CredentialName
FROM [dbo].[tbl_Contacts] AS cont
LEFT JOIN [dbo].[tbl_ContactsCredentials] AS cc ON cont.pk_int_Id = cc.fk_ContactId
LEFT JOIN [dbo].[tbl_Credentials] AS cred ON cc.fk_CredentialId = cred.pk_int_Id
So this query basically gives me a full list of contacts both with and without a credential name. I don't have a WHERE clause, so not surprised.
I get data basically like:
LastName | CredentialName
---------------------------
Stevens | Admin
Arnolds | User
Bishop | NULL
Evans | NULL
So if I add a WHERE clause like this:
WHERE cred.pk_int_Id IN (SELECT * FROM #TempIds)
I get zero results.
When I run this:
SELECT * FROM #TempIds
I get this:
Id
-----------
NULL
When I run it with "real values" in #SomeIds like '1,2' then it works fine.
I presume this is because my WHERE clause is looking in the cred table and there are no NULL values in that table, so that's why I'm not getting anything.
But I'm not sure how I fix it?
I guess I really want to do something like this:
WHERE CredentialName IN (SELECT * FROM #TempIds)
But I believe to do that, I'd have to run the first query into another temp table, then run a second query on that table.
Any help is greatly appreciated.
You can avoid a temp table
WHERE (NULLIF(#SomeIds,'') IS NULL OR cred.pk_int_Id IN (SELECT value FROM SplitString(#SomeIds,',')))
Or if your Sql Server version supports STRING_SPLIT
WHERE (NULLIF(#SomeIds,'') IS NULL OR cred.pk_int_Id IN (SELECT value FROM STRING_SPLIT(#SomeIds,',')))
And then don't initialize #SomeIds to make it get all records.
I would consider using a UNION for this. Putting an OR in a WHERE clause can make for some bad execution plans, and often makes indexes unusable.
SELECT cont.varchar_LastName AS LastName
,cred.varchar_CredentialName AS CredentialName
FROM [dbo].[tbl_Contacts] AS cont
LEFT JOIN [dbo].[tbl_ContactsCredentials] AS cc
ON cont.pk_int_Id = cc.fk_ContactId
LEFT JOIN [dbo].[tbl_Credentials] AS cred
ON cc.fk_CredentialId = cred.pk_int_Id
WHERE cred.pk_int_Id IN (SELECT value FROM STRING_SPLIT(#SomeIds,','))
UNION ALL
SELECT cont.varchar_LastName AS LastName
,cred.varchar_CredentialName AS CredentialName
FROM [dbo].[tbl_Contacts] AS cont
LEFT JOIN [dbo].[tbl_ContactsCredentials] AS cc
ON cont.pk_int_Id = cc.fk_ContactId
LEFT JOIN [dbo].[tbl_Credentials] AS cred
ON cc.fk_CredentialId = cred.pk_int_Id
WHERE NULLIF(#SomeIds,'') IS NULL
I talk about using OR in UPDATES statements here. But the same logic applies to SELECT.

Trim null values in AS400

I am doing a join between two tables A and B on A.Item = B.Item. I am not getting the records as expected. After doing some investigations, I saw that all the items in table B contains nulls at the end of the item.
I would like to be able to do something like:
SELECT * FROM A INNER JOIN B ON TRIMNULL(A.ITEM) = TRIMNULL(B.ITEM);
Is there any such method in AS400 to trim the null values?
Take a look at the TRIM function in the manual. You can specify a character to trim.
If assuming you mean a hex x'00' when you say NULL. Then this should work:
SELECT *
FROM A INNER JOIN B
ON TRIM(TRAILING x'00' FROM A.ITEM)
= TRIM(TRAILING x'00' FROM B.ITEM);

SQL query: Iterate over values in table and use them in subquery

I have a simple SQL table containing some values, for example:
id | value (table 'values')
----------
0 | 4
1 | 7
2 | 9
I want to iterate over these values, and use them in a query like so:
SELECT value[0], x1
FROM (some subquery where value[0] is used)
UNION
SELECT value[1], x2
FROM (some subquery where value[1] is used)
...
etc
In order to get a result set like this:
4 | x1
7 | x2
9 | x3
It has to be in SQL as it will actually represent a database view. Of course the real query is a lot more complicated, but I tried to simplify the question while keeping the essence as much as possible.
I think I have to select from values and join the subquery, but as the value should be used in the subquery I'm lost on how to accomplish this.
Edit: I oversimplified my question; in reality I want to have 2 rows from the subquery and not only one.
Edit 2: As suggested I'm posting the real query. I simplified it a bit to make it clearer, but it's a working query and the problem is there. Note that I have hardcoded the value '2' in this query two times. I want to replace that with values from a different table, in the example table above I would want a result set of the combined results of this query with 4, 7 and 9 as values instead of the currently hardcoded 2.
SELECT x.fantasycoach_id, SUM(round_points)
FROM (
SELECT DISTINCT fc.id AS fantasycoach_id,
ffv.formation_id AS formation_id,
fpc.round_sequence AS round_sequence,
round_points,
fpc.fantasyplayer_id
FROM fantasyworld_FantasyCoach AS fc
LEFT JOIN fantasyworld_fantasyformation AS ff ON ff.id = (
SELECT MAX(fantasyworld_fantasyformationvalidity.formation_id)
FROM fantasyworld_fantasyformationvalidity
LEFT JOIN realworld_round AS _rr ON _rr.id = round_id
LEFT JOIN fantasyworld_fantasyformation AS _ff ON _ff.id = formation_id
WHERE is_valid = TRUE
AND _ff.coach_id = fc.id
AND _rr.sequence <= 2 /* HARDCODED USE OF VALUE */
)
LEFT JOIN fantasyworld_FantasyFormationPlayer AS ffp
ON ffp.formation_id = ff.id
LEFT JOIN dbcache_fantasyplayercache AS fpc
ON ffp.player_id = fpc.fantasyplayer_id
AND fpc.round_sequence = 2 /* HARDCODED USE OF VALUE */
LEFT JOIN fantasyworld_fantasyformationvalidity AS ffv
ON ffv.formation_id = ff.id
) x
GROUP BY fantasycoach_id
Edit 3: I'm using PostgreSQL.
SQL works with tables as a whole, which basically involves set operations. There is no explicit iteration, and generally no need for any. In particular, the most straightforward implementation of what you described would be this:
SELECT value, (some subquery where value is used) AS x
FROM values
Do note, however, that a correlated subquery such as that is very hard on query performance. Depending on the details of what you're trying to do, it may well be possible to structure it around a simple join, an uncorrelated subquery, or a similar, better-performing alternative.
Update:
In view of the update to the question indicating that the subquery is expected to yield multiple rows for each value in table values, contrary to the example results, it seems a better approach would be to just rewrite the subquery as the main query. If it does not already do so (and maybe even if it does) then it would join table values as another base table.
Update 2:
Given the real query now presented, this is how the values from table values could be incorporated into it:
SELECT x.fantasycoach_id, SUM(round_points) FROM
(
SELECT DISTINCT
fc.id AS fantasycoach_id,
ffv.formation_id AS formation_id,
fpc.round_sequence AS round_sequence,
round_points,
fpc.fantasyplayer_id
FROM fantasyworld_FantasyCoach AS fc
-- one row for each combination of coach and value:
CROSS JOIN values
LEFT JOIN fantasyworld_fantasyformation AS ff
ON ff.id = (
SELECT MAX(fantasyworld_fantasyformationvalidity.formation_id)
FROM fantasyworld_fantasyformationvalidity
LEFT JOIN realworld_round AS _rr
ON _rr.id = round_id
LEFT JOIN fantasyworld_fantasyformation AS _ff
ON _ff.id = formation_id
WHERE is_valid = TRUE
AND _ff.coach_id = fc.id
-- use the value obtained from values:
AND _rr.sequence <= values.value
)
LEFT JOIN fantasyworld_FantasyFormationPlayer AS ffp
ON ffp.formation_id = ff.id
LEFT JOIN dbcache_fantasyplayercache AS fpc
ON ffp.player_id = fpc.fantasyplayer_id
-- use the value obtained from values again:
AND fpc.round_sequence = values.value
LEFT JOIN fantasyworld_fantasyformationvalidity AS ffv
ON ffv.formation_id = ff.id
) x
GROUP BY fantasycoach_id
Note in particular the CROSS JOIN which forms the cross product of two tables; this is the same thing as an INNER JOIN without any join predicate, and it can be written that way if desired.
The overall query could be at least a bit simplified, but I do not do so because it is a working example rather than an actual production query, so it is unclear what other changes would translate to the actual application.
In the example I create two tables. See how outer table have an alias you use in the inner select?
SQL Fiddle Demo
SELECT T.[value], (SELECT [property] FROM Table2 P WHERE P.[value] = T.[value])
FROM Table1 T
This is a better way for performance
SELECT T.[value], P.[property]
FROM Table1 T
INNER JOIN Table2 p
on P.[value] = T.[value];
Table 2 can be a QUERY instead of a real table
Third Option
Using a cte to calculate your values and then join back to the main table. This way you have the subquery logic separated from your final query.
WITH cte AS (
SELECT
T.[value],
T.[value] * T.[value] as property
FROM Table1 T
)
SELECT T.[value], C.[property]
FROM Table1 T
INNER JOIN cte C
on T.[value] = C.[value];
It might be helpful to extract the computation to a function that is called in the SELECT clause and is executed for each row of the result set
Here's the documentation for CREATE FUNCTION for SQL Server. It's probably similar to whatever database system you're using, and if not you can easily Google for it.
Here's an example of creating a function and using it in a query:
CREATE FUNCTION DoComputation(#parameter1 int)
RETURNS int
AS
BEGIN
-- Do some calculations here and return the function result.
-- This example returns the value of #parameter1 squared.
-- You can add additional parameters to the function definition if needed
DECLARE #Result int
SET #Result = #parameter1 * #parameter1
RETURN #Result
END
Here is an example of using the example function above in a query.
SELECT v.value, DoComputation(v.value) as ComputedValue
FROM [Values] v
ORDER BY value

SQL Join With Fallback

Given
CREATE TABLE Addresses
Id INT NOT NULL
Zip NVARCHAR(5) NULL
ZipPlus4 NVARCHAR(9) NULL
CREATE TABLE ZipLookup
Zip NVARCHAR(5) NULL
Code NVARCHAR(10) NULL
CREATE TABLE ZipPlus4Lookup
ZipPlus4 NVARCHAR(9) NULL
Code NVARCHAR(10) NULL
And data like
Addresses
1 | 92123 | 921234444
ZipLookup
92123 | Type A
ZipPlus4Lookup
921234444 | Type B
Is it possible to construct a query such that:
A given row in Addresses is outer joined to ZipPlus4Lookup if there is a match
Addresses.ZipPlus4 = ZipPlus4Lookup.ZipPlus4
Otherwise, the given row in Addresses is outer joined to ZipLookup if there is a match
Addresses.Zip = ZipLookup.Zip
Otherwise neither table is outer joined
In plain English, the Addresses table has a Zip and a ZipPlus4 column and I need to look up a code using the most precise match. If there's a match on Zip+4, use the code from that match. Otherwise, use the code from a Zip match.
I wish I had an attempted query to share, but with this one I don't know where to start.
This basic query will work:
SELECT
A.*,
Code = IsNull(Z4.Code, Z.Code)
FROM
dbo.Addresses A
LEFT JOIN dbo.ZipPlus4Lookup Z4
ON A.ZipPlus4 = Z4.ZipPlus4
LEFT JOIN dbo.ZipLookup Z
ON A.Zip = Z.Zip
AND Z4.ZipPlus4 IS NULL;
Or you could try something like this:
SELECT
A.*,
Z.Code
FROM
dbo.Addresses A
OUTER APPLY (
SELECT TOP 1 Code
FROM (
SELECT 0, Code FROM dbo.ZipPlus4Lookup Z4
WHERE A.ZipPlus4 = Z4.ZipPlus4
UNION ALL
SELECT 1, Code FROM dbo.ZipLookup Z
WHERE A.Zip = Z.Zip
) X (Seq, Code)
ORDER BY X.Seq
) Z;
They may have different performance characteristics. It's worth testing. My guess is the second query is unnecessary but it's still conceptually possible to be better.
See these in action in a SQL Fiddle.

How can I select a subset of columns from a table when relevant in an outer join?

select a.cust_xref_id, a.est_hour, a.phone_nbr as number, a.credit_calls, a.credit_rpcs, b.sdp_calls
from #temp0 a
full outer join #temp2 b
on a.cust_xref_id = b.sdp_cust_xref_id
and a.est_hour = b.sdp_hour
and a.phone_nbr = b.sdp_phone
Is there a way to get the data from table b with regard to sdp_cust_xref_id, sdp_hour, and sdp_phone when the data does not exist in both tables via the join? If b.sdp_calls does exist, the column values are null.
I read it a few more times and I think I know what you want. Try this. It will give you the values from table b if they are NULL in a:
select COALESCE(a.cust_xref_id, b.sdp_cust_xref_id) as cust_xref_id,
COALESCE(a.est_hour, b.spd_hour) as est_hour,
COALESCE(a.phone_nbr, b.spd_phone) as number,
a.credit_calls,
a.credit_rpcs,
b.sdp_calls
from #temp0 a
full outer join #temp2 b
on a.cust_xref_id = b.sdp_cust_xref_id
and a.est_hour = b.sdp_hour
and a.phone_nbr = b.sdp_phone