Select query to get all data from junction table to one field - sql

I have 2 tables and 1 junction table:
table 1 (Log): | Id | Title | Date | ...
table 2 (Category): | Id | Title | ...
junction table between table 1 and 2:
LogCategory: | Id | LogId | CategoryId
now, I want a sql query to get all logs with all categories title in one field,
something like this:
LogId, LogTitle, ..., Categories(that contains all category title assigned to this log id)
can any one help me solve this? thanks

Try this code:
DECLARE #results TABLE
(
idLog int,
LogTitle varchar(20),
idCategory int,
CategoryTitle varchar(20)
)
INSERT INTO #results
SELECT l.idLog, l.LogTitle, c.idCategory, c.CategoryTitle
FROM
LogCategory lc
INNER JOIN Log l
ON lc.IdLog = l.IdLog
INNER JOIN Category c
ON lc.IdCategory = c.IdCategory
SELECT DISTINCT
idLog,
LogTitle,
STUFF (
(SELECT ', ' + r1.CategoryTitle
FROM #results r1
WHERE r1.idLog = r2.idLog
ORDER BY r1.idLog
FOR XML PATH ('')
), 1, 2, '')
FROM
#results r2
Here you have a simple SQL Fiddle example
I'm sure this query can be written using only one select, but this way it is readable and I can explain what the code does.
The first select takes all Log - Category matches into a table variable.
The second part uses FOR XML to select the category names and return the result in an XML instead of in a table. by using FOR XML PATH ('') and placing a ', ' in the select, all the XML tags are removed from the result.
And finally, the STUFF instruction replaces the initial ', ' characters of every row and writes an empty string instead, this way the string formatting is correct.

Related

Combine multiple rows with different column values into a single one

I'm trying to create a single row starting from multiple ones and combining them based on different column values; here is the result i reached based on the following query:
select distinct ID, case info when 'name' then value end as 'NAME', case info when 'id' then value end as 'serial'
FROM TABLENAME t
WHERE info = 'name' or info = 'id'
Howerver the expected result should be something along the lines of
I tried with group by clauses but that doesn't seem to work.
The RDBMS is Microsoft SQL Server.
Thanks
SELECT X.ID,MAX(X.NAME)NAME,MAX(X.SERIAL)AS SERIAL FROM
(
SELECT 100 AS ID, NULL AS NAME, '24B6-97F3'AS SERIAL UNION ALL
SELECT 100,'A',NULL UNION ALL
SELECT 200,NULL,'8113-B600'UNION ALL
SELECT 200,'B',NULL
)X
GROUP BY X.ID
For me GROUP BY works
A simple PIVOT operator can achieve this for dynamic results:
SELECT *
FROM
(
SELECT id AS id_column, info, value
FROM tablename
) src
PIVOT
(
MAX(value) FOR info IN ([name], [id])
) piv
ORDER BY id ASC;
Result:
| id_column | name | id |
|-----------|------|------------|
| 100 | a | 24b6-97f3 |
| 200 | b | 8113-b600 |
Fiddle here.
I'm a fan of a self join for things like this
SELECT tName.ID, tName.Value AS Name, tSerial.Value AS Serial
FROM TableName AS tName
INNER JOIN TableName AS tSerial ON tSerial.ID = tName.ID AND tSerial.Info = 'Serial'
WHERE tName.Info = 'Name'
This initially selects only the Name rows, then self joins on the same IDs and now filter to the Serial rows. You may want to change the INNER JOIN to a LEFT JOIN if not everything has a Name and Serial and you want to know which Names don't have a Serial

How can join table with IN() in ON couse?

I have two table
User
id | name | category
1 | test | [2,4]
Category
id | name
1 | first
2 | second
3 | third
4 | fourth
now i need to join this both table and get data like:
name | category
test | second, fourth
i tried like:
select u.name as name, c.name as category
from user
INNER JOIN category on(c.id in (u.category))
but it's not working.
As others have suggested, if you have any control whatsoever over the design of this database, don't store multiple values in user.category, but instead have a bridging table between the two which maps one or more category values to each user record.
However, if you are not in a position to be able to redesign the database, here's a way to get the result you're looking for. First, let's create some test data:
create table [user]
(
id int,
[name] varchar(50),
category varchar(50) -- I'm assuming this is a string type
)
create table category
(
id int,
[name] varchar(50)
)
insert into [user] values
(1,'test','[2,4]'),
(2,'another test','[1,2,4]'),
(3,'more test','[1,3,2,4]')
insert into category values
(1,'first'),
(2,'second'),
(3,'third'),
(4,'fourth');
Then you can use a CTE with split_string to pull apart the individual category values, join them to their names, then recombine them into a single comma-separated value with for xml:
with r as
(
select
u.[name] as username,
cat.id,
cat.[name] as categoryname
from [user] u
outer apply
(
select value from string_split(substring(u.category,2,len(u.category)-2),',')
) c
left join category cat on c.value = cat.id
)
select
r.username,
stuff(
(select ',' + categoryname
from r r2
where r.username = r2.username
order by r2.id
for xml path ('')), 1, 1, '') as categories
from r
group by r.username
which gives the desired output:
/-----------------------------------------\
| username | categories |
|-------------|---------------------------|
|another test | first,second,fourth |
|more test | first,second,third,fourth |
|test | second,fourth |
\-----------------------------------------/
I'm making a couple of assumptions here:
You're using MS SQL Server
The category values always begin with [, end with ] and contain nothing but a comma-delimited string containing value category ids

Inserting data to multiple tables Postgres

I currently have a MongoDB database with the following schema:
Image: { name: String, src: String, category: String, tags: [String] }
I'd like to migrate this to Postgres and for that I'd have 4 tables
image (id, src, name, category_id)
tag (id, name)
image_tag (image_id, tag_id)
category (id, name)
There might be new tags on every image inserts so when using CTE I need to select all the tags (and only insert new tags if they don't exist). I was thinking about using a cache (redis) to store the already inserted tags (so I don't need to select them from the db).
So my question is should I go with CTE with insert into tags.. where not exists statements or CTE + redis and only inserting tags when it could not be found in the cache?
So here is the small statement to insert an image with a category and multiple tags into multiple tables of a postgres database. The following expression assumes that the name in the tables category and tag has an unique constraint defined. For completion I also created an statement without that constraint (see the examples section).
Postgres statement
WITH image_values(image_name, src, category) AS (
VALUES
('Goldkraut', 'goldkraut.jpg', 'logo')
),
tag_values(tag_name) AS (
VALUES
('music'), ('band')
),
category_select AS (
SELECT id, name FROM category
WHERE name IN (SELECT category FROM image_values)
),
category_insert AS (
INSERT INTO category(name)
SELECT category FROM image_values
ON CONFLICT (name) DO NOTHING
RETURNING id, name
),
category_created AS (
SELECT id, name FROM category_select
UNION ALL
SELECT id, name FROM category_insert
),
tag_select AS (
SELECT id, name FROM tag
WHERE name IN (SELECT tag_name FROM tag_values)
),
tag_insert AS (
INSERT INTO tag(name)
SELECT tag_name FROM tag_values
ON CONFLICT (name) DO NOTHING
RETURNING id, name
),
tag_created AS (
SELECT id, name FROM tag_select
UNION ALL
SELECT id, name FROM tag_insert
),
image_insert AS (
INSERT INTO image(src, name, category_id)
SELECT src, image_name, category_created.id
FROM image_values
LEFT JOIN category_created ON(image_values.category=category_created.name)
RETURNING id, src, name, category_id
),
image_tag_insert AS (
INSERT INTO image_tag(image_id, tag_id)
SELECT image_insert.id, tag_created.id FROM image_insert
CROSS JOIN tag_created
RETURNING image_id, tag_id
)
SELECT image_insert.*, category_created.name as category_name, image_tag_insert.*, tag_created.name as "tag.name"
FROM image_tag_insert
LEFT JOIN image_insert ON (image_id = image_insert.id)
LEFT JOIN category_created ON (category_created.id = image_insert.category_id)
LEFT JOIN tag_created ON (tag_created.id = tag_id)
Explanation to the statement
In the first common table expression (CTE) image_values you will define all values for an image that has in a 1:1 relation. In the next expression tag_values all tag names for that image are defined.
Now lets start with the categories. To know if a category with the name already exist, you query for an category entry in category_select. In the expression category_insert you will create an new entry for the category if not already exits (instead of querying again from the database we use the cte category_select to find out if we already have an category with this name). To store the category id in the image table we need the category entry whether the existing (from category_select) or the inserted (from category_insert) so we union this two expressions in category_created.
Now we use the same pattern for the tags. Query for existing tags tag_select, insert tags if not exist tag_insert and union this entries in tag_created.
At next we insert the image in image_insert. Therefore we select the values from the expression image_values and join the expression category_created to get the id of the category. To insert the the relation image to tag we will need the id of the inserted image so we will return this value. The other return values are not really necessary but we will use them to get a nicer result set in the final query.
Now we have the primary key of the inserted image and we can store the associations of the image to the tags. In the expression image_tag_insert we select the id of the inserted image and cross join this with every tag id we selected or inserted.
For the final statement it will be enough to just do SELECT * FROM image_tag_insert to execute all the expression. But for an overview what was stored in the database i joined all the relations. So the result will look like this:
Joined result
| id | src | name | category_id | category_name | image_id | tag_id | tag.name |
|----|---------------|-----------|-------------|---------------|----------|--------|----------|
| 1 | goldkraut.jpg | Goldkraut | 2 | logo | 1 | 3 | band |
| 1 | goldkraut.jpg | Goldkraut | 2 | logo | 1 | 1 | music |
Example
On this sqlfiddle you will see the given query in action. In another sqlfiddle i have add some extras to the last statement to format all inserted tags as a list. If you have not add a unique constrain to the name column in the tables tag and category you can use this example

SQL : Create a full record from 2 tables

I've got a DB structure as is (simplified to maximum for understanding concern):
Table "entry" ("id" integer primary key)
Table "fields" ("name" varchar primary key, and others)
Table "entry_fields" ("entryid" integer primary key, "name" varchar primary key, "value")
I would like to get, for a given "entry.id", the detail of this entry, ie. all the "entry_fields" linked to this entry, in a single SQL query.
An example would be better perhaps:
"fields":
"result"
"output"
"code"
"command"
"entry" contains:
id : 842
id : 850
"entry_fields" contains:
entryid : 842, name : "result", value : "ok"
entryid : 842, name : "output", value : "this is an output"
entryid : 842, name : "code", value : "42"
entryid : 850, name : "result", value : "ko"
entryid : 850, name : "command", value : "print ko"
The wanted output would be:
| id | command | output | code | result |
| 842 | NULL | "this is an output" | 42 | ok |
| 850 | "print ko" | NULL | NULL | ko |
The aim is to be able to add a "field" without changing anything to "entry" table structure
I tried something like:
SELECT e.*, (SELECT name FROM fields) FROM entry AS e
but Postgres complains:
ERROR: more than one row returned by a subquery used as an expression
Hope someone can help me!
Solution as requested
While stuck with this unfortunate design, the fastest query would be with crosstab(), provided by the additional module tablefunc. Ample details in this related answer:
PostgreSQL Crosstab Query
For the question asked:
SELECT * FROM crosstab(
$$SELECT e.id, ef.name, ef.value
FROM entry e
LEFT JOIN entry_fields ef
ON ef.entryid = e.id
AND ef.name = ANY ('{result,output,code,command}'::text[])
ORDER BY 1, 2$$
,$$SELECT unnest('{result,output,code,command}'::text[])$$
) AS ct (id int, result text, output text, code text, command text);
Database design
If you don't have a huge number of different fields, it will be much simpler and more efficient to merge all three tables into one simple table:
CREATE TABLE entry (
entry_id serial PRIMARY KEY
,field1 text
,field2 text
, ... more fields
);
Fields without values can be NULL. NULL storage is very cheap (basically 1 bit per column in the NULL bitmap):
How much disk-space is needed to store a NULL value using postgresql DB?
Do nullable columns occupy additional space in PostgreSQL?
Even if you have hundreds of different columns, and only few are filled per entry, this will still use much less disk space.
You query becomes trivial:
SELECT entry_id, result, output, code, command
FROM enty;
If you have too many columns1, and that's not just a misguided design (often, this can be folded into much fewer columns), consider the data types hstore or json / jsonb (in Postgres 9.4) for EAV storage.
1 Per Postgres "About" page:
Maximum Columns per Table 250 - 1600 depending on column types
Consider this related answer with alternatives:
Use case for hstore against multiple columns
And this question about typical use cases / problems of EAV structures on dba.SE:
Is there a name for this database structure?
Dynamic SQL:
CREATE TABLE fields (name varchar(100) PRIMARY KEY)
INSERT INTO FIELDS VALUES ('RESULT')
INSERT INTO FIELDS VALUES ('OUTPUT')
INSERT INTO FIELDS VALUES ('CODE')
INSERT INTO FIELDS VALUES ('COMMAND')
CREATE TABLE ENTRY_fields (ENTRYID INT, name varchar(100), VALUE VARCHAR(100) CONSTRAINT PK PRIMARY KEY(ENTRYID, name))
INSERT INTO ENTRY_fields VALUES(842, 'RESULT', 'OK')
INSERT INTO ENTRY_fields VALUES(842, 'OUTPUT', 'THIS IS AN OUTPUT')
INSERT INTO ENTRY_fields VALUES(842, 'CODE', '42')
INSERT INTO ENTRY_fields VALUES(850, 'RESULT', 'KO')
INSERT INTO ENTRY_fields VALUES(850, 'COMMAND', 'PRINT KO')
CREATE TABLE ENTRY (ID INT PRIMARY KEY)
INSERT INTO ENTRY VALUES(842)
INSERT INTO ENTRY VALUES(850)
DECLARE #COLS NVARCHAR(MAX), #SQL NVARCHAR(MAX)
select #Cols = stuff((select ', ' + quotename(dt)
from (select DISTINCT name as dt
from fields) X
FOR XML PATH('')),1,2,'')
PRINT #COLS
SET #SQL = 'SELECT * FROM (SELECT id, f.name, value
from fields F CROSS join ENTRY LEFT JOIN entry_fields ef on ef.name = f.name AND ID = ef.ENTRYID
) Y PIVOT (max(value) for name in ('+ #Cols +'))PVT '
--print #SQL
exec (#SQL)
If you think your values are going to be constant in the fields table:
SELECT * FROM (SELECT id, f.name ,value
from fields F CROSS join ENTRY LEFT JOIN entry_fields ef on ef.name = f.name AND ID = ef.ENTRYID
) Y PIVOT (max(value) for name in ([CODE], [COMMAND], [OUTPUT], [RESULT]))PVT
Query that may work with postgresql:
SELECT ID, MAX(CODE) as CODE, MAX(COMMAND) as COMMAND, MAX(OUTPUT) as OUTPUT, MAX(RESULT) as RESULT
FROM (SELECT ID,
CASE WHEN f.name = 'CODE' THEN VALUE END AS CODE,
CASE WHEN f.name = 'COMMAND' THEN VALUE END AS COMMAND,
CASE WHEN f.name = 'OUTPUT' THEN VALUE END AS OUTPUT,
CASE WHEN f.name = 'RESULT' THEN VALUE END AS RESULT
from fields F CROSS join ENTRY LEFT JOIN entry_fields ef on ef.name = f.name AND ID = ENTRYID
) Y
GROUP BY ID
The subquery (SELECT name FROM fields) would return 4 rows. You can't stuff 4 rows into 1 in SQL. You can use crosstab, which I'm not familiar enough to answer. Or you can use a crude query like this:
SELECT e.*,
(SELECT value FROM entry_fields AS ef WHERE name = 'command' AND ef.entryid = f.entryid) AS command,
(SELECT value FROM entry_fields AS ef WHERE name = 'output' AND ef.entryid = f.entryid) AS output,
(SELECT value FROM entry_fields AS ef WHERE name = 'code' AND ef.entryid = f.entryid) AS code,
(SELECT value FROM entry_fields AS ef WHERE name = 'result' AND ef.entryid = f.entryid) AS result
FROM entry AS e

Add a rownumber based on the sequence of values provided

SELECT Code, Value FROM dbo.Sample
Output:
Code Value
Alpha Pig
Beta Horse
Charlie Dog
Delta Cat
Echo Fish
I want to add a sequence column by specifying a list of Codes and sort the list based on the order specified in the IN clause.
SELECT Code, Value FROM dbo.Sample
WHERE Code in ('Beta', 'Echo', 'Alpha')
I could declare a variable at the top to specify the Codes if that is easier.
The key is that I want to add the row number based on the order that I specify them in.
Output:
Row Code Value
1 Beta Horse
2 Echo Fish
3 Alpha Pig
Edit: I realized after that my Codes are all a fixed length which makes a big difference in how it could be done. I marked the answer below as correct, but my solution is to use a comma-separated string of values:
DECLARE #CodeList TABLE (Seq int, Code nchar(3))
DECLARE #CodeSequence varchar(255)
DECLARE #ThisCode char(3)
DECLARE #Codes int
SET #Codes = 0
-- string of comma-separated codes
SET #CodeSequence = 'ZZZ,ABC,FGH,YYY,BBB,CCC'
----loop through and create index and populate #CodeList
WHILE #Codes*4 < LEN(#CodeSequence)
BEGIN
SET #ThisCode = SUBSTRING(#CodeSequence,#Codes*4+1,3)
SET #Codes = #Codes + 1
INSERT #CodeList (Seq, Code) VALUES (#Codes, #ThisCode)
END
SELECT Seq, Code from #CodeList
Here are the only 2 ways I've seen work accurately:
The first uses CHARINDEX (similar to Gordon's, but I think the WHERE statement is more accurate using IN):
SELECT *
FROM Sample
WHERE Code IN ('Beta','Echo','Alpha')
ORDER BY CHARINDEX(Code+',','Beta,Echo,Alpha,')
Concatenating the comma with code should ensure sub-matches don't affect the results.
Alternatively, you could use a CASE statement:
SELECT *
FROM Sample
WHERE Code in ('Beta','Echo','Alpha')
ORDER BY CASE
WHEN Code = 'Beta' THEN 1
WHEN Code = 'Echo' THEN 2
WHEN Code = 'Alpha' THEN 3
END
SQL Fiddle Demo
Updated Demo with sub-matches.
Also you can use Values as Table Source
SELECT Row, Code, Value
FROM [Sample] s JOIN (
SELECT ROW_NUMBER() OVER(ORDER BY(SELECT 1)) AS Row, Match
FROM (VALUES ('Beta'),
('Echo'),
('Alpha'))
x (Match)
) o ON s.Code = o.Match
ORDER BY Row
Demo on SQLFiddle
Here is solution for any lenght code list.
Create table with self incrementing field and code. Insert in given order. Join tables and order by ...
Some details. Please read this. You will find there function that creates table with auto increment field from string (delimited by commas), i.e.
mysql> call insertEngineer('dinusha,nuwan,nirosh');
Query OK, 1 row affected (0.12 sec)
mysql> select * from engineer;
+----+----------+
| ID | NAME |
+----+----------+
| 1 | dinusha |
| 2 | nuwan |
| 3 | nirosh |
+----+----------+
Next join your Sample table with result of above. GL
Just a lil bit of change to whats been done above to include the rownumbers as well.
SELECT CASE
WHEN Code = 'BetaBeta' THEN 1
WHEN Code = 'Beta' THEN 2
WHEN Code = 'Alpha' THEN 3
END CodeOrder,
*
FROM Sample
WHERE Code in ('BetaBeta','Beta','Alpha')
ORDER BY CodeOrder
SQL Fiddle Demo
I might be tempted to do this using string functions:
declare #list varchar(8000) = 'Beta,Echo,Alpha';
with Sample as (
select 'Alpha' as Code, 'Pig' as Value union all
select 'Beta', 'Horse' union all
select 'Charlie', 'Dog' union all
select 'Delta', 'Cat' union all
select 'Echo', 'Fish'
)
select * from Sample
where charindex(Code, #list) > 0
order by charindex(Code, #list)
If you are worried about submatches, just do the "delimiter" trick:
where #list like '%,'+Code+',%'