Select Relationships from table as columns - sql

I have an organization table with information about the organization and a table which stores the contacts within that organization. I want to create a query which will select an organization and all the phone numbers associated with their contacts in columns.
EX Org | Phone1 | Phone 2 | Phone 3
I have been looking around at pivot table selects and things like that but I have only seen information on how to select it in the same table not multiple tables.
I want to select it all in one row so I can have clean data for a custom excel sheet I'm working on.

I think the most performing way to do an unpivot on a large table (if you database does not support unpivot) is:
select org,
(case when n.n = 1 then phone1
when n.n = 2 then phone2
when n.n = 3 then phone3
end) as phone
from orgs o cross join
(select 1 as n union all select 2 union all select 3
) n;
(Note: in some databases you might need from dual for the n subqueries.)
You can also do this as a union all:
select org, phone1 from orgs union all
select org, phone2 from orgs union all
select org, phone3 from orgs;
The union all approach requires scanning the table three times, whereas the first approach will typically scan the data table only once.

The only way I can think to have it layout in the way you want seems like overkill but here goes some pseudo-code:
You're going to have to write some VBA.
First, write a query that gets every unique combination of phone number and org.
Second, set the active cell where you want to start.
Then iterate through that recordset and for each org and set the cell value to the org name.
Next iterate through and for each phone number with that org move the active cell to the right and make the cell value equal to the phone number.
Repeat until you reach the count of org instances then move back to the first cell and down a row.
Do this for every org not in your first column.

Related

How can I have UNION respect column aliases?

Sorry for the bad title, I couldn't think of anything better. Feel free to edit.
I have to work with a db table that uses one column to store different types of information (last name if person, company name if company). A nightmare, I know, but it's what it is.
To distinguish the meaning, there is another column with an integer that specifies the type of what's in the name column.
The schema of this table looks as follows (simplified):
ID int
dtype int
name varchar(50)
So, a sample could look like this:
ID dtype name
---------------------------
1 0 Smith
2 0 Trump
3 1 ABC Ltd.
4 1 XYZ Ltd.
I'm trying to normalize this using the following T-SQL code:
WITH companies AS
(
SELECT ID, name AS company
FROM nametable WHERE dtype=1
),
people AS
(
SELECT ID, name AS person
FROM nametable WHERE dtype=0
),
SELECT * FROM companies UNION ALL SELECT * FROM people;
What I hoped to get is a new table with the schema:
ID
dtype
company
person
Or, in table view:
ID dtype person company
------------------------------------------
1 0 Smith
2 0 Trump
3 1 ABC Ltd.
4 1 XYZ Ltd.
Instead, the field is now just called person instead of name but it's still just one field for 2 types of information.
I understand I could just create a new table and insert each partial result into it but it seems there should be a simpler way. Any advice appreciated.
It seems you need case when which helps you
select ID, dtype,case when dtype=0 then name end AS company,
case when dtype=1 then name end AS person
FROM nametable
The CASE statement goes through conditions and return a value when condition is met, from your sample input and output its clear you want to create type wise new column ,so i used case Statement
You don't need to use UNION for this at all. A better approach would be using a bit of aggregation.
SELECT ID,
MAX(CASE WHEN dtype = 0 THEN [name] END) AS company
MAX(CASE WHEN dtype = 1 THEN [name] END) AS person
FROM nametable
GROUP BY ID;
UNION (ALL) doesn't "care" for aliases though. It combines the datasets it receives into 1. All the datasets must have the same definition and the dataset returned will have the same definition. If the datasets have different aliases for columns, the aliases supplied in the first dataset will be used. UNION doesn't detect that the datasets have different names for the columns and therefore return the different names as different columns; that's not what a UNION does.
Edit: well this will give the OP the data they want, however, there's no need for the aggregation. I was honestly expected ID's to be a shared resource; because that's normally the only time you have such horrid tables. The fact that it isn't just makes this table even more confused...

SQL - Select TOP 1 from child table with WHERE condition, 2 possible values and 1 is preferred

I have 2 tables, parent and a child with 1-N relation.
Person: Id (INT), Name (VARCHAR)
PersonToCompany: Id (INT), PersonId (INT), Email (Varchar)
I want to JOIN both tables, but select just 1 record from the PersonToCompany table. I know I can do this using e.g. CROSS APPLY, but I also have some conditions.
I want to select only specific PersonToCompany records, like this:
WHERE (Email LIKE '%#abc.com%' OR Email LIKE '%xyz.com%')
Now the tricky part - some people can have 2 PersonToCompany records with both #abc.com and #xyz.com email domains. In this case, I want to be sure that the record with #abc.com will be selected. How can I do that?
This is my original subquery that selects #abc.com OR #xyz.com with no preference:
CROSS APPLY (
SELECT TOP 1
PersonToCompany.Email AS Email
FROM PersonToCompany
WHERE PersonToCompany.PersonId = Person.Id
AND (PersonToCompany.Email LIKE '%#abc.com%') OR (PersonToCompany.Email LIKE '%#xyz.com%')
) PersonToCompany
TOP 1 without ORDER BY is "give me a row, I don't care which one". So the simple fix is to add an ORDER BY:
CROSS APPLY (
SELECT TOP 1
PersonToCompany.Email AS Email
FROM PersonToCompany
WHERE PersonToCompany.PersonId = Person.Id
AND (PersonToCompany.Email LIKE '%#abc.com%' OR
PersonToCompany.Email LIKE '%#xyz.com%')
ORDER BY CASE WHEN PersonToCompany.Email LIKE '%#abc.com%' THEN 0 ELSE 1 END
) PersonToCompany
(I've also shifted some parentheses around to get the logic correct, I believe - you were bracketing individual predicates, which doesn't really do anything. I've bracketed the OR so that the PersonId match is required no matter which email address is found, which sounds more correct to me)

Querying SQL table with different values in same column with same ID

I have an SQL Server 2012 table with ID, First Name and Last name. The ID is unique per person but due to an error in the historical feed, different people were assigned the same id.
------------------------------
ID FirstName LastName
------------------------------
1 ABC M
1 ABC M
1 ABC M
1 ABC N
2 BCD S
3 CDE T
4 DEF T
4 DEG T
In this case, the people with ID’s 1 are different (their last name is clearly different) but they have the same ID. How do I query and get the result? The table in this case has millions of rows. If it was a smaller table, I would probably have queried all ID’s with a count > 1 and filtered them in an excel.
What I am trying to do is, get a list of all such ID's which have been assigned to two different users.
Any ideas or help would be very appreciated.
Edit: I dont think I framed the question very well.
There are two ID's which are present multiple time. 1 and 4. The rows with id 4 are identical. I dont want this in my result. The rows with ID 1, although the first name is same, the last name is different for 1 row. I want only those ID's whose ID is same but one of the first or last names is different.
I tried loading ID's which have multiple occurrences into a temp table and tried to compare it against the parent table albeit unsuccessfully. Any other ideas that I can try and implement?
SELECT
ID
FROM
<<Table>>
GROUP BY
ID
HAVING
COUNT(*) > 1;
SELECT *
FROM myTable
WHERE ID IN (
SELECT ID
FROM myTable
GROUP BY ID
HAVING MAX(LastName) <> MIN(LastName) OR MAX(FirstName) <> MIN(FirstName)
)
ORDER BY ID, LASTNAME

Assign unique ID's to three tables in SELECT query, ID's should not overlap

I am working on SQL Sever and I want to assign unique Id's to rows being pulled from those three tables, but the id's should not overlap.
Let's say, Table one contains cars data, table two contains house data, table three contains city data. I want to pull all this data into a single table with a unique id to each of them say cars from 1-100, house from 101 - 200 and city from 300- 400.
How can I achieve this using only select queries. I can't use insert statements.
To be more precise,
I have one table with computer systems/servers host information which has id from 500-700.
I have another tables, storage devices (id's from 200-600) and routers (ids from 700-900). I have already collected systems data. Now I want to pull storage systems and routers data in such a way that the consolidated data at my end should has a unique id for all records. This needs to be done only by using SELECT queries.
I was using SELECT ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) AS UniqueID and storing it in temp tables (separate for storage and routers). But I believe that this may lead to some overlapping. Please suggest any other way to do this.
An extension to this question:
Creating consistent integer from a string:
All I have is various strings like this
String1
String2Hello123
String3HelloHowAreYou
I Need to convert them in to positive integers say some thing like
String1 = 12
String2Hello123 = 25
String3HelloHowAreYou = 4567
Note that I am not expecting the numbers in any order.Only requirement is number generated for one string should not conflict with other
Now later after the reboot If I do not have 2nd string instead there is a new string
String1 = 12
String3HelloHowAreYou = 4567
String2Hello123HowAreyou = 28
Not that the number 25 generated for 2nd string earlier can not be sued for the new string.
Using extra storage (temp tables) is not allowed
if you dont care where the data comes from:
with dat as (
select 't1' src, id from table1
union all
select 't2' src, id from table2
union all
select 't3' src, id from table3
)
select *
, id2 = row_number() over( order by _some_column_ )
from dat

Reporting against a CSV field in a SQL server 2005 DB

Ok so I am writing a report against a third party database which is in sql server 2005. For the most part its normalized except for one field in one table. They have a table of users (which includes groups.) This table has a UserID field (PK), a IsGroup field (bit) , a members field (text) this members field has a comma separated list of all the members of this group or (if not a group) a comma separated list of the groups this member belongs to.
The question is what is the best way to write a stored procedure that displays what users are in what groups? I have a function that parses out the ids into a table. So the best way I could come up with was to create a cursor that cycles through each group and parse out the userid, write them to a temp table (with the group id) and then select out from the temp table?
UserTable
Example:
ID|IsGroup|Name|Members
1|True|Admin|3
2|True|Power|3,4
3|False|Bob|1,3
4|False|Susan|2
5|True|Normal|6
6|False|Bill|5
I want my query to show:
GroupID|UserID
1|3
2|3
2|4
5|6
Hope that makes sense...
If you have (or could create) a separate table containing the groups you could join it with the users table and match them with the charindex function with comma padding of your data on both sides. I would test the performance of this method with some fairly extreme workloads before deploying. However, it does have the advantage of being self-contained and simple. Note that changing the example to use a cross-join with a where clause produces the exact same execution plan as this one.
Example with data:
SELECT *
FROM (SELECT 1 AS ID,
'1,2,3' AS MEMBERS
UNION
SELECT 2,
'2'
UNION
SELECT 3,
'3,1'
UNION
SELECT 4,
'2,1') USERS
LEFT JOIN (SELECT '1' AS MEMBER
UNION
SELECT '2'
UNION
SELECT '3'
UNION
SELECT '4') GROUPS
ON CHARINDEX(',' + GROUPS.MEMBER + ',',',' + USERS.MEMBERS + ',') > 0
Results:
id members group
1 1,2,3 1
1 1,2,3 2
1 1,2,3 3
2 2 2
3 3,1 1
3 3,1 3
4 2,1 1
4 2,1 2
Your technique will probably be the best method.