Check if element in list string BigQuery - arrays

I have a comma separated list in BigQuery
select '1,2,3' as number_list
I want to return true if 1 is in the list without splitting into an array then unnesting
I want to be able to say
select if(1 in split('1,2,3'),1,0)
I also want to avoid saying
select if('1,2,3' like '%,1,%' or '1,2,3' like '1,%' or '1,2,3' like '%,1',1,0)

Below example for BigQuery Standard SQL
#standardSQL
CREATE TEMP FUNCTION InList(list STRING, num INT64) AS ((
SELECT COUNTIF(num = CAST(number AS INT64)) FROM UNNEST(SPLIT(list)) number
));
WITH `project.dataset.table` AS (
SELECT '1,2,3' AS number_list UNION ALL
SELECT '2,3,4'
)
SELECT number_list, InList(number_list, 1) in_list
FROM `project.dataset.table`
with result
Row number_list in_list
1 1,2,3 1
2 2,3,4 0
I also want to avoid saying
SELECT IF('1,2,3' LIKE '%,1,%' OR '1,2,3' LIKE '1,%' OR '1,2,3' LIKE '%,1',1,0)
to avoid such redundancy you can use below version
SELECT IF(CONCAT(',', number_list, ',') LIKE CONCAT('%,1,%'), 1, 0)
... And, finally - and most likely the winner :o)
I want to be able to say select if(1 in split('1,2,3'),1,0)
The closest is
SELECT IF('1' IN UNNEST(SPLIT(number_list)), 1, 0)

You can use subquery with MAX function matching your searched value
SELECT id,
(SELECT MAX(IF(n = 1, n, null)) = 1 FROM UNNEST(number_list) AS n)
FROM (
SELECT
1 AS id,
[1,2,3] AS number_list
)
or with
SELECT id,
(SELECT MAX(IF(n = '1', n, null)) = '1' FROM UNNEST(number_list) AS n)
FROM (
SELECT
1 AS id,
SPLIT('1,2,3',',') AS number_list
)

Related

distinct result by entire table when using cross apply on json and order by with case

I need to order my result by value from dictionary that stored as JSON in my table that equals a parameter.
In order to get it I'm using case on my order by to check if the value from the dictionary match the parameter.
After ordering the table I need to distinct the result however I'm getting an error and I couldn't figure it out.
here is my query:
declare #FilteredItemIDs -> temp table that filtered my items
declare #CurrentGroupID as int
select distinct item.*
from Items as items
outer apply openjson(json_query(Data, '$.itemOrderPerGroup'), '$') as X
where items.ItemID in (select ItemID from #FilteredItemIDs )
order by case
when #CurrentGroupID!= 0 and (JSON_VALUE(X.[Value], '$.Key') = #CurrentGroupID) then 1
else 2 end,
CONVERT(int, JSON_VALUE(X.[Value], '$.Value'))
When you DISTINCT over a resultset, you are effectively using GROUP BY over all columns. So the X.Value doesn't exist anymore when you get to the ORDER BY.
Using DISTINCT is usually a code smell, it indicates that joins have not been thought through. In this case, you should probably place the OPENJSON inside a subquery, with SELECT TOP (1), although it's hard to say without sample data and expected results.
select
item.*
from Items as items
outer apply (
select top (1)
X.[Key],
X.Value
from openjson(Data, '$.itemOrderPerGroup')
with (
[Key] int,
Value int
) as X
) X
where items.ItemID in (select ItemID from #FilteredItemIDs )
order by case
when #CurrentGroupID != 0 and X.Key = #CurrentGroupID then 1
else 2 end,
X.[Value];
Note the correct use of OPENJSON with a JSON path and property names.
If what you actually want is to filter the OPENJSON results, rather than ordering by them, then you can do that in an EXISTS
select
item.*
from Items as items
outer apply X
where items.ItemID in (select ItemID from #FilteredItemIDs )
and exists (select 1
from openjson(Data, '$.itemOrderPerGroup')
with (
[Key] int,
Value int
) as X
where #CurrentGroupID = 0 or X.Key = #CurrentGroupID
);

Create a SELECT with defined number of rows

I have a stored procedure that should insert some random rows in a table depending on the amount values
#amount1 INT --EligibilityID = 1
#amount2 INT --EligibilityID = 2
#amount3 INT --EligibilityID = 3
Maybe the obvious way is to use TOP(#amount) but there are a lot of amount values and the second select is much larger. So, I was looking for a way to do it in a single statement if possible.
INSERT INTO [dbo].[CaseInfo]
SELECT ([EligibilityID],[CaseNumber],[CaseMonth])
FROM (
SELECT TOP(#amount1) [EligibilityID],[CaseNumber],[CaseMonth]
FROM [dbo].[tempCases]
WHERE [EligibilityID] = 1
)
INSERT INTO [dbo].[CaseInfo]
SELECT ([EligibilityID],[CaseNumber],[CaseMonth])
FROM (
SELECT TOP(#amount2) [EligibilityID],[CaseNumber],[CaseMonth]
FROM [dbo].[tempCases]
WHERE [EligibilityID] = 2
)
INSERT INTO [dbo].[CaseInfo]
SELECT ([EligibilityID],[CaseNumber],[CaseMonth])
FROM (
SELECT TOP(#amount3) [EligibilityID],[CaseNumber],[CaseMonth]
FROM [dbo].[tempCases]
WHERE [EligibilityID] = 3
)
I would recommend to use row_number, partitioned by eligibilityID, and then compare it with a case statement to select the correct variable each time:
INSERT INTO [dbo].[CaseInfo]
SELECT ([EligibilityID],[CaseNumber],[CaseMonth])
FROM (
SELECT [EligibilityID],[CaseNumber],[CaseMonth]
,row_number() over (partition by EligibilityID order by CaseNumber) as rn -- you haven't mentioned an ORDER BY, you can change it here
FROM [dbo].[tempCases]
) as table1
where rn<=case
when EligibilityID=1 then #amount1
when EligibilityID=2 then #amount2
when EligibilityID=3 then #amount3
end

T-SQL select value where value contains less than 3 of the declared characters

Im trying to write a select statement which returns the value if it doesnt have at least 3 of the declared characters but I cant think of how to get it working, can someone point me in the right direction?
One thing to consider, I am not allowed to create a temporary table for this exercise.
I havn't really got any SQL so far as I cant think of a way to do it without a temp table.
the declared characters are any alpha characters between a and z, so if the value in the db is '1873' then it would return the value because it doesnt have at least 3 of the declared characters, but if the value was 'abcdefg' then it would not be returned as it has at least 3 of the declared characters.
Is anyone able to point me in a starting direction for this?
This will find all sys.objects with an x or a z:
Some explanations, as this is an exercise and you want to learn something:
You can split a delimitted string by transforming it into XML. x,z comes out as <x>x</x><x>z</x>. You can use this to create a derived table.
I use a CTE to avoid a created or declared table...
You can use CROSS APPLY for row-wise actions. Here I use CHARINDEX to find the position(s) of the chars you are looking for.
If all of them are not found, there SUM is zero. I use GROUP BY and HAVING to check this.
Hope this is clear :-)
DECLARE #chars VARCHAR(100)='x,z';
WITH Splitted AS
(
SELECT A.B.value('.','char') AS TheChar
FROM
(
SELECT CAST('<x>' + REPLACE(#chars,',','</x><x>')+ '</x>' AS XML) AS AsXml
) AS tbl
CROSS APPLY AsXml.nodes('/x') AS A(B)
)
SELECT name
FROM sys.objects
CROSS APPLY (SELECT CHARINDEX(TheChar,name) AS Found FROM Splitted) AS Found
GROUP BY name,Found
HAVING SUM(Found)>0
With
SrcTab As (
Select *
From (values ('Contains x y z')
, ('Contains x and y')
, ('Contains y only')) v (SrcField)),
CharList As ( --< CTE instead of temporary table
Select *
From (values ('x')
, ('y')
, ('z')) v (c))
Select SrcField
From SrcTab, CharList
Group By SrcField
Having SUM(SIGN(CharIndex(C, SrcField))) < 3 --< Count hits
;
If Distinct is not desirable and we need to only check count for each row:
With
SrcTab As ( --< Sample Data CTE
Select *
From (values ('Contains x y z')
, ('Contains x and y')
, ('Contains y only')
, ('Contains y only')) v (SrcField))
Select SrcField
From SrcTab
Where (
Select Count(*) --< Count hits
From (Values ('x'), ('y'), ('z')) v (c)
Where CharIndex(C, SrcField) > 0
) < 3
;
Using Numbers Table and Joins..I used declared characters as only 4 for demo purposes
Input:
12345
abcdef
ab
Declared table:used only 3 for demo..
a
b
c
Output:
12345
ab
Demo:
---Table population Scripts
Create table #t
(
val varchar(20)
)
insert into #t
select '12345'
union all
select 'abcdef'
union all
select 'ab'
create table #declarecharacters
(
dc char(1)
)
insert into #declarecharacters
select 'a'
union all
select 'b'
union all
select 'c'
Query used
;with cte
as
(
select * from #t
cross apply
(
select substring(val,n,1) as strr from numbers where n<=len(val))b(outputt)
)
select val from
cte c
left join
#declarecharacters dc1
on
dc1.dc=c.outputt
group by val
having
sum(case when dc is null then 0 else 1 end ) <3

How to query number based SQL Sets with Ranges in SQL

What I'm looking for is a way in MSSQL to create a complex IN or LIKE clause that contains a SET of values, some of which will be ranges.
Sort of like this, there are some single numbers, but also some ranges of numbers.
EX: SELECT * FROM table WHERE field LIKE/IN '1-10, 13, 24, 51-60'
I need to find a way to do this WITHOUT having to specify every number in the ranges separately AND without having to say "field LIKE blah OR field BETWEEN blah AND blah OR field LIKE blah.
This is just a simple example but the real query will have many groups and large ranges in it so all the OR's will not work.
One fairly easy way to do this would be to load a temp table with your values/ranges:
CREATE TABLE #Ranges (ValA int, ValB int)
INSERT INTO #Ranges
VALUES
(1, 10)
,(13, NULL)
,(24, NULL)
,(51,60)
SELECT *
FROM Table t
JOIN #Ranges R
ON (t.Field = R.ValA AND R.ValB IS NULL)
OR (t.Field BETWEEN R.ValA and R.ValB AND R.ValB IS NOT NULL)
The BETWEEN won't scale that well, though, so you may want to consider expanding this to include all values and eliminating ranges.
You can do this with CTEs.
First, create a numbers/tally table if you don't already have one (it might be better to make it permanent instead of temporary if you are going to use it a lot):
;WITH Numbers AS
(
SELECT
1 as Value
UNION ALL
SELECT
Numbers.Value + 1
FROM
Numbers
)
SELECT TOP 1000
Value
INTO ##Numbers
FROM
Numbers
OPTION (MAXRECURSION 1000)
Then you can use a CTE to parse the comma delimited string and join the ranges with the numbers table to get the "NewValue" column which contains the whole list of numbers you are looking for:
DECLARE #TestData varchar(50) = '1-10,13,24,51-60'
;WITH CTE AS
(
SELECT
1 AS RowCounter,
1 AS StartPosition,
CHARINDEX(',',#TestData) AS EndPosition
UNION ALL
SELECT
CTE.RowCounter + 1,
EndPosition + 1,
CHARINDEX(',',#TestData, CTE.EndPosition+1)
FROM CTE
WHERE
CTE.EndPosition > 0
)
SELECT
u.Value,
u.StartValue,
u.EndValue,
n.Value as NewValue
FROM
(
SELECT
Value,
SUBSTRING(Value,1,CASE WHEN CHARINDEX('-',Value) > 0 THEN CHARINDEX('-',Value)-1 ELSE LEN(Value) END) AS StartValue,
SUBSTRING(Value,CASE WHEN CHARINDEX('-',Value) > 0 THEN CHARINDEX('-',Value)+1 ELSE 1 END,LEN(Value)- CHARINDEX('-',Value)) AS EndValue
FROM
(
SELECT
SUBSTRING(#TestData, StartPosition, CASE WHEN EndPosition > 0 THEN EndPosition-StartPosition ELSE LEN(#TestData)-StartPosition+1 END) AS Value
FROM
CTE
)t
)u INNER JOIN ##Numbers n ON n.Value BETWEEN u.StartValue AND u.EndValue
All you would need to do once you have that is query the results using an IN statement, so something like
SELECT * FROM MyTable WHERE Value IN (SELECT NewValue FROM (/*subquery from above*/)t)

T-SQL: accessing temporary column in Common Table Expression

Is it possible to access a temporary column that was defined in a query for a Common Table Expression? Say I have
select * from myTable
;with cte as
(
select
*, Salary * 4 as FourTimesSalary
from
Employees
where
Name = #name
and ID >= 100
)
Is there a way to use the temporary column FourTimesSalary when querying cte like so?
select top 2 *
from cte
order by FourTimesSalary, Name
TIA.
Yes you can do that. Example:
with temp as
(
select 1 as id, 2*4 as val
UNION
select 2 as id, 3*4 as val
)
SELECT * FROM temp ORDER BY VAL desc
Your example looks fine, did you get an error when you tried that or something?

Resources