how to transform vertical fields in a table to horizontal result by SQL - sql

I have a table like this:
create table t1 {
person_id int,
item_name varchar(30),
item_value varchar(100)
};
Suppose person_id+item_name is the composite key, now I have some data (5 records) in table t1 as below:
person_id ====item_name ====== item_value
1 'NAME' 'john'
1 'GENDER' 'M'
1 'DOB' '1970/02/01'
1 'M_PHONE' '1234567890'
1 'ADDRESS' 'Some Addresses unknown'
Now I want to use SQL (or combing store procedure/function or whatever) to query the above result (1 result set) become:
NAME==GENDER==DOB========M_PHONE=======ADDRESS===============
1 M 1970/02/01 1234567890 Some Addresses unknown
How should I do ?
Thank you for your help.

Regardless of the database you are using, the concept of what you are trying to achieve is called "Pivot Table".
Here's an example for mysql:
http://en.wikibooks.org/wiki/MySQL/Pivot_table
Some databases have builtin features for that, see the links below.
SQLServer:
http://msdn.microsoft.com/de-de/library/ms177410.aspx
Oracle:
http://www.dba-oracle.com/t_pivot_examples.htm
You can always create a pivot by hand. Just select all the aggregations in a result set and then select from that result set. Note, in your case, you can put all the names into one column using concat (i think that's group_concat in mysql), since you cannot know how many names are related to a person_id.

Finally, I found the solution in PostgreSQL:
select * from crosstab ('select person_id, item_name, item_value from t1 where person_id = 1 ')
as virtual_table ( person_id integer, name varchar, gender varchar, dob varchar, m_phone varchar, address varchar)
Also need to install the crosstab function on Postgres. See more: http://www.postgresql.org/docs/8.3/static/tablefunc.html

Related

Search using LIKE operator with multiple dynamic values accepting both full and partial text match

Is there any way to do multiple term search in a column using like operator dynamically in SQL Server? Like below
SELECT ID
FROM table
WHERE
Company LIKE '%goog%' OR
Company LIKE '%micros%' OR
Company LIKE '%amazon%'
For example: input values "goog; micro; amazon;" (input value should auto split by delimiter ';' and check the text exist in the table) means that Search term 'goog' or 'micros' or 'amazon' from company column, if exists return.
Table - sample data:
ID Company
------------------------------------------
1 Google; Microsoft;
2 oracle; microsoft; apple; walmart; tesla
3 amazon; apple;
4 google;
5 tesla;
6 amazon;
Basically, The above query should return the results as like below,
Desired results:
ID
-----
1
2
4
6
Is it possible to achieve in SQL Server by splitting, then search in query? I look forward to an experts answer.
If you pass in a table valued parameter, you can join on that.
So for example
CREATE TYPE StringList AS TABLE (str varchar(100));
DECLARE #tmp StringList;
INSERT #tmp (str)
VALUES
('%goog%'),
('%micros%'),
('%amazon%');
SELECT t.ID
FROM table t
WHERE EXISTS (SELECT 1
FROM #tmp tmp
WHERE t.Company LIKE tmp.str);
The one issue with this is that someone could write le; Mic and still get a result.
Strictly speaking, your table design is flawed, because you are storing multiple different items in the same column. You really should have this normalized into rows, so every Company is a separate row. Then your code would look like this:
SELECT t.ID
FROM table t
JOIN #tmp tmp ON t.Company LIKE tmp.str
GROUP BY t.ID
You can simulate it by splitting your string
SELECT t.ID
FROM table t
WHERE EXISTS (SELECT 1
FROM STRING_SPLIT(t.Company) s
JOIN #tmp tmp ON s.value LIKE tmp.str);

How to execute a select with a WHERE using a not-always-existing column

Simple example: I have some (nearly) identical tables with personal data (age, name, weight, ...)
Now I have a simple, but long SELECT to find missing data:
Select ID
from personal_data_a
where
born is null
or age < 1
or weight > 500
or (name is 'John' and surname is 'Doe')
Now the problem is:
I have some personal_data tables where the column "surname" does not exit, but I want to use the same SQL-statement for all of them. So I have to check (inside the WHERE clause) that the last OR-condition is only used "IF the column surname exists".
Can it be done in a simple way?
You should have all people in the same table.
If you can't do that for some reason, consider creating a view. Something like this:
CREATE OR REPLACE VIEW v_personal_data
AS
SELECT id,
born,
name,
surname,
age,
weight
FROM personal_data_a
UNION ALL
SELECT id,
born,
name,
NULL AS surname, --> this table doesn't contain surname
age,
weight
FROM personal_data_b;
and then
SELECT id
FROM v_personal_data
WHERE born IS NULL
OR age < 1
OR ( name = 'John'
AND ( surname = 'Doe'
OR surname IS NULL))
Can it be done in a simple way?
No, SQL statements work with static columns and the statements will raise an exception if you try to refer to a column that does not exist.
You will either:
need to have a different query for tables with the surname column and those without;
have to check in the data dictionary whether the table has the column or not and then use dynamic SQL to build your query; or
to build a VIEW of the tables which do not have that column and add the column to the view (or add a GENERATED surname column with a NULL value to the tables that are missing it) and use that instead.
While dynamic predicates are usually best handled by the application or by custom PL/SQL objects that use dynamic SQL, you can solve this problem with a single SQL statement using DBMS_XMLGEN, XMLTABLE, and the data dictionary. The following code is not what I would call "simple", but it is simple in the sense that it does not require any schema changes.
--Get the ID column from a PERSONAL table.
--
--#4: Get the IDs from the XMLType.
select id
from
(
--#3: Convert the XML to an XMLType.
select xmltype(personal_xml) personal_xmltype
from
(
--#2: Convert the SQL to XML.
select dbms_xmlgen.getxml(v_sql) personal_xml
from
(
--#1: Use data dictionary to create SQL statement that may or may not include
-- the surname predicate.
select max(replace(replace(
q'[
Select ID
from #TABLE_NAME#
where
born is null
or age < 1
or weight > 500
or (name = 'John' #OPTIONAL_SURNAME_PREDICATE#)
]'
, '#TABLE_NAME#', table_name)
, '#OPTIONAL_SURNAME_PREDICATE#', case when column_name = 'SURNAME' then
'and surname = ''Doe''' else null end)) v_sql
from all_tab_columns
--Change this literal to the desired table.
where table_name = 'PERSONAL_DATA_A'
)
)
where personal_xml is not null
)
cross join xmltable
(
'/ROWSET/ROW'
passing personal_xmltype
columns
id number path 'ID'
);
See this db<>fiddle for a runnable example.

Will a PIVOT meet my requirements?

Please see the DDL below:
CREATE TABLE Person (PersonNo int, Name varchar, Age int,address varchar(100))
Is it possible to SELECT the data in the following format using a single query:
PersonNo Name
PersonNo Age
PersonNo Address
I would need a column to identify the type of row e.g. Name. The more I think about this the more I don't think I can do it. I am currently trying to do it with a pivot.
One option would be to use UNION ALL (just remember to convert the int to varchar):
select personno, name as value, 'Name' as type
from person
union all
select personno, CONVERT(varchar(10), age), 'Age' as type
from person
union all
select personno, address, 'Address' as type
from person
SQL Fiddle Demo
Also please note -- you should define a length for the Name field. As is, it can only contain a single character.
You are looking for an UNPIVOT, not a PIVOT
SELECT
PersonNo
,ColumnName
,ColumnValue
FROM Person t1
UNPIVOT(ColumnValue FOR ColumnName in ([Name],[Age],[Address])) t2

SQL table column values to select query list

So I have a table that has EMAIL and Order ID.
EMAIL | id
--------------
Y#a.com | 1
Y#a.com | 2
X#a.com | 3
And I need to SELECT it so that I'd have email column that is distinct and ids column that is array of int's
EMAIL | ids
--------------
Y#a.com | [1,2]
X#a.com | [3]
I use PSQL 9.3. I looked at aggregate functions, but since I'm not too good with SQL atm, I didn't really understand them. My code so far is:
SELECT DISTINCT ON (email) email
FROM order
WHERE date > '2013-01-01';
Use the aggregate function array_agg():
SELECT email, array_agg(id) AS id_array
FROM "order"
WHERE date > '2013-01-01'
GROUP BY email;
Aside: your identifiers ...
Don't use order as table name, it's a reserved word.
Don't use date as column name, it's a reserved word in standard SQL and a basic type name in Postgres.
I wouldn't use id as column name either, that's a common anti-pattern, but "id" is not a descriptive name. Once you join a couple of tables you have n columns named "id" and you need to start dealing out column aliases, not to speak of the confusion it may cause.
Instead, use something like this:
CREATE TABLE order_data (
order_data_id serial PRIMARY KEY
, email text
, order_date date
);

What the simplest way to sub-query a variable number of rows into fields of the parent query?

What the simplest way to sub-query a variable number of rows into fields of the parent query?
PeopleTBL
NameID int - unique
Name varchar
Data: 1,joe
2,frank
3,sam
HobbyTBL
HobbyID int - unique
HobbyName varchar
Data: 1,skiing
2,swimming
HobbiesTBL
NameID int
HobbyID int
Data: 1,1
2,1
2,2
The app defines 0-2 Hobbies per NameID.
What the simplest way to query the Hobbies into fields retrieved with "Select * from PeopleTBL"
Result desired based on above data:
NameID Name Hobby1 Hobby2
1 joe skiing
2 frank skiing swimming
3 sam
I'm not sure if I understand correctly, but if you want to fetch all the hobbies for a person in one row, the following query might be useful (MySQL):
SELECT NameID, Name, GROUP_CONCAT(HobbyName) AS Hobbies
FROM PeopleTBL
JOIN HobbiesTBL USING NameID
JOIN HobbyTBL USING HobbyID
Hobbies column will contain all hobbies of a person separated by ,.
See documentation for GROUP_CONCAT for details.
I don't know what engine are you using, so I've provided an example with MySQL (I don't know what other sql engines support this).
Select P.NameId, P.Name
, Min( Case When H2.HobbyId = 1 Then H.HobbyName End ) As Hobby1
, Min( Case When H2.HobbyId = 2 Then H.HobbyName End ) As Hobby2
From HobbyTbl As H
Join HobbiesTbl As H2
On H2.HobbyId = H.HobbyId
Join PeopleTbl As P
On P.NameId = H2.NameId
Group By P.NameId, P.Name
What you are seeking is called a crosstab query. As long as the columns are static, you can use the above solution. However, if you want to dynamic build the columns, you need to build the SQL statement in middle-tier code or use a reporting tool.