Nested loop to match colors - sql

Basically I have a table of colors, now I implemented a query which matches all colors together. I was wondering is it possible to do this with a loop? (perhaps it is a nested loop).
My idea is to loop the first color with every other color and then loop the second color with every other etc. Help is greatly appreciated.
My table - contains different colors
CREATE TABLE Colors
(c_ID VARCHAR2(3) NOT NULL,
c_NAME VARCHAR2(11));
INSERT INTO Colors VALUES
('T01','RED');
INSERT INTO Colors VALUES
('T02','BLUE');
INSERT INTO Colors VALUES
('T03','BLACK');
INSERT INTO Colors VALUES
('T04','YELLOW');
INSERT INTO Colors VALUES
('T05','ORANGE');
The sql query that I used to match different colors:
select a.c_id as HM, s.c_id as AW
from colors a, colors s
where a.c_id <> s.c_id
order by a.c_id;

Recursive query. (this is for Postgres, Your Syntax May Vary)
CREATE TABLE Colors
(c_ID INTEGER NOT NULL
, c_NAME VARCHAR
);
INSERT INTO Colors VALUES
(1,'RED'), (2,'BLUE'), (3,'BLACK'), (4,'YELLOW'), (5,'ORANGE');
WITH RECURSIVE xxx AS (
SELECT
c1.c_ID AS last_id
, c1.c_NAME::text AS all_colors
FROM Colors c1
UNION ALL
SELECT c2.c_ID AS last_id
, x.all_colors|| '+' || c2.c_NAME::text AS all_colors
FROM Colors c2
JOIN xxx x ON x.last_id < c2.c_ID
)
SELECT all_colors
FROM xxx
;
Results:
CREATE TABLE
INSERT 0 5
all_colors
------------------------------
RED
BLUE
BLACK
YELLOW
ORANGE
RED+BLUE
RED+BLACK
RED+YELLOW
RED+ORANGE
BLUE+BLACK
BLUE+YELLOW
BLUE+ORANGE
BLACK+YELLOW
BLACK+ORANGE
YELLOW+ORANGE
RED+BLUE+BLACK
RED+BLUE+YELLOW
RED+BLUE+ORANGE
RED+BLACK+YELLOW
RED+BLACK+ORANGE
RED+YELLOW+ORANGE
BLUE+BLACK+YELLOW
BLUE+BLACK+ORANGE
BLUE+YELLOW+ORANGE
BLACK+YELLOW+ORANGE
RED+BLUE+BLACK+YELLOW
RED+BLUE+BLACK+ORANGE
RED+BLUE+YELLOW+ORANGE
RED+BLACK+YELLOW+ORANGE
BLUE+BLACK+YELLOW+ORANGE
RED+BLUE+BLACK+YELLOW+ORANGE
(31 rows)

If I understand you correctly you want all colors in a single row, or even in a single column of a single row?
The query you show, will only result in pairs of colors. If you want all colors, you need to self-join as many times as you have colors. So adding or removing a color will turn your ugly query into a broken query. In general you will have a query result, where the number of columns depends on the number of colors. This does not play well with the relational paradigm.
If you just want all colors as a single value, then you need to aggregeate colors. The query result will then be a single value, with all the colors combined, possibly separated by commas.
To aggregate things you need an aggregate function. Well-known aggregate functions are SUM, MIN or AVG, none of which do what you need here. What aggregate function to chose depends on your particular SQL dialect.
For oracle you may look into pivot or xmlagg.
You may also consider wrapping the whole thing in procedural code.

Related

How to tally distinct combinations of rows in sqlite?

Say I have a collection of glass jars with some plastic balls of different colors in them. There can be at most one ball of each color in every jar and every jar contains one or more balls.
I represent this collection with an sqlite table with the following columns:
JarID INTEGER, Color TEXT
A collection of three jars might then look like this:
JarID Color
0, 'Red'
1, 'Red'
1, 'Green'
2, 'Red'
2, 'Blue'
3, 'Red'
I'd like to write a query that will find all the different color combinations that exist in my jar collection, and list each combination alongside the total number of jars with that combination.
For the table above, the query should return either:
'Red', 2
'Red,Green', 1
'Red,Blue', 1
Or:
Red
2
Red
Green
1
Red
Blue
1
Currently I have a terrible mess of common table expressions and window functions that seems to achieve the desired result, but I can't help feeling that I'm missing some elegant, standard SQL solution to this.
The Group_Concat function is what you're looking for, but first you need to aggregate and order your list so red-green-red comes out the same as green-red-red.
Do that with:
Select Jar_Id, Color
From Jars
Group By Jar_Id, Color
Order by Jar_Id, Color
Now feed that to a group_concat subquery to make a list of colors by jar_id:
Select Jar_Id, Group_Concat(Color) as Combos
From <first subquery>
Group By Jar_Id
Then feed that to an aggregator to count occurrences, and you're done:
Select Combos, count(*) as Occurrences
From <second subquery>
Group By Combos
Order by Combos
Put it all together with:
Select Combos,count(*) as Occurrences
From (
Select Jar_Id, Group_Concat(Color) as Combos
From (
Select Jar_Id, Color
From Jars
Group By Jar_Id, Color
Order by Jar_Id, Color
)
Group By Jar_Id
)
Group By Combos
Order by Combos
One caution: Order Bys in a subquery are not strictly defined in SQL and might be ignored, but most implementations will do them.

Return which element matched each row in SQL result

I am programmatically writing SQL queries from user input. I want to show the user for each row returned, which part of their supplied query information matched against that row.
For instance user supplied information looks like:
red:[11,202]
blue:[36]
green:[202]
yellow:[11,36]
This information would build the following query:
SELECT name, list FROM test WHERE list SIMILAR TO '%(11|202|36)%'
But I would like to add a result field similar to the "Matched Element shown below:
name
list
Matched Element
foobar
15,11,19,20
red, yellow
hello
17,30,36,20
blue, yellow
bar
101,202,330,460
red, green
test
15,36,23
blue
I cannot add attempted examples as I do not know the proper language by which to seach for information. My current solution so far is to process the data row by row after the query has returned the result to my script to add the new column, but I would like to know if its possible to achieve this using postgres.
Here is a db-fiddle: https://www.db-fiddle.com/f/nMMB3wVFRTGeZgobmA4F6k/1
I am running postgresql 12.3 however I am able to change version if needed.
I agree with Hambone that you should take advantage of arrays. If you don't want to create an additional table, you could also do it all in one query:
select name, list,
array_to_string(ARRAY[
CASE WHEN list LIKE ANY(ARRAY['%11%', '%202%']) then 'red' else null end,
CASE WHEN list LIKE ANY(ARRAY['%36%']) then 'blue' else null end,
CASE WHEN list LIKE ANY(ARRAY['%202']) then 'green' else null end,
CASE WHEN list LIKE ANY(ARRAY['%11%', '%36%']) then 'yellow' else null end
], ', ')
from test
where list LIKE ANY(ARRAY['%11%', '%202%', '%36%', '%202%', '%11%', '%36%']);
Note that I'm using LIKE ANY instead of SIMILAR TO. You could continue to use SIMILAR TO, but it seems easier to me to use LIKE ANY.
Here's a fiddle.
It sounds like you really need to take advantage of PostgreSQL's most excellent array support. I feel like I say that a lot.
If you change your structure to arrays:
create table test2 (
name varchar(255),
list integer[]
)
Then, notionally you can even put your input into another table:
create table matches (
color text,
list integer[]
);
insert into matches values
('red', '{11,202}'),
('blue', '{36}'),
('green', '{202}'),
('yellow', '{11,36}');
The following query should yield the results you seek:
select
t.name, t.list, array_agg (m.color)
from
test2 t
join matches m on
m.list && t.list
group by
t.name, t.list

function to sum all first value of Results SQL

I have a table with "Number", "Name" and "Result" Column. Result is a 2D text Array and I need to create a Column with the name "Average" that sum all first values of Result Array and divide by 2, can somebody help me Pls, I must use the create function for this. Its look like this:
Table1
Number
Name
Result
Average
01
Kevin
{{2.0,10},{3.0,50}}
2.5
02
Max
{{1.0,10},{4.0,30},{5.0,20}}
5.0
Average = ((2.0+3.0)/2) = 2.5
= ((1.0+4.0+5.0)/2) = 5.0
First of all: You should always avoid storing arrays in the table (or generate them in a subquery if not extremely necessary). Normalize it, it makes life much easier in nearly every single use case.
Second: You should avoid more-dimensional arrays. The are very hard to handle. See Unnest array by one level
However, in your special case you could do something like this:
demo:db<>fiddle
SELECT
number,
name,
SUM(value) FILTER (WHERE idx % 2 = 1) / 2 -- 2
FROM mytable,
unnest(avg_result) WITH ORDINALITY as elements(value, idx) -- 1
GROUP BY number, name
unnest() expands the array elements into one element per record. But this is not an one-level expand: It expand ALL elements in depth. To keep track of your elements, you could add an index using WITH ORDINALITY.
Because you have nested two-elemented arrays, the unnested data can be used as follows: You want to sum all first of two elements, which is every second (the odd ones) element. Using the FILTER clause in the aggregation helps you to aggregate only exact these elements.
However: If that's was a result of a subquery, you should think about doing the operation BEFORE array aggregation (if this is really necessary). This makes things easier.
Assumptions:
number column is Primary key.
result column is text or varchar type
Here are the steps for your requirements:
Add the column in your table using following query (you can skip this step if column is already added)
alter table table1 add column average decimal;
Update the calculated value by using below query:
update table1 t1
set average = t2.value_
from
(
select
number,
sum(t::decimal)/2 as value_
from table1
cross join lateral unnest((result::text[][])[1:999][1]) as t
group by 1
) t2
where t1.number=t2.number
Explanation: Here unnest((result::text[][])[1:999][1]) will return the first value of each child array (considering you can have up to 999 child arrays in your 2D array. You can increase or decrease it as per your requirement)
DEMO
Now you can create your function as per your requirement with above query.

multiple count(0 on table

I have a vehicle database and want to count how many cars have a specific colour.
But I don't know what colours there are as there are many, also combinations.
So this code does not do the trick for me:
SELECT
SUM(CASE WHEN colour='red' THEN 1 ELSE 0 END) red,
SUM(CASE WHEN colour='green' THEN 1 ELSE 0 END) green
(etc)
FROM vehicles
To get all colours, I could do:
select distinct colour from vehicles
But how can I use that information in a sql statement like the one above?
I am using MS sql server.
You could put the result set in rows rather than columns:
SELECT colour, count(*)
FROM vehicles
GROUP BY colour;
The alternative is that you would need to use dynamic SQL or express the result set as XML or JSON.
Why not simply do the aggregation ?
select colour, count(*) as no_vehicles
from vehicles v
group by colour;
This will pull a list of all colours found in table vehicles:
SELECT distinct colour
from vehicles
But what you really want to use is the group by clause, like so:
SELECT
colours
,count(*) HowMany
from vehicles
group by
colours
This will produce one row for every distinct value in column colours. It will NOT parse out color combinations; "red with black trim" will be its own column, and not +1 for red, _1 for black--that would be a much more complex problem.

SQL select like - containing only 1 word

A SQL Table has a field with the name:
image_colors
The value of this field can be a row of different colors - example:
green red blue white
When I search for a specific color, I use:
SELECT *
FROM `gallery_images`
WHERE `image_colors` LIKE '%green%'
In this case, the value:
green red blue white
contains green and will be selected.
Question:
Is it possible with SQL to find only values with only 1 Word:
green
Yes, use simple equality comparison to select only values matching green:
select *
from gallery_images
where image_colors = 'green'
Notes:
backticks are not necessary in your case and should be avoided when not needed
you should probably change your data model for many-to-many with colors dictionary table and junction table between gallery_images and colors to normalize your data and make lookups faster
Try to use
SELECT *
FROM gallery_images
WHERE trim(image_colors)= 'green'
Your existing query will work for single word also. No need to do any change for single word value in search field.
But of course this query will degrade performance of your application.
Instead of storing multiple values in single column, it is better to create another table to store colors in form of Integer value as multiple rows.
Do not use Like operator when you want records with only 'green' color.
select *
from gallery_images
where image_colors = 'green'