Home > Mobile >  Query only takes the first value of subquery
Query only takes the first value of subquery

Time:07-23

I am creating an SQL function to report, the parameters I receive are several strings containing the PK separated by , example:

@ID_Model_list = '1,2'
@ID_Station_list = '1,4,7,8,10'

in my SQL query I perform a subquery, with it I convert the variables I receive into a column, example:

SELECT CAST(VALUE AS INT) AS ID FROM string_split(@ID_Model_list,',')

would be the same as: SELECT CAST(value AS int) AS ID FROM string_split('1,2',',')

Result:

enter image description here

If I add the code above to my query, it only takes the first value of the column that results from the subquery

CREATE FUNCTION V_Scrap_Report
 (@ID_Model_list varchar, @ID_Station_list varchar, @fecha datetime, @fechafin datetime)
 RETURNS TABLE
 AS RETURN
(SELECT   S.IDScrap
        , S.fecha
        , M.modelo
        , E.estacion 
        , C.correccion
        , S.elemento
        , P.nombre
        , P.numeroparte
        , Sp.cantidad
FROM            dbo.Scrap           S 
FULL OUTER JOIN dbo.Modelo          M   ON  S.IDModelo      =   M.IDModelo
FULL OUTER JOIN dbo.Estacion        E   ON  E.IDEstacion    =   S.IDEstacion
FULL OUTER JOIN dbo.Scrapcorreccion Sc  ON  S.IDScrap       =   Sc.IDScrap
FULL OUTER JOIN dbo.Correccion      C   ON  C.IDCorrecion   =   Sc.IDCorrecion 
FULL OUTER JOIN dbo.Scraparte       Sp  ON  S.IDScrap       =   Sp.IDScrap
JOIN        dbo.Parte           P   ON  Sp.IDParte      =   P.IDParte 
WHERE S.fecha   >= @fecha 
AND   S.fecha   <= DATEADD(HOUR,23.9999,@fechafin)
AND   S.IDModelo = (SELECT CAST(VALUE AS INT) AS ID FROM string_split(@ID_Model_list,','))
AND   S.IDEstacion = (SELECT VALUE FROM string_split(@ID_Station_list,',')))

The above function is only returning results when S.IDModelo = 1 AND S.IDEstacion = 1 does not take into account that there is:

S.IDModelo = 2 AND S.IDEstacion = 1

S.IDModelo = 1 AND S.IDEstacion = 4

S.IDModelo = 1 AND S.IDEstacion = 7

S.IDModelo = 1 AND S.IDEstacion = 8

S.IDModelo = 2 AND S.IDEstacion = 10

When I call the function I do it like this:

SELECT * FROM V_Scrap_Report('1,2','1,4,7,8,10','2022-07-18','2022-07-20')

oddish, if i change ... V_Scrap_Report('1,2'... by ... V_Scrap_Report('2,1'... just bring

S.IDModelo = 2 AND S.IDEstacion = 1

what could be missing in the query so as not to skip matches?

CodePudding user response:

The comments and Bohemian's answer give you a few specific things that are wrong with your query that you need to look at, but I think what you really need is a different understanding of what you're doing. So...

A select returns a set. (Technically a bag because it can contain duplicates, but we'll ignore that).

A set can have zero members, or one member, or more members. There's a set of integers greater than 1 and less than 4, and that set is {2, 3}. There's a set of integers less than 1 and greater than 4, and that set is {} aka "the empty set".

So a set is a collection of zero or more things, not one thing.

When you're comparing things, you can't compare a collection of things with just one thing. Suppose I have a bag of oranges in my left hand, and one orange in my right hand. Does the orange in my right hand taste the same as the bag of oranges in my left hand? The question doesn't really make sense, does it? You have to compare the one orange in my right hand with each orange in the bag, individually.

So if I ask SQL to evaluate whether orange_in_right_hand = { first_orange_in_bag, second_orange_in_bag, third_orange_in_bag } what do you want it to do? It's a question that doesn't really make sense.

You have a situation like this in your query:

where S.IDEstacion = (SELECT VALUE FROM string_split(@ID_Station_list,',')))

You do a similar comparison with @ID_model_list.

The left hand side of that operation is one value. The right hand side of that operation is the result of a select, which can return more than one value. In this case, the output of the string_split function. You are asking SQL to determine whether the one thing is equal to potentially many things.

This doesn't really make sense as we saw with the oranges. And because it doesn't make sense, it will actually cause an error.

So why aren't you getting an error? Because in your specific case, the set returned by string_split will happen to only have one member, because you have a bug in the code.

Let's look at @ID_station_list. Your input to the string_split is the @ID_Station_list parameter, which you have said is a varchar. But you didn't say how long it is. In this case, that means it will be treated as being one character long:

declare @ID_station_list varchar;
set @ID_station_list = '1,4,7,8,10';
select @ID_station_list;

What do you think this will return? It will return the string value '1'. All of the other characters got thrown away, because you didn't say @ID_station_list was a varchar big enough to hold the value you gave it. You just said it was a varchar, and SQL will assume you meant varchar(1) in this case.

So the value you are passing to string_split function is just the value '1'. So you get one value back when you split this string.

SQL Server will then look at that and think "well, ok, you are asking me whether a single value is equal to the result of a select, which you really shouldn't be doing because it doesn't make sense, but in this particular case I will do it for you without telling you the problem because there was only one member in the set".

If you fix your parameter declaration by, say, making @ID_station_list a varchar(100) and giving it the value '1,2,3', you'll get an error.

So how should you compare the single value IDEstacion with the set returned by string_split? You tell SQL to check whether the value is in the set instead of checking whether it equals the set. Hence Stu's comment.

CodePudding user response:

If you put where clause conditions on an outer joined table, you effectively convert the join to an inner join.

Move such conditions to the join condition:

...
FROM            dbo.Scrap           S 
FULL OUTER JOIN dbo.Modelo          M   ON  S.IDModelo      =   M.IDModelo
  AND S.IDModelo = (SELECT CAST(VALUE AS INT) AS ID FROM string_split(@ID_Model_list,','))
FULL OUTER JOIN dbo.Estacion        E   ON  E.IDEstacion    =   S.IDEstacion
  AND S.IDEstacion = (SELECT VALUE FROM string_split(@ID_Station_list,',')))
FULL OUTER JOIN dbo.Scrapcorreccion Sc  ON  S.IDScrap       =   Sc.IDScrap
FULL OUTER JOIN dbo.Correccion      C   ON  C.IDCorrecion   =   Sc.IDCorrecion 
FULL OUTER JOIN dbo.Scraparte       Sp  ON  S.IDScrap       =   Sp.IDScrap
JOIN        dbo.Parte           P   ON  Sp.IDParte      =   P.IDParte 
WHERE S.fecha   >= @fecha 
AND   S.fecha   <= DATEADD(HOUR,23.9999,@fechafin)

CodePudding user response:

I followed your recommendations, for now the query is formed as follows, I will continue working on it, to improve it

CREATE FUNCTION V_Scrap_Report
 (@ID_Model_list varchar(10), @ID_Station_list varchar(150), @fecha datetime, @fechafin datetime)
 RETURNS TABLE
 AS RETURN
(SELECT S.IDScrap
        , S.fecha
        , M.modelo
        , E.estacion 
        , C.correccion
        , S.elemento
        , P.nombre
        , P.numeroparte
        , Sp.cantidad
FROM            dbo.Scrap       S 
LEFT JOIN       dbo.Modelo      M   ON  S.IDModelo      =   M.IDModelo
AND   S.IDModelo IN (SELECT CAST(VALUE AS INT) AS ID FROM string_split(@ID_Model_list,','))
LEFT JOIN       dbo.Estacion    E   ON  S.IDEstacion    =   E.IDEstacion
AND   S.IDEstacion IN (SELECT CAST(VALUE AS INT) AS IDE FROM string_split(@ID_Station_list,','))
FULL OUTER JOIN dbo.Scrapcorreccion Sc  ON  S.IDScrap   =   Sc.IDScrap
LEFT JOIN dbo.Correccion        C   ON  Sc.IDCorrecion  =   C.IDCorrecion 
LEFT JOIN dbo.Scraparte         Sp  ON  S.IDScrap   =   Sp.IDScrap
LEFT JOIN dbo.Parte         P   ON  Sp.IDParte  =   P.IDParte
WHERE S.fecha   >= @fecha 
AND   S.fecha   <= DATEADD(HOUR,23.9999,@fechafin)
)
  • Related