How to count null columns in a row? - sql

How to count all the columns in a table that have a null value?
The table having a large number of columns and the method should iterate over the columns in a dynamic manner.
In any given row (selected by an identifier), count the null cells.
Select count(number of null value cells) where id=1 from table
e.g:
I have a table consisting of 200 columns I want to know how many null
cells does the row with id=1 have

Basically, you need to check every column in a selected row if it's null or not.
Since you have 200+ columns in your table, manual approach seems tedious, so you can automate it a little bit and construct the query dynamically by querying user_tab_columns:
-- set up
create table t1(
rid number primary key,
c1 varchar2(17),
c2 date,
c3 timestamp,
c4 number
);
insert into t1
valueS(1, 'string', null, systimestamp, null);
commit ;
-- going to use DECODE function - doesnt require type consistency.
select 'decode('||column_name||', null, 1, 0)+' as res
from user_tab_columns
where table_name = 'T1'
Result:
RES
------------------------------
decode(RID, null, 1, 0)+
decode(C1, null, 1, 0)+
decode(C2, null, 1, 0)+
decode(C3, null, 1, 0)+
decode(C4, null, 1, 0)+
And final query:
select decode(C4, null, 1, 0)+
decode(C3, null, 1, 0)+
decode(C2, null, 1, 0)+
decode(C1, null, 1, 0)+
decode(RID, null, 1, 0) as num_of_nulls
from t1
where rid = 1
Result:
NUM_OF_NULLS
--------------
2

Try this. You can pass any ID and get the number of columns with NULL value.
CREATE TABLE TEST (ID NUMBER, B VARCHAR2(20), C NUMBER, D VARCHAR2(200));
INSERT INTO TEST VALUES (1,NULL,NULL,'XX');
SELECT COUNT(NULL_COLS)
FROM (
SELECT
to_number(extractvalue(xmltype(dbms_xmlgen.getxml('SELECT CASE WHEN '||COLUMN_NAME||' IS NULL THEN 0 ELSE NULL END COL_VAL FROM '||TABLE_NAME||' WHERE ID=&VALUE')),'/ROWSET/ROW/COL_VAL')) NULL_COLS
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME='TEST');

Related

sql SERVER - distinct selection based on priority columns

hello I would like to find a solution to solve my problem in a single request if possible.
For the moment I take all the records then I go through the lines one by one to eliminate what I don't want.
I have 2 tables : first table with links
the second with the prefered label for the url
the second table must be consulted keeping only the row with maximum priority
priority rules are
the current user then
the user group and finally
everyone.
if the hidden column is true, exclude any reference to the url
here is the expected result.
Unfortunately, I don't see any other solution than to multiply the conditions on several selects and unions.
if you have a idea to solve my problem, thank you in advance for your help
It appears as though you can rely on pref_id for the preference ordering, correct? If so, you could try:
SELECT *
FROM table2
INNER JOIN table1 ON table2.url_id = table1.url_id
QUALIFY ROW_NUMBER() OVER (
PARTITION BY table1.url
ORDER BY pref_id ASC
) = 1
This will partition by the url and then provide only the one with lowest pref_id.
I didn't test this SQL as I wasn't sure which RDBMS you're running on, but I used Rasgo to translate the SQL.
maybe of interest in this tricky query:
select so.*, table1.url from
(select distinct t.url_id,
(select pref_id from table2 s where s.url_id = t.url_id order by "user" is null, "group" is null limit 1) pref_id
from table2 t
where not exists(select 1 from table2 s where s.hide and s.url_id = t.url_id)
) ids
join table2 so on so.pref_id = ids.pref_id
join table1 ON table1.url_id = ids.url_id
order by so.url_id;
here is my solution but i think there is better to do.
in the condition's select, I built a column which gives a level note according to the priorities
DECLARE #CUR_USER VARCHAR(10) = 'ROBERT'
DECLARE #CUR_GROUP VARCHAR(10) = 'DEV'
DECLARE #TABLE1 TABLE (
URL_ID INT
,URLNAME VARCHAR(100)
);
DECLARE #TABLE2 TABLE (
PREF_ID INT
,URL_ID INT
,FAVORITE_LABEL VARCHAR(100)
,USER_GROUP VARCHAR(10)
,USER_CODE VARCHAR(10)
,HIDE_URL DECIMAL(1, 0) DEFAULT 0
);
INSERT INTO #TABLE1
VALUES
(1, 'https://stackoverflow.com/')
,(2, 'https://www.microsoft.com/')
,(3, 'https://www.apple.com/')
,(4, 'https://www.wikipedia.org/')
;
INSERT INTO #TABLE2
VALUES
(1000, 1, 'find everything', NULL, 'ROBERT', 0)
,(1001, 1, 'a question ? find the answer here', 'DEV', NULL, 0)
,(1002, 1, 'StackOverFlow', NULL, NULL, 0)
,(1003, 2, 'Windows', 'DEV', NULL, 0)
,(1004, 2, 'Microsoft', NULL, NULL, 0)
,(1005, 3, 'Apple', NULL, NULL, 0)
,(1006, 4, 'Free encyclopedia', NULL, 'ROBERT', 1)
,(1007, 4, 'Wikipedia', NULL, NULL, 0)
,(1008, 1, 'StackOverFlow FOR MAT', 'MAT', NULL, 0)
,(1009, 2, 'Microsoft FOR MAT', 'MAT', NULL, 0)
,(1010, 3, 'Apple', 'MAT', NULL, 1)
,(1011, 4, 'Wikipedia FOR MAT', 'MAT', NULL, 0)
,(1012, 1, 'StackOverFlow', NULL, 'JEAN', 1)
,(1013, 2, 'Microsoft ', NULL, 'JEAN', 0)
,(1014, 3, 'Apple', NULL, 'JEAN', 0)
,(1015, 4, 'great encyclopedia', NULL, 'JEAN', 0)
;
SELECT t2.* ,t1.URLName
FROM #TABLE1 t1
INNER JOIN #TABLE2 t2 ON t1.URL_ID = t2.URL_ID
WHERE EXISTS (
SELECT 1
FROM (
SELECT TOP (1) test.PREF_ID
,CASE
-- if I do not comment this case: jean from the MAT group will not see apple
-- WHEN Hide_Url = 1
-- THEN 3
WHEN USER_code IS NOT NULL
THEN 2
WHEN USER_GROUP IS NOT NULL
THEN 1
ELSE 0
END AS ROW_LEVEL
FROM #TABLE2 test
WHERE (
(
test.USER_GROUP IS NULL
AND test.user_group IS NULL
AND test.USER_code IS NULL
)
OR (test.USER_GROUP = #CUR_GROUP)
OR (test.USER_code = #CUR_USER)
)
AND t2.URL_ID = test.URL_ID
ORDER BY ROW_LEVEL DESC
) test
WHERE test.PREF_ID = t2.PREF_ID
AND Hide_Url = 0
)
Simply use an ORDER BY clause that puts the preferred row first. You can use this in the window function ROW_NUMBER and work with this or use a lateral top(1) join with CROSS APPLY.
select *
from urls
cross apply
(
select top(1) *
from labels
where labels.url_id = urls.url_id
where [Group] is not null or [user] is not null or hide is not null
order by
case when [Group] is null then 2 else 1 end,
case when [user] is null then 2 else 1 end,
case when hide is null then 2 else 1 end
) top_labels
order by urls.url_id;

Get dynamic variable for SQL insert

I have a query like this:
INSERT INTO my_table
VALUES (SPECIAL_ID, 62, 0, 1, -1, NULL, NULL, -1)
WHERE sp_id IN (SELECT id = SPECIAL_ID
FROM foo
WHERE lock IS NULL)
SPECIAL_ID is not yet defined, but it should be equal to the id that comes from the inner SELECT statement from foo.
Just dont use values, use a select
INSERT INTO my_table (need To declare list of column names for table insert)
Select SPECIAL_ID, 62, 0, 1, -1, NULL, NULL, -1
From Table
WHERE sp_id IN(
SELECT id = SPECIAL_ID
FROM foo
WHERE lock IS NULL
)
If there is no table for the sp_id to be in, you could get rid of that where caluse and move the sub select up like this:
INSERT INTO my_table (need To declare list of column names for table insert)
Select SPECIAL_ID, 62, 0, 1, -1, NULL, NULL, -1
FROM foo
WHERE lock IS NULL
SELECT SPECIAL_ID, 62, 0, 1, -1, NULL, NULL, -1
INTO my_table
FROM your_source_table
WHERE sp_id in (SELECT id = SPECIAL_ID
FROM foo
WHERE lock IS NULL)
Listing column names is not necessary unless their ordinal position is different or you are only applying this to certain columns, not all of them

SQL return bitwise flag value for non null columns indicating data significance

I have a case where I need to create a unique weight value based on how much data is contained with a distinct row in a dataset. Each column is assigned a bit value indicating its significance. For instance Col1 = 1, Col2 = 2 would signify that Col2 carries more weight than Col1. Data in both Col1 and Col2 (Col1 | Col2) = 3 would be more significant than a row with data in either column.
Columns at a later time can be reclassified by significance. Therefore I am looking for a solution which would be more versatile than hard coding the bitwise operation into the SQL query (see Option 1).
I have devised Option 2 as a way to overcome the reclassification requirement but I am not sure if this is the best way to write the query. My production dataset will range from 4M-25M records.
Can any provide another example, or improve this example, of how to write this query (Option 2) to improve performance and the ability for a user to change the column's significance?
Test Code Below:
Environment: SQL Server 2014
--EXAMPLE SETUP
IF OBJECT_ID('tempdb..#DataImage') IS NOT NULL
BEGIN
DROP TABLE #DataImage
DROP TABLE #Source
END
CREATE TABLE #DataImage (ID INT PRIMARY KEY CLUSTERED IDENTITY(1,1), ColumnOne BIT, ColumnTwo BIT, Flag INT)
CREATE TABLE #Source (ID INT PRIMARY KEY CLUSTERED IDENTITY(1,1), DataOne NVARCHAR(50), DataTwo INT)
--END SETUP
--CREATE ROW COMBINATION FLAGS (Real Flag Size = 10 combinations)
INSERT INTO #DataImage (ColumnOne, ColumnTwo, Flag) SELECT 1, 0, 1
INSERT INTO #DataImage (ColumnOne, ColumnTwo, Flag) SELECT 0, 1, 2
INSERT INTO #DataImage (ColumnOne, ColumnTwo, Flag) SELECT 1, 1, 3
--CREATE TEST DATA (Real Data Size = 10M records)
INSERT INTO #Source (DataOne, DataTwo) SELECT NULL, 2
INSERT INTO #Source (DataOne, DataTwo) SELECT N'Foo', NULL
INSERT INTO #Source (DataOne, DataTwo) SELECT N'Bar', 100
--QUERY SETUPS--
--OPTION 1: CALCULATE THE FLAG FOR EVERY ROW
SELECT SourceId = s.ID, Flag = IIF(s.DataOne IS NULL, 0, 1) + IIF(s.DataTwo IS NULL, 0, 2)
FROM #Source s
--OPTION 2: RETURN FLAG FOR SOURCE DATA
;WITH t0 (SourceId, Flag, C1, C2) AS (
SELECT s.ID, i.Flag, IIF(s.DataOne IS NULL, 0, 1), IIF(s.DataTwo IS NULL, 0, 1)
FROM #Source s CROSS JOIN #DataImage i
INTERSECT
SELECT s.ID, i.Flag, ColumnOne, ColumnTwo
FROM #Source s CROSS JOIN #DataImage i
)
SELECT t0.SourceId, t0.Flag
FROM t0

How to display row data into different column ORACLE

I'm new to SQL and currently this is what I'm trying to do:
Display multiple rows of data into different columns within the same row
I have a table like this:
CREATE TABLE TRIPLEG(
T# NUMBER(10) NOT NULL,
LEG# NUMBER(2) NOT NULL,
DEPARTURE VARCHAR(30) NOT NULL,
DESTINATION VARCHAR(30) NOT NULL,
CONSTRAINT TRIPLEG_PKEY PRIMARY KEY (T#, LEG#),
CONSTRAINT TRIPLEG_UNIQUE UNIQUE(T#, DEPARTURE, DESTINATION),
CONSTRAINT TRIPLEG_FKEY1 FOREIGN KEY (T#) REFERENCES TRIP(T#) );
INSERT INTO TRIPLEG VALUES( 1, 1, 'Sydney', 'Melbourne');
INSERT INTO TRIPLEG VALUES( 1, 2, 'Melbourne', 'Adelaide');
The result should be something like this:
T# | ORIGIN | DESTINATION1 | DESTINATION2
1 | SYDNEY | MELBORUNE | ADELAIDE
The origin is the DEPARTURE.
DESTINATION1 could either be DEPARTURE OR DESTINATION.
DESTINATION2 is the DESTINATION.
The query should include the COUNT(T#) < 3 since I only need to display the records less than 3. How can i do this with 2 relational view to achieve this result?
Try this:
select t1.T#,
(select t2.departure from tripleg t2 where t1.T# = t2.T# and t2.LEG# = 1) Origin,
(select t2.destination from tripleg t2 where t1.T# = t2.T# and t2.LEG# = 1) Destination1,
(select t2.destination from tripleg t2 where t1.T# = t2.T# and t2.LEG# = 2) Destination2
from tripleg t1
group by t1.T#
having count(1) < 3
You can make use of hierarchical queries.
Data Setup:
CREATE TABLE TRIPLEG ( T# NUMBER ( 10 ) NOT NULL,
LEG# NUMBER ( 2 ) NOT NULL,
DEPARTURE VARCHAR ( 30 ) NOT NULL,
DESTINATION VARCHAR ( 30 ) NOT NULL );
INSERT INTO
TRIPLEG
VALUES
( 1,
1,
'Sydney',
'Melbourne' );
INSERT INTO
TRIPLEG
VALUES
( 1,
2,
'Melbourne',
'Adelaide' );
INSERT INTO
TRIPLEG
VALUES
( 2,
1,
'A',
'B' );
INSERT INTO
TRIPLEG
VALUES
( 2,
2,
'B',
'C' );
INSERT INTO
TRIPLEG
VALUES
( 2,
3,
'C',
'D' );
INSERT INTO
TRIPLEG
VALUES
( 3,
1,
'A',
'B' );
COMMIT;
Query:
SELECT
T#,
CONNECT_BY_ROOT DEPARTURE
|| SYS_CONNECT_BY_PATH ( DESTINATION,
' ~ ' )
AS ROUTE
FROM
TRIPLEG
WHERE
LEVEL = 2
CONNECT BY
NOCYCLE DEPARTURE = PRIOR DESTINATION
START WITH
T# = '1';
Explanation:
Connect rows based on the relationship between Departure and Destination.
Filter for rows satisfying a transit (Strictly)
LEG# is not used.
Kindly try this
select t#,regexp_substr(tree,'[^:]+',2) origin,
regexp_substr(tree,'[^:]+',2,2) destination1,
regexp_substr(tree,'[^:]+',2,3) destination2
from
(
select a.*,
sys_connect_by_path(departure,':') tree,
level lvl
from tripleg a
connect by nocycle prior destination = departure and prior t# = t#
start with leg# = 1
and level < = 3
)
where lvl = 3;

How to concatenate 2 same-table queries results keeping a unique key in SQLite?

Considering the following table:
CREATE TABLE `foo`(
`aaa` VARCHAR(45) NOT NULL,
`bbb` VARCHAR(45) NOT NULL,
`ccc` INT NOT NULL,
`ddd` INT NOT NULL,
PRIMARY KEY(`aaa`, `bbb`, `ccc`)
);
the following data:
INSERT INTO `foo` (`aaa`, `bbb`, `ccc`, `ddd`)
VALUES
('qwe', 'rty', 0, 123),
('asd', 'fgh', 0, 456),
('asd', 'fgh', 1, 678);
the following result is to be gotten:
'qwe', 'rty', 0, 123
'asd', 'fgh', 1, 678
The objective is to get
Only one record for each (aaa, bbb) keypair
Favour records with ccc = 1 if there are more than 1 record for a (aaa, bbb) keypair in the table
So I think I need to concatenate result sets of 2 queries:
SELECT * FROM `foo` WHERE `ccc` = 1
and
SELECT * FROM `foo` WHERE `ccc` = 0
discarding a second query result row when there is already a same (aaa, bbb) keypair row in the first query result.
How to code it in SQLite?
Try this one,
SELECT a.*
FROM foo a
INNER JOIN
(
SELECT aaa, bbb, MAX(ccc) maxC
FROM foo
GROUP BY aaa, bbb
) b ON a.aaa = b.aaa AND
a.ccc = b.maxC AND
a.bbb = b.bbb
SQLFiddle Demo