SQL Create a new column with if/else condition - sql

I've been working on this task for way too long and I'm still stuck. What I need to do is, working on database AdventureWorks2014, retrieve CustomerIDs and Average of orders they made (all customers of course). My problem begins when I try to insert another column (called Valuable), that is supposed to have values:
'Y' if CustomerOrders > 10*AverageOfOrders and
'N' if CustomerOrders < 10*AverageOfOrders.
I should use CTE, that's not that important though. I did it with CASE statement, yet I keep getting an error
Incorrect syntax near the keyword 'CASE'.
If anyone could explain to me what am I doing wrong, that would be amazing. Below is my code:
WITH CustID AS
(
SELECT CustomerID, COUNT(CustomerID) AS "NrOfOrdersPerCustomer"
FROM Sales.SalesOrderHeader
GROUP BY CustomerID
),
AvgNr AS
(
SELECT AVG("NrOfOrdersPerCustomer") AS "AvgNrOfOrders"
FROM CustID
),
Joint AS
(
SELECT CustID.CustomerID, 'NULL' AS "Valuable", CustID."NrOfOrdersPerCustomer", AvgNr."AvgNrOfOrders", 10*AvgNr."AvgNrOfOrders" AS "MultipliedBy10"
CASE
WHEN "NrOfOrdersPerCustomer" > "MultipliedBy10" THEN 'Y'
ELSE 'N'
FROM CustID, AvgNr
END
)
SELECT * FROM Joint;

There is already an answer that explains where your syntax errors are (missing comma and incorrect case expression), however, I thought I would just post an alternative solution that is much simpler:
WITH CustID AS
(
SELECT CustomerID, COUNT(CustomerID) AS NrOfOrdersPerCustomer
FROM Sales.SalesOrderHeader
GROUP BY CustomerID
)
SELECT CustomerID,
NrOfOrdersPerCustomer,
AVG(NrOfOrdersPerCustomer) OVER() AS AvgNrOfOrders,
AVG(NrOfOrdersPerCustomer) OVER() * 10 AS MultipliedBy10,
CASE WHEN NrOfOrdersPerCustomer > AVG(NrOfOrdersPerCustomer) OVER() * 10 THEN 'Y'
ELSE 'N'
END AS Valuable
FROM CustID;
This leverages the use a window function - AVG(NrOfOrdersPerCustomer) OVER() - meaning you can get the average of all customers without having to do a separate subquery. I have also removed quote marks from your aliases, for no other reason that they are not necessary unless your alias contains special characters, and I find them quite distracting, especially when not used consistently.

Last CTE is wrong as
Joint AS
(
SELECT CustID.CustomerID,
'NULL' AS "Valuable",
CustID."NrOfOrdersPerCustomer",
AvgNr."AvgNrOfOrders",
10*AvgNr."AvgNrOfOrders" AS "MultipliedBy10" <-- missing , here
CASE
WHEN "NrOfOrdersPerCustomer" > "MultipliedBy10" THEN 'Y'
ELSE 'N' <-- END should come here
FROM CustID, AvgNr
END <-- not here
)
Your last CTE should look like
Joint AS
(
SELECT CustID.CustomerID,
NULL AS "Valuable",
CustID."NrOfOrdersPerCustomer",
AvgNr."AvgNrOfOrders",
10*AvgNr."AvgNrOfOrders" AS "MultipliedBy10",
CASE
WHEN "NrOfOrdersPerCustomer" > "MultipliedBy10" THEN 'Y'
ELSE 'N' END AS Computed_Column
FROM CustID, AvgNr
)

At the end of your SELECT line after AS "MultipliedBy10", you need a comma. Also, your CASE statement is out of order at the end. END needs to be before the FROM statement. Corrected below:
WITH CustID AS
(
SELECT CustomerID, COUNT(CustomerID) AS "NrOfOrdersPerCustomer"
FROM Sales.SalesOrderHeader
GROUP BY CustomerID
),
AvgNr AS
(
SELECT AVG("NrOfOrdersPerCustomer") AS "AvgNrOfOrders"
FROM CustID
),
Joint AS
(
SELECT CustID.CustomerID, 'NULL' AS "Valuable", CustID."NrOfOrdersPerCustomer", AvgNr."AvgNrOfOrders", 10*AvgNr."AvgNrOfOrders" AS "MultipliedBy10",
CASE
WHEN "NrOfOrdersPerCustomer" > "MultipliedBy10" THEN 'Y'
ELSE 'N'
END
FROM CustID, AvgNr
)
SELECT * FROM Joint;

Related

How to check unique values in SQL

I have a table named Bank that contains a Bank_Values column. I need a calculated Bank_Value_Unique column to shows whether each Bank_Value exists somewhere else in the table (i.e. whether its count is greater than 1).
I prepared this query, but it does not work. Could anyone help me with this and/or modify this query?
SELECT
CASE
WHEN NULLIF(LTRIM(RTRIM(Bank_Value)), '') =
(SELECT Bank_Value
FROM [Bank]
GROUP BY Bank_Value
HAVING COUNT(*) = 1)
THEN '0' ELSE '1'
END AS Bank_Key_Unique
FROM [Bank]
A windowed count should work:
SELECT
*,
CASE
COUNT(*) OVER (PARTITION BY Bank_Value)
WHEN 1 THEN 1 ELSE 0
END AS Bank_Value_Unique
FROM
Bank
;
It works also, but I found solution also:
select CASE WHEN NULLIF(LTRIM(RTRIM(Bank_Value)),'') =
(select Bank_Value
from Bank
group by Bank_Value
having (count(distinct Bank_Value) > 2 )) THEN '1' ELSE '0' END AS
Bank_Value_Uniquness
from Bank
It was missing "distinct" in having part.

How to combine CASE statement with Inner Join for Alphanumeric OrderBY

In this query, I am trying to select all distinct (alphanumeric) machine names and order them correctly (1,2,5,10,15 instead of 1,10,15,2,5). The CASE statement is proven to work when the LocalName is not joined by INNER JOIN, so I suspect this is where the problem lies.
SELECT DISTINCT MCGroup, VisionMachinePerformance.MCSAP, ZAssetRegister.LocalName
FROM [VisionMachinePerformance] INNER JOIN ZAssetRegister ON VisionMachinePerformance.MCSAP=ZAssetRegister.SAP_Number
ORDER BY
CASE WHEN PATINDEX('%[0-9]%',LocalName) > 1 THEN
LEFT(LocalName,PATINDEX('%[0-9]%',LocalName)-1)
ELSE LocalName END ,
CASE WHEN PATINDEX('%[0-9]%',LocalName) > 1 THEN
CAST(SUBSTRING(LocalName,PATINDEX('%[0-9]%',LocalName),LEN(LocalName)) as float)
ELSE NULL END
The error that is reported is "SQL Error (145): ORDER BY items must appear in the select list if SELECT DISTINCT is specified".
I have tried changing all references in the CASE statement to ZAssetRegister.LocalName and VisionMachinePerformance.LocalName without success.
Removing all of the CASE statement and ordering by LocalName does work, but with the wrong order as mentioned above (1,10,15,2,5).
Could anybody suggest how to make this work?
TIA!
You can separate both parts using a subquery:
SELECT * FROM (
SELECT DISTINCT MCGroup, VisionMachinePerformance.MCSAP, ZAssetRegister.LocalName
FROM [VisionMachinePerformance]
INNER JOIN ZAssetRegister ON VisionMachinePerformance.MCSAP=ZAssetRegister.SAP_Number
) DISTINCT_DATA
ORDER BY
CASE WHEN PATINDEX('%[0-9]%',LocalName) > 1
THEN LEFT(LocalName,PATINDEX('%[0-9]%',LocalName)-1)
ELSE LocalName END,
CASE WHEN PATINDEX('%[0-9]%',LocalName) > 1
THEN CAST(SUBSTRING(LocalName,PATINDEX('%[0-9]%',LocalName),LEN(LocalName)) as float)
ELSE NULL END

Is there a way to avoid columns from GROUP BY

My table has columns such as ID,Perdium and Location so I want to calculate all the perdiums given to an employee and the perdium share given in NY. The issue which I am facing is that SQL Server engine is throwing as error stating that location column isnt present in the GROUP BY clause(as needed in my use-case).If I include the location in the Group By clause I always get NYPerdiumShare as 1 which is not what I am expecting. Is there any workaround to this?
WITH CTE_Employee AS
(
SELECT ID,
SUM(Perdium) AS TotalPerdium,
CASE WHEN Location='NY' THEN SUM(Perdium) ELSE NULL END AS NYPerdium FROM EmployeePerdium
GROUP BY ID
)
SELECT ID,
TotalPerdium,
NYPerdium/TotalPerdium AS NYPerdiumShare
FROM CTE_Employee
You can eliminate the need to group by on anything other than ID by rewriting your query as follows to hide CASE inside an aggregate function:
WITH CTE_Employee AS (
SELECT
ID
, SUM(Perdium) AS TotalPerdium
, SUM(CASE WHEN Location='NY' THEN Perdium ELSE 0 END) AS NYPerdium
FROM EmployeePerdium
GROUP BY ID
)
SELECT
ID
, TotalPerdium
, NYPerdium/TotalPerdium AS NYPerdiumShare
FROM CTE_Employee
You don't need a cte here. Just use the sum window function.
SELECT DISTINCT
ID,
SUM(Perdium) OVER() as TotalPerdium
SUM(CASE WHEN Location='NY' THEN 1.0*Perdium ELSE 0 END) OVER(PARTITION BY ID)
/SUM(Perdium) OVER() AS NYPerdium
FROM EmployeePerdium

Joining a Temp Table to Actual Table

I need to verify that each order has been acknowledged. The problem is that each order can have multiple codes. The query I had (utilizing a CASE statement) would check for blank fields or fields with the string "None" to verify the order has not been acknowledged. It would return the appropriate result, but multiple rows (once for each possible response) and I only need (1).
I'm attempting to create a temp table that will return the appropriate result and join (via an order unique ID) the two tables together hoping to correct the multiple row issue. Here is the code:
DROP TABLE staging_TABLE;
CREATE TEMP TABLE staging_TABLE(
ORDERID varchar(256) ,
CODE varchar(256) );
/*Keeping data types consistent with the real table*/
INSERT INTO staging_TABLE
SELECT ORDERID,
CASE CODE
WHEN 'None' THEN 'No'
WHEN '' THEN 'No'
ELSE 'Yes'
END
FROM ORDERS
WHERE UTCDATE > SYSDATE - 10
AND CODE IS NOT NULL;
SELECT R.QUESTION,
R.ORDERNAME,
T.CODE
FROM ORDERS R
INNER JOIN staging_TABLE T
ON R.ORDERID= T.ORDERID
WHERE R.UTCDATE > SYSDATE - 10
AND R.CODE IS NOT NULL
AND R.CATEGORY IS NOT NULL
AND R.UTCDATE IS NOT NULL
GROUP BY
R.ORDER,
T.CODE,
R.ORDERNAME,
R.CODE
ORDER BY
R.ORDERNAME,
R.ORDER;
Am I doing this correctly? Or is this even the right approach?
Am I doing this correctly? Or is this even the right approach?
No. You don't need a temp table for this. Your query might look like this:
SELECT question, ordername
, CASE WHEN code IN ('None', '') THEN 'No' ELSE 'Yes' END AS code
FROM orders
WHERE utcdate > sysdate - 10
AND code IS NOT NULL
AND category IS NOT NULL
GROUP BY question, ordername, 3, "order"
ORDER BY ordername, "order";
ORDER is a reserved word. It's not possible to use it as column name unless double quoted. There is something wrong there.
AND R.UTCDATE IS NOT NULL is redundant. It can't be NULL anyway with WHERE R.UTCDATE > SYSDATE - 10
3 in my GROUP BY clause is a positional reference to the CASE expression. Alternatively you can spell it out again:
....
GROUP BY question, ordername
, CASE WHEN code IN ('None', '') THEN 'No' ELSE 'Yes' END
, "order"
You can use the DISTINCT keyword as follows so you will not need a temp table:
SELECT DISTINCT QUESTION,
ORDERNAME,
CASE CODE
WHEN 'None' THEN 'No'
WHEN '' THEN 'No'
ELSE 'Yes'
FROM ORDERS
WHERE UTCDATE > SYSDATE - 10
AND CODE IS NOT NULL
AND CATEGORY IS NOT NULL
AND UTCDATE IS NOT NULL
ORDER BY 2,3;

Counting null and non-null values in a single query

I have a table
create table us
(
a number
);
Now I have data like:
a
1
2
3
4
null
null
null
8
9
Now I need a single query to count null and not null values in column a
This works for Oracle and SQL Server (you might be able to get it to work on another RDBMS):
select sum(case when a is null then 1 else 0 end) count_nulls
, count(a) count_not_nulls
from us;
Or:
select count(*) - count(a), count(a) from us;
If I understood correctly you want to count all NULL and all NOT NULL in a column...
If that is correct:
SELECT count(*) FROM us WHERE a IS NULL
UNION ALL
SELECT count(*) FROM us WHERE a IS NOT NULL
Edited to have the full query, after reading the comments :]
SELECT COUNT(*), 'null_tally' AS narrative
FROM us
WHERE a IS NULL
UNION
SELECT COUNT(*), 'not_null_tally' AS narrative
FROM us
WHERE a IS NOT NULL;
Here is a quick and dirty version that works on Oracle :
select sum(case a when null then 1 else 0) "Null values",
sum(case a when null then 0 else 1) "Non-null values"
from us
for non nulls
select count(a)
from us
for nulls
select count(*)
from us
minus
select count(a)
from us
Hence
SELECT COUNT(A) NOT_NULLS
FROM US
UNION
SELECT COUNT(*) - COUNT(A) NULLS
FROM US
ought to do the job
Better in that the column titles come out correct.
SELECT COUNT(A) NOT_NULL, COUNT(*) - COUNT(A) NULLS
FROM US
In some testing on my system, it costs a full table scan.
As i understood your query, You just run this script and get Total Null,Total NotNull rows,
select count(*) - count(a) as 'Null', count(a) as 'Not Null' from us;
usually i use this trick
select sum(case when a is null then 0 else 1 end) as count_notnull,
sum(case when a is null then 1 else 0 end) as count_null
from tab
group by a
Just to provide yet another alternative, Postgres 9.4+ allows applying a FILTER to aggregates:
SELECT
COUNT(*) FILTER (WHERE a IS NULL) count_nulls,
COUNT(*) FILTER (WHERE a IS NOT NULL) count_not_nulls
FROM us;
SQLFiddle: http://sqlfiddle.com/#!17/80a24/5
This is little tricky. Assume the table has just one column, then the Count(1) and Count(*) will give different values.
set nocount on
declare #table1 table (empid int)
insert #table1 values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(NULL),(11),(12),(NULL),(13),(14);
select * from #table1
select COUNT(1) as "COUNT(1)" from #table1
select COUNT(empid) "Count(empid)" from #table1
Query Results
As you can see in the image, The first result shows the table has 16 rows. out of which two rows are NULL. So when we use Count(*) the query engine counts the number of rows, So we got count result as 16. But in case of Count(empid) it counted the non-NULL-values in the column empid. So we got the result as 14.
so whenever we are using COUNT(Column) make sure we take care of NULL values as shown below.
select COUNT(isnull(empid,1)) from #table1
will count both NULL and Non-NULL values.
Note: Same thing applies even when the table is made up of more than one column. Count(1) will give total number of rows irrespective of NULL/Non-NULL values. Only when the column values are counted using Count(Column) we need to take care of NULL values.
I had a similar issue: to count all distinct values, counting null values as 1, too. A simple count doesn't work in this case, as it does not take null values into account.
Here's a snippet that works on SQL and does not involve selection of new values.
Basically, once performed the distinct, also return the row number in a new column (n) using the row_number() function, then perform a count on that column:
SELECT COUNT(n)
FROM (
SELECT *, row_number() OVER (ORDER BY [MyColumn] ASC) n
FROM (
SELECT DISTINCT [MyColumn]
FROM [MyTable]
) items
) distinctItems
Try this..
SELECT CASE
WHEN a IS NULL THEN 'Null'
ELSE 'Not Null'
END a,
Count(1)
FROM us
GROUP BY CASE
WHEN a IS NULL THEN 'Null'
ELSE 'Not Null'
END
Here are two solutions:
Select count(columnname) as countofNotNulls, count(isnull(columnname,1))-count(columnname) AS Countofnulls from table name
OR
Select count(columnname) as countofNotNulls, count(*)-count(columnname) AS Countofnulls from table name
Try
SELECT
SUM(ISNULL(a)) AS all_null,
SUM(!ISNULL(a)) AS all_not_null
FROM us;
Simple!
If you're using MS Sql Server...
SELECT COUNT(0) AS 'Null_ColumnA_Records',
(
SELECT COUNT(0)
FROM your_table
WHERE ColumnA IS NOT NULL
) AS 'NOT_Null_ColumnA_Records'
FROM your_table
WHERE ColumnA IS NULL;
I don't recomend you doing this... but here you have it (in the same table as result)
use ISNULL embedded function.
All the answers are either wrong or extremely out of date.
The simple and correct way of doing this query is using COUNT_IF function.
SELECT
COUNT_IF(a IS NULL) AS nulls,
COUNT_IF(a IS NOT NULL) AS not_nulls
FROM
us
SELECT SUM(NULLs) AS 'NULLS', SUM(NOTNULLs) AS 'NOTNULLs' FROM
(select count(*) AS 'NULLs', 0 as 'NOTNULLs' FROM us WHERE a is null
UNION select 0 as 'NULLs', count(*) AS 'NOTNULLs' FROM us WHERE a is not null) AS x
It's fugly, but it will return a single record with 2 cols indicating the count of nulls vs non nulls.
This works in T-SQL. If you're just counting the number of something and you want to include the nulls, use COALESCE instead of case.
IF OBJECT_ID('tempdb..#us') IS NOT NULL
DROP TABLE #us
CREATE TABLE #us
(
a INT NULL
);
INSERT INTO #us VALUES (1),(2),(3),(4),(NULL),(NULL),(NULL),(8),(9)
SELECT * FROM #us
SELECT CASE WHEN a IS NULL THEN 'NULL' ELSE 'NON-NULL' END AS 'NULL?',
COUNT(CASE WHEN a IS NULL THEN 'NULL' ELSE 'NON-NULL' END) AS 'Count'
FROM #us
GROUP BY CASE WHEN a IS NULL THEN 'NULL' ELSE 'NON-NULL' END
SELECT COALESCE(CAST(a AS NVARCHAR),'NULL') AS a,
COUNT(COALESCE(CAST(a AS NVARCHAR),'NULL')) AS 'Count'
FROM #us
GROUP BY COALESCE(CAST(a AS NVARCHAR),'NULL')
Building off of Alberto, I added the rollup.
SELECT [Narrative] = CASE
WHEN [Narrative] IS NULL THEN 'count_total' ELSE [Narrative] END
,[Count]=SUM([Count]) FROM (SELECT COUNT(*) [Count], 'count_nulls' AS [Narrative]
FROM [CrmDW].[CRM].[User]
WHERE [EmployeeID] IS NULL
UNION
SELECT COUNT(*), 'count_not_nulls ' AS narrative
FROM [CrmDW].[CRM].[User]
WHERE [EmployeeID] IS NOT NULL) S
GROUP BY [Narrative] WITH CUBE;
SELECT
ALL_VALUES
,COUNT(ALL_VALUES)
FROM(
SELECT
NVL2(A,'NOT NULL','NULL') AS ALL_VALUES
,NVL(A,0)
FROM US
)
GROUP BY ALL_VALUES
select count(isnull(NullableColumn,-1))
if its mysql, you can try something like this.
select
(select count(*) from TABLENAME WHERE a = 'null') as total_null,
(select count(*) from TABLENAME WHERE a != 'null') as total_not_null
FROM TABLENAME
Just in case you wanted it in a single record:
select
(select count(*) from tbl where colName is null) Nulls,
(select count(*) from tbl where colName is not null) NonNulls
;-)
for counting not null values
select count(*) from us where a is not null;
for counting null values
select count(*) from us where a is null;
I created the table in postgres 10 and both of the following worked:
select count(*) from us
and
select count(a is null) from us
In my case I wanted the "null distribution" amongst multiple columns:
SELECT
(CASE WHEN a IS NULL THEN 'NULL' ELSE 'NOT-NULL' END) AS a_null,
(CASE WHEN b IS NULL THEN 'NULL' ELSE 'NOT-NULL' END) AS b_null,
(CASE WHEN c IS NULL THEN 'NULL' ELSE 'NOT-NULL' END) AS c_null,
...
count(*)
FROM us
GROUP BY 1, 2, 3,...
ORDER BY 1, 2, 3,...
As per the '...' it is easily extendable to more columns, as many as needed
Number of elements where a is null:
select count(a) from us where a is null;
Number of elements where a is not null:
select count(a) from us where a is not null;