SQL value of a row as column name - sql

I would like to consider possibilities of making the following thing:
create table customers
(
ID int identity,
name varchar(50)
)
create table additional
(
ID int identity,
customer_id int references customers(ID),
input_name varchar(50),
input_value varchar(50)
)
It should be able to define name and value of additional input for a particular customer and display it in a column. Example:
insert into customers (name) values ("aaa");
insert into additional (customer_id,input_name,input_value) values (1,"last name","bbb");
Now the result that I need is:
customerID | first name | last name
-----------------------------------
1 | aaa | bbb
so that additional field is displayed as column name.

This is easy if you are happy with a static query:
SELECT c.customerID, a2.input_value AS first_name, a1.input_value AS last_name
FROM customers c
LEFT JOIN additional a1 ON a1.ID = c.ID and a1.input_name = 'last name'
LEFT JOIN additional a2 ON a2.ID = c.ID and a2.input_name = 'first name'
If you are not happy with a static query, this means that the number and the names of the columns will change with the data. So in this case, you will have to dynamically construct your query.

Related

SQL insert multiple rows depending on number of rows returned from subquery

I have 3 SQL tables Companies, Materials and Suppliers as follows.
Tables
I need to insert values into Suppliers from a list which contains Company Name and Material Name as headers. However, I have multiple companies with the same name in the database and i need to add a new value into suppliers for each one of those companies.
For e.g. my list containes values ['Wickes','Bricks'] . I have this sql below to add a new entry into the suppliers table but since i have multple companies called 'Wickes' I'll get an error as the subquery will return more than 1 value.
INSERT INTO Suppliers(Id,CompanyId,MaterialId) VALUES (NEWID(), (SELECT Id FROM Companies WHERE Name = 'Wickes'),(SELECT Id FROM Materials WHERE Name = 'Bricks'))
Whats the best solution to get the Id of all the companies there are called 'Wickes' and then add vales into the suppliers table with that Id and the relevant material Id of 'Bricks'.
You can use INSERT () SELECT.. rather than INSERT () VALUES(), e.g
INSERT INTO Suppliers (Id, CompanyId, MaterialId)
SELECT NEWID(), c.Id, m.Id
FROM Companies AS c
CROSS JOIN Materials AS m
WHERE c.Name = 'Wickes'
AND m.Name = 'Bricks';
This will ensure that if you have multiple companies/materials with the same name, all permutations are inserted. Example on db<>fiddle
Although based on your image Suppliers.Id is an integer, so I think NEWID() is not doing what you think it is here, you probably just want:
INSERT INTO Suppliers (CompanyId, MaterialId)
SELECT c.Id, m.Id
FROM Companies AS c
CROSS JOIN Materials AS m
WHERE c.Name = 'Wickes'
AND m.Name = 'Bricks';
And let IDENTITY take care of the Id column in Suppliers.
As a further aside, I've also just noted that MaterialId is VARCHAR in your Suppliers table, that looks like an error if it is supposed to reference the integer Id column in Materials.
INSERT INTO Suppliers(Id,CompanyId,MaterialId) VALUES (NEWID(), (SELECT distict Id FROM Companies WHERE Name = 'Wickes'),(SELECT distict Id FROM Materials WHERE Name = 'Bricks'));
If I understand rightly Companies are the suppliers and the Suppliers table is the one that says where you can buy each material from.
Why do you have duplicates? Do you have an account for different branches of Wickes for example? If they are really duplicates and you don't care which one you use a function like MIN() will do the job of ensuring that only one value is returned. If you have duplicates it would be a good idea to find a way of disactivating all except one. This will make is simpler for you everytime you want to deal with the supplier: minimum orders, chasing overdue orders, payments etc.
Also Companies.ID and Materials.ID should be foreign keys of the Suppliers table. It is also a good idea for the ID column to be auto-incrementing, which makes it easier to add new products as you do not need to specify the ID column.
If you cannot or do not want to modify the id column to auto-incrementing IDENTITY you can continue to use NEWID().
create table Companies(
id INT PRIMARY KEY NOT NULL IDENTITY,
name VARCHAR(25));
create table Materials(
id INT PRIMARY KEY NOT NULL IDENTITY,
name VARCHAR(25));
create table Suppliers(
id INT PRIMARY KEY NOT NULL IDENTITY,
CompanyId INT FOREIGN KEY REFERENCES Companies(id),
MaterialId INT FOREIGN KEY REFERENCES Materials(id)
);
INSERT INTO Companies (name) VALUES ('Wickes');
INSERT INTO Materials (name) VALUES ('Bricks');
INSERT INTO Suppliers ( CompanyId, MaterialId)
SELECT c.Id, M.Id
FROM Companies AS c
CROSS JOIN Materials AS m
WHERE c.Name = 'Wickes'
AND m.Name = 'Bricks';
SELECT * FROM Companies;
SELECT * FROM Materials;
SELECT * FROM Suppliers;
GO
id | name
-: | :-----
1 | Wickes
id | name
-: | :-----
1 | Bricks
id | CompanyId | MaterialId
-: | --------: | ---------:
1 | 1 | 1
db<>fiddle here
INSERT INTO SUPPLIERS
(ID, COMPANYID, MATERIALID)
VALUES (NEWID(),
(SELECT DISTINCT ID FROM COMPANIES WHERE NAME = 'Wickes'), (SELECT DISTINCT ID FROM MATERIALS WHERE NAME = 'Bricks'))

Is there a way to select this name from more than one table?

I need to select the item name and the vendor name for each item that belongs to the vendor with a rating bigger than 4. And I can't find a way, I know it's something with joins but the 2 of them have the same column name.
CREATE TABLE venedors(
id int PRIMARY KEY,
name varchar2(20),
rating int)
CREATE TABLE items(
id int PRIMARY KEY,
name varchar2(20),
venedorId int references venedors(id))
If i understanded your problem.
Select items.name as itemName, venedors.name as vendorName
from items
inner join venedors
on items.venedorId = venedors.id
where venedors.rating > 4
If you want get all the vendors irrespective whether there are items associated with vendors or not, then try with left join as shown below:
Select v.name as vendorName, i.name as itemName
from venedors v
left join items i
on i.venedorId = v.id
where v.rating > 4

Count and group by in a referenced table, selecting other attributes

I have two tables: account_table and branch_table.
create table branch_table(
id int,
address varchar
);
create table account_table(
accid int,
balance int,
type varchar,
branch_ref ref branch_type
branch_ref SCOPE IS branch_table
);
How do I list the number of accounts of type 'saving' per branch, and select the address of this branch as well?
For each branch there will be unique address and we can do group by operation on both of those columns or we can do group by on branchid and then take max of address this doesnt change anything. And also need to do group by on account type to get type wise details.
SELECT b.id, b.address,COUNT(accid) no_of_accts
FROM account_table a
INNER JOIN
branch_table b
ON a.branch_ref = b.id
WHERE a.type = 'saving'
GROUP BY b.id,b.address,a.type;
SELECT b.address,COUNT(*) AS no_of_accts
FROM account_table a
INNER JOIN
branch_table b
ON a.branch_ref = b.id
WHERE a.type = 'saving'
GROUP BY b.address;

How to loop records table row by row in SQL Server?

Below are my tables :
Table:
ID Name category
--------------------
1 test 1
2 test 2
3 test1 3
4 test1 4
Category:
ID Name
------------
1 simple
2 complex
3 ordinory
4 ex-ordinory
In my table have 'Name' column. Each name has a category.
I need to fetch all names with categories wise.
select *
from table
inner join category ca on ca.id = table.category
where name = test
and category = 1
This query will return only name = test with category = 1 records
But I need to fetch all the names with their categories.
I am thinking to loop all the records row by row. Please suggest possible way to do this operation.
Is this what you're looking for?
CODE
CREATE TABLE TABLE_NAME (ID INT IDENTITY, NAME VARCHAR(25), CATEGORY INT)
INSERT INTO TABLE_NAME
VALUES ('test',1),('test',2),('test1',3),('test1',4)
CREATE TABLE CATEGORY (ID INT IDENTITY, NAME VARCHAR(25))
INSERT INTO CATEGORY
VALUES ('Simple'),('Complex'),('Ordinary'),('Ex-Ordinary')
SELECT DISTINCT
T.NAME
,C.NAME
FROM TABLE_NAME T
JOIN CATEGORY C
ON C.ID = T.CATEGORY
RESULT
TABLE_NAME CATEGORY_NAME
test Complex
test Simple
test1 Ex-Ordinary
test1 Ordinary

How to transform vertical table into horizontal table?

I have one table Person:
Id Name
1 Person1
2 Person2
3 Person3
And I have its child table Profile:
Id PersonId FieldName Value
1 1 Firstname Alex
2 1 Lastname Balmer
3 1 Email some_email#test.com
4 1 Phone +1 2 30004000
And I want to get data from these two tables in one row like this:
Id Name Firstname Lastname Email Phone
1 Person1 Alex Balmer some_email#test.com +1 2 30004000
What is the most optimized query to get these vertical (key, value) values in one row like this? Now I have a problem that I done four joins of child table to parent table because I need to get these four fields. Some optimization is for sure possible.
I would like to be able to modify this query in easy way when I add new field (key,value). What is the best way to do this? To create some stored procedure?
I would like to have strongly types in my DB layer (C#) and using LINQ (when programming) so it means when I add some new Key, Value pair in Profile table I would like to do minimal modifications in DB and C# if possible. Actually I am trying to get some best practices in this case.
Select
P.ID
, P.Name
, Case When C.FieldName = 'FirstName' Then C.Value Else NULL END AS FirstName
, Case When C.FieldName = 'LastName' Then C.Value Else NULL END AS LastName
, Case When C.FieldName = 'Email' Then C.Value Else NULL END AS Email
, Case When C.FieldName = 'Phone' Then C.Value Else NULL END AS Phone
From Person AS P
Inner JOIN Child AS C
ON P.ID = C.PersonID
You could use PIVOT; not sure which one would be the easiest for you to add a new column.
best optimized way with strongly typed fields, is to do it this way:
CREATE TABLE Persons
(PersonID int identity(1,1) primary key
,Firstname varchar(...)
,Lastname varchar(...)
,Email varchar(...)
,Phone varchar(...)
,....
)
then the most optimized query would be:
SELECT
PersonID,Firstname,Lastname,Email,Phone
FROM Persons
WHERE ...
Add all main columns into the persons table. if you need to specialize create additional tables:
--one person can play many instruments with this table
CREATE TABLE PersonMusicians
(PersonID int --pk fk to Persons.PersonID
,InstrumentCode char(1) --pk
,...
)
--only one row per person with this table
CREATE TABLE PersonTeachers
(PersonID int --pk fk to Persons.PersonID
,FavoriteSubjectCode char(1)
,SchoolName varchar(...)
)
if you have to have unlimited dynamic attribute fields, then I would create the above structure as fully as possible (as many common fields as possible) and then have an "AdditionalInfo" table where you store all the info like:
AdditionalInfoFields
FieldID int identity(1,1) primary key
FieldName varchar(...)
AdditionalInfo
AdditionalInfoID int identity(1,1) primary key
PersonID int fk to Persons.PersonID
FieldID int fk to AdditionalInfoFields.FieldID
FieldValue varchar(..) or you can look into sql_variant
have an index on AdditionalInfo.PersonID+FieldID and if you will search for all people that have attribute X, then also another like AdditionalInfo.FieldID+PersonID
short of any of the above, you will need to use the four left outer joins like you have mentioned in your option #1:
SELECT
P.ID, p.Name
, p1.Value AS Firstname
, p2.value AS Lastname
, p3.Value AS Email
, p4.Value AS Phone
FROM Persons p
LEFT OUTER JOIN Profile p1 ON p.PersonID=p1.PersonID AND p1.FieldName='Firstname'
LEFT OUTER JOIN Profile p1 ON p.PersonID=p1.PersonID AND p1.FieldName='Lastname'
LEFT OUTER JOIN Profile p1 ON p.PersonID=p1.PersonID AND p1.FieldName='Email'
LEFT OUTER JOIN Profile p1 ON p.PersonID=p1.PersonID AND p1.FieldName='Phone'
WHERE ....
you could always make a materialized view with an index out of this 4 left join query and have the data precalculated for you which should speed it up.