Sql server not in clause not working - sql-server-2008

Why this does not give any records in sql server 2008?
;with pricedCategories as
(
select * from Product.Category where CategoryID not in
(select Parent_CategoryID from Product.Category)
)
select * from pricedCategories

It seems that your query doesn't return values when there are NULL values in subquery inside CTE (if I replace NULL in insert (1, NULL) with let's say (1, 0) your query will work). If you want to get the categories that are not any other category's parents even with NULL values, you can do it like this:
DECLARE #Category TABLE (CategoryID INT, Parent_CategoryID INT)
INSERT #Category VALUES
(1, NULL),
(2, 1),
(3, 1),
(4, 2)
;WITH pricedCategories AS
(
SELECT * FROM #Category y WHERE NOT EXISTS
(SELECT Parent_CategoryID FROM #Category x
WHERE x.Parent_CategoryID = y.CategoryID)
)
SELECT * FROM pricedCategories
It is interesting to see that the following approach works the same as the approach described in your question:
;WITH pricedCategories AS
(
SELECT * FROM #Category y
WHERE y.CategoryID <> ALL(SELECT DISTINCT Parent_CategoryID FROM #Category)
)
SELECT * FROM pricedCategories
You could change your query to use the ISNULL function to replace NULL with some numeric value that is never used as CategoryID, like this:
;WITH pricedCategories AS
(
SELECT * FROM #Category WHERE CategoryID NOT IN
(SELECT ISNULL(Parent_CategoryID, -1) FROM #Category)
)
SELECT * FROM pricedCategories
But then the NULL value which means "nothing" would be changed to actual value of -1 which is not true and you shouldn't use it.

Related

Find employees who have worked on at least 2 projects

I need to make basically this without using the "Group by"
SELECT idEmploye , COUNT(*) AS nbrProjet
FROM ressourcesprojet_
GROUP BY idEmploye
HAVING COUNT(*) > 1;
My Database code for creating it : https://sourceb.in/JePvGUccpU
Things i tried :
SELECT e.idEtape, e.idProjet, e.dateDebut, e.dateFin
FROM (
SELECT idProjet, dateDebut, dateFin
FROM Projet) AS p
RIGHT JOIN EtapexProjet AS e
ON e.dateDebut > p.dateDebut AND e.dateFin > p.dateFin
ORDER BY p.idProjet;
SELECT E.idEmploye ,
(SELECT COUNT(idProjet)
FROM ressourcesprojet_
Where E.idEmploye = idEmploye) AS nbrProjet
From employe_ E;
SELECT idEmploye
FROM ressourcesprojet_ WHERE 1 < (
SELECT COUNT(idProjet)
FROM ressourcesprojet_
where idEmploye = idEmploye);
I just can't wrap my head around it
You can use this:
SELECT DISTINCT p1.idEmploye
FROM RessourcesProjet_ AS p1
JOIN RessourcesProjet_ AS p2 ON p1.idEmploye = p2.idEmploye AND p1.idProjet != p2.idProjet
This joins the RessourcesProjet_ with itself to find all pairs of rows with the same employee and different projects, so this will find anyone who has worked on more than one project.
Then it uses DISTINCT so we don't see all the duplications.
This doesn't generalize easily to "at least N" like the methods that use COUNT() do. It needs to self-join N copies of the table, and all the ON clauses will need to check that the project isn't in any of the previous copies of the table.
The reason why you want, would affect if this is usable. Here is an example, which technically doesn't use the grouping, but if you added any other columns would easily require it:
DECLARE #test2 TABLE (num INT)
INSERT INTO #test2 VALUES (1), (2), (3), (2), (4)
SELECT DISTINCT *
FROM #Test2 t
WHERE (SELECT COUNT(*) FROM #Test2 it WHERE it.num = t.num) > 1
P.S - I have to assume this is either a puzzle or test question
Alternative Edit:
DECLARE #test2 TABLE (id INT, num INT)
INSERT INTO #test2 VALUES (1, 1), (2, 1), (2, 3), (3, 2), (3, 4)
SELECT *
FROM #Test2 t
WHERE NOT EXISTS (SELECT * FROM #Test2 it WHERE it.id = t.id AND it.num <> t.num)
This results in the following being returned:
id, num
1, 1

How to replace multiple values in a single column with SQL?

I have a column with complex user id. I want to replace the text within my select query.
This creates a new column as updated_by for every single value. I want them to be replaced in a single column. How can I achieve this?
select replace(updated_by, '5eaf5d368141560012161636', 'A'),
replace(updated_by, '5e79d03e9abae00012ffdbb3', 'B'),
replace(updated_by, '5e7b501e9abae00012ffdbd6', 'C'),
replace(updated_by, '5e7b5b199abae00012ffdbde', 'D'),
replace(updated_by, '5e7c817c9ca5540012ea6cba', 'E'),
updated_by
from my_table
GROUP BY updated_by;
In Postgres I would use a VALUES expression to form a derived table:
To just select:
SELECT *
FROM my_table m
JOIN (
VALUES
('5eaf5d368141560012161636', 'A')
, ('5e79d03e9abae00012ffdbb3', 'B')
, ('5e7b501e9abae00012ffdbd6', 'C')
, ('5e7b5b199abae00012ffdbde', 'D')
, ('5e7c817c9ca5540012ea6cba', 'E')
) u(updated_by, new_value) USING (updated_by);
Or LEFT JOIN to include rows without replacement.
You may need explicit type casts with non-default data types. See:
Casting NULL type when updating multiple rows
For repeated use, create a persisted translation table.
CREATE TABLE updated_by_translation (updated_by text PRIMARY KEY, new_value text);
INSERT INTO my_table
VALUES
('5eaf5d368141560012161636', 'A')
, ('5e79d03e9abae00012ffdbb3', 'B')
, ('5e7b501e9abae00012ffdbd6', 'C')
, ('5e7b5b199abae00012ffdbde', 'D')
, ('5e7c817c9ca5540012ea6cba', 'E')
;
Data types and constraints according to your actual use case.
SELECT *
FROM my_table m
LEFT JOIN updated_by_translation u USING (updated_by);
MySQL recently added a VALUES statement, too. The manual:
VALUES is a DML statement introduced in MySQL 8.0.19
But it requires the keyword ROW for every row. So:
...
VALUES
ROW('5eaf5d368141560012161636', 'A')
, ROW('5e79d03e9abae00012ffdbb3', 'B')
, ROW('5e7b501e9abae00012ffdbd6', 'C')
, ROW('5e7b5b199abae00012ffdbde', 'D')
, ROW('5e7c817c9ca5540012ea6cba', 'E')
...
Use case:
select case updated_by
when '5eaf5d368141560012161636' then 'A'
when '5e79d03e9abae00012ffdbb3' then 'B'
when '5e7b501e9abae00012ffdbd6' then 'C'
when '5e7b5b199abae00012ffdbde' then 'D'
when '5e7c817c9ca5540012ea6cba' then 'E'
end as updated_by
from my_table
This has to be nested liek this
SELECT
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(updated_by,
'5e7c817c9ca5540012ea6cba',
'E'),
'5e7b5b199abae00012ffdbde',
'D'),
'5e7b501e9abae00012ffdbd6',
'C'),
'5e79d03e9abae00012ffdbb3',
'B'),
'5eaf5d368141560012161636',
'A'),
updated_by
FROM
my_table
GROUP BY updated_by
This will replace all occurring, patterns, if they are not foung nothing happens
You can use a recursive CTE if you need to handle multiple values within a single row:
with replacements as (
select '5eaf5d368141560012161636' as oldval, 'A' as newval union all
select '5e79d03e9abae00012ffdbb3' as oldval, 'B' union all
select '5e7b501e9abae00012ffdbd6' as oldval, 'C' union all
select '5e7b5b199abae00012ffdbde' as oldval, 'D' union all
select '5e7c817c9ca5540012ea6cba' as oldval, 'E'
),
r as (
select r.*, row_number() over (order by oldval) as seqnum
from replacements r
),
recursive cte (
select r.seqnum, replace(t.updated_by, r.oldval, r.newval) as updated_by
from my_table t join
r
on r.seqnum = 1
union all
select r.seqnum, replace(cte.updated_by, r.oldval, r.newval) as updated_by
from cte t join
r
on r.seqnum = cte.seqnum + 1
)
select cte.*
from cte
where seqnum = (select count(*) from replacements);

How do I insert multiple rows int MySQL where the values of one column are from a single list of values and the others are constant

I'm using MariaDB 10.4. I have a list of values, i.e. one#email.com, two#email.com, and three#email.com (my actual list is much longer).
I would like to make an SQL insert equivalent to the following:
insert into my_table(email, foreign_key_id, timestamp) values
('one#email.com', 1, now()),
('two#email.com', 1, now()),
('three#email.com', 1, now());
While only having to write something like select email from ('one#email.com', 'two#email.com', and 'three#email.com') somewhere in the insert query without duplicating the constants/functions on each line. Can I do this in SQL without any temporary tables?
INSERT INTO my_table (email)
SELECT email
FROM JSON_TABLE( #value,
"$[*]" COLUMNS ( email VARCHAR(32) PATH "$"
)
) AS parse_JSON;
fiddle
Applicable to MySQL 8+
For MariaDB 10.2.3+ use something close to (online fiddle issues incomprehensible errors where there are clearly no errors - so I cannot test)
INSERT INTO my_table (email)
WITH RECURSIVE
cte AS ( SELECT 0 AS num, JSON_VALUE(#json, '$[0]') AS email
UNION
SELECT num + 1, JSON_VALUE(#json, CONCAT('$[', num + 1, ']'))
FROM cte
WHERE JSON_VALUE(#json, CONCAT('$[', num + 1, ']') IS NOT NULL
)
SELECT email
FROM cte;
You can use select:
insert into my_table(email, foreign_key_id, timestamp) values
select e.email, 1, now())
from (select 'one#email.com' as email union all
select 'two#email.com' union all
select 'three#email.com'
) e

How many different ways are there to get the second row in a SQL search?

Let's say I was looking for the second most highest record.
Sample Table:
CREATE TABLE `my_table` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`value` int(10),
PRIMARY KEY (`id`)
);
INSERT INTO `my_table` (`id`, `name`, `value`) VALUES (NULL, 'foo', '200'), (NULL, 'bar', '100'), (NULL, 'baz', '0'), (NULL, 'quux', '300');
The second highest value is foo. How many ways can you get this result?
The obvious example is:
SELECT name FROM my_table ORDER BY value DESC LIMIT 1 OFFSET 1;
Can you think of other examples?
I was trying this one, but LIMIT & IN/ALL/ANY/SOME subquery is not supported.
SELECT name FROM my_table WHERE value IN (
SELECT MIN(value) FROM my_table ORDER BY value DESC LIMIT 1
) LIMIT 1;
Eduardo's solution in standard SQL
select *
from (
select id,
name,
value,
row_number() over (order by value) as rn
from my_table t
) t
where rn = 1 -- can pick any row using this
This works on any modern DBMS except MySQL. This solution is usually faster than solutions using sub-selects. It also can easily return the 2nd, 3rd, ... row (again this is achievable with Eduardo's solution as well).
It can also be adjusted to count by groups (adding a partition by) so the "greatest-n-per-group" problem can be solved with the same pattern.
Here is a SQLFiddle to play around with: http://sqlfiddle.com/#!12/286d0/1
This only works for exactly the second highest:
SELECT * FROM my_table two
WHERE EXISTS (
SELECT * FROM my_table one
WHERE one.value > two.value
AND NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
)
LIMIT 1
;
This one emulates a window function rank() for platforms that don't have them. It can also be adapted for ranks <> 2 by altering one constant:
SELECT one.*
-- , 1+COALESCE(agg.rnk,0) AS rnk
FROM my_table one
LEFT JOIN (
SELECT one.id , COUNT(*) AS rnk
FROM my_table one
JOIN my_table cnt ON cnt.value > one.value
GROUP BY one.id
) agg ON agg.id = one.id
WHERE agg.rnk=1 -- the aggregate starts counting at zero
;
Both solutions need functional self-joins (I don't know if mysql allows them, IIRC it only disallows them if the table is the target for updates or deletes)
The below one does not need window functions, but uses a recursive query to enumerate the rankings:
WITH RECURSIVE agg AS (
SELECT one.id
, one.value
, 1 AS rnk
FROM my_table one
WHERE NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
UNION ALL
SELECT two.id
, two.value
, agg.rnk+1 AS rnk
FROM my_table two
JOIN agg ON two.value < agg.value
WHERE NOT EXISTS (
SELECT * FROM my_table nx
WHERE nx.value > two.value
AND nx.value < agg.value
)
)
SELECT * FROM agg
WHERE rnk = 2
;
(the recursive query will not work in mysql, obviously)
You can use inline initialization like this:
select * from (
select id,
name,
value,
#curRank := #curRank + 1 AS rank
from my_table t, (SELECT #curRank := 0) r
order by value desc
) tb
where tb.rank = 2
SELECT name
FROM my_table
WHERE value < (SELECT max(value) FROM my_table)
ORDER BY value DESC
LIMIT 1
SELECT name
FROM my_table
WHERE value = (
SELECT min(r.value)
FROM (
SELECT name, value
FROM my_table
ORDER BY value DESC
LIMIT 2
) r
)
LIMIT 1

Unable to find the matching child record using recursive CTE

I have the following table #t:
ParentId SkuName ChildId
P1 X1 C1
C1 X2 C2
C2 X3 C2
If I pass the ParentId = P1, the desired output is x3
i.e. the stopping condition is the last row matching record and get the sku name for that
row. If no row matched, then return null
My attempt (no working though)
DECLARE #t TABLE (ParentId Varchar(100), Name VARCHAR(20), ChildId Varchar(100))
INSERT INTO #t(ParentId, Name, ChildId)
SELECT 'P1', 'X1', 'C1' UNION ALL
SELECT 'C1', 'X2', 'C2' UNION ALL
SELECT 'C2', 'X3', 'C2'
Declare #ParentId Varchar(10) = 'P1'
;With CTE As
(
Select
Rn = ROW_NUMBER() Over(Order By (Select 1))
,ParentId
, Name
, ChildId
From #t Where ParentId = #ParentId
Union All
Select
Rn + 1
,pc.ParentId as Parents
,pc.Name
,pc.ChildId
FROM #t pc
JOIN CTE gp on pc.Childid = gp.Parentid
)
Select *
From CTE
Please help
The problem is with the JOIN in your second UNION query. You are joining on pc.Childid = gp.Parentid. You should be joining on pc.ParentId = gp.Childid.
Also, since your data has both the child and the parent as the same value, you will end up with infinite recursion unless you specify that the recursion should stop when the ParentId equals the Childid. (WHERE gp.ParentId <> gp.Childid)
Is this the result you are looking for?
DECLARE #t TABLE (ParentId VARCHAR(100), Name VARCHAR(20), ChildId VARCHAR(100))
INSERT INTO #t(ParentId, Name, ChildId)
SELECT 'P1', 'X1', 'C1' UNION ALL
SELECT 'C1', 'X2', 'C2' UNION ALL
SELECT 'C2', 'X3', 'C2'
DECLARE #ParentId VARCHAR(10) = 'P1'
;With CTE As
(
SELECT
Rn = 1
,ParentId
,Name
,ChildId
FROM #t WHERE ParentId = #ParentId
UNION All
SELECT
Rn + 1
,pc.ParentId as Parents
,pc.Name
,pc.ChildId
FROM #t pc
JOIN CTE gp
on pc.ParentId = gp.Childid
WHERE gp.ParentId <> gp.Childid
)
SELECT TOP 1 *
FROM CTE
ORDER BY Rn DESC
OPTION (MAXRECURSION 0);