How to filter out a "NULL" valued line if there's another line that is otherwise identical but doesn't have a "NULL" value? - sql

As you can see from the image. Lines 5 and 6 are otherwise identical but line 5 has "NULL" value. So I want to filter line 5 out but leave line 6.
Then there are also single lines that have "NULL" value but I also want to keep those. So how can I do this? How can I keep the single/unique lines that have "NULL" value and simultaneously filter out lines that have an identical counterpart but the counterpart has a "NULL" value?
I've tried to use coalesce group by -function but haven't got the wanted results yet.

You can use not exists:
select t.*
from mytable t
where
date_updated is not null
or not exists (
select 1
from mytable t1
where
t1.state = t.state
and t1.foo = t.foo
and t1.date_created = t.date_created
and t1 date_created is not null
)
Another option is window functions, if your database supports them:
select *
from (
select
t.*,
max(date_updated) over(partition by state, foo, date_created) max_date_updated
from mytable t
) t
where date_updated is not null or max_date_updated is null

Related

Count for a list of items with zero for does not exist

If I have a table t1 with:
my_col
------
foo
foo
bar
And I have a list with foo and hello
How can I get:
my_col | count
-------|-------
foo | 2
hello | 0
If I just do
SELECT my_col, COUNT(*)
FROM t1
WHERE my_col in ('foo', 'hello')
GROUP BY my_col
I get
my_col | count
-------|------
foo | 2
without any value for hello.
I'm specifically wanting this to be in reference to a list of items because this will be called in a program where the list is a variable.
Ideally you should maintain a separate table with all the possible column values which you want to appear in your report. In the absence of that, we can try using a CTE here:
WITH cte AS (
SELECT 'foo' AS my_col UNION ALL
SELECT 'bar' UNION ALL
SELECT 'hello'
)
SELECT
a.my_col,
COUNT(b.my_col) AS count
FROM cte a
LEFT JOIN t1 b
ON a.my_col = b.my_col
WHERE
a.my_col IN ('foo', 'hello')
GROUP BY
a.my_col;
Demo
Here's yet another way, using values:
select
t2.my_col, count (t1.my_col)
from
(values ('foo'), ('hello')) as t2 (my_col)
left join t1 on t1.my_col = t2.my_col
group by
t2.my_col
Note that count (t1.my_col) returns a 0 for "hello" since nulls are not counted. count (*) by contast would have returned 1 for "hello" because it was counting the row.
You can turn your list into a set of rows and use a LEFT JOIN, like :
SELECT x.val, COUNT(t.my_col)
FROM
(SELECT 'foo' val UNION SELECT 'hello') x
LEFT JOIN t ON t.my_col = x.val
GROUP BY x.val
Postgres solution:
One way is to place the 'list' into an ARRAY, and then convert the ARRAY into a column using unnest. Then perform a left join on that column with the other table and perform a count.
WITH t1 AS (
SELECT 'foo' AS my_col UNION ALL
SELECT 'foo' UNION ALL
SELECT 'bar'
)
SELECT
a.my_col,
COUNT(b.my_col) AS count
FROM unnest(ARRAY['foo', 'hello']) a (my_col)
LEFT JOIN t1 b
ON a.my_col = b.my_col
GROUP BY
a.my_col;
The issue I had with the other answers is that (while they they helped me get to the solution) they did not provide a solution where the items of interest were in a single list (which isn't an actual sql term, so the fault is on me).
However, my real use case is to perform a native query using java and hibernate, and unfortunately the above does not work because the typing cannot be determined. Instead I converted my list into a single string and used string_to_array in place of the ARRAY function.
So the solution that worked best for my use case is below (but at this point, the other answers would be just as correct since I'm now having to do manual string manipulation, but I'm leaving this here for the sake of posterity)
WITH t1 AS (
SELECT 'foo' AS my_col UNION ALL
SELECT 'foo' UNION ALL
SELECT 'bar'
)
SELECT
a.my_col,
COUNT(b.my_col) AS count
FROM unnest(string_to_array('foo, hello', ',')) a (my_col)
LEFT JOIN t1 b
ON a.my_col = b.my_col
GROUP BY
a.my_col;

Why this sql will cause type conversion error?

WITH tb_testl AS (
SELECT 1 AS id ,'hehe' AS value
UNION ALL
SELECT 1 AS id, '1' AS value
UNION ALL
SELECT 2 AS id, '2' AS value
UNION ALL
SELECT 2 AS id, '2' AS value
), tb_test2 AS (
SELECT CONVERT(INT , value) AS value FROM tb_testl WHERE id = 2
)
SELECT * FROM tb_test2 WHERE value = 2;
this sql will cause error
Conversion failed when converting the varchar value 'hehe' to data
type int.
but the table tb_test2 dosen't have the value 'hehe' which is in the anthor table tb_test1. And I found that this sql will work well if I don't append the statement WHERE value = 2; .I've tried ISNUMBERIC function but it didn't work.
version:mssql2008 R2
With respect to the why this occurs:
There is a Logical Processing Order, which describes the order in which clauses are evaluated. The order is:
FROM
ON
JOIN
WHERE
GROUP BY
WITH CUBE or WITH ROLLUP
HAVING
SELECT
DISTINCT
ORDER BY
TOP
You can also see the processing order when you SET SHOWPLAN_ALL ON. For this query, the processing is as follows:
Constant scan - this is the FROM clause, which consists of hard coded values, hence the constants.
Filter - this is the WHERE clause. While it looks like there are two where clauses (WHERE id = 2 and WHERE value = 2). SQL Server sees this differently, it considers a single WHERE clause: WHERE CONVERT(INT , value) = 2 AND id = 2.
Compute scaler. This is the CONVERT function in the select.
Because both WHERE clauses are executed simultaneously, the hehe value is not filtered out of the CONVERT scope.
Effectively, the query is simplified to something like:
SELECT CONVERT(INT, tb_testl.value) AS Cvalue
FROM (
SELECT 1 AS id
, 'hehe' AS value
UNION ALL
SELECT 1 AS id
, '1' AS value
UNION ALL
SELECT 2 AS id
, '2' AS value
UNION ALL
SELECT 2 AS id
, '2' AS value
) tb_testl
WHERE CONVERT(INT, tb_testl.value) = 2
AND tb_testl.id = 2
Which should clarify why the error occurs.
With SQL, you cannot read code in the same way as imperative languages like C. Lines of SQL code are not necessarily (mostly not at all, in fact) executed in the same order it is written in. In this case, it's an error to think the inner where is executed before the outer where.
SQL Server does not guarantee the order of processing of statements (with one exception below). That is, there is no guarantee that WHERE filtering happens before the SELECT. Or that one CTE is evaluated before another. This is considered an advantage because it allows SQL Server to rearrange the processing to optimize performance (although I consider the issue that you are seeing a bug).
Obviously, the problem is in this part of the code:
tb_test2 AS (
SELECT CONVERT(INT, value) AS value
FROM tb_testl
WHERE id = 2
)
(Well, actually, it is where tb_test2 is referenced.)
What is happening is that SQL Server pushes the CONVERT() to where the values are being read, so the conversion is attempted before the WHERE clause is processed. Hence, the error.
In SQL Server 2012+, you can easily solve this using TRY_CNVERT():
tb_test2 AS (
SELECT TRY_CONVERT(INT, value) AS value
FROM tb_testl
WHERE id = 2
)
However, that doesn't work in SQL Server 2008. You can use the fact that CASE does have some guarantees on the order of processing:
tb_test2 AS (
SELECT (CASE WHEN value NOT LIKE '%[^0-9]%' THEN CONVERT(INT, value)
END) AS value
FROM tb_testl
WHERE id = 2
)
error caused by this part of statement
), tb_test2 AS (
SELECT CONVERT(INT , value) AS value FROM tb_testl WHERE id = 2
value has type of varchar and 'hehe' value cannot be converted to integer
WITH tb_testl AS (
SELECT 1 AS id ,'hehe' AS value
UPDATE: sql try convert all value(s) to integer in you statement. to avoid error rewrite statement as
WITH tb_testl AS (
SELECT 1 AS id ,'hehe' AS value
UNION ALL SELECT 1 AS id, '1' AS value
UNION ALL SELECT 2 AS id, '2' AS value
UNION ALL SELECT 2 AS id, '2' AS value
), tb_test2 AS (
SELECT value AS value FROM tb_testl WHERE id = 2
),
tb_test3 AS (
SELECT cast(value as int) AS value FROM tb_test2
)
SELECT * FROM tb_test3

No records found when running not in operator

I am trying to get records from one table excluding some records (Order No.'s in the Union). Can anybody tell me what could be wrong with this query. I am getting no records after running it.
SELECT *
FROM [dbo].[FMD15_18]
WHERE [OrderNo] NOT IN ((SELECT OrderNo
FROM [dbo].[FMD15_18]
WHERE [Item Description] Like '%AP%')
UNION ALL
SELECT [OrderNo] FROM [dbo].[AP&C]
)
I would use NOT EXISTS instead :
SELECT t.*
FROM [dbo].[FMD15_18] t
WHERE NOT EXISTS (SELECT 1
FROM [dbo].[FMD15_18] t1
WHERE t1.OrderNo = t.OrderNo AND
t1.[Item Description] Like '%AP%') AND
NOT EXISTS (SELECT 1
FROM [dbo].[AP&C] a
WHERE a.OrderNo = t.OrderNo);
However, i suspect some nullable issues with current query. If so, then you need to fiter out with IS NOT NULL in subquery.
NOT IN is tricky. I guess that OrderNo is nullable that is why you don't get any rows.
SELECT *
FROM [dbo].[FMD15_18]
WHERE [OrderNo] NOT IN (SELECT COALESCE(OrderNo, '^')
FROM [dbo].[FMD15_18]
WHERE [Item Description] Like '%AP%'
UNION ALL
SELECT COALESCE([OrderNo], '^') FROM [dbo].[AP&C]
);
Explanation:
1 IN (1, NULL)
<=>
1=1 OR 1 = NULL
-- 1 row returned
And NOT NULL:
1 NOT IN (1, NULL)
1!=1 AND 1 != NULL
-- always not true
-- always 0 rows returned
You should be able to avoid using sub-queries entirely. It sounds like you want orders (from FMD15_18) where the description does not contain "AP", and the order number is not in the AP&C table. If that's the case, you could do something like the following:
select FMD15_18.*
from FMD15_18
left join [AP&C] on
[AP&C].OrderNo = FMD15_18.OrderNo
where
FMD15_18.[Item Description] NOT like '%AP%'
and [AP&C].OrderNo is null
I don't know what kind of data is in the [FMD15_18].[Item Description] field, but it seems heavy-handed to exclude items where the description contains 2 letters. How long does the description column tend to be? Might there be records that contain "AP" that you're excluding inadvertently? Items with descriptions as varied as "APPLE", "MAPLE SYRUP", and "BURLAP" would be excluded based on this condition.

Select the last non-NULL value when current row is NULL

I know that there are a lot of solutions for this but unfortunately I cannot use partition or keyword TOP. Nothing I tried on earlier posts works.
My table looks like this:
The result I want is when any completion percentage is NULL it should get the value from last non-value completion percentage, like this:
I tried this query but nothing works. Can you tell me where I am going wrong?
SELECT sequence,project_for_lookup,
CASE WHEN completion_percentage IS NOT NULL THEN completion_percentage
ELSE
(SELECT max(completion_percentage) FROM [project_completion_percentage] AS t2
WHERE t1.project_for_lookup=t2.project_for_lookup and
t1.sequence<t2.sequence and
t2.completion_percentage IS NOT null
END
FROM [project_completion_percentage] AS t1
SQL Server 2008 doesn't support cumulative window functions. So, I would suggest outer apply:
select cp.projectname, cp.sequence,
coalesce(cp.completion_percentage, cp2.completion_percentage) as completion_percentage
from completion_percentage cp outer apply
(select top 1 cp2.*
from completion_percentage cp2
where cp2.projectname = cp.projectname and
cp2.sequence < cp.sequence and
cp2.completion_percentage is not null
order by cp2.sequence desc
) cp2;
Does this work? It seems to for me. You were missing a parenthesis and had the sequence backwards.
http://sqlfiddle.com/#!3/465f2/4
SELECT sequence,project_for_lookup,
CASE WHEN completion_percentage IS NOT NULL THEN completion_percentage
ELSE
(
SELECT max(completion_percentage)
FROM [project_completion_percentage] AS t2
WHERE t1.project_for_lookup=t2.project_for_lookup
-- sequence was reversed. You're on the row t1, and want t2 that is from a prior sequence.
and t2.sequence<t1.sequence
and t2.completion_percentage IS NOT null
--missing a closing paren
)
END
FROM [project_completion_percentage] AS t1

SQL Subquery returned more than 1 value

My query causes the following error:
Msg 512, Level 16, State 1, Procedure Item_insupd, Line 17
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Query:
INSERT INTO [Total_Database].[dbo].[Item]
(
ItemID,
ItemNo,
ItemDescription,
Notes,
StandardCost,
SalesGLAccountID,
ItemTypeID,
Backorderable
)
(
SELECT [nr],
[nr],
[Latijn]+' '+[Subgroep]+' '+CAST([nr] as VARCHAR(255)),
[NL]+' '+[Vorm]+' '+[Kenmerk]+' '+[Hoogte],[Inkoopprijs],
(4),
(case when [Productgroep]='PB' then 1 else 5 end),
(1)
FROM [ACCESDATA].[dbo].[Planten]
);
I suspect this to happen because my subquery does not contain a WHERE, unfortunately I do not know how to construct a correct WHERE clause.
I suspect the problem is in this string (line 26 in your code):
IF NOT (EXISTS (SELECT G.GLAccountID FROM GLAccnt G INNER JOIN Inserted I ON G.GLAccountID = I.SalesGLAccountID))
OR ((SELECT I.COGSGLAccountID FROM Inserted I) IS NOT NULL) AND NOT (EXISTS (SELECT G.GLAccountID FROM GLAccnt G INNER JOIN Inserted I ON G.GLAccountID = I.COGSGLAccountID))
It looks like (SELECT I.COGSGLAccountID FROM Inserted I) return more than one row, so you're getting an error.
You're treating inserted as a one row table (for example, you're getting parameters from it like SELECT #ItemNo = I.ItemNo, #ItemDescription = I.ItemDescription FROM Inserted I, but inserted table can have more than one row. So in your case I think you have 3 options - check that there's only 1 row in inserted, rewrite trigger as set-based, or use cursor.
Here's sql fiddle with somewhat similar example.
If you really only want to insert one row, then You just add Where at the end, followed by some predicate (logical statement) that will be true for only one row in the table the query is reading from.
INSERT INTO [Total_Database].[dbo].[Item](ItemID,
ItemNo,ItemDescription,Notes,StandardCost,SalesGLAccountID,
ItemTypeID,Backorderable)
SELECT [nr],[nr],[Latijn]+' '+[Subgroep]+' '+CAST([nr] as VARCHAR(255)),
[NL]+' '+[Vorm]+' '+[Kenmerk]+' '+[Hoogte],[Inkoopprijs],(4),
(case when [Productgroep]='PB' then 1 else 5 end),(1)
FROM [ACCESDATA].[dbo].[Planten]
Where [SomeColumnName] = [some Value];
... but when performing an Insert using a select to generate the rows to be inserted, you just type the Select statement instead of the Values() clause, without surrounding parentheses. I think that because you had the surrounding parentheses, the query processor assumed you wanted that to be a single value. Do you want to only insert one row of data? or a whole set of data rows ?
INSERT INTO [Total_Database].[dbo].[Item]
(
ItemID,
ItemNo,
ItemDescription,
Notes,
StandardCost,
SalesGLAccountID,
ItemTypeID,
Backorderable
) IN
(
SELECT [nr],
[nr],
[Latijn]+' '+[Subgroep]+' '+CAST([nr] as VARCHAR(255)),
[NL]+' '+[Vorm]+' '+[Kenmerk]+' '+[Hoogte],[Inkoopprijs],
(4),
(case when [Productgroep]='PB' then 1 else 5 end),
(1)
FROM [ACCESDATA].[dbo].[Planten]
);