Sql select query based on a column value - sql

I have a Table1 like this:
ApplicableTo IdApplicable
---------------------------
Dept 1
Grade 3
section 1
Designation 2
There other tables like:
tblDept:
ID Name
1 dept1
2 baking
3 other
tblGrade:
ID Name
1 Grd1
2 Manager
3 gr3
tblSection:
id Name
1 Sec1
2 sec2
3 sec3
tblDesignation:
id Name
1 Executive
2 Developer
3 desig3
What I need is a query for table1 in such a way that gives me
ApplicableTo (table1)
Name (from the relevant table based on the value in `ApplicableTo` column)
Is this possible?
Desired Result:
eg: ApplicableTo IdApplicable Name
Dept 1 dept1
grade 3 gr3
Section 1 sec1
Designation 2 Developer.
This is the result I desire.

You could do something like the following so the applicable to becomes part of the JOIN predicate:
SELECT t1.ApplicableTo, t1.IdApplicable, n.Name
FROM Table1 AS t1
INNER JOIN
( SELECT ID, Name, 'Dept' AS ApplicableTo
FROM tblDept
UNION ALL
SELECT ID, Name, 'Grade' AS ApplicableTo
FROM tblGrade
UNION ALL
SELECT ID, Name, 'section' AS ApplicableTo
FROM tblSection
UNION ALL
SELECT ID, Name, 'Designation' AS ApplicableTo
FROM tblDesignation
) AS n
ON n.ID = t1.IdApplicable
AND n.ApplicableTo = t1.ApplicableTo
I would generally advise against this approach, although it may seem like a more consice approach, you would be better having 4 separate nullable columns in your table:
ApplicableTo | IdDept | IdGrade | IdSection | IdDesignation
-------------+--------+---------+-----------+---------------
Dept | 1 | NULL | NULL | NULL
Grade | NULL | 3 | NULL | NULL
section | NULL | NULL | 1 | NULL
Designation | NULL | NULL | NULL | 2
This allows you to use foreign keys to manage your referential integrity properly.

You can use CASE here,
SELECT ApplicableTo,
IdApplicable,
CASE
WHEN ApplicableTo = 'Dept' THEN (SELECT Name FROM tblDept WHERE tblDept.ID = IdApplicable)
WHEN ApplicableTo = 'Grade' THEN (SELECT Name FROM tblGrade WHERE tblGrade.ID = IdApplicable)
WHEN ApplicableTo = 'Section' THEN (SELECT Name FROM tblSection WHERE tblSection.ID = IdApplicable)
WHEN ApplicableTo = 'Designation' THEN (SELECT Name FROM tblDesignation WHERE tblDesignation.ID = IdApplicable)
END AS 'Name'
FROM Table1

The easiest way to achieve that is to add an extra column in table1 to keep the table where id is refferred to. Otherwise you can't know in which table the applicable id is reffered to.
Or you can create the applicable id in a way that you can extract the table afterwords from it for example a1 for id 1 in tblDept. And then use [case] (http://dev.mysql.com/doc/refman/5.0/en/case.html) (for mysql) in order to make the correct Join.

Related

How to select from two different columns based on one having a null value in oracle

I have the following example data in Oracle.
Table Name
ID Name city
1 Atik 1
2 Tania null
3 Anabia 3
Table City
ID Name
1 A
2 b
3 C
I am trying to select a value from table CITY if the value in table NAME is null, otherwise I want the value in NAME. The result should look like this:
Result
ID Name city
1 Atik A
2 Tania null
3 Anabia B
This is only 3 columns, but I have a lot of columns like this.
Use a correlated subquery, in this way:
SELECT id, name,
(SELECT Name FROM City c
WHERE c.id = n.city ) as City
FROM Name n
You can also use LEFT JOIN:
SELECT n.id,
n.name,
c.name as city
FROM Name n
LEFT JOIN City c
ON c.id = n.city
Demo: https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=600cdfc1cbf08b8bc2798229f02a3d96
| ID | NAME | CITY |
|----|--------|--------|
| 1 | Atik | A |
| 2 | Tania | (null) |
| 3 | Anabia | C |
Use NVL2 to check for NULL value in table Name:
SELECT n.ID, n.Name, NVL2(n.city, c.Name, NULL) AS City
FROM Name n LEFT JOIN City c
ON c.ID = n.city
You can use a left join on the ID field, and then use a CASE statement to choose your value. It should be flexible enough to help with your other columns too.
SELECT
n.ID,
n.NAME,
CASE WHEN n.CITY IS NULL THEN NULL ELSE c.NAME END AS CITY
FROM
NAME n LEFT JOIN CITY c ON n.ID = c.ID

Comparing different columns in SQL for each row

after some transformation I have a result from a cross join (from table a and b) where I want to do some analysis on. The table for this looks like this:
+-----+------+------+------+------+-----+------+------+------+------+
| id | 10_1 | 10_2 | 11_1 | 11_2 | id | 10_1 | 10_2 | 11_1 | 11_2 |
+-----+------+------+------+------+-----+------+------+------+------+
| 111 | 1 | 0 | 1 | 0 | 222 | 1 | 0 | 1 | 0 |
| 111 | 1 | 0 | 1 | 0 | 333 | 0 | 0 | 0 | 0 |
| 111 | 1 | 0 | 1 | 0 | 444 | 1 | 0 | 1 | 1 |
| 112 | 0 | 1 | 1 | 0 | 222 | 1 | 0 | 1 | 0 |
+-----+------+------+------+------+-----+------+------+------+------+
The ids in the first column are different from the ids in the sixth column.
In a row are always two different IDs that are matched with each other. The other columns always have either 0 or 1 as a value.
I am now trying to find out how many values(meaning both have "1" in 10_1, 10_2 etc) two IDs have on average in common, but I don't really know how to do so.
I was trying something like this as a start:
SELECT SUM(CASE WHEN a.10_1 = 1 AND b.10_1 = 1 then 1 end)
But this would obviously only count how often two ids have 10_1 in common. I could make something like this for example for different columns:
SELECT SUM(CASE WHEN (a.10_1 = 1 AND b.10_1 = 1)
OR (a.10_2 = 1 AND b.10_1 = 1) OR [...] then 1 end)
To count in general how often two IDs have one thing in common, but this would of course also count if they have two or more things in common. Plus, I would also like to know how often two IDS have two things, three things etc in common.
One "problem" in my case is also that I have like ~30 columns I want to look at, so I can hardly write down for each case every possible combination.
Does anyone know how I can approach my problem in a better way?
Thanks in advance.
Edit:
A possible result could look like this:
+-----------+---------+
| in_common | count |
+-----------+---------+
| 0 | 100 |
| 1 | 500 |
| 2 | 1500 |
| 3 | 5000 |
| 4 | 3000 |
+-----------+---------+
With the codes as column names, you're going to have to write some code that explicitly references each column name. To keep that to a minimum, you could write those references in a single union statement that normalizes the data, such as:
select id, '10_1' where "10_1" = 1
union
select id, '10_2' where "10_2" = 1
union
select id, '11_1' where "11_1" = 1
union
select id, '11_2' where "11_2" = 1;
This needs to be modified to include whatever additional columns you need to link up different IDs. For the purpose of this illustration, I assume the following data model
create table p (
id integer not null primary key,
sex character(1) not null,
age integer not null
);
create table t1 (
id integer not null,
code character varying(4) not null,
constraint pk_t1 primary key (id, code)
);
Though your data evidently does not currently resemble this structure, normalizing your data into a form like this would allow you to apply the following solution to summarize your data in the desired form.
select
in_common,
count(*) as count
from (
select
count(*) as in_common
from (
select
a.id as a_id, a.code,
b.id as b_id, b.code
from
(select p.*, t1.code
from p left join t1 on p.id=t1.id
) as a
inner join (select p.*, t1.code
from p left join t1 on p.id=t1.id
) as b on b.sex <> a.sex and b.age between a.age-10 and a.age+10
where
a.id < b.id
and a.code = b.code
) as c
group by
a_id, b_id
) as summ
group by
in_common;
The proposed solution requires first to take one step back from the cross-join table, as the identical column names are super annoying. Instead, we take the ids from the two tables and put them in a temporary table. The following query gets the result wanted in the question. It assumes table_a and table_b from the question are the same and called tbl, but this assumption is not needed and tbl can be replaced by table_a and table_b in the two sub-SELECT queries. It looks complicated and uses the JSON trick to flatten the columns, but it works here:
WITH idtable AS (
SELECT a.id as id_1, b.id as id_2 FROM
-- put cross join of table a and table b here
)
SELECT in_common,
count(*)
FROM
(SELECT idtable.*,
sum(CASE
WHEN meltedR.value::text=meltedL.value::text THEN 1
ELSE 0
END) AS in_common
FROM idtable
JOIN
(SELECT tbl.id,
b.*
FROM tbl, -- change here to table_a
json_each(row_to_json(tbl)) b -- and here too
WHERE KEY<>'id' ) meltedL ON (idtable.id_1 = meltedL.id)
JOIN
(SELECT tbl.id,
b.*
FROM tbl, -- change here to table_b
json_each(row_to_json(tbl)) b -- and here too
WHERE KEY<>'id' ) meltedR ON (idtable.id_2 = meltedR.id
AND meltedL.key = meltedR.key)
GROUP BY idtable.id_1,
idtable.id_2) tt
GROUP BY in_common ORDER BY in_common;
The output here looks like this:
in_common | count
-----------+-------
2 | 2
3 | 1
4 | 1
(3 rows)

How to select all attributes (*) with distinct values in a particular column(s)?

Here is link to the w3school database for learners:
W3School Database
If we execute the following query:
SELECT DISTINCT city FROM Customers
it returns us a list of different City attributes from the table.
What to do if we want to get all the rows like that we get from SELECT * FROM Customers query, with unique value for City attribute in each row.
DISTINCT when used with multiple columns, is applied for all the columns together. So, the set of values of all columns is considered and not just one column.
If you want to have distinct values, then concatenate all the columns, which will make it distinct.
Or, you could group the rows using GROUP BY.
You need to select all values from customers table, where city is unique. So, logically, I came with such query:
SELECT * FROM `customers` WHERE `city` in (SELECT DISTINCT `city` FROM `customers`)
I think you want something like this:
(change PK field to your Customers Table primary key or index like Id)
In SQL Server (and standard SQL)
SELECT
*
FROM (
SELECT
*, ROW_NUMBER() OVER (PARTITION BY City ORDER BY PK) rn
FROM
Customers ) Dt
WHERE
(rn = 1)
In MySQL
SELECT
*
FORM (
SELECT
a.City, a.PK, count(*) as rn
FROM
Customers a
JOIN
Customers b ON a.City = b.City AND a.PK >= b.PK
GROUP BY a.City, a.PK ) As DT
WHERE (rn = 1)
This query -I hope - will return your Cities distinctly and also shows other columns.
You can use GROUP BY clause for getting distinct values in a particular column. Consider the following table - 'contact':
+---------+------+---------+
| id | name | city |
+---------+------+---------+
| 1 | ABC | Chennai |
+---------+------+---------+
| 2 | PQR | Chennai |
+---------+------+---------+
| 3 | XYZ | Mumbai |
+---------+------+---------+
To select all columns with distinct values in City attribute, use the following query:
SELECT *
FROM contact
GROUP BY city;
This will give you the output as follows:
+---------+------+---------+
| id | name | city |
+---------+------+---------+
| 1 | ABC | Chennai |
+---------+------+---------+
| 3 | XYZ | Mumbai |
+---------+------+---------+

SQL group by with a count

I have a table (simplified below)
|company|name |age|
| 1 | a | 3 |
| 1 | a | 3 |
| 1 | a | 2 |
| 2 | b | 8 |
| 3 | c | 1 |
| 3 | c | 1 |
For various reason the age column should be the same for each company. I have another process that is updating this table and sometimes it put an incorrect age in. For company 1 the age should always be 3
I want to find out which companies have a mismatch of age.
Ive done this
select company, name age from table group by company, name, age
but dont know how to get the rows where the age is different. this table is a lot wider and has loads of columns so I cannot really eyeball it.
Can anyone help?
Thanks
You should not be including age in the group by clause.
SELECT company
FROM tableName
GROUP BY company, name
HAVING COUNT(DISTINCT age) <> 1
SQLFiddle Demo
If you want to find the row(s) with a different age than the max-count age of each company/name group:
WITH CTE AS
(
select company, name, age,
maxAge=(select top 1 age
from dbo.table1 t2
group by company,name, age
having( t1.company=t2.company and t1.name=t2.name)
order by count(*) desc)
from dbo.table1 t1
)
select * from cte
where age <> maxAge
Demontration
If you want to update the incorrect with the correct ages you just need to replace the SELECT with UPDATE:
WITH CTE AS
(
select company, name, age,
maxAge=(select top 1 age
from dbo.table1 t2
group by company,name, age
having( t1.company=t2.company and t1.name=t2.name)
order by count(*) desc)
from dbo.table1 t1
)
UPDATE cte SET AGE = maxAge
WHERE age <> maxAge
Demonstration
Since you mentioned "how to get the rows where the age is different" and not just the comapnies:
Add a unique row id (a primary key) if there isn't already one. Let's call it id.
Then, do
select id from table
where company in
(select company from table
group by company
having count(distinct age)>1)

UPDATE rows with no match in other table

I have two tables:
TABLE A
id | user_id | name
------+-----------+-------
1 | 122 | 'Bill'
2 | 123 | 'Jim'
3 | 124 | 'Sally'
TABLE B
id | user_id | title
------+-----------+-------
1 | 122 | 'Boss'
2 | 999 | 'Manager'
3 | 124 | 'Worker'
I want to update all of A with name = 'foo' where there is no matching user_id in table B. Do not update the row if user_id exists in table B.
So in this case it would only update Jim to the name 'foo'.
NOT EXISTS should be simplest, safest & fastest:
UPDATE tbl_a a
SET name = 'foo'
WHERE NOT EXISTS (SELECT FROM tbl_b b WHERE b.user_id = a.user_id);
This also works as expected with NULL values. As opposed to NOT IN. See:
https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_NOT_IN
With Rails:
user_ids_in_table_b = MobelB.pluck(:user_id)
ModelA.where.not(user_id: user_ids_in_table_b).update_all(name: 'foo')
You need to change the MobelA & MobelB models name to your models name in your app.
In pure SQL:
UPDATE table_a
SET name = 'foo'
WHERE user_id NOT IN (SELECT user_id FROM table_b);