Find all rows, that have at least n values in common with a specific item - sql

I have a table, sort of like this:
Items
-----------
ID Value1 Value2 Value3 Value4 Value5 Value6
1 345895 435234 342534 678767 5455 423555
2 3245 549238 230944 923948 234488 234997
3 490458 49349 234234 87810 903481 3940102
4 849545 435234 67678 98741 99084 978897
How would I write a query, that finds all the items, that have at least 3 values (just an example, could be more than 3) in common with a specific item i.e. I have an item
345895 435234 67678 98741 5455 423555
and running this query would give me
1 345895 435234 342534 678767 5455 423555
4 849545 435234 67678 98741 99084 978897
Any help would be greatly appreciated. Thank you.

You can use CASE statements in the WHERE clause in order to calculate the number of matches:
SELECT i.*
FROM Items AS i
CROSS JOIN ( VALUES ( 345895, 435234, 67678, 98741, 5455, 423555) ) AS Item(v1, v2, v3, v4, v5, v6)
WHERE (CASE WHEN i.Value1 = Item.v1 THEN 1 ELSE 0 END) +
(CASE WHEN i.Value2 = Item.v2 THEN 1 ELSE 0 END) +
(CASE WHEN i.Value3 = Item.v3 THEN 1 ELSE 0 END) +
(CASE WHEN i.Value4 = Item.v4 THEN 1 ELSE 0 END) +
(CASE WHEN i.Value5 = Item.v5 THEN 1 ELSE 0 END) +
(CASE WHEN i.Value6 = Item.v6 THEN 1 ELSE 0 END) >= 3

This is one way:
; with sub as(
select 345895 as mynum
union all select 435234
union all select 67678
union all select 98741
union all select 5455
union all select 423555
)
select i.*
from items i
join
(
select x.id
from(
select id, value1 as val from items union all
select id, value2 from items union all
select id, value3 from items union all
select id, value4 from items union all
select id, value5 from items union all
select id, value6 from items
) x join sub s on x.val = s.mynum
group by x.id
having count(*) >= 3
) x on x.id = i.id
Fiddle: http://sqlfiddle.com/#!6/1dff3/2/0

Related

Subtract from rows with certain values

I have a table created in Materialized view from 10 different tables.
Part of it looks like this
group_name
value1
value2
group1
100
20
group2
200
40
unknown
300
60
TOTAL
600
120
I have to rearrange all values from rows with value group_name = 'unknown' to other rows. The final table should look like this
group_name
value1
value2
group1
200
40
group2
400
80
TOTAL
600
120
So formula for 'group1' would be:
unknown x group1 x (TOTAL-unknown) + group1
The table is created with massive code and please note - I didn't write it, it was given to me and I have to work with it. I don't like how it looks, so please spare your anger. Anyway, the query looks like this:
TABLESPACE pg_default
AS
WITH table_value1 AS (
SELECT
table1.group_name,
table1.value1,
FROM table1
), table_value2 AS (
SELECT
table2.group_name,
table2.value2,
FROM table2
), TOTAL_groups AS (
SELECT
'value1'::text AS group_name,
sum(xy_table."value1")::numeric as results
FROM xy_table
UNION ALL
SELECT
'value2'::text AS group_name,
sum(xy_table."value2")::numeric as results
FROM xy_table
UNION ALL
SELECT
'unknown'::text AS group_name,
sum(xy_table."unknown")::numeric as results
FROM xy_table
), TOTAL AS (
SELECT
TOTAL_groups.group_name,
TOTAL_groups.results
FROM TOTAL_groups
UNION ALL
'TOTAL'::text AS group_name,
round(sum(TOTAL_groups.raba), 1) as results
FROM skupaj_energenti
)
SELECT
a.group_name,
COALESCE(a.results, 0::numeric) AS value1,
COALESCE(a.results, 0::numeric) AS value2
FROM table_value1 a
LEFT JOIN table_value2 b ON b.group_name = a.group_name
LEFT JOIN TOTAL c ON f.group_name = a.group_name
WITH DATA;
I have no idea how should I write such conditions in SQL. Please help.
Distribute 'unknown' row to other rows. Assuming value1, value2 are DECIMAL
select group_name, value1 * (1 + k1) value1, value2 * (1 + k2) value2
from tbl
cross join (
select sum(case group_name when 'unknown' then value1 end) / sum(case group_name when 'TOTAL' then value1 else -value1 end) k1,
sum(case group_name when 'unknown' then value2 end) / sum(case group_name when 'TOTAL' then value2 else -value2 end) k2
from tbl
where group_name in ('TOTAL', 'unknown')
) t
where tbl.group_name not in ('TOTAL', 'unknown')
db<>fiddle

SQL Sum Using logic to choose Plus or Minus (Oracle)

Not sure if this is possible or not in straight SQL.
Is it possible to choose the operator for a sum using the likes of a CASE Statement or similar logic.
e.g.
select
(select 1 from dual)
(case when (select 1 from dual) = 1 then + else - end)
(select 2 from dual)
from dual
Thanks in return.
- and + cannot be results of CASE WHEN, because they are not values. -1 and +1 however are. Multiply this -1 or +1 with the desired value in order to get the positive or negative value. E.g:
select case when type = 'withdrawal' then -1 else +1 end * value as balance_change
For your example:
select
(select 1 from dual) +
(case when (select 1 from dual) = 1 then +1 else -1 end) *
(select 2 from dual)
from dual
select 1 + (case when (select 1 from dual) = 1 then +1 else -1 end) from dual
I mean:
with
sub_q as (
select 2000 val1, -1500 val2 from dual
union all
select 1000 val1, +2000 val2 from dual
)
select val1, val2, SIGN(val2) s, val1 + (case when SIGN(val2) = 1 then +val2 else -val2 end) res
from sub_q

SQL using GROUP BY and COUNT

I created the MariaDB(10.1.21) table named 'group_test' and saved some data as below.
Group Item Value1 Value2 Value3
A a1 1 0 0
A a2 1 1 1
A a3 1 1 2
B b1 1 1 0
B b2 1 1 1
B b3 1 0 0
B b4 1 1 3
C c1 1 1 0
C c2 1 1 1
Using a query, I want to make the result as below at once.
Group Items Value1_1 Value2_1 Value3_1
A 3 3 2 1
B 4 4 3 1
C 2 2 2 1
Items means the total number of 'Item' in the 'Group'.
ValueN_1 means the total number of 'ValueN' value equal to 1 in the 'Group'.
I think I would use GROUP BY and COUNT but I don't know exactly what to do.
How do I write SQL to get the above results in one query?
Thanks.
Simply do a GROUP BY. Since Value1 to Value3 are only 0's and 1's, you can use SUM() to count the 1's.
select Group, count(Item), sum(Value1), sum(Value2), sum(Value3)
from tablename
group by Group
Edit: "ValueN can have a value from 0 to 4":
select Group,
count(Item),
sum(case when Value1 = 1 then 1 else 0 end) Value1_1,
sum(case when Value2 = 1 then 1 else 0 end) Value2_1,
sum(case when Value3 = 1 then 1 else 0 end) Value3_1
from tablename
group by Group
Assuming value1, value2 and value3 can only be either 0 or 1, you can write it like this
select Group,
count(*) as Items,
sum(value1) as Value1_1,
sum(value2) as Value2_1,
sum(value3) as Value3_1
from yourTable t1
group by Group
If that's not the case, you will have to use a case in the sum
select Group,
count(*) as Items,
sum(case when value1 = 1 then 1 else 0 end) as Value1_1,
sum(case when value2 = 1 then 1 else 0 end) as Value2_1,
sum(case when value3 = 1 then 1 else 0 end) as Value3_1
from yourTable t1
group by Group
Use GROUP BY clause and SUM aggregate function in SELECT statement
SELECT *
FROM
(
SELECT [Group], count(Item) Item, sum(Value1) Value1, sum(Value2) Value2,
sum(Value3) Value3
FROM Your_tableName
GROUP BY [Group]
) A

Merge multiple columns into one column with multiple rows

In PostgreSQL, how can I merge multiple columns into one column with multiple rows?
The columns are all boolean, so I want to:
Filter for true values only
Replace the true value (1) with the name of the column (A, B or C)
I have this table:
ID | A | B | C
1 0 1 0
2 1 1 0
3 0 0 1
4 1 0 1
5 1 0 0
6 0 1 1
I want to get this table:
ID | Letter
1 B
2 A
2 B
3 C
4 A
4 C
5 A
6 B
6 C
I think you need something like this:
SELECT ID, 'A' as Letter FROM table WHERE A=1
UNION ALL
SELECT ID, 'B' as Letter FROM table WHERE B=1
UNION ALL
SELECT ID, 'C'as Letter FROM table WHERE C=1
ORDER BY ID, Letter
SELECT ID,
(CASE
WHEN TABLE.A = 1 then 'A'
WHEN TABLE.B = 1 then 'B'
WHEN TABLE.C = 1 then 'C'
ELSE NULL END) AS LETTER
from TABLE
You may try this.
insert into t2 select id, 'A' from t1 where A=1;
insert into t2 select id, 'B' from t2 where B=1;
insert into t2 select id, 'C' from t3 where C=1;
If you care about the order, then you can do this.
insert into t3 select id, letter from t2 order by id, letter;
W/o UNION
You can use a single query to get the desired output.Real time example
select id
,regexp_split_to_table((
concat_ws(',', case
when a = 0
then null
else 'a'
end, case
when b = 0
then null
else 'b'
end, case
when c = 0
then null
else 'c'
end)
), ',') l
from c1;
regexp_split_to_table() & concat_ws()

SQL query - sum of values by status for date interval

I get crazy because of one query. I have a table like following and I want to get a data - Summa of Values by Status For every Date in interval.
Table
Id Name Value Date Status
1 pro1 2 01.04.14 0
2 pro1 8 02.04.14 1
3 pro2 6 02.04.14 1
4 pro3 0 03.04.14 0
5 pro4 7 03.04.14 0
6 pro4 2 03.04.14 0
7 pro4 4 03.04.14 1
8 pro4 6 04.04.14 1
9 pro4 1 04.04.14 1
For example,
Input: Name = pro4, minDate = 01.02.14, maxDate = 04.09.14
Output:
Date Values sum for 0 Status Values sum for 1 Status
01.04.14 0 0
02.04.14 0 0
03.04.14 9 (=7+2) 4 (only 4 exist)
04.04.14 0 7 (6+1)
In 01.02.14 and 02.04.14 dates, pro4 has not values by status, but I want to show that rows, because I need all dates in that interval. Can anyone help me to create this query?
Edit:
I can not change structure, I have already that table with data. Every day exist in table many times (minimum 1 time)
Thanks in advance.
Assuming you have a row for each date in the table, use conditional aggregation:
select date,
sum(Case when name = 'pro4' and status = 0 then Value else 0 end) as values_0,
sum(case when name = 'pro4' and status = 1 then Value else 0 end) as values_1
from Table t
where date >= '2014-04-01' and date <= '2014-04-09'
group by date
order by date;
If you don't have this list of dates, you can take this approach instead:
with dates as (
select cast('2014-04-01' as date) as thedate
union all
select dateadd(day, 1, thedate)
from dates
where thedate < '2014-04-09'
)
select dates.thedate,
sum(Case when status = 0 then Value else 0 end) as values_0,
sum(case when status = 1 then Value else 0 end) as values_1
from dates left outer join
table t
on t.date = dates.thedate and t.name = 'pro4'
group by dates.thedate;
just an assumption query :
select Distinct date ,case when status = 0 and MAX(date) then SUM(value) ELSE 0 END Status0 ,
case when status = 1 and MAX(date) then SUM(value) ELSE 0 END Status1 from table
To expand my comment the complete query is
WITH [counter](N) AS
(SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1)
, days(N) AS (
SELECT row_number() over (ORDER BY (SELECT NULL)) FROM [counter])
, months (N) AS (
SELECT N - 1 FROM days WHERE N < 13)
, calendar ([date]) AS (
SELECT DISTINCT cast(dateadd(DAY, days.n
, dateadd(MONTH, months.n, '20131231')) AS date)
FROM months
CROSS JOIN days
)
SELECT a.Name
, c.Date
, [Sum of 0] = SUM(CASE Status WHEN 0 THEN Value ELSE 0 END)
, [Sum of 1] = SUM(CASE Status WHEN 1 THEN Value ELSE 0 END)
FROM Calendar c
LEFT JOIN myTable a ON c.Date = a.Date AND a.name = 'pro4'
WHERE c.date BETWEEN '20140201' AND '20140904'
GROUP BY c.Date, a.Name
ORDER BY c.Date
Note that the condition on the name need to be in the JOIN, otherwise you'll get only the date of your table.
If you need multiple years just add another CTE for the count and a dateadd(YEAR,...) in the CTE calendar
This is not really the exact query, but I think you can get that by having a query that looks like:
select date, status, sum(value) from table
where (date between mindate and maxdate) and name = product_name
group by date, status;
this page gives more info.
EDIT
So the above query only gives a part of the answer required by the OP. A LEFT OUTER JOIN of the original table and the result of the above query on thedate and status fields will give the missing info.
e.g.
select x.date, x.status, x.sum_of_values from table as y
left outer join
(select date, status, sum(value) as sum_of_values
from table
where (date between mindate and maxdate) and name = product_name
group by date, status) as x
on y.date= x.date and y.status = x.status
order by x.date;