Binary "OR" operation on a SQL column - sql

I have a query that returns weekdays
Select PERSON_NAME, PERSON_DAY from PERSON_DAYS WHERE PERSON_ID = #myId
say I obtain
John 1 (mo)
John 3 (mo tu)
John 8 (th)
I need to obtain for John all the days when is busy. How do I a logical OR on the PERSON_DAY column in this query?
the result should be 11 (mo tu th)

well here my best so far
;with PowersOf2
as
(
select 1 as Number
union all
select A.Number * 2 from PowersOf2 as A where A.Number < 64
)
select P.PERSON_NAME, sum(distinct P.PERSON_DAY & PowersOf2.Number)
from PERSON_DAYS as P
left outer join PowersOf2 on PowersOf2.Number <= P.PERSON_DAY
where P.PERSON_ID = #myId
group by P.PERSON_NAME
SQL FIDDLE EXAMPLE

If I understand you correctly, you can use a combination of bitwise operator and and aggregate function sum to do what you want.
Example:
with person_days as (
select 'John' as person_name, 1 as weekday --mo
union select 'John', 3 -- mo, tu
union select 'John', 8 -- th
union select 'Jane', 1 -- mo
union select 'Jane', 9 -- mo, th
union select 'Jane', 40 -- th, sa
),
Bits AS (
SELECT 1 AS BitMask --mo
UNION ALL SELECT 2 --tu
UNION ALL SELECT 4 --we
UNION ALL SELECT 8 --th
UNION ALL SELECT 16 --fr
UNION ALL SELECT 32 --sa
UNION ALL SELECT 64 --su
UNION ALL SELECT 128
)
, person_single_days as (
select distinct person_name, weekday & bits.BitMask single_weekday
from person_days
inner join bits on person_days.weekday & bits.BitMask > 0
)
select person_name, sum(single_weekday) weekdays
from person_single_days
group by person_name;
result:
person_name weekdays
----------- -----------
Jane 41
John 11

"inspired" by Roman's CTE: (note that the first CTE just generates demo data)
with p as
(
select 'John' as PERSON_NAME, 1 as PERSON_DAY
union
select 'John', 3
union
select 'John', 8
union
select 'Jane', 2
union
select 'Jane', 4
),
cte as
(
select PERSON_NAME, PERSON_DAY from p
union all
select cte2.PERSON_NAME, p.PERSON_DAY | cte2.PERSON_DAY
from p
inner join cte as cte2 on p.PERSON_NAME = cte2.PERSON_NAME
where p.PERSON_DAY & cte2.PERSON_DAY = 0
)
select PERSON_NAME, MAX(PERSON_DAY) from cte
group by PERSON_NAME

I think what you are looking for is a custom aggregate that does an OR. You can write that using SQL CLR in .NET. This is probably the cleanest solution. It will be reusable, too.
Alternatively, you could use cursor-based loops to calculate the result.
You could also (mis)use CTE's for this purpose.

Related

Need to find the max term_code value for each person in my table

I have a list of people with multiple term_code values. I need to find the max for each person that has a 201930 or 201940 record. I need to take the 201930 if there is both such as the case with Bob. I then need to return other fields for each person with that term. Only the red records will be returned. Fred should not show up in the output.
Here is the query I currently have, but it grabs the 201940 record for Bob. The total number of records is correct with it, but it gets some incorrect values.
SELECT userid, term_code, race, gender
FROM mytable a JOIN (
SELECT userid, MAX(term_code) AS term_code
FROM mytable
WHERE term_code <= '201940'
GROUP BY userid
) b ON (a.userid = b.userid and a.term_code = b.term_code)
WHERE term_code IN ('201930', '201940');
Using this line seems logical to me and it gets the right value for Bob, but it cuts my results by about 30%.
WHERE term_code <= COALESCE ('201930','201940')
Any suggestions?
With NOT EXISTS:
select m.* from mytable m
where m.term_code = (
case when not exists (select 1 from mytable where userid = m.userid and term_code = 201930)
then 201940
else 201930
end
)
Or if you only want the userid and term_code then you can do it with simple aggregation:
select userid, min(term_code) term_code
from mytable
where term_code in (201930, 201940)
group by userid
If you want the full row from the table then you can join to the table:
select m.*
from mytable m inner join (
select userid, min(term_code) term_code
from mytable
where term_code in (201930, 201940)
group by userid
) t on t.userid = m.userid and t.term_code = m.term_code
Or with ROW_NUMBER() window function:
select t.userid, t.term_code, t.race, t.gender
from (
select m.*,
row_number() over (partition by userid order by term_code) rn
from mytable m
where m.term_code in (201930, 201940)
) t
where t.rn = 1
See the demo.
Results:
> USERID | TERM_CODE | RACE | GENDER
> :----- | --------: | :--- | :-----
> Bob | 201930 | null | null
> Tim | 201940 | null | null
with t (USERID, term_code ) as (
select 'Bob', 201601 from dual union all
select 'Bob', 201605 from dual union all
select 'Bob', 201609 from dual union all
select 'Bob', 202930 from dual union all
select 'Bob', 202940 from dual union all
select 'Bob', 202950 from dual union all
select 'Tom', 202940 from dual union all
select 'Tom', 201605 from dual union all
select 'Tom', 201609 from dual union all
select 'Mac', 201601 from dual union all
select 'Mac', 201605 from dual union all
select 'Mac', 201609 from dual
)
select userid, term_code from
(
SELECT t.*
, sum(case when term_code in (202930, 202940) then 1 end) over (partition by userid order by term_code) rnk
FROM t
)
where rnk = 1
USE TERM_CODE
--- ----------
Bob 202930
Tom 202940
Please note the term_code values are not the same except for the ones that you are interested in.
For each USERID the term_code is ranked based on your condition using a SUM() analytic function. Once that is worked out, the outer query simply filters out the 1st ranked row produced in the inner query.

How can I find unoccupied id numbers in a table?

In my table I want to see a list of unoccupied id numbers in a certain range.
For example there are 10 records in my table with id's: "2,3,4,5,10,12,16,18,21,22" and say that I want to see available ones between 1 and 25. So I want to see a list like:
1,6,7,89,11,13,14,15,17,19,20,23,24,25
How should I write my sql query?
Select the numbers form 1 to 25 and show only those that are not in your table
select n from
( select rownum n from dual connect by level <= 25)
where n not in (select id from table);
Let's say you a #numbers table with three numbers -
CREATE TABLE #numbers (num INT)
INSERT INTO #numbers (num)
SELECT 1
UNION
SELECT 3
UNION
SELECT 6
Now, you can use CTE to generate numbers recursively from 1-25 and deselect those which are in your #numbers table in the WHERE clause -
;WITH n(n) AS
(
SELECT 1
UNION ALL
SELECT n+1 FROM n WHERE n < 25
)
SELECT n FROM n
WHERE n NOT IN (select num from #numbers)
ORDER BY n
OPTION (MAXRECURSION 25);
You can try using the "NOT IN" clause:
select
u1.user_id + 1 as start
from users as u1
left outer join users as u2 on u1.user_id + 1 = u2.id
where
u2.id is null
see also SQL query to find Missing sequence numbers
You need LISTAGG to get the output in a single row.
SQL> WITH DATA1 AS(
2 SELECT LEVEL rn FROM dual CONNECT BY LEVEL <=25
3 ),
4 data2 AS(
5 SELECT 2 num FROM dual UNION ALL
6 SELECT 3 FROM dual UNION ALL
7 SELECT 4 from dual union all
8 SELECT 5 FROM dual UNION ALL
9 SELECT 10 FROM dual UNION ALL
10 SELECT 12 from dual union all
11 SELECT 16 from dual union all
12 SELECT 18 FROM dual UNION ALL
13 SELECT 21 FROM dual UNION ALL
14 SELECT 22 FROM dual)
15 SELECT listagg(rn, ',')
16 WITHIN GROUP (ORDER BY rn) num_list FROM data1
17 WHERE rn NOT IN(SELECT num FROM data2)
18 /
NUM_LIST
----------------------------------------------------
1,6,7,8,9,11,13,14,15,17,19,20,23,24,25
SQL>

How to do select count(*) group by and select * at same time?

For example, I have table:
ID | Value
1 hi
1 yo
2 foo
2 bar
2 hehe
3 ha
6 gaga
I want my query to get ID, Value; meanwhile the returned set should be in the order of frequency count of each ID.
I tried the query below but don't know how to get the ID and Value column at the same time:
SELECT COUNT(*) FROM TABLE group by ID order by COUNT(*) desc;
The count number doesn't matter to me, I just need the data to be in such order.
Desire Result:
ID | Value
2 foo
2 bar
2 hehe
1 hi
1 yo
3 ha
6 gaga
As you can see because ID:2 appears most times(3 times), it's first on the list,
then ID:1(2 times) etc.
you can try this -
select id, value, count(*) over (partition by id) freq_count
from
(
select 2 as ID, 'foo' as value
from dual
union all
select 2, 'bar'
from dual
union all
select 2, 'hehe'
from dual
union all
select 1 , 'hi'
from dual
union all
select 1 , 'yo'
from dual
union all
select 3 , 'ha'
from dual
union all
select 6 , 'gaga'
from dual
)
order by 3 desc;
select t.id, t.value
from TABLE t
inner join
(
SELECT id, count(*) as cnt
FROM TABLE
group by ID
)
x on x.id = t.id
order by x.cnt desc
How about something like
SELECT t.ID,
t.Value,
c.Cnt
FROM TABLE t INNER JOIN
(
SELECT ID,
COUNT(*) Cnt
FROM TABLE
GROUP BY ID
) c ON t.ID = c.ID
ORDER BY c.Cnt DESC
SQL Fiddle DEMO
I see the question is already answered, but since the most obvious and most simple solution is missing, I'm posting it anyway. It doesn't use self joins nor subqueries:
SQL> create table t (id,value)
2 as
3 select 1, 'hi' from dual union all
4 select 1, 'yo' from dual union all
5 select 2, 'foo' from dual union all
6 select 2, 'bar' from dual union all
7 select 2, 'hehe' from dual union all
8 select 3, 'ha' from dual union all
9 select 6, 'gaga' from dual
10 /
Table created.
SQL> select id
2 , value
3 from t
4 order by count(*) over (partition by id) desc
5 /
ID VALU
---------- ----
2 bar
2 hehe
2 foo
1 yo
1 hi
6 gaga
3 ha
7 rows selected.

Use a CTE to traverse to 2nd level in tree

I'm trying to use a CTE to traverse a tree in SQL Server. Ideally what I would like as output is a table which shows for each node in the tree the corresponding node that is second from the top in the tree.
I have some basic code to traverse the tree from a given node, but how can I modify it so it produces the desired output ?
DECLARE #temp TABLE
(
Id INT
, Name VARCHAR(50)
, Parent INT
)
INSERT #temp
SELECT 1,' Great GrandFather Thomas Bishop', null UNION ALL
SELECT 2,'Grand Mom Elian Thomas Wilson' , 1 UNION ALL
SELECT 3, 'Dad James Wilson',2 UNION ALL
SELECT 4, 'Uncle Michael Wilson', 2 UNION ALL
SELECT 5, 'Aunt Nancy Manor', 2 UNION ALL
SELECT 6, 'Grand Uncle Michael Bishop', 1 UNION ALL
SELECT 7, 'Brother David James Wilson',3 UNION ALL
SELECT 8, 'Sister Michelle Clark', 3 UNION ALL
SELECT 9, 'Brother Robert James Wilson', 3 UNION ALL
SELECT 10, 'Me Steve James Wilson', 3
;WITH cte AS
(
SELECT Id, Name, Parent, 1 as Depth
FROM #temp
WHERE Id = 8
UNION ALL
SELECT t2.*, Depth + 1 as 'Depth'
FROM cte t
JOIN #temp t2 ON t.Parent = t2.Id
)
SELECT *
, MAX(Depth) OVER() - Depth + 1 AS InverseDepth
FROM cte
As output I would like something like
Id Name depth2_id depth2_name
8 Sister Michelle .. 2 Grand Mom Elian ....
7 Brother David .. 2 Grand Mom Elian ....
4 Uncle Michael .. 2 Grand Mom Elian ...
Thanks for any tips or pointers.
a bit hard to get what your goal, but you can use smth like this:
;with cte AS
(
select
t.Id, t.Name, t.Parent, 1 as Depth,
null as Depth2Parent
from #temp as t
where t.Parent is null
union all
select
t.Id, t.Name, t.Parent, c.Depth + 1 as 'Depth',
isnull(c.Depth2Parent, case when c.Depth = 1 then t.Id end) as Depth2Parent
from cte as c
inner join #temp as t on t.Parent = c.Id
)
select *
from cte
sql fiddle demo

SQL Monthly Summary

I have a table that contains a startdate for each item
for example:
ID - Startdate
1 - 2011-01-01
2 - 2011-02-01
3 - 2011-04-01
...
I need a query that will give me the count of each item within each month, i need a full 12 month report. I tried simply grouping by the Month(StartDate) but this doesnt give me a zero for the months with no values, in the case above, for march.
so i would like the output to be along the lines of..
Month - Count
1 20
2 14
3 0
...
Any ideas?
Thanks.
SELECT A.Month, ISNULL(B.countvalue,0) Count
FROM (SELECT 1 AS MONTH
UNION
SELECT 2
UNION
SELECT 3
UNION
SELECT 4
UNION
SELECT 5
UNION
SELECT 6
UNION
SELECT 7
UNION
SELECT 8
UNION
SELECT 9
UNION
SELECT 10
UNION
SELECT 11
UNION
SELECT 12 ) A LEFT JOIN (SELECT datepart(month,Startdate) AS Month, Count(ID) as countvalue FROM yourTable GROUP BY datepart(month,Startdate))B
ON A.month = B.month
Hope this helps
Another way to do this using SQL Server 2005+ or Oracle.
SQL Statement
;WITH q (Month) AS (
SELECT 1
UNION ALL
SELECT Month + 1
FROM q
WHERE q.Month < 12
)
SELECT q.Month
, COUNT(i.ID)
FROM q
LEFT OUTER JOIN Input i ON MONTH(i.StartDate) = q.Month
GROUP BY
q.Month
Test script
;WITH Input (ID, StartDate) AS (
SELECT 1, '2011-01-01'
UNION ALL SELECT 2, '2011-02-01'
UNION ALL SELECT 3, '2011-04-01'
)
, q (Month) AS (
SELECT 1
UNION ALL
SELECT Month + 1
FROM q
WHERE q.Month < 12
)
SELECT q.Month
, COUNT(i.ID)
FROM q
LEFT OUTER JOIN Input i ON MONTH(i.StartDate) = q.Month
GROUP BY
q.Month