Apply conditions in projection level - sql

Is there a simple way to apply conditions in projection level? I mean In a series of fields(columns) how can we choose only the fields those have specific conditions?
Example:
Assume we have a query with below result:
Id Col1 Col2 Col3 Col4 Col5 Col6 Col7 Col8 Col9 Col10 Col11
- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
1 V1_1 NULL NULL V4_1 NULL NULL NULL NULL NULL V10_1 NULL
2 V1_2 NULL NULL NULL NULL NULL NULL NULL NULL V10_2 NULL
3 V1_3 NULL NULL V4_3 NULL NULL NULL NULL NULL V10_3 NULL
how can we select only the fields which contains none null values while we don't know which of them contains none null values? so result would be this:
Id Col1 Col4 Col10
- ---- ---- ----
1 V1_1 V4_1 V10_1
2 V1_2 NULL V10_2
3 V1_3 V4_3 V10_3
Usually I do such these queries with hardcoding which I may query sys.all_obejcts, sys.all_columns in MsSQL Server and all_tab_cols, all_tables , etc in Oracle. and use none, one or more cursors , case statements, pivot, multiple ifs or dynamic sql in it.
in example: for a table with above data in T-SQL I can do:
DECLARE #sql VARCHAR(500)
SET #sql='SELECT Id,'
IF EXISTS (SELECT Col1 FROM tblTEST WHERE Col1 IS NOT NULL) SET #sql=#sql+'Col1,'
/* 9 other IF statements here */
IF EXISTS (SELECT Col11 FROM tblTEST WHERE Col11 IS NOT NULL) SET #sql=#sql+'Col11,'
SET #sql=SUBSTRING(#sql,1,LEN(#sql)-1)+' FROM tblTEST'
EXEC(#sql)
but as you see the idea is not common at all and it's a little hard to do such coding for such these scenarios
Do you know a simple and better way or a pattern for such this queries?
The RDBMS does not matter, a good solution for any RDBMS is really useful.
Please I want answers to be a good idea in general not only for the example I mentioned.

This is a T-SQL solution. All you should have to do is change #TargetTable to your desired table. Hope this helps! I commented my code explaining everything, but if you have any questions, let me know!
IF OBJECT_ID('myTable') IS NOT NULL
DROP TABLE dbo.myTable;
CREATE TABLE myTable
(
ID INT IDENTITY(1,1),
Col1 VARCHAR(25),
Col2 VARCHAR(25),
Col3 VARCHAR(25)
)
INSERT INTO dbo.myTable(Col1,Col2,Col3)
VALUES ('v1_1',NULL,NULL),
('v1_2','v2_2',NULL),
('v1_3',NULL,NULL);
/* Now that I've set up the problem, here's for the actual code*/
--Set your desired table
DECLARE #TargetTable VARCHAR(100) = 'myTable'
--If this global temp table exists, drop it
IF OBJECT_ID('##tempTable') IS NOT NULL
EXEC('DROP TABLE ##tempTable');
--This is going to take one character and cast it to NCHAR(1)
--If there are no characters, it will simply return NULL
DECLARE #AllColumns VARCHAR(MAX);
SELECT #AllColumns = COALESCE(#AllColumns + ',','') + 'CAST(LEFT(' + QUOTENAME(COLUMN_NAME) + ',1) AS NCHAR(1)) AS ' + QUOTENAME(COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS A
WHERE TABLE_NAME = #TargetTable
/*Load your char results into a global temp table*/
EXEC('SELECT ' + #AllColumns + ' INTO ##tempTable FROM ' + #TargetTable);
/*Here's where the fun beings*/
/* #DesiredCol holds the name of columns that are not null.
UNPIVOT the data.
Where clause gets rid of columns with only NULL values.
Group by only allows distinct columns.
You the select from your ORIGINAL table, not my funky nchar(1) global temp table.
*/
EXEC(
'DECLARE #DesiredCol VARCHAR(MAX);
SELECT #DesiredCol = COALESCE(#DesiredCol + '','','''') + QUOTENAME(col)
FROM ##temptable
UNPIVOT
(
val FOR col IN ([ID],[Col1],[Col2],[Col3])
) pvt
WHERE pvt.val IS NOT NULL
GROUP BY col
EXEC (''SELECT '' + #DesiredCol + '' FROM ##' + #TargetTable + ''')'
)
--Cleanup
IF OBJECT_ID('##tempTable') IS NOT NULL
EXEC('DROP TABLE ##tempTable');

Related

SQL pivot table1 into table2 without using dynamic SQL pivot or hardcode query

I have seen many questions and answers given about pivoting table with SQL, with dynamic SQL pivot or hard code query with CASE WHEN.
However is there any way I can pivot table without using those 2?
Table 1:
| col1 | col2 | col3 |
|--------|-------|--------|
| ABCD | 1 | XY123 |
| ABCD | 2 | RT789 |
| PQST | 3 | XY123 |
| PQST | 4 | RT789 |
Pivoting to
| col1 | ABCD | PQST |
|--------|-------|-------|
| XY123 | 1 | 3 |
| RT789 | 2 | 4 |
My idea was to retrieve the structure of the col with:
WITH
structure AS (
SELECT DISTINCT
col3 AS col1, col1 AS colName, col2 AS values
FROM table1 ori
)
and then extracting matched values of each cell with joins and storing them temporarily. At last JOIN again populating them in the output. However I am stuck after the above step. I can't use PIVOT and have to do this dynamically (i.e. can't use the method to hardcode each value with CASE WHEN)
How can I achieve this?
This is not as efficient (and not as easy to code) as a dynamic pivot. However, it is doable.
It does all need to be dynamic e.g., creating each SQL statement as a string and executing that.
The process involves
Determine the column names (store in a temporary table)
Creating the table with the first column only
Populating that first column
For each additional column name
Adding a column to the table (dynamically)
Populating that column with data
You haven't specified the database - I'll illustrate the following below using SQL Server/T-SQL.
The following are in this db<>fiddle so you can see what's going on.
CREATE TABLE #ColNames (ColNum int IDENTITY(1,1), ColName nvarchar(100), ColNametxt nvarchar(100));
INSERT INTO #ColNames (ColName, ColNametxt)
SELECT DISTINCT QUOTENAME(Col1), Col1
FROM table1;
This will populate the #ColNames table with the values 1, [ABCD], ABCD, 2, [PQST], PQST.
The next step is to create your output table - I'll call it #pvttable
CREATE TABLE #pvttable (col1 nvarchar(100) PRIMARY KEY);
INSERT INTO #pvttable (col1)
SELECT DISTINCT Col3
FROM table1;
This creates your table with 1 column (col1) with values XY123 and RT789).
The write your favorite loop (e.g., cursor, while loop). In each step
Get the next column name
Add the column to the table
Update that column with appropriate data
e.g., the following is an illustrative example with your data.
DECLARE #CustomSQL nvarchar(4000);
DECLARE #n int = 1;
DECLARE #ColName nvarchar(100);
DECLARE #ColNametxt nvarchar(100);
SELECT #ColName = ColName,
#ColNameTxt = ColNameTxt
FROM #ColNames
WHERE ColNum = #n;
WHILE #ColName IS NOT NULL
BEGIN
SET #CustomSQL = N'ALTER TABLE #pvttable ADD ' + #ColName + N' nvarchar(100);';
EXEC (#CustomSQL);
SET #CustomSQL =
N'UPDATE #pvttable SET ' + #Colname + N' = table1.col2'
+ N' FROM #pvttable INNER JOIN table1 ON #pvttable.col1 = table1.col3'
+ N' WHERE table1.col1 = N''' + #ColNametxt + N''';';
EXEC (#CustomSQL);
SET #n += 1;
SET #ColName = NULL;
SET #ColNametxt = NULL;
SELECT #ColName = ColName,
#ColNameTxt = ColNameTxt
FROM #ColNames
WHERE ColNum = #n;
END;
SELECT * FROM #pvttable;

Get Dataset as Header for Another Table

I get the columns and the data of all surveys I have in the online database through my API an separately.
The first example are the column headers of the first two surveys
Column Table
ID Col1 Col2 Col3
------------------------------------
0 Name Birhtdate Country
1 Time Name Address
The data I get for the first survey is like
Data Table
ID Col1 Col2 Col3
------------------------------------
0 James 11-11-2011 Japan
1 Tobi 26-02-2014 India
Now I want to merge the data and the columns so that the column names are the header of the final table like
Final Table
ID Name Birthday Country
------------------------------------
0 James 11-11-20111 Japan
1 Tobi 26-02-2014 India
All the attemps I have made yet with Select statements gave me
Wrong Result Table
ID Col1 Col2 Col3
------------------------------------
0 Name Birthdate Country
1 James 11-11-20111 Japan
2 Tobi 26-02-2014 India
Could anyone help me get the final table (view, temp Table... whatever) out of my data? I am working with SQL-Server 2016
You can use dynamic SQL to define column alias names coming from another table:
declare #col1 varchar(50), #col2 varchar(50), #col3 varchar(50)
declare #sql nvarchar(max)
create table #t1 (ID int, Col1 varchar(50), Col2 varchar(50), Col3 varchar(50))
insert into #t1 values
(0, 'Name', 'Birhtdate', 'Country')
,(1, 'Time', 'Name' , 'Address')
create table #t2 (ID int, Col1 varchar(50), Col2 varchar(50), Col3 varchar(50))
insert into #t2 values
(0, 'James','2011-11-11', 'Japan')
, (1, 'Tobi' ,'2014-02-26', 'India')
select #col1=col1, #col2=col2, #col3=col3 from #t1 where id = 0
set #sql=concat('select id, col1 as ', #col1, ', col2 as ',
#col2, ', col3 as ', #col3, ' from #t2')
exec (#sql)
Result:
The only way to achieve such a result using pure T-SQL is to use dynamic SQL.
You can do something like this:
DECLARE #SQL nvarchar(max)
SELECT #SQL = ' SELECT Col1 AS '+ Col1 +', Col2 AS '+ Col2 +', Col3 AS '+ Col3 +
' FROM DataTable'
FROM ColumnsTable
WHERE Id = 0
EXEC (#SQL)

SQL server trigger concat

I'm trying to write a SQL Server trigger that concats 3 columns, but inserts a string in place of the third column based on a condition.
For example when col 1 is inserted concat col 1, col 2 and col 3. But when col 3 = 'DR' concat Drive or col 3 = 'ST' concat Street etc.
I can write the trigger for all 3 columns, but having trouble with the conditional.
CREATE TRIGGER updateAddress
ON [ARCHIVENEW].dbo.a11
AFTER UPDATE
AS BEGIN
SET NOCOUNT ON;
IF UPDATE(STRNAME)
SELECT CASE
WHEN a11.STRTYPE = 'DR' THEN dbo.a11.STRFNAME = cast(dbo.a11.STRADDLF as varchar(2)) + ' ' + STRNAME + ' ' + 'Drive'
WHEN a11.STRTYPE = 'RP' THEN dbo.a11.STRFNAME = cast(dbo.a11.STRADDLF as varchar(2)) + ' ' + STRNAME + ' ' + 'Ramp'
WHEN a11.STRTYPE = 'EX' THEN dbo.a11.STRFNAME = cast(dbo.a11.STRADDLF as varchar(2)) + ' ' + dbo.a11.STRNAME + ' ' + 'Express Way'
ELSE dbo.a11.STRFNAME = cast(dbo.a11.STRADDLF as varchar(2)) + ' ' + dbo.a11.STRNAME
END
Sorry it took a few days to get back to this and add the code sample. I'm getting a syntax error near the '=' where I try and set my STRFNAME to the concatenated string. To clarify further, col1 is "1234" col2 is "Main" and col3 is "ST". When a row is inserted I'd like col4 to say "1234 Main Street".
Sample table:
CREATE TABLE dbo.TestTable(
Id INT IDENTITY(1,1) PRIMARY KEY
ColA VARCHAR(50),
ColB VARCHAR(50),
ColC VARCHAR(50),
ColD VARCHAR(50)
);
This trigger concats values of ColA, ColB for ColC and validates ColB value for ColD.
CREATE TRIGGER TestTable_UpdateTwoColumns
ON dbo.TestTable
AFTER INSERT,UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF UPDATE(ColA)
BEGIN
UPDATE dbo.TestTable
SET ColC=new.ColA + new.ColB
FROM INSERTED new
END
IF UPDATE(ColB)
BEGIN
UPDATE dbo.TestTable
SET ColD=CASE new.ColB WHEN 'DR' THEN 'Drive' WHEN 'ST' THEN
'Street' END
FROM INSERTED new
END
END
Thanks,
You cannot change a table while the INSERT trigger is firing. You can, however, create a trigger before inserting the record.
CREATE TRIGGER emplog_update AFTER UPDATE ON emp
FOR EACH ROW
INSERT INTO emplog (id, lastnmae, firstname, . . .)
select NEW.id,NEW.lastname,NEW.firstname,NEW.gender,NEW.dob,NEW.marital,NEW.SSN,'U',NULL,USER(),
concat_ws(',',
(case when new.id <> old.id then 'id' end),
(case when new.lastname <> old.lastname then 'lastname' end),
(case when new.firstname <> old.firstname then 'firstname' end),
. . .
);
If your motto is insert into third column, good to use computed column. Here is an example. But as in my comment on question, I did not understand, how col3 value comes?
For this, I used col4 to get the proper result, also in computed column define, it not take reference of own.
CREATE TABLE dbo.Products
(
ProductID int IDENTITY (1,1) NOT NULL
, col1 varchar(50)
, col2 varchar(50)
, col3 varchar(50)
, col4 AS case col3 when 'DR' then 'Drive' when 'ST' then 'Street' else col1 + col2 end
--while computed column define, you can use all other column, except own.
);
-- Insert values into the table.
INSERT INTO dbo.Products (col1, col2,col3)
VALUES ('a', 'a1','a2'), ('b', 'b1', 'DR'), ('c', 'c1', 'ST'), ('d', 'd1', 'd2');
-- Display the rows in the table.
SELECT ProductID, col1, col2, col3, col4
FROM dbo.Products;
select * from products
drop table products

How Can I optimize this query (OR inside AND validation)?

I'm having problems with this query:
SELECT col1, col2,col3,...,coln FROM MyTable
WHERE col1 = #value1
AND
(
ISNULL(col2,'c') = ISNULL(#value2,ISNULL(col2,'c'))
OR
ISNULL(col3,'c') = ISNULL(#value2,ISNULL(col3,'c'))
)
AND coln = 'valueN'
I have to stop the execution, it's so slow. But editing:
SELECT col1, col2,col3,...,coln FROM MyTable
WHERE col1 = #value1
AND
(
ISNULL(col2,'c') = ISNULL(#value2,ISNULL(col2,'c'))
)
AND coln = 'valueN'
This query is faster. Can someone help me? How can I replace the or statement or replace the query but validating col1 and col2?.
Thanks.
UPDATE:
Thank you very much guys. Really my query does not use '=' but use 'Like', sorry about that. However I use your suggestions to build my query and it works fine:
SELECT col1, col2,col3,...,coln FROM MyTable
WHERE col1 like '%' + #value1 + '%'
AND
(
(#value2 IS NULL)
OR
(col2 IS NOT NULL AND col2 LIKE '%' + #value2 + '%')
OR
(col3 IS NOT NULL AND col3 LIKE '%' + #value2 + '%')
)
AND coln = 'valueN'
I use this query with a page where I have many fields to filter a search and I need that a col2 textbox apply to col3 in database too, I want to mean, only one textbox for name1 and name2 in database.
Sorry about my wrong question and thanks for your suggestions.
ISNULL(col2,'c') = ISNULL('value2',ISNULL(col2,'c'))
is the same as
col2 = 'value2' or (col2 is null and 'value2' is null)
Replace the occurrences and you will most likely have a better performance.
Update
There's one fundamental difference between this solution and the one proposed by #onedaywhen: when the value provided in 'value2' (which I suppose is just a parameter assembled into a SQL string) is NULL, OP only wants to bring back only records where col2 is NULL. Take a closer look into OP's logic, you will see that there. OP's logic always filters: when the parameter is NULL, OP wants records where col2 is NULL.
#onedaywhen's solution brings every record when the parameter is NULL. Although this is a very common query, it's not what OP is looking for.
ISNULL(col2,'c') = ISNULL(#value2,ISNULL(col2,'c'))
is the same as
( ( col2 = #value2 ) OR ( #value2 IS NULL ) )
I'm not sure if this will improve performance, though: I've read that, at least for SQL Server 2005 and earlier, to get an efficient, scalable and performant solution either use IF ELSE control of flow blocks (one per parameter combination) or use dynamic SQL.
UPDATE: ...and here's the proof:
Query 1: When the parameter is a value that is not null:
DECLARE #value2 VARCHAR(10);
SET #value2 = 'Apples';
WITH T
AS
(
SELECT *
FROM (
VALUES (1, 'When col2 is null', NULL),
(2, 'When col2 is the same value as #value2', 'Apples'),
(3, 'When col2 is not the same value as #value2', 'Oranges')
) AS T (ID, narrative, col2)
)
SELECT *,
CASE WHEN ISNULL(col2,'c') = ISNULL(#value2,ISNULL(col2,'c')) THEN 'T' END AS OP,
CASE WHEN ( ( col2 = #value2 ) OR ( #value2 IS NULL ) ) THEN 'T' END AS OneDayWhen,
CASE WHEN col2 = #value2 or (col2 is null and #value2 is null) THEN 'T' END AS Adrian
FROM T;
Output 1:
ID narrative col2 OP OneDayWhen Adrian
----------- ------------------------------------------ ------- ---- ---------- ------
1 When col2 is null NULL NULL NULL NULL
2 When col2 is the same value as #value2 Apples T T T
3 When col2 is not the same value as #value2 Oranges NULL NULL NULL
Note all agree for all rows :)
Query 2: When the parameter is a null:
DECLARE #value2 VARCHAR(10);
SET #value2 = NULL;
WITH T
AS
(
SELECT *
FROM (
VALUES (1, 'When col2 is null', NULL),
(2, 'When col2 is the same value as #value2', 'Apples'),
(3, 'When col2 is not the same value as #value2', 'Oranges')
) AS T (ID, narrative, col2)
)
SELECT *,
CASE WHEN ISNULL(col2,'c') = ISNULL(#value2,ISNULL(col2,'c')) THEN 'T' END AS OP,
CASE WHEN ( ( col2 = #value2 ) OR ( #value2 IS NULL ) ) THEN 'T' END AS OneDayWhen,
CASE WHEN col2 = #value2 or (col2 is null and #value2 is null) THEN 'T' END AS Adrian
FROM T;
Output 2:
ID narrative col2 OP OneDayWhen Adrian
----------- ------------------------------------------ ------- ---- ---------- ------
1 When col2 is null NULL T T T
2 When col2 is the same value as #value2 Apples T T NULL
3 When col2 is not the same value as #value2 Oranges T T NULL
Note OP and OneDayWhen match for all rows, Adrian only matches for row ID = 1.

SQL Query to check if 40 columns in table is null

How do I select few columns in a table that only contain NULL values for all the rows?
Suppose if Table has 100 columns, among this 100 columns 60 columns has null values.
How can I write where condition to check if 60 columns are null.
maybe with a COALESCE
SELECT * FROM table WHERE coalesce(col1, col2, col3, ..., colN) IS NULL
where c1 is null and c2 is null ... and c60 is null
shortcut using string concatenation (Oracle syntax):
where c1||c2||c3 ... c59||c60 is null
First of all, if you have a table that has so many nulls and you use SQL Server 2008 - you might want to define the table using sparse columns (http://msdn.microsoft.com/en-us/library/cc280604.aspx).
Secondly I am not sure if coalesce solves the question asks - it seems like Ammu might actually want to find the list of columns that are null for all rows, but I might have misunderstood. Nevertheless - it is an interesting question, so I wrote a procedure to list null columns for any given table:
IF (OBJECT_ID(N'PrintNullColumns') IS NOT NULL)
DROP PROC dbo.PrintNullColumns;
go
CREATE PROC dbo.PrintNullColumns(#tablename sysname)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #query nvarchar(max);
DECLARE #column sysname;
DECLARE columns_cursor CURSOR FOR
SELECT c.name
FROM sys.tables t JOIN sys.columns c ON t.object_id = c.object_id
WHERE t.name = #tablename AND c.is_nullable = 1;
OPEN columns_cursor;
FETCH NEXT FROM columns_cursor INTO #column;
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #query = N'
DECLARE #c int
SELECT #c = COUNT(*) FROM ' + #tablename + ' WHERE ' + #column + N' IS NOT NULL
IF (#c = 0)
PRINT (''' + #column + N''');'
EXEC (#query);
FETCH NEXT FROM columns_cursor INTO #column;
END
CLOSE columns_cursor;
DEALLOCATE columns_cursor;
SET NOCOUNT OFF;
RETURN;
END;
go
If you don't want to write the columns names, Try can do something like this.
This will show you all the rows when all of the columns values are null except for the columns you specified (IgnoreThisColumn1 & IgnoreThisColumn2).
DECLARE #query NVARCHAR(MAX);
SELECT #query = ISNULL(#query+', ','') + [name]
FROM sys.columns
WHERE object_id = OBJECT_ID('yourTableName')
AND [name] != 'IgnoreThisColumn1'
AND [name] != 'IgnoreThisColumn2';
SET #query = N'SELECT * FROM TmpTable WHERE COALESCE('+ #query +') IS NULL';
EXECUTE(#query)
Result
If you don't want rows when all the columns are null except for the columns you specified, you can simply use IS NOT NULL instead of IS NULL
SET #query = N'SELECT * FROM TmpTable WHERE COALESCE('+ #query +') IS NOT NULL';
Result
[
Are you trying to find out if a specific set of 60 columns are null, or do you just want to find out if any 60 out of the 100 columns are null (not necessarily the same 60 for each row?)
If it is the latter, one way to do it in oracle would be to use the nvl2 function, like so:
select ... where (nvl2(col1,0,1)+nvl2(col2,0,1)+...+nvl2(col100,0,1) > 59)
A quick test of this idea:
select 'dummy' from dual where nvl2('somevalue',0,1) + nvl2(null,0,1) > 1
Returns 0 rows while:
select 'dummy' from dual where nvl2(null,0,1) + nvl2(null,0,1) > 1
Returns 1 row as expected since more than one of the columns are null.
It would help to know which db you are using and perhaps which language or db framework if using one.
This should work though on any database.
Something like this would probably be a good stored procedure, since there are no input parameters for it.
select count(*) from table where col1 is null or col2 is null ...
Here is another method that seems to me to be logical as well (use Netezza or TSQL)
SELECT KeyColumn, MAX(NVL2(TEST_COLUMN,1,0) AS TEST_COLUMN
FROM TABLE1
GROUP BY KeyColumn
So every TEST_COLUMN that has MAX value of 0 is a column that contains all nulls for the record set. The function NVL2 is saying if the column data is not null return a 1, but if it is null then return a 0.
Taking the MAX of that column will reveal if any of the rows are not null. A value of 1 means that there is at least 1 row that has data. Zero (0) means that each row is null.
I use the below query when i have to check for multiple columns NULL. I hope this is helpful . If the SUM comes to a value other than Zero , then you have NULL in that column
select SUM (CASE WHEN col1 is null then 1 else 0 end) as null_col1,
SUM (CASE WHEN col2 is null then 1 else 0 end) as null_col2,
SUM (CASE WHEN col3 is null then 1 else 0 end) as null_col3, ....
.
.
.
from tablename
you can use
select NUM_NULLS , COLUMN_NAME from all_tab_cols where table_name = 'ABC' and COLUMN_NAME in ('PQR','XYZ');