Window function for is_unique? - sql

I'm looking to see if a value is unique in the column. For example:
; with tbl (value) as (
select 'hello' UNION ALL
select 'hello' UNION ALL
select 'abc' UNION ALL
select null
) select
value,
COUNT(1) OVER (PARTITION BY VALUE) = 1 value_is_unique
from tbl
And the result:
VALUE VALUE_IS_UNIQUE
hello FALSE
hello FALSE
abc TRUE
TRUE
Is there a window function that basically does what I'm doing with the COUNT(1) OVER (PARTITION BY VALUE) = 1? Or is the above the suggested way to do this?
https://docs.snowflake.com/en/sql-reference/functions-analytic.html

There's no built-in is_unique function. Counting and comparing to one, as you did, is probably the best approach to achieve this functionality.

Related

Oracle- Need some changes to the Query

I have the below Query. My Expected output would be as below. Please help me make changes to the Query
select
ID,TERM,
case
when TERM like '____1_' then
function_toget_hrs(ID, TERM,sysdate) else null
end fall_hours,
case
when TERM like '____2_' then
function_toget_hrs(ID, TERM,sysdate) else null
end winter_hours
from TABLE_TERM
where ID='12087762'
Expecting one row for each ID. Please help me the ways
Pivoting is what you need:
WITH TABLE_TERM AS
(
SELECT 12087762 AS ID, '202110____1_' AS term, 12 AS func FROM dual UNION ALL
SELECT 12087762 AS ID, '202120____2_' AS term, 16 FROM dual UNION ALL
SELECT 12087762 AS ID, '202140____1_' AS term, 0 FROM dual
)
SELECT *
FROM (SELECT ID
, DECODE(SUBSTR(term,-6),'____1_','fall_hours','winter_hours') AS hrs
, func --function_toget_hrs(ID, TERM,sysdate) for test purposes
FROM TABLE_TERM
WHERE ID = '12087762'
)
PIVOT (SUM(func) FOR hrs IN ('fall_hours','winter_hours'));

BigQuery: Get the first non-null value in a window?

I would like to obtain the first non-null, non-"undefined" value in a list of values as part of a window.
Minimal example:
Given the following code:
SELECT
FIRST_VALUE(
CASE WHEN val = "undefined" THEN NULL ELSE val END
IGNORE NULLS
)
OVER (ORDER BY order_key)
AS res
FROM (
SELECT 1 AS order_key, CAST(NULL AS STRING) AS val
UNION ALL
SELECT 2 AS order_key, "undefined" AS val
UNION ALL
SELECT 3 AS order_key, "value" AS val
) base
I'd expect
res
value
value
value
as the result set. Yet, the result given by the above is the following:
res
null
null
value
The documentation states the following:
FIRST_VALUE (value_expression [{RESPECT | IGNORE} NULLS])
Returns the value of the value_expression for the first row in the current window frame.
This function includes NULL values in the calculation unless IGNORE NULLS is present. If IGNORE NULLS is present, the function excludes NULL values from the calculation.
Yet it seems like value_expression is not what is tested for NULLs in this case.
It seems that instead FIRST_VALUE checks NULLs against the source field, not the CASE statement (effectively value_expression in the above).
While the problem can easily be fixed by doing the case as part of the subquery, I'd like to better understand why this is an issue. Why does FIRST_VALUE not ignore the NULLs provided through the CASE statement?
Alternative to the logic above:
If you are willing to remodel your query, instead of using a window function (FIRST_VALUE), the same effect can be achieved via an ARRAY_AGG(expr IGNORE NULLS ORDER BY ordering)[OFFSET(0)]:
SELECT
id,
ARRAY_AGG(
CASE WHEN val = 'undefined' THEN NULL ELSE val END
IGNORE NULLS
ORDER BY order_key
)[OFFSET(0)]
AS res
FROM (
SELECT 1 AS id, 1 AS order_key, CAST(NULL AS STRING) AS val
UNION ALL
SELECT 1 AS id, 2 AS order_key, 'undefined' AS val
UNION ALL
SELECT 1 AS id, 3 AS order_key, "value" AS val
UNION ALL
SELECT 2 AS id, 1 AS order_key, CAST(NULL AS STRING) AS val
UNION ALL
SELECT 2 AS id, 2 AS order_key, 'undefined' AS val
UNION ALL
SELECT 2 AS id, 3 AS order_key, "value" AS val
) base
GROUP BY id
Given an empty record set for the group, ARRAY_AGG(...)[OFFSET(0)] will return NULL
Given a non-empty record set for the group, ARRAY_AGG(...)[OFFSET(0)] will return the first result of value_expression that is non-NULL, ordered by the ORDER BY clause provided.
The only downside (beside maybe performance?) is that you'll need to create a common table expression with this logic and then join it with your table that was using window functions.
To get expected result you just need to add DESC to the ORDER BY as in below
SELECT
FIRST_VALUE(
CASE WHEN val = "undefined" THEN NULL ELSE val END
IGNORE NULLS
)
OVER (ORDER BY order_key DESC)
AS res
FROM (
SELECT 1 AS order_key, CAST(NULL AS STRING) AS val UNION ALL
SELECT 2 AS order_key, "undefined" AS val UNION ALL
SELECT 3 AS order_key, "value" AS val
) base
so the result now is

How to convert column names to multiple rows?

I've a legacy database containing a table with multiple columns of type boolean. E.g.:
Table_1
id name has_lights has_engine has_brakes has_tyres can_move
1 bullock_cart false false false true true
2 car true true true true true
3 tank true true true false true
I'd like to write an SQL query for Table1 to fetch the id and name and the attributes (represented by the name of the column) that are true.
Expected output:
id name attributes
-- ---- ----------
1 bullock_cart has_tyres
1 bullock_cart can_move
2 car has_lights
2 car has_engine
2 car has_brakes
2 car has_tyres
2 car can_move
3 tank has_lights
3 tank has_engine
3 tank has_brakes
3 tank can_move
I wrote:
SELECT id, name,
CASE
WHEN has_lights THEN 'has_lights'
WHEN has_engine THEN 'has_engine'
WHEN has_brakes THEN 'has_brakes'
WHEN has_tyres THEN 'has_tyres'
WHEN can_move THEN 'can_move'
END
FROM TABLE1;
But this gets me only the 1st matching attribute for each row in Table1 (by virtue of CASE-WHEN).
What is the correct way to retrieve the data in the format I want? Any inputs/help will be greatly appreciated?
Notes:
The table structure isn't ideal but this is a legacy system and we cannot modify the schema.
Nested queries are ok as long as they aren't too slow - say for the sample above (I understand the number of matching rows/column factor in the slow-ness).
The simplest method is union all:
select id, name, 'has_lights' as attribute from t where has_lights union all
select id, name, 'has_engine' from t where has_engine union all
select id, name, 'has_brakes' from t where has_brakes union all
select id, name, 'has_tyres' from t where has_tyres union all
select id, name, 'can_move' from t where can_move;
If you have a very large table, then a lateral join is probably more efficient:
select t.id, t.name, v.attribute
from t, lateral
(select attribute
from (values (has_lights, 'has_lights'),
(has_engine, 'has_engine'),
(has_brakes, 'has_brakes'),
(has_tyres, 'has_tyres'),
(can_move, 'can_move')
) v(flag, attribute)
where flag
) v;
You can do it using UNION ALL :
SELECT name,'has_lights' as attributes FROM YourTable where has_lights = 'TRUE'
UNION ALL
SELECT name,'has_engine' as attributes FROM YourTable where has_engine= 'TRUE'
UNION ALL
SELECT name,'has_brakes' as attributes FROM YourTable where has_brakes = 'TRUE'
UNION ALL
SELECT name,'has_tyres' as attributes FROM YourTable where has_tyres = 'TRUE'
UNION ALL
SELECT name,'can_move' as attributes FROM YourTable where can_move = 'TRUE'
This is much like the brilliant query #Gordon posted:
SELECT t.id, t.name, v.attribute
FROM table1 t
JOIN LATERAL (
VALUES (has_lights, 'has_lights')
, (has_engine, 'has_engine')
, (has_brakes, 'has_brakes')
, (has_tyres , 'has_tyres')
, (can_move , 'can_move')
) v(flag, attribute) ON v.flag;
Just a bit shorter since the VALUES expression can stand on its own.

How to transpose dynamically in Oracle

Here is my table
Equipmentid Application Value
=========== =========== =====
k001 THK True
k001 BHK False
k001 KHK True
Here is what I expected:
Equipmentid THK BHK KHK
=========== === === ===
k001 True False True
I'm trying to use normal transpose Oracle using max decode but in the end need to mention AS [tablename], I want to dynamically create row to column base on row name, this database will involve very much application. Thank guys
Hi try using PIVOT,
WITH x(equipment_id, application, VALUE )
AS (SELECT 'k001', 'THK', 'TRUE' FROM DUAL UNION ALL
SELECT 'k001', 'BHK', 'FALSE' FROM DUAL UNION ALL
SELECT 'k001', 'KHK', 'TRUE' FROM DUAL UNION ALL
SELECT 'k002', 'KHK', 'FALSE' FROM DUAL UNION ALL
SELECT 'k002', 'THK', 'FALSE' FROM DUAL UNION ALL
SELECT 'k002', 'BHK', 'FALSE' FROM DUAL )
SELECT * FROM
(
SELECT equipment_id, value, application
FROM x
)
PIVOT
(
MAX(value)
FOR application IN ('THK', 'BHK', 'KHK')
) order by equipment_id;
Alternatively, if you want to have dynamic column, you can use subquery in the IN clause then use PIVOT XML,but result will be of XML TYPE which i dont know how to extract the values.(just saying) if you want to know more about how to do it dynamically with pl/sql. Read here .Here's the source
SELECT * FROM
(
SELECT equipment_id, value, application
FROM x
)
PIVOT XML
(
MAX(value)
FOR application IN (SELECT DISTINCT application from x)
) order by equipment_id;
Try this one.
SELECT EQUIPMENTID,
max(case when APPLICATION = 'THK' then VALUE end) as "THK",
max(case when APPLICATION = 'BHK' then VALUE end) as "BHK",
max(case when APPLICATION = 'KHK' then VALUE end) as "KHK"
FROM [tablename]
group by EQUIPMENTID;
You can left join in this case.
SELECT t1.Equipmentid, t2.Value AS 'THK', t3.Value AS 'BHK', t4.Value AS 'KHK' FROM TABLE t1
LEFT JOIN (SELECT Equipmentid, Value FROM TABLE WHERE Application = 'THK') AS t2 ON (t1.Equipmentid = t2.Equipmentid)
LEFT JOIN (SELECT Equipmentid, Value FROM TABLE WHERE Application = 'BHK') AS t3 ON (t1.Equipmentid = t3.Equipmentid)
LEFT JOIN (SELECT Equipmentid, Value FROM TABLE WHERE Application = 'KHK') AS t4 ON (t1.Equipmentid = t4.Equipmentid)
Even though it can be solve. But this method is not good in my opinion. Hope it help you anyway

Multiple Columns in an "in" statement

I am using DB 2 and i am trying to write a query which checks multiple columns against a given set of values.Like field a, field b and field c against values x,y,z,f. One way that i can think for is writing same condition 3 times with or i.e. field a in ('x','y','z','f') or field b in .... and so on . Please let me know if there is some other efficient and easy way to accomplish this. I am looking for a query like if any of the condition is true return yes else no . Please suggest !
This may or may not work on as400:
create table a (a int not null, b int not null);
insert into a (a,b) values (1,1),(1,3),(2,3),(0,23);
select a.*
from a
where a in (1,2) or b in (1,2);
A B
----------- -----------
1 1
1 3
2 3
Rewriting as a join:
select a.*
from a
join ( values (1),(2) ) b (x)
on b.x in (a.a, a.b);
A B
----------- -----------
1 1
1 3
2 3
Assuming the column data types are the same, Create a subquery joining all the columns you want to search with your IN into one column with a union
SELECT *
FROM (
SELECT
YOUR_TABLE_PRIMARY_KEY
,A AS Col
FROM YOUR_TABLE
UNION ALL
SELECT
YOUR_TABLE_PRIMARY_KEY
,B AS Col
FROM YOUR_TABLE
UNION ALL
SELECT
YOUR_TABLE_PRIMARY_KEY
,C AS Col
FROM YOUR_TABLE
) AS SQ
WHERE
SQ.Col IN ('x','y','z','f')
Make sure to include the table key so you know which row the data refers to
You can create a regular expression that describe the set of characters and use it with xquery
Assuming you're on a supported version of the OS (tested on 7.1 TR6), this should work...
with sel (val) as (values ('x'),('y'),('f'))
select * from mytbl
where flda in (select val from sel)
or fldb in (select val from sel)
or fldc in (select val from sel)
Expanding on the above since your OP asked for "condition is true return yes else no"
Assuming you've got the key to a row to check, would 'yes' or the empty set be good enough? somekey is the key for the row you want to check.
with sel (val) as (values ('x'),('y'),('f'))
select 'yes' from mytbl
where thekey = somekey
and ( flda in (select val from sel)
or fldb in (select val from sel)
or fldc in (select val from sel)
)
It's actually rather difficult to return a value when you don't have a matching row. Here's one way. Note I've switch to 1=yes, 0=no..
with sel (val) as (values ('x'),('y'),('f'))
select 1 from mytbl
where thekey = somekey
and ( flda in (select val from sel)
or fldb in (select val from sel)
or fldc in (select val from sel)
)
UNION ALL
select 0
from sysibm.sysdummy1
order by 1 desc
fetch first row only